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.
5 #include "tools/gn/input_conversion.h"
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"
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
);
26 // Maybe the result starts with a blank line or something, which we don't
28 TrimWhitespaceASCII(result
, TRIM_ALL
, &result
);
30 // Now take the first line, or the first set of chars, whichever is shorter.
32 size_t newline_offset
= result
.find('\n');
33 if (newline_offset
!= std::string::npos
) {
35 result
.resize(newline_offset
);
37 TrimWhitespaceASCII(result
, TRIM_ALL
, &result
);
39 const size_t kMaxSize
= 50;
40 if (result
.size() > kMaxSize
) {
42 result
.resize(kMaxSize
);
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().
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
,
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"
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:");
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(),
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
);
88 Value
ParseString(const std::string
& input
,
89 const ParseNode
* origin
,
91 SourceFile empty_source_for_most_vexing_parse
;
92 InputFile
input_file(empty_source_for_most_vexing_parse
);
93 input_file
.SetContents(input
);
95 std::vector
<Token
> tokens
= Tokenizer::Tokenize(&input_file
, err
);
96 if (err
->has_error()) {
97 *err
= MakeParseErr(input
, origin
, *err
);
101 scoped_ptr
<ParseNode
> expression
= Parser::ParseExpression(tokens
, err
);
102 if (err
->has_error()) {
103 *err
= MakeParseErr(input
, origin
, *err
);
107 // It's valid for the result to be a null pointer, this just means that the
108 // script returned nothing.
112 // The result should either be a list or a literal, anything else is
114 if (!expression
->AsList() && !expression
->AsLiteral()) {
115 *err
= MakeParseErr(input
, origin
, Err());
119 BuildSettings build_settings
;
120 Settings
settings(&build_settings
, std::string());
121 Scope
scope(&settings
);
124 Value result
= expression
->Execute(&scope
, &nested_err
);
125 if (nested_err
.has_error()) {
126 *err
= MakeParseErr(input
, origin
, nested_err
);
130 // The returned value will have references to the temporary parse nodes we
131 // made on the stack. If the values are used in an error message in the
132 // future, this will crash. Reset the origin of all values to be our
133 // containing origin.
134 RecursivelySetOrigin(&result
, origin
);
138 Value
ParseList(const std::string
& input
,
139 const ParseNode
* origin
,
141 Value
ret(origin
, Value::LIST
);
142 std::vector
<std::string
> as_lines
;
143 base::SplitString(input
, '\n', &as_lines
);
145 // Trim empty lines from the end.
146 // Do we want to make this configurable?
147 while (!as_lines
.empty() && as_lines
[as_lines
.size() - 1].empty())
148 as_lines
.resize(as_lines
.size() - 1);
150 ret
.list_value().reserve(as_lines
.size());
151 for (size_t i
= 0; i
< as_lines
.size(); i
++)
152 ret
.list_value().push_back(Value(origin
, as_lines
[i
]));
158 extern const char kInputConversion_Help
[] =
159 "input_conversion: Specifies how to transform input to a variable.\n"
161 " input_conversion is an argument to read_file and exec_script that\n"
162 " specifies how the result of the read operation should be converted\n"
163 " into a variable.\n"
166 " Return the file contents as a list, with a string for each line.\n"
167 " The newlines will not be present in the result. Empty newlines\n"
168 " will be trimmed from the trailing end of the returned list.\n"
171 " Parse the input as if it was a literal rvalue in a buildfile.\n"
172 " Examples of typical program output using this mode:\n"
173 " [ \"foo\", \"bar\" ] (result will be a list)\n"
175 " \"foo bar\" (result will be a string)\n"
177 " 5 (result will be an integer)\n"
179 " Note that if the input is empty, the result will be a null value\n"
180 " which will produce an error if assigned to a variable.\n"
183 " Return the file contents into a single string.\n";
185 Value
ConvertInputToValue(const std::string
& input
,
186 const ParseNode
* origin
,
187 const Value
& input_conversion_value
,
189 if (!input_conversion_value
.VerifyTypeIs(Value::STRING
, err
))
191 const std::string
& input_conversion
= input_conversion_value
.string_value();
193 if (input_conversion
== "value")
194 return ParseString(input
, origin
, err
);
195 if (input_conversion
== "string")
196 return Value(origin
, input
);
197 if (input_conversion
== "list lines")
198 return ParseList(input
, origin
, err
);
200 *err
= Err(input_conversion_value
, "Not a valid read file mode.",
201 "Have you considered a career in retail?");