Index: tools/gn/input_conversion.cc |
diff --git a/tools/gn/input_conversion.cc b/tools/gn/input_conversion.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c0a0685ca36877fca9116358f698c74ed39313bf |
--- /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(); |
+} |