summaryrefslogtreecommitdiffstats
path: root/tools/gn/input_conversion.cc
blob: 3d9d355c8a346c3e38be5bff40cceabb45aa4d06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// 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 "tools/gn/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/scheduler.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
#include "tools/gn/tokenizer.h"
#include "tools/gn/value.h"

namespace {

enum ValueOrScope {
  PARSE_VALUE,  // Treat the input as an expression.
  PARSE_SCOPE,  // Treat the input as code and return the resulting scope.
};

// Sets the origin of the value and any nested values with the given node.
Value ParseValueOrScope(const Settings* settings,
                        const std::string& input,
                        ValueOrScope what,
                        const ParseNode* origin,
                        Err* err) {
  // The memory for these will be kept around by the input file manager
  // so the origin parse nodes for the values will be preserved.
  InputFile* input_file;
  std::vector<Token>* tokens;
  scoped_ptr<ParseNode>* parse_root_ptr;
  g_scheduler->input_file_manager()->AddDynamicInput(
      SourceFile(), &input_file, &tokens, &parse_root_ptr);

  input_file->SetContents(input);
  if (origin) {
    // This description will be the blame for any error messages caused by
    // script parsing or if a value is blamed. It will say
    // "Error at <...>:line:char" so here we try to make a string for <...>
    // that reads well in this context.
    input_file->set_friendly_name(
        "dynamically parsed input that " +
        origin->GetRange().begin().Describe(true) +
        " loaded ");
  } else {
    input_file->set_friendly_name("dynamic input");
  }

  *tokens = Tokenizer::Tokenize(input_file, err);
  if (err->has_error())
    return Value();

  // Parse the file according to what we're looking for.
  if (what == PARSE_VALUE)
    *parse_root_ptr = Parser::ParseValue(*tokens, err);
  else
    *parse_root_ptr = Parser::Parse(*tokens, err);  // Will return a Block.
  if (err->has_error())
    return Value();
  ParseNode* parse_root = parse_root_ptr->get();  // For nicer syntax below.

  // It's valid for the result to be a null pointer, this just means that the
  // script returned nothing.
  if (!parse_root)
    return Value();

  scoped_ptr<Scope> scope(new Scope(settings));
  Value result = parse_root->Execute(scope.get(), err);
  if (err->has_error())
    return Value();

  // When we want the result as a scope, the result is actually the scope
  // we made, rather than the result of running the block (which will be empty).
  if (what == PARSE_SCOPE) {
    DCHECK(result.type() == Value::NONE);
    result = Value(origin, scope.Pass());
  }
  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 one empty line from the end since the last line might end in a
  // newline. If the user wants more trimming, they'll specify "trim" in the
  // input conversion options.
  if (!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 (const auto& line : as_lines)
    ret.list_value().push_back(Value(origin, line));
  return ret;
}

// Backend for ConvertInputToValue, this takes the extracted string for the
// input conversion so we can recursively call ourselves to handle the optional
// "trim" prefix. This original value is also kept for the purposes of throwing
// errors.
Value DoConvertInputToValue(const Settings* settings,
                            const std::string& input,
                            const ParseNode* origin,
                            const Value& original_input_conversion,
                            const std::string& input_conversion,
                            Err* err) {
  if (input_conversion.empty())
    return Value();  // Empty string means discard the result.

  const char kTrimPrefix[] = "trim ";
  if (StartsWithASCII(input_conversion, kTrimPrefix, true)) {
    std::string trimmed;
    base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed);

    // Remove "trim" prefix from the input conversion and re-run.
    return DoConvertInputToValue(
        settings, trimmed, origin, original_input_conversion,
        input_conversion.substr(arraysize(kTrimPrefix) - 1), err);
  }

  if (input_conversion == "value")
    return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err);
  if (input_conversion == "string")
    return Value(origin, input);
  if (input_conversion == "list lines")
    return ParseList(input, origin, err);
  if (input_conversion == "scope")
    return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err);

  *err = Err(original_input_conversion, "Not a valid input_conversion.",
             "Have you considered a career in retail?");
  return Value();
}

}  // namespace

extern const char kInputConversion_Help[] =
    "input_conversion: Specifies how to transform input to a variable.\n"
    "\n"
    "  input_conversion is an argument to read_file and exec_script that\n"
    "  specifies how the result of the read operation should be converted\n"
    "  into a variable.\n"
    "\n"
    "  \"\" (the default)\n"
    "      Discard the result and return None.\n"
    "\n"
    "  \"list lines\"\n"
    "      Return the file contents as a list, with a string for each line.\n"
    "      The newlines will not be present in the result. The last line may\n"
    "      or may not end in a newline.\n"
    "\n"
    "      After splitting, each individual line will be trimmed of\n"
    "      whitespace on both ends.\n"
    "\n"
    "  \"scope\"\n"
    "      Execute the block as GN code and return a scope with the\n"
    "      resulting values in it. If the input was:\n"
    "        a = [ \"hello.cc\", \"world.cc\" ]\n"
    "        b = 26\n"
    "      and you read the result into a variable named \"val\", then you\n"
    "      could access contents the \".\" operator on \"val\":\n"
    "        sources = val.a\n"
    "        some_count = val.b\n"
    "\n"
    "  \"string\"\n"
    "      Return the file contents into a single string.\n"
    "\n"
    "  \"value\"\n"
    "      Parse the input as if it was a literal rvalue in a buildfile.\n"
    "      Examples of typical program output using this mode:\n"
    "        [ \"foo\", \"bar\" ]     (result will be a list)\n"
    "      or\n"
    "        \"foo bar\"            (result will be a string)\n"
    "      or\n"
    "        5                    (result will be an integer)\n"
    "\n"
    "      Note that if the input is empty, the result will be a null value\n"
    "      which will produce an error if assigned to a variable.\n"
    "\n"
    "  \"trim ...\"\n"
    "      Prefixing any of the other transformations with the word \"trim\"\n"
    "      will result in whitespace being trimmed from the beginning and end\n"
    "      of the result before processing.\n"
    "\n"
    "      Examples: \"trim string\" or \"trim list lines\"\n"
    "\n"
    "      Note that \"trim value\" is useless because the value parser skips\n"
    "      whitespace anyway.\n";

Value ConvertInputToValue(const Settings* settings,
                          const std::string& input,
                          const ParseNode* origin,
                          const Value& input_conversion_value,
                          Err* err) {
  if (input_conversion_value.type() == Value::NONE)
    return Value();  // Allow null inputs to mean discard the result.
  if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
    return Value();
  return DoConvertInputToValue(settings, input, origin, input_conversion_value,
                               input_conversion_value.string_value(), err);
}