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