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/scheduler.h"
16#include "tools/gn/scope.h"
17#include "tools/gn/settings.h"
18#include "tools/gn/tokenizer.h"
19#include "tools/gn/value.h"
20
21namespace {
22
23enum ValueOrScope {
24  PARSE_VALUE,  // Treat the input as an expression.
25  PARSE_SCOPE,  // Treat the input as code and return the resulting scope.
26};
27
28// Sets the origin of the value and any nested values with the given node.
29Value ParseValueOrScope(const Settings* settings,
30                        const std::string& input,
31                        ValueOrScope what,
32                        const ParseNode* origin,
33                        Err* err) {
34  // The memory for these will be kept around by the input file manager
35  // so the origin parse nodes for the values will be preserved.
36  InputFile* input_file;
37  std::vector<Token>* tokens;
38  scoped_ptr<ParseNode>* parse_root_ptr;
39  g_scheduler->input_file_manager()->AddDynamicInput(
40      SourceFile(), &input_file, &tokens, &parse_root_ptr);
41
42  input_file->SetContents(input);
43  if (origin) {
44    // This description will be the blame for any error messages caused by
45    // script parsing or if a value is blamed. It will say
46    // "Error at <...>:line:char" so here we try to make a string for <...>
47    // that reads well in this context.
48    input_file->set_friendly_name(
49        "dynamically parsed input that " +
50        origin->GetRange().begin().Describe(true) +
51        " loaded ");
52  } else {
53    input_file->set_friendly_name("dynamic input");
54  }
55
56  *tokens = Tokenizer::Tokenize(input_file, err);
57  if (err->has_error())
58    return Value();
59
60  // Parse the file according to what we're looking for.
61  if (what == PARSE_VALUE)
62    *parse_root_ptr = Parser::ParseExpression(*tokens, err);
63  else
64    *parse_root_ptr = Parser::Parse(*tokens, err);  // Will return a Block.
65  if (err->has_error())
66    return Value();
67  ParseNode* parse_root = parse_root_ptr->get();  // For nicer syntax below.
68
69  // It's valid for the result to be a null pointer, this just means that the
70  // script returned nothing.
71  if (!parse_root)
72    return Value();
73
74  // When parsing as a value, the result should either be a list or a literal,
75  // anything else is invalid.
76  if (what == PARSE_VALUE) {
77    if (!parse_root->AsList() && !parse_root->AsLiteral())
78      return Value();
79  }
80
81  scoped_ptr<Scope> scope(new Scope(settings));
82
83  Value result = parse_root->Execute(scope.get(), err);
84  if (err->has_error())
85    return Value();
86
87  // When we want the result as a scope, the result is actually the scope
88  // we made, rather than the result of running the block (which will be empty).
89  if (what == PARSE_SCOPE) {
90    DCHECK(result.type() == Value::NONE);
91    result = Value(origin, scope.Pass());
92  }
93  return result;
94}
95
96Value ParseList(const std::string& input, const ParseNode* origin, Err* err) {
97  Value ret(origin, Value::LIST);
98  std::vector<std::string> as_lines;
99  base::SplitString(input, '\n', &as_lines);
100
101  // Trim one empty line from the end since the last line might end in a
102  // newline. If the user wants more trimming, they'll specify "trim" in the
103  // input conversion options.
104  if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty())
105    as_lines.resize(as_lines.size() - 1);
106
107  ret.list_value().reserve(as_lines.size());
108  for (size_t i = 0; i < as_lines.size(); i++)
109    ret.list_value().push_back(Value(origin, as_lines[i]));
110  return ret;
111}
112
113// Backend for ConvertInputToValue, this takes the extracted string for the
114// input conversion so we can recursively call ourselves to handle the optional
115// "trim" prefix. This original value is also kept for the purposes of throwing
116// errors.
117Value DoConvertInputToValue(const Settings* settings,
118                            const std::string& input,
119                            const ParseNode* origin,
120                            const Value& original_input_conversion,
121                            const std::string& input_conversion,
122                            Err* err) {
123  if (input_conversion.empty())
124    return Value();  // Empty string means discard the result.
125
126  const char kTrimPrefix[] = "trim ";
127  if (StartsWithASCII(input_conversion, kTrimPrefix, true)) {
128    std::string trimmed;
129    base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed);
130
131    // Remove "trim" prefix from the input conversion and re-run.
132    return DoConvertInputToValue(
133        settings, trimmed, origin, original_input_conversion,
134        input_conversion.substr(arraysize(kTrimPrefix) - 1), err);
135  }
136
137  if (input_conversion == "value")
138    return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err);
139  if (input_conversion == "string")
140    return Value(origin, input);
141  if (input_conversion == "list lines")
142    return ParseList(input, origin, err);
143  if (input_conversion == "scope")
144    return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err);
145
146  *err = Err(original_input_conversion, "Not a valid input_conversion.",
147             "Have you considered a career in retail?");
148  return Value();
149}
150
151}  // namespace
152
153extern const char kInputConversion_Help[] =
154    "input_conversion: Specifies how to transform input to a variable.\n"
155    "\n"
156    "  input_conversion is an argument to read_file and exec_script that\n"
157    "  specifies how the result of the read operation should be converted\n"
158    "  into a variable.\n"
159    "\n"
160    "  \"\" (the default)\n"
161    "      Discard the result and return None.\n"
162    "\n"
163    "  \"list lines\"\n"
164    "      Return the file contents as a list, with a string for each line.\n"
165    "      The newlines will not be present in the result. The last line may\n"
166    "      or may not end in a newline.\n"
167    "\n"
168    "      After splitting, each individual line will be trimmed of\n"
169    "      whitespace on both ends.\n"
170    "\n"
171    "  \"scope\"\n"
172    "      Execute the block as GN code and return a scope with the\n"
173    "      resulting values in it. If the input was:\n"
174    "        a = [ \"hello.cc\", \"world.cc\" ]\n"
175    "        b = 26\n"
176    "      and you read the result into a variable named \"val\", then you\n"
177    "      could access contents the \".\" operator on \"val\":\n"
178    "        sources = val.a\n"
179    "        some_count = val.b\n"
180    "\n"
181    "  \"string\"\n"
182    "      Return the file contents into a single string.\n"
183    "\n"
184    "  \"value\"\n"
185    "      Parse the input as if it was a literal rvalue in a buildfile.\n"
186    "      Examples of typical program output using this mode:\n"
187    "        [ \"foo\", \"bar\" ]     (result will be a list)\n"
188    "      or\n"
189    "        \"foo bar\"            (result will be a string)\n"
190    "      or\n"
191    "        5                    (result will be an integer)\n"
192    "\n"
193    "      Note that if the input is empty, the result will be a null value\n"
194    "      which will produce an error if assigned to a variable.\n"
195    "\n"
196    "  \"trim ...\"\n"
197    "      Prefixing any of the other transformations with the word \"trim\"\n"
198    "      will result in whitespace being trimmed from the beginning and end\n"
199    "      of the result before processing.\n"
200    "\n"
201    "      Examples: \"trim string\" or \"trim list lines\"\n"
202    "\n"
203    "      Note that \"trim value\" is useless because the value parser skips\n"
204    "      whitespace anyway.\n";
205
206Value ConvertInputToValue(const Settings* settings,
207                          const std::string& input,
208                          const ParseNode* origin,
209                          const Value& input_conversion_value,
210                          Err* err) {
211  if (input_conversion_value.type() == Value::NONE)
212    return Value();  // Allow null inputs to mean discard the result.
213  if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
214    return Value();
215  return DoConvertInputToValue(settings, input, origin, input_conversion_value,
216                               input_conversion_value.string_value(), err);
217}
218