Home | History | Annotate | Download | only in gn
      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