| OLD | NEW | 
|    1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |    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 |    2 // Use of this source code is governed by a BSD-style license that can be | 
|    3 // found in the LICENSE file. |    3 // found in the LICENSE file. | 
|    4  |    4  | 
|    5 #include "tools/gn/input_conversion.h" |    5 #include "tools/gn/input_conversion.h" | 
|    6  |    6  | 
|    7 #include "base/strings/string_split.h" |    7 #include "base/strings/string_split.h" | 
|    8 #include "base/strings/string_util.h" |    8 #include "base/strings/string_util.h" | 
|    9 #include "tools/gn/build_settings.h" |    9 #include "tools/gn/build_settings.h" | 
|   10 #include "tools/gn/err.h" |   10 #include "tools/gn/err.h" | 
|   11 #include "tools/gn/input_file.h" |   11 #include "tools/gn/input_file.h" | 
|   12 #include "tools/gn/label.h" |   12 #include "tools/gn/label.h" | 
|   13 #include "tools/gn/parse_tree.h" |   13 #include "tools/gn/parse_tree.h" | 
|   14 #include "tools/gn/parser.h" |   14 #include "tools/gn/parser.h" | 
 |   15 #include "tools/gn/scheduler.h" | 
|   15 #include "tools/gn/scope.h" |   16 #include "tools/gn/scope.h" | 
|   16 #include "tools/gn/settings.h" |   17 #include "tools/gn/settings.h" | 
|   17 #include "tools/gn/tokenizer.h" |   18 #include "tools/gn/tokenizer.h" | 
|   18 #include "tools/gn/value.h" |   19 #include "tools/gn/value.h" | 
|   19  |   20  | 
|   20 namespace { |   21 namespace { | 
|   21  |   22  | 
|   22 // When parsing the result as a value, we may get various types of errors. |   23 enum ValueOrScope { | 
|   23 // This creates an error message for this case with an optional nested error |   24   PARSE_VALUE,  // Treat the input as an expression. | 
|   24 // message to reference. If there is no nested err, pass Err(). |   25   PARSE_SCOPE,  // Treat the input as code and return the resulting scope. | 
|   25 // |   26 }; | 
|   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  |   27  | 
|   42   Err result(origin, "Script result wasn't a valid value.", help_text); |   28 // Sets the origin of the value and any nested values with the given node. | 
|   43   if (nested.has_error()) { |   29 Value ParseValueOrScope(const Settings* settings, | 
|   44     result.AppendSubErr(Err(LocationRange(), nested.message(), |   30                         const std::string& input, | 
|   45                             nested.help_text())); |   31                         ValueOrScope what, | 
 |   32                         const ParseNode* origin, | 
 |   33                         Err* err) { | 
 |   34   // The memory for these will be kept around by the input file manager | 
 |   35   // so the origin parse nodes for the values will be preserved. | 
 |   36   InputFile* input_file; | 
 |   37   std::vector<Token>* tokens; | 
 |   38   scoped_ptr<ParseNode>* parse_root_ptr; | 
 |   39   g_scheduler->input_file_manager()->AddDynamicInput( | 
 |   40       &input_file, &tokens, &parse_root_ptr); | 
 |   41  | 
 |   42   input_file->SetContents(input); | 
 |   43   if (origin) { | 
 |   44     // This description will be the blame for any error messages caused by | 
 |   45     // script parsing or if a value is blamed. It will say | 
 |   46     // "Error at <...>:line:char" so here we try to make a string for <...> | 
 |   47     // that reads well in this context. | 
 |   48     input_file->set_friendly_name( | 
 |   49         "dynamically parsed input that " + | 
 |   50         origin->GetRange().begin().Describe(true) + | 
 |   51         " loaded "); | 
 |   52   } else { | 
 |   53     input_file->set_friendly_name("dynamic input"); | 
 |   54   } | 
 |   55  | 
 |   56   *tokens = Tokenizer::Tokenize(input_file, err); | 
 |   57   if (err->has_error()) | 
 |   58     return Value(); | 
 |   59  | 
 |   60   // Parse the file according to what we're looking for. | 
 |   61   if (what == PARSE_VALUE) | 
 |   62     *parse_root_ptr = Parser::ParseExpression(*tokens, err); | 
 |   63   else | 
 |   64     *parse_root_ptr = Parser::Parse(*tokens, err);  // Will return a Block. | 
 |   65   if (err->has_error()) | 
 |   66     return Value(); | 
 |   67   ParseNode* parse_root = parse_root_ptr->get();  // For nicer syntax below. | 
 |   68  | 
 |   69   // It's valid for the result to be a null pointer, this just means that the | 
 |   70   // script returned nothing. | 
 |   71   if (!parse_root) | 
 |   72     return Value(); | 
 |   73  | 
 |   74   // When parsing as a value, the result should either be a list or a literal, | 
 |   75   // anything else is invalid. | 
 |   76   if (what == PARSE_VALUE) { | 
 |   77     if (!parse_root->AsList() && !parse_root->AsLiteral()) | 
 |   78       return Value(); | 
 |   79   } | 
 |   80  | 
 |   81   scoped_ptr<Scope> scope(new Scope(settings)); | 
 |   82  | 
 |   83   Value result = parse_root->Execute(scope.get(), err); | 
 |   84   if (err->has_error()) | 
 |   85     return Value(); | 
 |   86  | 
 |   87   // When we want the result as a scope, the result is actually the scope | 
 |   88   // we made, rather than the result of running the block (which will be empty). | 
 |   89   if (what == PARSE_SCOPE) { | 
 |   90     DCHECK(result.type() == Value::NONE); | 
 |   91     result = Value(origin, scope.Pass()); | 
|   46   } |   92   } | 
|   47   return result; |   93   return result; | 
|   48 } |   94 } | 
|   49  |   95  | 
|   50 // Sets the origin of the value and any nested values with the given node. |   96 Value ParseList(const std::string& input, const ParseNode* origin, Err* err) { | 
|   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); |   97   Value ret(origin, Value::LIST); | 
|  105   std::vector<std::string> as_lines; |   98   std::vector<std::string> as_lines; | 
|  106   base::SplitString(input, '\n', &as_lines); |   99   base::SplitString(input, '\n', &as_lines); | 
|  107  |  100  | 
|  108   // Trim one empty line from the end since the last line might end in a |  101   // Trim one empty line from the end since the last line might end in a | 
|  109   // newline. If the user wants more trimming, they'll specify "trim" in the |  102   // newline. If the user wants more trimming, they'll specify "trim" in the | 
|  110   // input conversion options. |  103   // input conversion options. | 
|  111   if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) |  104   if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) | 
|  112     as_lines.resize(as_lines.size() - 1); |  105     as_lines.resize(as_lines.size() - 1); | 
|  113  |  106  | 
|  114   ret.list_value().reserve(as_lines.size()); |  107   ret.list_value().reserve(as_lines.size()); | 
|  115   for (size_t i = 0; i < as_lines.size(); i++) |  108   for (size_t i = 0; i < as_lines.size(); i++) | 
|  116     ret.list_value().push_back(Value(origin, as_lines[i])); |  109     ret.list_value().push_back(Value(origin, as_lines[i])); | 
|  117   return ret; |  110   return ret; | 
|  118 } |  111 } | 
|  119  |  112  | 
|  120 // Backend for ConvertInputToValue, this takes the extracted string for the |  113 // Backend for ConvertInputToValue, this takes the extracted string for the | 
|  121 // input conversion so we can recursively call ourselves to handle the optional |  114 // input conversion so we can recursively call ourselves to handle the optional | 
|  122 // "trim" prefix. This original value is also kept for the purposes of throwing |  115 // "trim" prefix. This original value is also kept for the purposes of throwing | 
|  123 // errors. |  116 // errors. | 
|  124 Value DoConvertInputToValue(const std::string& input, |  117 Value DoConvertInputToValue(const Settings* settings, | 
 |  118                             const std::string& input, | 
|  125                             const ParseNode* origin, |  119                             const ParseNode* origin, | 
|  126                             const Value& original_input_conversion, |  120                             const Value& original_input_conversion, | 
|  127                             const std::string& input_conversion, |  121                             const std::string& input_conversion, | 
|  128                             Err* err) { |  122                             Err* err) { | 
|  129   if (input_conversion.empty()) |  123   if (input_conversion.empty()) | 
|  130     return Value();  // Empty string means discard the result. |  124     return Value();  // Empty string means discard the result. | 
|  131  |  125  | 
|  132   const char kTrimPrefix[] = "trim "; |  126   const char kTrimPrefix[] = "trim "; | 
|  133   if (StartsWithASCII(input_conversion, kTrimPrefix, true)) { |  127   if (StartsWithASCII(input_conversion, kTrimPrefix, true)) { | 
|  134     std::string trimmed; |  128     std::string trimmed; | 
|  135     base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed); |  129     base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed); | 
|  136  |  130  | 
|  137     // Remove "trim" prefix from the input conversion and re-run. |  131     // Remove "trim" prefix from the input conversion and re-run. | 
|  138     return DoConvertInputToValue( |  132     return DoConvertInputToValue( | 
|  139         trimmed, origin, original_input_conversion, |  133         settings, trimmed, origin, original_input_conversion, | 
|  140         input_conversion.substr(arraysize(kTrimPrefix) - 1), err); |  134         input_conversion.substr(arraysize(kTrimPrefix) - 1), err); | 
|  141   } |  135   } | 
|  142  |  136  | 
|  143   if (input_conversion == "value") |  137   if (input_conversion == "value") | 
|  144     return ParseString(input, origin, err); |  138     return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err); | 
|  145   if (input_conversion == "string") |  139   if (input_conversion == "string") | 
|  146     return Value(origin, input); |  140     return Value(origin, input); | 
|  147   if (input_conversion == "list lines") |  141   if (input_conversion == "list lines") | 
|  148     return ParseList(input, origin, err); |  142     return ParseList(input, origin, err); | 
 |  143   if (input_conversion == "scope") | 
 |  144     return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err); | 
