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 extern const char kInputConversion_Help[] = 161 "input_conversion: Specifies how to transform input to a variable.\n" 162 "\n" 163 " input_conversion is an argument to read_file and exec_script that\n" 164 " specifies how the result of the read operation should be converted\n" 165 " into a variable.\n" 166 "\n" 167 " \"list lines\"\n" 168 " Return the file contents as a list, with a string for each line.\n" 169 " The newlines will not be present in the result. Empty newlines\n" 170 " will be trimmed from the trailing end of the returned list.\n" 171 "\n" 172 " \"value\"\n" 173 " Parse the input as if it was a literal rvalue in a buildfile.\n" 174 " Examples of typical program output using this mode:\n" 175 " [ \"foo\", \"bar\" ] (result will be a list)\n" 176 " or\n" 177 " \"foo bar\" (result will be a string)\n" 178 " or\n" 179 " 5 (result will be an integer)\n" 180 "\n" 181 " Note that if the input is empty, the result will be a null value\n" 182 " which will produce an error if assigned to a variable.\n" 183 "\n" 184 " \"string\"\n" 185 " Return the file contents into a single string.\n"; 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 } 206