diff options
Diffstat (limited to 'tools/gn/input_conversion.cc')
-rw-r--r-- | tools/gn/input_conversion.cc | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/tools/gn/input_conversion.cc b/tools/gn/input_conversion.cc new file mode 100644 index 0000000..c0a0685 --- /dev/null +++ b/tools/gn/input_conversion.cc @@ -0,0 +1,205 @@ +// 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 int 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<Value>& 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<Token> tokens = Tokenizer::Tokenize(&input_file, err); + if (err->has_error()) { + *err = MakeParseErr(input, origin, *err); + return Value(); + } + + scoped_ptr<ParseNode> 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<std::string> 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 + +/* +input_conversion: Specifies how to transform input to a variable. + + input_conversion is an argument to read_file and exec_script that specifies + how the result of the read operation should be converted into a variable. + + "list lines": + Return the file contents as a list, with a string for each line. The + newlines will not be present in the result. Empty newlines will be + trimmed from the trailing end of the returned list. + + "value": + Parse the input as if it was a literal rvalue in a buildfile. + Examples of typical program output using this mode: + [ "foo", "bar" ] (result will be a list) + or + "foo bar" (result will be a string) + or + 5 (result will be an integer) + + Note that if the input is empty, the result will be a null value which + will produce an error if assigned to a variable. + + "string": + Return the file contents into a single string. +*/ + +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(); +} |