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