OLD | NEW |
| (Empty) |
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 "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 // Returns the "first bit" of some script output for writing to error messages. | |
23 std::string GetExampleOfBadInput(const std::string& input) { | |
24 std::string result(input); | |
25 | |
26 // Maybe the result starts with a blank line or something, which we don't | |
27 // want. | |
28 TrimWhitespaceASCII(result, TRIM_ALL, &result); | |
29 | |
30 // Now take the first line, or the first set of chars, whichever is shorter. | |
31 bool trimmed = false; | |
32 size_t newline_offset = result.find('\n'); | |
33 if (newline_offset != std::string::npos) { | |
34 trimmed = true; | |
35 result.resize(newline_offset); | |
36 } | |
37 TrimWhitespaceASCII(result, TRIM_ALL, &result); | |
38 | |
39 const int kMaxSize = 50; | |
40 if (result.size() > kMaxSize) { | |
41 trimmed = true; | |
42 result.resize(kMaxSize); | |
43 } | |
44 | |
45 if (trimmed) | |
46 result.append("..."); | |
47 return result; | |
48 } | |
49 | |
50 // When parsing the result as a value, we may get various types of errors. | |
51 // This creates an error message for this case with an optional nested error | |
52 // message to reference. If there is no nested err, pass Err(). | |
53 // | |
54 // This code also takes care to rewrite the original error which will reference | |
55 // the temporary InputFile which won't exist when the error is propogated | |
56 // out to a higher level. | |
57 Err MakeParseErr(const std::string& input, | |
58 const ParseNode* origin, | |
59 const Err& nested) { | |
60 std::string help_text = | |
61 "When parsing a result as a \"value\" it should look like a list:\n" | |
62 " [ \"a\", \"b\", 5 ]\n" | |
63 "or a single literal:\n" | |
64 " \"my result\"\n" | |
65 "but instead I got this, which I find very confusing:\n"; | |
66 help_text.append(input); | |
67 if (nested.has_error()) | |
68 help_text.append("\nThe exact error was:"); | |
69 | |
70 Err result(origin, "Script result wasn't a valid value.", help_text); | |
71 if (nested.has_error()) { | |
72 result.AppendSubErr(Err(LocationRange(), nested.message(), | |
73 nested.help_text())); | |
74 } | |
75 return result; | |
76 } | |
77 | |
78 // Sets the origin of the value and any nested values with the given node. | |
79 void RecursivelySetOrigin(Value* value, const ParseNode* origin) { | |
80 value->set_origin(origin); | |
81 if (value->type() == Value::LIST) { | |
82 std::vector<Value>& list_value = value->list_value(); | |
83 for (size_t i = 0; i < list_value.size(); i++) | |
84 RecursivelySetOrigin(&list_value[i], origin); | |
85 } | |
86 } | |
87 | |
88 Value ParseString(const std::string& input, | |
89 const ParseNode* origin, | |
90 Err* err) { | |
91 SourceFile empty_source_for_most_vexing_parse; | |
92 InputFile input_file(empty_source_for_most_vexing_parse); | |
93 input_file.SetContents(input); | |
94 | |
95 std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, err); | |
96 if (err->has_error()) { | |
97 *err = MakeParseErr(input, origin, *err); | |
98 return Value(); | |
99 } | |
100 | |
101 scoped_ptr<ParseNode> expression = Parser::ParseExpression(tokens, err); | |
102 if (err->has_error()) { | |
103 *err = MakeParseErr(input, origin, *err); | |
104 return Value(); | |
105 } | |
106 | |
107 // It's valid for the result to be a null pointer, this just means that the | |
108 // script returned nothing. | |
109 if (!expression) | |
110 return Value(); | |
111 | |
112 // The result should either be a list or a literal, anything else is | |
113 // invalid. | |
114 if (!expression->AsList() && !expression->AsLiteral()) { | |
115 *err = MakeParseErr(input, origin, Err()); | |
116 return Value(); | |
117 } | |
118 | |
119 BuildSettings build_settings; | |
120 Label empty_label; | |
121 Toolchain toolchain(empty_label); | |
122 Settings settings(&build_settings, &toolchain, std::string()); | |
123 Scope scope(&settings); | |
124 | |
125 Err nested_err; | |
126 Value result = expression->Execute(&scope, &nested_err); | |
127 if (nested_err.has_error()) { | |
128 *err = MakeParseErr(input, origin, nested_err); | |
129 return Value(); | |
130 } | |
131 | |
132 // The returned value will have references to the temporary parse nodes we | |
133 // made on the stack. If the values are used in an error message in the | |
134 // future, this will crash. Reset the origin of all values to be our | |
135 // containing origin. | |
136 RecursivelySetOrigin(&result, origin); | |
137 return result; | |
138 } | |
139 | |
140 Value ParseList(const std::string& input, | |
141 const ParseNode* origin, | |
142 Err* err) { | |
143 Value ret(origin, Value::LIST); | |
144 std::vector<std::string> as_lines; | |
145 base::SplitString(input, '\n', &as_lines); | |
146 | |
147 // Trim empty lines from the end. | |
148 // Do we want to make this configurable? | |
149 while (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) | |
150 as_lines.resize(as_lines.size() - 1); | |
151 | |
152 ret.list_value().reserve(as_lines.size()); | |
153 for (size_t i = 0; i < as_lines.size(); i++) | |
154 ret.list_value().push_back(Value(origin, as_lines[i])); | |
155 return ret; | |
156 } | |
157 | |
158 } // namespace | |
159 | |
160 /* | |
161 input_conversion: Specifies how to transform input to a variable. | |
162 | |
163 input_conversion is an argument to read_file and exec_script that specifies | |
164 how the result of the read operation should be converted into a variable. | |
165 | |
166 "list lines": | |
167 Return the file contents as a list, with a string for each line. The | |
168 newlines will not be present in the result. Empty newlines will be | |
169 trimmed from the trailing end of the returned list. | |
170 | |
171 "value": | |
172 Parse the input as if it was a literal rvalue in a buildfile. | |
173 Examples of typical program output using this mode: | |
174 [ "foo", "bar" ] (result will be a list) | |
175 or | |
176 "foo bar" (result will be a string) | |
177 or | |
178 5 (result will be an integer) | |
179 | |
180 Note that if the input is empty, the result will be a null value which | |
181 will produce an error if assigned to a variable. | |
182 | |
183 "string": | |
184 Return the file contents into a single string. | |
185 */ | |
186 | |
187 Value ConvertInputToValue(const std::string& input, | |
188 const ParseNode* origin, | |
189 const Value& input_conversion_value, | |
190 Err* err) { | |
191 if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) | |
192 return Value(); | |
193 const std::string& input_conversion = input_conversion_value.string_value(); | |
194 | |
195 if (input_conversion == "value") | |
196 return ParseString(input, origin, err); | |
197 if (input_conversion == "string") | |
198 return Value(origin, input); | |
199 if (input_conversion == "list lines") | |
200 return ParseList(input, origin, err); | |
201 | |
202 *err = Err(input_conversion_value, "Not a valid read file mode.", | |
203 "Have you considered a career in retail?"); | |
204 return Value(); | |
205 } | |
OLD | NEW |