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