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 |