• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "gn/input_conversion.h"
6 
7 #include <iterator>
8 #include <memory>
9 #include <utility>
10 
11 #include "base/json/json_reader.h"
12 #include "base/macros.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/values.h"
16 #include "gn/build_settings.h"
17 #include "gn/err.h"
18 #include "gn/input_file.h"
19 #include "gn/label.h"
20 #include "gn/parse_tree.h"
21 #include "gn/parser.h"
22 #include "gn/scheduler.h"
23 #include "gn/scope.h"
24 #include "gn/settings.h"
25 #include "gn/tokenizer.h"
26 #include "gn/value.h"
27 
28 namespace {
29 
30 enum ValueOrScope {
31   PARSE_VALUE,  // Treat the input as an expression.
32   PARSE_SCOPE,  // Treat the input as code and return the resulting scope.
33 };
34 
35 // Sets the origin of the value and any nested values with the given node.
ParseValueOrScope(const Settings * settings,const std::string & input,ValueOrScope what,const ParseNode * origin,Err * err)36 Value ParseValueOrScope(const Settings* settings,
37                         const std::string& input,
38                         ValueOrScope what,
39                         const ParseNode* origin,
40                         Err* err) {
41   // The memory for these will be kept around by the input file manager
42   // so the origin parse nodes for the values will be preserved.
43   InputFile* input_file;
44   std::vector<Token>* tokens;
45   std::unique_ptr<ParseNode>* parse_root_ptr;
46   g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file,
47                                                      &tokens, &parse_root_ptr);
48 
49   input_file->SetContents(input);
50   if (origin) {
51     // This description will be the blame for any error messages caused by
52     // script parsing or if a value is blamed. It will say
53     // "Error at <...>:line:char" so here we try to make a string for <...>
54     // that reads well in this context.
55     input_file->set_friendly_name("dynamically parsed input that " +
56                                   origin->GetRange().begin().Describe(true) +
57                                   " loaded ");
58   } else {
59     input_file->set_friendly_name("dynamic input");
60   }
61 
62   *tokens = Tokenizer::Tokenize(input_file, err);
63   if (err->has_error())
64     return Value();
65 
66   // Parse the file according to what we're looking for.
67   if (what == PARSE_VALUE)
68     *parse_root_ptr = Parser::ParseValue(*tokens, err);
69   else
70     *parse_root_ptr = Parser::Parse(*tokens, err);  // Will return a Block.
71   if (err->has_error())
72     return Value();
73   ParseNode* parse_root = parse_root_ptr->get();  // For nicer syntax below.
74 
75   // It's valid for the result to be a null pointer, this just means that the
76   // script returned nothing.
77   if (!parse_root)
78     return Value();
79 
80   std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings);
81   Value result = parse_root->Execute(scope.get(), err);
82   if (err->has_error())
83     return Value();
84 
85   // When we want the result as a scope, the result is actually the scope
86   // we made, rather than the result of running the block (which will be empty).
87   if (what == PARSE_SCOPE) {
88     DCHECK(result.type() == Value::NONE);
89     result = Value(origin, std::move(scope));
90   }
91   return result;
92 }
93 
ParseList(const std::string & input,const ParseNode * origin,Err * err)94 Value ParseList(const std::string& input, const ParseNode* origin, Err* err) {
95   Value ret(origin, Value::LIST);
96   std::vector<std::string> as_lines = base::SplitString(
97       input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
98 
99   // Trim one empty line from the end since the last line might end in a
100   // newline. If the user wants more trimming, they'll specify "trim" in the
101   // input conversion options.
102   if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty())
103     as_lines.resize(as_lines.size() - 1);
104 
105   ret.list_value().reserve(as_lines.size());
106   for (const auto& line : as_lines)
107     ret.list_value().push_back(Value(origin, line));
108   return ret;
109 }
110 
IsIdentifier(const std::string_view & buffer)111 bool IsIdentifier(const std::string_view& buffer) {
112   DCHECK(buffer.size() > 0);
113   if (!Tokenizer::IsIdentifierFirstChar(buffer[0]))
114     return false;
115   for (size_t i = 1; i < buffer.size(); i++)
116     if (!Tokenizer::IsIdentifierContinuingChar(buffer[i]))
117       return false;
118   return true;
119 }
120 
ParseJSONValue(const Settings * settings,const base::Value & value,const ParseNode * origin,InputFile * input_file,Err * err)121 Value ParseJSONValue(const Settings* settings,
122                      const base::Value& value,
123                      const ParseNode* origin,
124                      InputFile* input_file,
125                      Err* err) {
126   switch (value.type()) {
127     case base::Value::Type::NONE:
128       *err = Err(origin, "Null values are not supported.");
129       return Value();
130     case base::Value::Type::BOOLEAN:
131       return Value(origin, value.GetBool());
132     case base::Value::Type::INTEGER:
133       return Value(origin, static_cast<int64_t>(value.GetInt()));
134     case base::Value::Type::STRING:
135       return Value(origin, value.GetString());
136     case base::Value::Type::BINARY:
137       *err = Err(origin, "Binary values are not supported.");
138       return Value();
139     case base::Value::Type::DICTIONARY: {
140       std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings);
141       for (const auto& it : value.DictItems()) {
142         Value parsed_value =
143             ParseJSONValue(settings, it.second, origin, input_file, err);
144         if (!IsIdentifier(it.first)) {
145           *err = Err(origin, "Invalid identifier \"" + it.first + "\".");
146           return Value();
147         }
148         // Search for the key in the input file. We know it's present because
149         // it was parsed by the JSON reader, but we need its location to
150         // construct a std::string_view that can be used as key in the Scope.
151         size_t off = input_file->contents().find("\"" + it.first + "\"");
152         if (off == std::string::npos) {
153           *err = Err(origin, "Invalid encoding \"" + it.first + "\".");
154           return Value();
155         }
156         std::string_view key(&input_file->contents()[off + 1], it.first.size());
157         scope->SetValue(key, std::move(parsed_value), origin);
158       }
159       return Value(origin, std::move(scope));
160     }
161     case base::Value::Type::LIST: {
162       Value result(origin, Value::LIST);
163       result.list_value().reserve(value.GetList().size());
164       for (const auto& val : value.GetList()) {
165         Value parsed_value =
166             ParseJSONValue(settings, val, origin, input_file, err);
167         result.list_value().push_back(parsed_value);
168       }
169       return result;
170     }
171   }
172   return Value();
173 }
174 
175 // Parses the JSON string and converts it to GN value.
ParseJSON(const Settings * settings,const std::string & input,const ParseNode * origin,Err * err)176 Value ParseJSON(const Settings* settings,
177                 const std::string& input,
178                 const ParseNode* origin,
179                 Err* err) {
180   InputFile* input_file;
181   std::vector<Token>* tokens;
182   std::unique_ptr<ParseNode>* parse_root_ptr;
183   g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file,
184                                                      &tokens, &parse_root_ptr);
185   input_file->SetContents(input);
186 
187   int error_code_out;
188   std::string error_msg_out;
189   std::unique_ptr<base::Value> value = base::JSONReader::ReadAndReturnError(
190       input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out,
191       &error_msg_out);
192   if (!value) {
193     *err = Err(origin, "Input is not a valid JSON: " + error_msg_out);
194     return Value();
195   }
196 
197   return ParseJSONValue(settings, *value, origin, input_file, err);
198 }
199 
200 // Backend for ConvertInputToValue, this takes the extracted string for the
201 // input conversion so we can recursively call ourselves to handle the optional
202 // "trim" prefix. This original value is also kept for the purposes of throwing
203 // errors.
DoConvertInputToValue(const Settings * settings,const std::string & input,const ParseNode * origin,const Value & original_input_conversion,const std::string & input_conversion,Err * err)204 Value DoConvertInputToValue(const Settings* settings,
205                             const std::string& input,
206                             const ParseNode* origin,
207                             const Value& original_input_conversion,
208                             const std::string& input_conversion,
209                             Err* err) {
210   if (input_conversion.empty())
211     return Value();  // Empty string means discard the result.
212 
213   const char kTrimPrefix[] = "trim ";
214   if (base::StartsWith(input_conversion, kTrimPrefix,
215                        base::CompareCase::SENSITIVE)) {
216     std::string trimmed;
217     base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed);
218 
219     // Remove "trim" prefix from the input conversion and re-run.
220     return DoConvertInputToValue(
221         settings, trimmed, origin, original_input_conversion,
222         input_conversion.substr(std::size(kTrimPrefix) - 1), err);
223   }
224 
225   if (input_conversion == "value")
226     return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err);
227   if (input_conversion == "string")
228     return Value(origin, input);
229   if (input_conversion == "list lines")
230     return ParseList(input, origin, err);
231   if (input_conversion == "scope")
232     return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err);
233   if (input_conversion == "json")
234     return ParseJSON(settings, input, origin, err);
235 
236   *err = Err(original_input_conversion, "Not a valid input_conversion.",
237              "Run gn help input_conversion to see your options.");
238   return Value();
239 }
240 
241 }  // namespace
242 
243 const char kInputOutputConversion_Help[] =
244     R"(Input and output conversion
245 
246   Input and output conversions are arguments to file and process functions
247   that specify how to convert data to or from external formats. The possible
248   values for parameters specifying conversions are:
249 
250   "" (the default)
251       input: Discard the result and return None.
252 
253       output: If value is a list, then "list lines"; otherwise "value".
254 
255   "list lines"
256       input:
257         Return the file contents as a list, with a string for each line. The
258         newlines will not be present in the result. The last line may or may
259         not end in a newline.
260 
261         After splitting, each individual line will be trimmed of whitespace on
262         both ends.
263 
264       output:
265         Renders the value contents as a list, with a string for each line. The
266         newlines will not be present in the result. The last line will end in
267         with a newline.
268 
269   "scope"
270       input:
271         Execute the block as GN code and return a scope with the resulting
272         values in it. If the input was:
273           a = [ "hello.cc", "world.cc" ]
274           b = 26
275         and you read the result into a variable named "val", then you could
276         access contents the "." operator on "val":
277           sources = val.a
278           some_count = val.b
279 
280       output:
281         Renders the value contents as a GN code block, reversing the input
282         result above.
283 
284   "string"
285       input: Return the file contents into a single string.
286 
287       output:
288         Render the value contents into a single string. The output is:
289         a string renders with quotes, e.g. "str"
290         an integer renders as a stringified integer, e.g. "6"
291         a boolean renders as the associated string, e.g. "true"
292         a list renders as a representation of its contents, e.g. "[\"str\", 6]"
293         a scope renders as a GN code block of its values. If the Value was:
294             Value val;
295             val.a = [ "hello.cc", "world.cc" ];
296             val.b = 26
297           the resulting output would be:
298             "{
299                 a = [ \"hello.cc\", \"world.cc\" ]
300                 b = 26
301             }"
302 
303   "value"
304       input:
305         Parse the input as if it was a literal rvalue in a buildfile. Examples of
306         typical program output using this mode:
307           [ "foo", "bar" ]     (result will be a list)
308         or
309           "foo bar"            (result will be a string)
310         or
311           5                    (result will be an integer)
312 
313         Note that if the input is empty, the result will be a null value which
314         will produce an error if assigned to a variable.
315 
316       output:
317         Render the value contents as a literal rvalue. Strings render with
318         escaped quotes.
319 
320   "json"
321       input: Parse the input as a JSON and convert it to equivalent GN rvalue.
322 
323       output: Convert the Value to equivalent JSON value.
324 
325       The data type mapping is:
326         a string in JSON maps to string in GN
327         an integer in JSON maps to integer in GN
328         a float in JSON is unsupported and will result in an error
329         an object in JSON maps to scope in GN
330         an array in JSON maps to list in GN
331         a boolean in JSON maps to boolean in GN
332         a null in JSON is unsupported and will result in an error
333 
334       Nota that the input dictionary keys have to be valid GN identifiers
335       otherwise they will produce an error.
336 
337   "trim ..." (input only)
338       Prefixing any of the other transformations with the word "trim" will
339       result in whitespace being trimmed from the beginning and end of the
340       result before processing.
341 
342       Examples: "trim string" or "trim list lines"
343 
344       Note that "trim value" is useless because the value parser skips
345       whitespace anyway.
346 )";
347 
ConvertInputToValue(const Settings * settings,const std::string & input,const ParseNode * origin,const Value & input_conversion_value,Err * err)348 Value ConvertInputToValue(const Settings* settings,
349                           const std::string& input,
350                           const ParseNode* origin,
351                           const Value& input_conversion_value,
352                           Err* err) {
353   if (input_conversion_value.type() == Value::NONE)
354     return Value();  // Allow null inputs to mean discard the result.
355   if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
356     return Value();
357   return DoConvertInputToValue(settings, input, origin, input_conversion_value,
358                                input_conversion_value.string_value(), err);
359 }
360