• 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::StartsWith(input_conversion, kTrimPrefix,
214                        base::CompareCase::SENSITIVE)) {
215     std::string trimmed;
216     base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed);
217 
218     // Remove "trim" prefix from the input conversion and re-run.
219     return DoConvertInputToValue(
220         settings, trimmed, origin, original_input_conversion,
221         input_conversion.substr(std::size(kTrimPrefix) - 1), err);
222   }
223 
224   if (input_conversion == "value")
225     return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err);
226   if (input_conversion == "string")
227     return Value(origin, input);
228   if (input_conversion == "list lines")
229     return ParseList(input, origin, err);
230   if (input_conversion == "scope")
231     return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err);
232   if (input_conversion == "json")
233     return ParseJSON(settings, input, origin, err);
234 
235   *err = Err(original_input_conversion, "Not a valid input_conversion.",
236              "Run `gn help io_conversion` to see your options.");
237   return Value();
238 }
239 
240 }  // namespace
241 
242 const char kInputOutputConversion_Help[] =
243     R"(Input and output conversion
244 
245   Input and output conversions are arguments to file and process functions
246   that specify how to convert data to or from external formats. The possible
247   values for parameters specifying conversions are:
248 
249   "" (the default)
250       input: Discard the result and return None.
251 
252       output: If value is a list, then "list lines"; otherwise "value".
253 
254   "list lines"
255       input:
256         Return the file contents as a list, with a string for each line. The
257         newlines will not be present in the result. The last line may or may
258         not end in a newline.
259 
260         After splitting, each individual line will be trimmed of whitespace on
261         both ends.
262 
263       output:
264         Renders the value contents as a list, with a string for each line. The
265         newlines will not be present in the result. The last line will end in
266         with a newline.
267 
268   "scope"
269       input:
270         Execute the block as GN code and return a scope with the resulting
271         values in it. If the input was:
272           a = [ "hello.cc", "world.cc" ]
273           b = 26
274         and you read the result into a variable named "val", then you could
275         access contents the "." operator on "val":
276           sources = val.a
277           some_count = val.b
278 
279       output:
280         Renders the value contents as a GN code block, reversing the input
281         result above.
282 
283   "string"
284       input: Return the file contents into a single string.
285 
286       output:
287         Render the value contents into a single string. The output is:
288         a string renders with quotes, e.g. "str"
289         an integer renders as a stringified integer, e.g. "6"
290         a boolean renders as the associated string, e.g. "true"
291         a list renders as a representation of its contents, e.g. "[\"str\", 6]"
292         a scope renders as a GN code block of its values. If the Value was:
293             Value val;
294             val.a = [ "hello.cc", "world.cc" ];
295             val.b = 26
296           the resulting output would be:
297             "{
298                 a = [ \"hello.cc\", \"world.cc\" ]
299                 b = 26
300             }"
301 
302   "value"
303       input:
304         Parse the input as if it was a literal rvalue in a buildfile. Examples of
305         typical program output using this mode:
306           [ "foo", "bar" ]     (result will be a list)
307         or
308           "foo bar"            (result will be a string)
309         or
310           5                    (result will be an integer)
311 
312         Note that if the input is empty, the result will be a null value which
313         will produce an error if assigned to a variable.
314 
315       output:
316         Render the value contents as a literal rvalue. Strings render with
317         escaped quotes.
318 
319   "json"
320       input: Parse the input as a JSON and convert it to equivalent GN rvalue.
321 
322       output: Convert the Value to equivalent JSON value.
323 
324       The data type mapping is:
325         a string in JSON maps to string in GN
326         an integer in JSON maps to integer in GN
327         a float in JSON is unsupported and will result in an error
328         an object in JSON maps to scope in GN
329         an array in JSON maps to list in GN
330         a boolean in JSON maps to boolean in GN
331         a null in JSON is unsupported and will result in an error
332 
333       Nota that the input dictionary keys have to be valid GN identifiers
334       otherwise they will produce an error.
335 
336   "trim ..." (input only)
337       Prefixing any of the other transformations with the word "trim" will
338       result in whitespace being trimmed from the beginning and end of the
339       result before processing.
340 
341       Examples: "trim string" or "trim list lines"
342 
343       Note that "trim value" is useless because the value parser skips
344       whitespace anyway.
345 )";
346 
ConvertInputToValue(const Settings * settings,const std::string & input,const ParseNode * origin,const Value & input_conversion_value,Err * err)347 Value ConvertInputToValue(const Settings* settings,
348                           const std::string& input,
349                           const ParseNode* origin,
350                           const Value& input_conversion_value,
351                           Err* err) {
352   if (input_conversion_value.type() == Value::NONE)
353     return Value();  // Allow null inputs to mean discard the result.
354   if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
355     return Value();
356   return DoConvertInputToValue(settings, input, origin, input_conversion_value,
357                                input_conversion_value.string_value(), err);
358 }
359