// 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 "input_conversion.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/scope.h" #include "tools/gn/settings.h" #include "tools/gn/tokenizer.h" #include "tools/gn/value.h" namespace { // Returns the "first bit" of some script output for writing to error messages. std::string GetExampleOfBadInput(const std::string& input) { std::string result(input); // Maybe the result starts with a blank line or something, which we don't // want. TrimWhitespaceASCII(result, TRIM_ALL, &result); // Now take the first line, or the first set of chars, whichever is shorter. bool trimmed = false; size_t newline_offset = result.find('\n'); if (newline_offset != std::string::npos) { trimmed = true; result.resize(newline_offset); } TrimWhitespaceASCII(result, TRIM_ALL, &result); const size_t kMaxSize = 50; if (result.size() > kMaxSize) { trimmed = true; result.resize(kMaxSize); } if (trimmed) result.append("..."); return result; } // When parsing the result as a value, we may get various types of errors. // This creates an error message for this case with an optional nested error // message to reference. If there is no nested err, pass Err(). // // This code also takes care to rewrite the original error which will reference // the temporary InputFile which won't exist when the error is propogated // out to a higher level. Err MakeParseErr(const std::string& input, const ParseNode* origin, const Err& nested) { std::string help_text = "When parsing a result as a \"value\" it should look like a list:\n" " [ \"a\", \"b\", 5 ]\n" "or a single literal:\n" " \"my result\"\n" "but instead I got this, which I find very confusing:\n"; help_text.append(input); if (nested.has_error()) help_text.append("\nThe exact error was:"); Err result(origin, "Script result wasn't a valid value.", help_text); if (nested.has_error()) { result.AppendSubErr(Err(LocationRange(), nested.message(), nested.help_text())); } return result; } // Sets the origin of the value and any nested values with the given node. void RecursivelySetOrigin(Value* value, const ParseNode* origin) { value->set_origin(origin); if (value->type() == Value::LIST) { std::vector& list_value = value->list_value(); for (size_t i = 0; i < list_value.size(); i++) RecursivelySetOrigin(&list_value[i], origin); } } Value ParseString(const std::string& input, const ParseNode* origin, Err* err) { SourceFile empty_source_for_most_vexing_parse; InputFile input_file(empty_source_for_most_vexing_parse); input_file.SetContents(input); std::vector tokens = Tokenizer::Tokenize(&input_file, err); if (err->has_error()) { *err = MakeParseErr(input, origin, *err); return Value(); } scoped_ptr expression = Parser::ParseExpression(tokens, err); if (err->has_error()) { *err = MakeParseErr(input, origin, *err); return Value(); } // It's valid for the result to be a null pointer, this just means that the // script returned nothing. if (!expression) return Value(); // The result should either be a list or a literal, anything else is // invalid. if (!expression->AsList() && !expression->AsLiteral()) { *err = MakeParseErr(input, origin, Err()); return Value(); } BuildSettings build_settings; Label empty_label; Toolchain toolchain(empty_label); Settings settings(&build_settings, &toolchain, std::string()); Scope scope(&settings); Err nested_err; Value result = expression->Execute(&scope, &nested_err); if (nested_err.has_error()) { *err = MakeParseErr(input, origin, nested_err); return Value(); } // The returned value will have references to the temporary parse nodes we // made on the stack. If the values are used in an error message in the // future, this will crash. Reset the origin of all values to be our // containing origin. RecursivelySetOrigin(&result, origin); return result; } Value ParseList(const std::string& input, const ParseNode* origin, Err* err) { Value ret(origin, Value::LIST); std::vector as_lines; base::SplitString(input, '\n', &as_lines); // Trim empty lines from the end. // Do we want to make this configurable? while (!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 (size_t i = 0; i < as_lines.size(); i++) ret.list_value().push_back(Value(origin, as_lines[i])); return ret; } } // 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" " \"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. Empty newlines\n" " will be trimmed from the trailing end of the returned list.\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" " \"string\"\n" " Return the file contents into a single string.\n"; Value ConvertInputToValue(const std::string& input, const ParseNode* origin, const Value& input_conversion_value, Err* err) { if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) return Value(); const std::string& input_conversion = input_conversion_value.string_value(); if (input_conversion == "value") return ParseString(input, origin, err); if (input_conversion == "string") return Value(origin, input); if (input_conversion == "list lines") return ParseList(input, origin, err); *err = Err(input_conversion_value, "Not a valid read file mode.", "Have you considered a career in retail?"); return Value(); }