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/scheduler.h"
     16 #include "tools/gn/scope.h"
     17 #include "tools/gn/settings.h"
     18 #include "tools/gn/tokenizer.h"
     19 #include "tools/gn/value.h"
     20 
     21 namespace {
     22 
     23 enum ValueOrScope {
     24   PARSE_VALUE,  // Treat the input as an expression.
     25   PARSE_SCOPE,  // Treat the input as code and return the resulting scope.
     26 };
     27 
     28 // Sets the origin of the value and any nested values with the given node.
     29 Value ParseValueOrScope(const Settings* settings,
     30                         const std::string& input,
     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       SourceFile(), &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());
     92   }
     93   return result;
     94 }
     95 
     96 Value ParseList(const std::string& input, const ParseNode* origin, Err* err) {
     97   Value ret(origin, Value::LIST);
     98   std::vector<std::string> as_lines;
     99   base::SplitString(input, '\n', &as_lines);
    100 
    101   // Trim one empty line from the end since the last line might end in a
    102   // newline. If the user wants more trimming, they'll specify "trim" in the
    103   // input conversion options.
    104   if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty())
    105     as_lines.resize(as_lines.size() - 1);
    106 
    107   ret.list_value().reserve(as_lines.size());
    108   for (size_t i = 0; i < as_lines.size(); i++)
    109     ret.list_value().push_back(Value(origin, as_lines[i]));
    110   return ret;
    111 }
    112 
    113 // Backend for ConvertInputToValue, this takes the extracted string for the
    114 // input conversion so we can recursively call ourselves to handle the optional
    115 // "trim" prefix. This original value is also kept for the purposes of throwing
    116 // errors.
    117 Value DoConvertInputToValue(const Settings* settings,
    118                             const std::string& input,
    119                             const ParseNode* origin,
    120                             const Value& original_input_conversion,
    121                             const std::string& input_conversion,
    122                             Err* err) {
    123   if (input_conversion.empty())
    124     return Value();  // Empty string means discard the result.
    125 
    126   const char kTrimPrefix[] = "trim ";
    127   if (StartsWithASCII(input_conversion, kTrimPrefix, true)) {
    128     std::string trimmed;
    129     base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed);
    130 
    131     // Remove "trim" prefix from the input conversion and re-run.
    132     return DoConvertInputToValue(
    133         settings, trimmed, origin, original_input_conversion,
    134         input_conversion.substr(arraysize(kTrimPrefix) - 1), err);
    135   }
    136 
    137   if (input_conversion == "value")
    138     return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err);
    139   if (input_conversion == "string")
    140     return Value(origin, input);
    141   if (input_conversion == "list lines")
    142     return ParseList(input, origin, err);
    143   if (input_conversion == "scope")
    144     return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err);
    145 
    146   *err = Err(original_input_conversion, "Not a valid input_conversion.",
    147              "Have you considered a career in retail?");
    148   return Value();
    149 }
    150 
    151 }  // namespace
    152 
    153 extern const char kInputConversion_Help[] =
    154     "input_conversion: Specifies how to transform input to a variable.\n"
    155     "\n"
    156     "  input_conversion is an argument to read_file and exec_script that\n"
    157     "  specifies how the result of the read operation should be converted\n"
    158     "  into a variable.\n"
    159     "\n"
    160     "  \"\" (the default)\n"
    161     "      Discard the result and return None.\n"
    162     "\n"
    163     "  \"list lines\"\n"
    164     "      Return the file contents as a list, with a string for each line.\n"
    165     "      The newlines will not be present in the result. The last line may\n"
    166     "      or may not end in a newline.\n"
    167     "\n"
    168     "      After splitting, each individual line will be trimmed of\n"
    169     "      whitespace on both ends.\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"
    184     "  \"value\"\n"
    185     "      Parse the input as if it was a literal rvalue in a buildfile.\n"
    186     "      Examples of typical program output using this mode:\n"
    187     "        [ \"foo\", \"bar\" ]     (result will be a list)\n"
    188     "      or\n"
    189     "        \"foo bar\"            (result will be a string)\n"
    190     "      or\n"
    191     "        5                    (result will be an integer)\n"
    192     "\n"
    193     "      Note that if the input is empty, the result will be a null value\n"
    194     "      which will produce an error if assigned to a variable.\n"
    195     "\n"
    196     "  \"trim ...\"\n"
    197     "      Prefixing any of the other transformations with the word \"trim\"\n"
    198     "      will result in whitespace being trimmed from the beginning and end\n"
    199     "      of the result before processing.\n"
    200     "\n"
    201     "      Examples: \"trim string\" or \"trim list lines\"\n"
    202     "\n"
    203     "      Note that \"trim value\" is useless because the value parser skips\n"
    204     "      whitespace anyway.\n";
    205 
    206 Value ConvertInputToValue(const Settings* settings,
    207                           const std::string& input,
    208                           const ParseNode* origin,
    209                           const Value& input_conversion_value,
    210                           Err* err) {
    211   if (input_conversion_value.type() == Value::NONE)
    212     return Value();  // Allow null inputs to mean discard the result.
    213   if (!input_conversion_value.VerifyTypeIs(Value::STRING, err))
    214     return Value();
    215   return DoConvertInputToValue(settings, input, origin, input_conversion_value,
    216                                input_conversion_value.string_value(), err);
    217 }
    218