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