|  149  |  145  | 
|  150   *err = Err(original_input_conversion, "Not a valid input_conversion.", |  146   *err = Err(original_input_conversion, "Not a valid input_conversion.", | 
|  151              "Have you considered a career in retail?"); |  147              "Have you considered a career in retail?"); | 
|  152   return Value(); |  148   return Value(); | 
|  153 } |  149 } | 
|  154  |  150  | 
|  155 }  // namespace |  151 }  // namespace | 
|  156  |  152  | 
|  157 extern const char kInputConversion_Help[] = |  153 extern const char kInputConversion_Help[] = | 
|  158     "input_conversion: Specifies how to transform input to a variable.\n" |  154     "input_conversion: Specifies how to transform input to a variable.\n" | 
|  159     "\n" |  155     "\n" | 
|  160     "  input_conversion is an argument to read_file and exec_script that\n" |  156     "  input_conversion is an argument to read_file and exec_script that\n" | 
|  161     "  specifies how the result of the read operation should be converted\n" |  157     "  specifies how the result of the read operation should be converted\n" | 
|  162     "  into a variable.\n" |  158     "  into a variable.\n" | 
|  163     "\n" |  159     "\n" | 
|  164     "  \"\" (the default)\n" |  160     "  \"\" (the default)\n" | 
|  165     "      Discard the result and return None.\n" |  161     "      Discard the result and return None.\n" | 
|  166     "\n" |  162     "\n" | 
|  167     "  \"list lines\"\n" |  163     "  \"list lines\"\n" | 
|  168     "      Return the file contents as a list, with a string for each line.\n" |  164     "      Return the file contents as a list, with a string for each line.\n" | 
|  169     "      The newlines will not be present in the result. The last line may\n" |  165     "      The newlines will not be present in the result. The last line may\n" | 
|  170     "      or may not end in a newline.\n" |  166     "      or may not end in a newline.\n" | 
|  171     "\n" |  167     "\n" | 
|  172     "      After splitting, each individual line will be trimmed of\n" |  168     "      After splitting, each individual line will be trimmed of\n" | 
|  173     "      whitespace on both ends.\n" |  169     "      whitespace on both ends.\n" | 
|  174     "\n" |  170     "\n" | 
 |  171     "  \"scope\"\n" | 
 |  172     "      Execute the block as GN code and return a scope with the\n" | 
 |  173     "      resulting values in it. If the input was:\n" | 
 |  174     "        a = [ \"hello.cc\", \"world.cc\" ]\n" | 
 |  175     "        b = 26\n" | 
 |  176     "      and you read the result into a variable named \"val\", then you\n" | 
 |  177     "      could access contents the \".\" operator on \"val\":\n" | 
 |  178     "        sources = val.a\n" | 
 |  179     "        some_count = val.b\n" | 
 |  180     "\n" | 
 |  181     "  \"string\"\n" | 
 |  182     "      Return the file contents into a single string.\n" | 
 |  183     "\n" | 
