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 "tools/gn/input_conversion.h"
6
7 #include "base/strings/string_split.h"
8 #include "base/strings/string_util.h"
9 #include "tools/gn/build_settings.h"
10 #include "tools/gn/err.h"
11 #include "tools/gn/input_file.h"
12 #include "tools/gn/label.h"
13 #include "tools/gn/parse_tree.h"
14 #include "tools/gn/parser.h"
15 #include "tools/gn/scope.h"
16 #include "tools/gn/settings.h"
17 #include "tools/gn/tokenizer.h"
18 #include "tools/gn/value.h"
19
20 namespace {
21
22 // When parsing the result as a value, we may get various types of errors.
23 // This creates an error message for this case with an optional nested error
24 // message to reference. If there is no nested err, pass Err().
25 //
26 // This code also takes care to rewrite the original error which will reference
27 // the temporary InputFile which won't exist when the error is propogated
28 // out to a higher level.
MakeParseErr(const std::string & input,const ParseNode * origin,const Err & nested)29 Err MakeParseErr(const std::string& input,
30 const ParseNode* origin,
31 const Err& nested) {
32 std::string help_text =
33 "When parsing a result as a \"value\" it should look like a list:\n"
34 " [ \"a\", \"b\", 5 ]\n"
35 "or a single literal:\n"
36 " \"my result\"\n"
37 "but instead I got this, which I find very confusing:\n";
38 help_text.append(input);
39 if (nested.has_error())
40 help_text.append("\nThe exact error was:");
41
42 Err result(origin, "Script result wasn't a valid value.", help_text);
43 if (nested.has_error()) {
44 result.AppendSubErr(Err(LocationRange(), nested.message(),
45 nested.help_text()));
46 }
47 return result;
48 }
49
50 // Sets the origin of the value and any nested values with the given node.
ParseString(const std::string & input,const ParseNode * origin,Err * err)51 Value ParseString(const std::string& input,
52 const ParseNode* origin,
53 Err* err) {
54 SourceFile empty_source_for_most_vexing_parse;
55 InputFile input_file(empty_source_for_most_vexing_parse);
56 input_file.SetContents(input);
57
58 std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, err);
59 if (err->has_error()) {
60 *err = MakeParseErr(input, origin, *err);
61 return Value();
62 }
63
64 scoped_ptr<ParseNode> expression = Parser::ParseExpression(tokens, err);
65 if (err->has_error()) {
66 *err = MakeParseErr(input, origin, *err);
67 return Value();
68 }
69
70 // It's valid for the result to be a null pointer, this just means that the
71 // script returned nothing.
72 if (!expression)
73 return Value();
74
75 // The result should either be a list or a literal, anything else is
76 // invalid.
77 if (!expression->AsList() && !expression->AsLiteral()) {
78 *err = MakeParseErr(input, origin, Err());
79 return Value();
80 }
81
82 BuildSettings build_settings;
83 Settings settings(&build_settings, std::string());
84 Scope scope(&settings);
85
86 Err nested_err;
87 Value result = expression->Execute(&scope, &nested_err);
88 if (nested_err.has_error()) {
89 *err = MakeParseErr(input, origin, nested_err);
90 return Value();
91 }
92
93 // The returned value will have references to the temporary parse nodes we
94 // made on the stack. If the values are used in an error message in the
95 // future, this will crash. Reset the origin of all values to be our
96 // containing origin.
97 result.RecursivelySetOrigin(origin);
98 return result;
99 }
100
ParseList(const std::string & input,const ParseNode * origin,Err * err)101 Value ParseList(const std::string& input,
102 const ParseNode* origin,
103 Err* err) {
104 Value ret(origin, Value::LIST);
105 std::vector<std::string> as_lines;
106 base::SplitString(input, '\n', &as_lines);
107
108 // Trim empty lines from the end.
109 // Do we want to make this configurable?
110 while (!as_lines.empty() && as_lines[as_lines.size() - 1].empty())
111 as_lines.resize(as_lines.size() - 1);
112
113 ret.list_value().reserve(as_lines.size());
114 for (size_t i = 0; i < as_lines.size(); i++)
115 ret.list_value().push_back(Value(origin, as_lines[i]));
116 return ret;
117 }
118
119 } // namespace
120
121 extern const char kInputConversion_Help[] =
122 "input_conversion: Specifies how to transform input to a variable.\n"
123 "\n"
124 " input_conversion is an argument to read_file and exec_script that\n"
125 " specifies how the result of the read operation should be converted\n"
126 " into a variable.\n"
127 "\n"
128 " \"list lines\"\n"
129 " Return the file contents as a list, with a string for each line.\n"
130 " The newlines will not be present in the result. Empty newlines\n"
131 " will be trimmed from the trailing end of the returned list.\n"
132 "\n"
133 " \"value\"\n"
134 " Parse the input as if it was a literal rvalue in a buildfile.\n"
135 " Examples of typical program output using this mode:\n"
136 " [ \"foo\", \"bar\" ] (result will be a list)\n"
137 " or\n"
138 " \"foo bar\" (result will be a string)\n"
139 " or\n"
140 " 5 (result will be an integer)\n"
141 "\n"
142 " Note that if the input is empty, the result will be a null value\n"
143 " which will produce an error if assigned to a variable.\n"
144 "\n"
145 " \"string\"\n"
146 " Return the file contents into a single string.\n";
147
ConvertInputToValue(const std::string & input,const ParseNode * origin,const Value & input_conversion_value,Err * err)148 Value ConvertInputToValue(const std::string& input,
149 const ParseNode* origin,
150 const Value& input_conversion_value,
151 Err* err) {
152 if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
153 return Value();
154 const std::string& input_conversion = input_conversion_value.string_value();
155
156 if (input_conversion == "value")
157 return ParseString(input, origin, err);
158 if (input_conversion == "string")
159 return Value(origin, input);
160 if (input_conversion == "list lines")
161 return ParseList(input, origin, err);
162
163 *err = Err(input_conversion_value, "Not a valid read file mode.",
164 "Have you considered a career in retail?");
165 return Value();
166 }
167