| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tools/gn/input_conversion.h" |
| |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "tools/gn/build_settings.h" |
| #include "tools/gn/err.h" |
| #include "tools/gn/input_file.h" |
| #include "tools/gn/label.h" |
| #include "tools/gn/parse_tree.h" |
| #include "tools/gn/parser.h" |
| #include "tools/gn/scheduler.h" |
| #include "tools/gn/scope.h" |
| #include "tools/gn/settings.h" |
| #include "tools/gn/tokenizer.h" |
| #include "tools/gn/value.h" |
| |
| namespace { |
| |
| enum ValueOrScope { |
| PARSE_VALUE, // Treat the input as an expression. |
| PARSE_SCOPE, // Treat the input as code and return the resulting scope. |
| }; |
| |
| // Sets the origin of the value and any nested values with the given node. |
| Value ParseValueOrScope(const Settings* settings, |
| const std::string& input, |
| ValueOrScope what, |
| const ParseNode* origin, |
| Err* err) { |
| // The memory for these will be kept around by the input file manager |
| // so the origin parse nodes for the values will be preserved. |
| InputFile* input_file; |
| std::vector<Token>* tokens; |
| std::unique_ptr<ParseNode>* parse_root_ptr; |
| g_scheduler->input_file_manager()->AddDynamicInput( |
| SourceFile(), &input_file, &tokens, &parse_root_ptr); |
| |
| input_file->SetContents(input); |
| if (origin) { |
| // This description will be the blame for any error messages caused by |
| // script parsing or if a value is blamed. It will say |
| // "Error at <...>:line:char" so here we try to make a string for <...> |
| // that reads well in this context. |
| input_file->set_friendly_name( |
| "dynamically parsed input that " + |
| origin->GetRange().begin().Describe(true) + |
| " loaded "); |
| } else { |
| input_file->set_friendly_name("dynamic input"); |
| } |
| |
| *tokens = Tokenizer::Tokenize(input_file, err); |
| if (err->has_error()) |
| return Value(); |
| |
| // Parse the file according to what we're looking for. |
| if (what == PARSE_VALUE) |
| *parse_root_ptr = Parser::ParseValue(*tokens, err); |
| else |
| *parse_root_ptr = Parser::Parse(*tokens, err); // Will return a Block. |
| if (err->has_error()) |
| return Value(); |
| ParseNode* parse_root = parse_root_ptr->get(); // For nicer syntax below. |
| |
| // It's valid for the result to be a null pointer, this just means that the |
| // script returned nothing. |
| if (!parse_root) |
| return Value(); |
| |
| std::unique_ptr<Scope> scope(new Scope(settings)); |
| Value result = parse_root->Execute(scope.get(), err); |
| if (err->has_error()) |
| return Value(); |
| |
| // When we want the result as a scope, the result is actually the scope |
| // we made, rather than the result of running the block (which will be empty). |
| if (what == PARSE_SCOPE) { |
| DCHECK(result.type() == Value::NONE); |
| result = Value(origin, std::move(scope)); |
| } |
| return result; |
| } |
| |
| Value ParseList(const std::string& input, const ParseNode* origin, Err* err) { |
| Value ret(origin, Value::LIST); |
| std::vector<std::string> as_lines = base::SplitString( |
| input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| // Trim one empty line from the end since the last line might end in a |
| // newline. If the user wants more trimming, they'll specify "trim" in the |
| // input conversion options. |
| if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) |
| as_lines.resize(as_lines.size() - 1); |
| |
| ret.list_value().reserve(as_lines.size()); |
| for (const auto& line : as_lines) |
| ret.list_value().push_back(Value(origin, line)); |
| return ret; |
| } |
| |
| // Backend for ConvertInputToValue, this takes the extracted string for the |
| // input conversion so we can recursively call ourselves to handle the optional |
| // "trim" prefix. This original value is also kept for the purposes of throwing |
| // errors. |
| Value DoConvertInputToValue(const Settings* settings, |
| const std::string& input, |
| const ParseNode* origin, |
| const Value& original_input_conversion, |
| const std::string& input_conversion, |
| Err* err) { |
| if (input_conversion.empty()) |
| return Value(); // Empty string means discard the result. |
| |
| const char kTrimPrefix[] = "trim "; |
| if (base::StartsWith(input_conversion, kTrimPrefix, |
| base::CompareCase::SENSITIVE)) { |
| std::string trimmed; |
| base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed); |
| |
| // Remove "trim" prefix from the input conversion and re-run. |
| return DoConvertInputToValue( |
| settings, trimmed, origin, original_input_conversion, |
| input_conversion.substr(arraysize(kTrimPrefix) - 1), err); |
| } |
| |
| if (input_conversion == "value") |
| return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err); |
| if (input_conversion == "string") |
| return Value(origin, input); |
| if (input_conversion == "list lines") |
| return ParseList(input, origin, err); |
| if (input_conversion == "scope") |
| return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err); |
| |
| *err = Err(original_input_conversion, "Not a valid input_conversion.", |
| "Have you considered a career in retail?"); |
| return Value(); |
| } |
| |
| } // namespace |
| |
| extern const char kInputConversion_Help[] = |
| "input_conversion: Specifies how to transform input to a variable.\n" |
| "\n" |
| " input_conversion is an argument to read_file and exec_script that\n" |
| " specifies how the result of the read operation should be converted\n" |
| " into a variable.\n" |
| "\n" |
| " \"\" (the default)\n" |
| " Discard the result and return None.\n" |
| "\n" |
| " \"list lines\"\n" |
| " Return the file contents as a list, with a string for each line.\n" |
| " The newlines will not be present in the result. The last line may\n" |
| " or may not end in a newline.\n" |
| "\n" |
| " After splitting, each individual line will be trimmed of\n" |
| " whitespace on both ends.\n" |
| "\n" |
| " \"scope\"\n" |
| " Execute the block as GN code and return a scope with the\n" |
| " resulting values in it. If the input was:\n" |
| " a = [ \"hello.cc\", \"world.cc\" ]\n" |
| " b = 26\n" |
| " and you read the result into a variable named \"val\", then you\n" |
| " could access contents the \".\" operator on \"val\":\n" |
| " sources = val.a\n" |
| " some_count = val.b\n" |
| "\n" |
| " \"string\"\n" |
| " Return the file contents into a single string.\n" |
| "\n" |
| " \"value\"\n" |
| " Parse the input as if it was a literal rvalue in a buildfile.\n" |
| " Examples of typical program output using this mode:\n" |
| " [ \"foo\", \"bar\" ] (result will be a list)\n" |
| " or\n" |
| " \"foo bar\" (result will be a string)\n" |
| " or\n" |
| " 5 (result will be an integer)\n" |
| "\n" |
| " Note that if the input is empty, the result will be a null value\n" |
| " which will produce an error if assigned to a variable.\n" |
| "\n" |
| " \"trim ...\"\n" |
| " Prefixing any of the other transformations with the word \"trim\"\n" |
| " will result in whitespace being trimmed from the beginning and end\n" |
| " of the result before processing.\n" |
| "\n" |
| " Examples: \"trim string\" or \"trim list lines\"\n" |
| "\n" |
| " Note that \"trim value\" is useless because the value parser skips\n" |
| " whitespace anyway.\n"; |
| |
| Value ConvertInputToValue(const Settings* settings, |
| const std::string& input, |
| const ParseNode* origin, |
| const Value& input_conversion_value, |
| Err* err) { |
| if (input_conversion_value.type() == Value::NONE) |
| return Value(); // Allow null inputs to mean discard the result. |
| if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) |
| return Value(); |
| return DoConvertInputToValue(settings, input, origin, input_conversion_value, |
| input_conversion_value.string_value(), err); |
| } |