|  175     "  \"value\"\n" |  184     "  \"value\"\n" | 
|  176     "      Parse the input as if it was a literal rvalue in a buildfile.\n" |  185     "      Parse the input as if it was a literal rvalue in a buildfile.\n" | 
|  177     "      Examples of typical program output using this mode:\n" |  186     "      Examples of typical program output using this mode:\n" | 
|  178     "        [ \"foo\", \"bar\" ]     (result will be a list)\n" |  187     "        [ \"foo\", \"bar\" ]     (result will be a list)\n" | 
|  179     "      or\n" |  188     "      or\n" | 
|  180     "        \"foo bar\"            (result will be a string)\n" |  189     "        \"foo bar\"            (result will be a string)\n" | 
|  181     "      or\n" |  190     "      or\n" | 
|  182     "        5                    (result will be an integer)\n" |  191     "        5                    (result will be an integer)\n" | 
|  183     "\n" |  192     "\n" | 
|  184     "      Note that if the input is empty, the result will be a null value\n" |  193     "      Note that if the input is empty, the result will be a null value\n" | 
|  185     "      which will produce an error if assigned to a variable.\n" |  194     "      which will produce an error if assigned to a variable.\n" | 
|  186     "\n" |  195     "\n" | 
|  187     "  \"string\"\n" |  | 
|  188     "      Return the file contents into a single string.\n" |  | 
|  189     "\n" |  | 
|  190     "  \"trim ...\"\n" |  196     "  \"trim ...\"\n" | 
|  191     "      Prefixing any of the other transformations with the word \"trim\"\n" |  197     "      Prefixing any of the other transformations with the word \"trim\"\n" | 
|  192     "      will result in whitespace being trimmed from the beginning and end\n" |  198     "      will result in whitespace being trimmed from the beginning and end\n" | 
|  193     "      of the result before processing.\n" |  199     "      of the result before processing.\n" | 
|  194     "\n" |  200     "\n" | 
|  195     "      Examples: \"trim string\" or \"trim list lines\"\n" |  201     "      Examples: \"trim string\" or \"trim list lines\"\n" | 
|  196     "\n" |  202     "\n" | 
|  197     "      Note that \"trim value\" is useless because the value parser skips\n" |  203     "      Note that \"trim value\" is useless because the value parser skips\n" | 
|  198     "      whitespace anyway.\n"; |  204     "      whitespace anyway.\n"; | 
|  199  |  205  | 
|  200 Value ConvertInputToValue(const std::string& input, |  206 Value ConvertInputToValue(const Settings* settings, | 
 |  207                           const std::string& input, | 
|  201                           const ParseNode* origin, |  208                           const ParseNode* origin, | 
|  202                           const Value& input_conversion_value, |  209                           const Value& input_conversion_value, | 
|  203                           Err* err) { |  210                           Err* err) { | 
|  204   if (input_conversion_value.type() == Value::NONE) |  211   if (input_conversion_value.type() == Value::NONE) | 
|  205     return Value();  // Allow null inputs to mean discard the result. |  212     return Value();  // Allow null inputs to mean discard the result. | 
|  206   if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) |  213   if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) | 
|  207     return Value(); |  214     return Value(); | 
|  208   return DoConvertInputToValue(input, origin, input_conversion_value, |  215   return DoConvertInputToValue(settings, input, origin, input_conversion_value, | 
|  209                                input_conversion_value.string_value(), err); |  216                                input_conversion_value.string_value(), err); | 
|  210 } |  217 } | 
| OLD | NEW |