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/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 // When parsing the result as a value, we may get various types of errors. 23 // This creates an error message for this case with an optional nested error 24 // message to reference. If there is no nested err, pass Err(). 25 // 26 // This code also takes care to rewrite the original error which will reference 27 // the temporary InputFile which won't exist when the error is propogated 28 // out to a higher level. 29 Err MakeParseErr(const std::string& input, 30 const ParseNode* origin, 31 const Err& nested) { 32 std::string help_text = 33 "When parsing a result as a \"value\" it should look like a list:\n" 34 " [ \"a\", \"b\", 5 ]\n" 35 "or a single literal:\n" 36 " \"my result\"\n" 37 "but instead I got this, which I find very confusing:\n"; 38 help_text.append(input); 39 if (nested.has_error()) 40 help_text.append("\nThe exact error was:"); 41 42 Err result(origin, "Script result wasn't a valid value.", help_text); 43 if (nested.has_error()) { 44 result.AppendSubErr(Err(LocationRange(), nested.message(), 45 nested.help_text())); 46 } 47 return result; 48 } 49 50 // Sets the origin of the value and any nested values with the given node. 51 Value ParseString(const std::string& input, 52 const ParseNode* origin, 53 Err* err) { 54 SourceFile empty_source_for_most_vexing_parse; 55 InputFile input_file(empty_source_for_most_vexing_parse); 56 input_file.SetContents(input); 57 58 std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, err); 59 if (err->has_error()) { 60 *err = MakeParseErr(input, origin, *err); 61 return Value(); 62 } 63 64 scoped_ptr<ParseNode> expression = Parser::ParseExpression(tokens, err); 65 if (err->has_error()) { 66 *err = MakeParseErr(input, origin, *err); 67 return Value(); 68 } 69 70 // It's valid for the result to be a null pointer, this just means that the 71 // script returned nothing. 72 if (!expression) 73 return Value(); 74 75 // The result should either be a list or a literal, anything else is 76 // invalid. 77 if (!expression->AsList() && !expression->AsLiteral()) { 78 *err = MakeParseErr(input, origin, Err()); 79 return Value(); 80 } 81 82 BuildSettings build_settings; 83 Settings settings(&build_settings, std::string()); 84 Scope scope(&settings); 85 86 Err nested_err; 87 Value result = expression->Execute(&scope, &nested_err); 88 if (nested_err.has_error()) { 89 *err = MakeParseErr(input, origin, nested_err); 90 return Value(); 91 } 92 93 // The returned value will have references to the temporary parse nodes we 94 // made on the stack. If the values are used in an error message in the 95 // future, this will crash. Reset the origin of all values to be our 96 // containing origin. 97 result.RecursivelySetOrigin(origin); 98 return result; 99 } 100 101 Value ParseList(const std::string& input, 102 const ParseNode* origin, 103 Err* err) { 104 Value ret(origin, Value::LIST); 105 std::vector<std::string> as_lines; 106 base::SplitString(input, '\n', &as_lines); 107 108 // Trim empty lines from the end. 109 // Do we want to make this configurable? 110 while (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) 111 as_lines.resize(as_lines.size() - 1); 112 113 ret.list_value().reserve(as_lines.size()); 114 for (size_t i = 0; i < as_lines.size(); i++) 115 ret.list_value().push_back(Value(origin, as_lines[i])); 116 return ret; 117 } 118 119 } // namespace 120 121 extern const char kInputConversion_Help[] = 122 "input_conversion: Specifies how to transform input to a variable.\n" 123 "\n" 124 " input_conversion is an argument to read_file and exec_script that\n" 125 " specifies how the result of the read operation should be converted\n" 126 " into a variable.\n" 127 "\n" 128 " \"list lines\"\n" 129 " Return the file contents as a list, with a string for each line.\n" 130 " The newlines will not be present in the result. Empty newlines\n" 131 " will be trimmed from the trailing end of the returned list.\n" 132 "\n" 133 " \"value\"\n" 134 " Parse the input as if it was a literal rvalue in a buildfile.\n" 135 " Examples of typical program output using this mode:\n" 136 " [ \"foo\", \"bar\" ] (result will be a list)\n" 137 " or\n" 138 " \"foo bar\" (result will be a string)\n" 139 " or\n" 140 " 5 (result will be an integer)\n" 141 "\n" 142 " Note that if the input is empty, the result will be a null value\n" 143 " which will produce an error if assigned to a variable.\n" 144 "\n" 145 " \"string\"\n" 146 " Return the file contents into a single string.\n"; 147 148 Value ConvertInputToValue(const std::string& input, 149 const ParseNode* origin, 150 const Value& input_conversion_value, 151 Err* err) { 152 if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) 153 return Value(); 154 const std::string& input_conversion = input_conversion_value.string_value(); 155 156 if (input_conversion == "value") 157 return ParseString(input, origin, err); 158 if (input_conversion == "string") 159 return Value(origin, input); 160 if (input_conversion == "list lines") 161 return ParseList(input, origin, err); 162 163 *err = Err(input_conversion_value, "Not a valid read file mode.", 164 "Have you considered a career in retail?"); 165 return Value(); 166 } 167