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