| 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 |