| // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "tools/gn/input_conversion.h" | 
 |  | 
 | #include <memory> | 
 | #include <utility> | 
 |  | 
 | #include "base/json/json_reader.h" | 
 | #include "base/macros.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/values.h" | 
 | #include "tools/gn/build_settings.h" | 
 | #include "tools/gn/err.h" | 
 | #include "tools/gn/input_file.h" | 
 | #include "tools/gn/label.h" | 
 | #include "tools/gn/parse_tree.h" | 
 | #include "tools/gn/parser.h" | 
 | #include "tools/gn/scheduler.h" | 
 | #include "tools/gn/scope.h" | 
 | #include "tools/gn/settings.h" | 
 | #include "tools/gn/tokenizer.h" | 
 | #include "tools/gn/value.h" | 
 |  | 
 | namespace { | 
 |  | 
 | enum ValueOrScope { | 
 |   PARSE_VALUE,  // Treat the input as an expression. | 
 |   PARSE_SCOPE,  // Treat the input as code and return the resulting scope. | 
 | }; | 
 |  | 
 | // Sets the origin of the value and any nested values with the given node. | 
 | Value ParseValueOrScope(const Settings* settings, | 
 |                         const std::string& input, | 
 |                         ValueOrScope what, | 
 |                         const ParseNode* origin, | 
 |                         Err* err) { | 
 |   // The memory for these will be kept around by the input file manager | 
 |   // so the origin parse nodes for the values will be preserved. | 
 |   InputFile* input_file; | 
 |   std::vector<Token>* tokens; | 
 |   std::unique_ptr<ParseNode>* parse_root_ptr; | 
 |   g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file, | 
 |                                                      &tokens, &parse_root_ptr); | 
 |  | 
 |   input_file->SetContents(input); | 
 |   if (origin) { | 
 |     // This description will be the blame for any error messages caused by | 
 |     // script parsing or if a value is blamed. It will say | 
 |     // "Error at <...>:line:char" so here we try to make a string for <...> | 
 |     // that reads well in this context. | 
 |     input_file->set_friendly_name("dynamically parsed input that " + | 
 |                                   origin->GetRange().begin().Describe(true) + | 
 |                                   " loaded "); | 
 |   } else { | 
 |     input_file->set_friendly_name("dynamic input"); | 
 |   } | 
 |  | 
 |   *tokens = Tokenizer::Tokenize(input_file, err); | 
 |   if (err->has_error()) | 
 |     return Value(); | 
 |  | 
 |   // Parse the file according to what we're looking for. | 
 |   if (what == PARSE_VALUE) | 
 |     *parse_root_ptr = Parser::ParseValue(*tokens, err); | 
 |   else | 
 |     *parse_root_ptr = Parser::Parse(*tokens, err);  // Will return a Block. | 
 |   if (err->has_error()) | 
 |     return Value(); | 
 |   ParseNode* parse_root = parse_root_ptr->get();  // For nicer syntax below. | 
 |  | 
 |   // It's valid for the result to be a null pointer, this just means that the | 
 |   // script returned nothing. | 
 |   if (!parse_root) | 
 |     return Value(); | 
 |  | 
 |   std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings); | 
 |   Value result = parse_root->Execute(scope.get(), err); | 
 |   if (err->has_error()) | 
 |     return Value(); | 
 |  | 
 |   // When we want the result as a scope, the result is actually the scope | 
 |   // we made, rather than the result of running the block (which will be empty). | 
 |   if (what == PARSE_SCOPE) { | 
 |     DCHECK(result.type() == Value::NONE); | 
 |     result = Value(origin, std::move(scope)); | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | Value ParseList(const std::string& input, const ParseNode* origin, Err* err) { | 
 |   Value ret(origin, Value::LIST); | 
 |   std::vector<std::string> as_lines = base::SplitString( | 
 |       input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 
 |  | 
 |   // Trim one empty line from the end since the last line might end in a | 
 |   // newline. If the user wants more trimming, they'll specify "trim" in the | 
 |   // input conversion options. | 
 |   if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) | 
 |     as_lines.resize(as_lines.size() - 1); | 
 |  | 
 |   ret.list_value().reserve(as_lines.size()); | 
 |   for (const auto& line : as_lines) | 
 |     ret.list_value().push_back(Value(origin, line)); | 
 |   return ret; | 
 | } | 
 |  | 
 | bool IsIdentifier(const base::StringPiece& buffer) { | 
 |   DCHECK(buffer.size() > 0); | 
 |   if (!Tokenizer::IsIdentifierFirstChar(buffer[0])) | 
 |     return false; | 
 |   for (size_t i = 1; i < buffer.size(); i++) | 
 |     if (!Tokenizer::IsIdentifierContinuingChar(buffer[i])) | 
 |       return false; | 
 |   return true; | 
 | } | 
 |  | 
 | Value ParseJSONValue(const Settings* settings, | 
 |                      const base::Value& value, | 
 |                      const ParseNode* origin, | 
 |                      InputFile* input_file, | 
 |                      Err* err) { | 
 |   switch (value.type()) { | 
 |     case base::Value::Type::NONE: | 
 |       *err = Err(origin, "Null values are not supported."); | 
 |       return Value(); | 
 |     case base::Value::Type::BOOLEAN: | 
 |       return Value(origin, value.GetBool()); | 
 |     case base::Value::Type::INTEGER: | 
 |       return Value(origin, static_cast<int64_t>(value.GetInt())); | 
 |     case base::Value::Type::STRING: | 
 |       return Value(origin, value.GetString()); | 
 |     case base::Value::Type::BINARY: | 
 |       *err = Err(origin, "Binary values are not supported."); | 
 |       return Value(); | 
 |     case base::Value::Type::DICTIONARY: { | 
 |       std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings); | 
 |       for (const auto& it : value.DictItems()) { | 
 |         Value parsed_value = | 
 |             ParseJSONValue(settings, it.second, origin, input_file, err); | 
 |         if (!IsIdentifier(it.first)) { | 
 |           *err = Err(origin, "Invalid identifier \"" + it.first + "\"."); | 
 |           return Value(); | 
 |         } | 
 |         // Search for the key in the input file. We know it's present because | 
 |         // it was parsed by the JSON reader, but we need its location to | 
 |         // construct a StringPiece that can be used as key in the Scope. | 
 |         size_t off = input_file->contents().find("\"" + it.first + "\""); | 
 |         if (off == std::string::npos) { | 
 |           *err = Err(origin, "Invalid encoding \"" + it.first + "\"."); | 
 |           return Value(); | 
 |         } | 
 |         base::StringPiece key(&input_file->contents()[off + 1], | 
 |                               it.first.size()); | 
 |         scope->SetValue(key, std::move(parsed_value), origin); | 
 |       } | 
 |       return Value(origin, std::move(scope)); | 
 |     } | 
 |     case base::Value::Type::LIST: { | 
 |       Value result(origin, Value::LIST); | 
 |       result.list_value().reserve(value.GetList().size()); | 
 |       for (const auto& val : value.GetList()) { | 
 |         Value parsed_value = | 
 |             ParseJSONValue(settings, val, origin, input_file, err); | 
 |         result.list_value().push_back(parsed_value); | 
 |       } | 
 |       return result; | 
 |     } | 
 |   } | 
 |   return Value(); | 
 | } | 
 |  | 
 | // Parses the JSON string and converts it to GN value. | 
 | Value ParseJSON(const Settings* settings, | 
 |                 const std::string& input, | 
 |                 const ParseNode* origin, | 
 |                 Err* err) { | 
 |   InputFile* input_file; | 
 |   std::vector<Token>* tokens; | 
 |   std::unique_ptr<ParseNode>* parse_root_ptr; | 
 |   g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file, | 
 |                                                      &tokens, &parse_root_ptr); | 
 |   input_file->SetContents(input); | 
 |  | 
 |   int error_code_out; | 
 |   std::string error_msg_out; | 
 |   std::unique_ptr<base::Value> value = base::JSONReader::ReadAndReturnError( | 
 |       input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out, | 
 |       &error_msg_out); | 
 |   if (!value) { | 
 |     *err = Err(origin, "Input is not a valid JSON: " + error_msg_out); | 
 |     return Value(); | 
 |   } | 
 |  | 
 |   return ParseJSONValue(settings, *value, origin, input_file, err); | 
 | } | 
 |  | 
 | // Backend for ConvertInputToValue, this takes the extracted string for the | 
 | // input conversion so we can recursively call ourselves to handle the optional | 
 | // "trim" prefix. This original value is also kept for the purposes of throwing | 
 | // errors. | 
 | Value DoConvertInputToValue(const Settings* settings, | 
 |                             const std::string& input, | 
 |                             const ParseNode* origin, | 
 |                             const Value& original_input_conversion, | 
 |                             const std::string& input_conversion, | 
 |                             Err* err) { | 
 |   if (input_conversion.empty()) | 
 |     return Value();  // Empty string means discard the result. | 
 |  | 
 |   const char kTrimPrefix[] = "trim "; | 
 |   if (base::StartsWith(input_conversion, kTrimPrefix, | 
 |                        base::CompareCase::SENSITIVE)) { | 
 |     std::string trimmed; | 
 |     base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed); | 
 |  | 
 |     // Remove "trim" prefix from the input conversion and re-run. | 
 |     return DoConvertInputToValue( | 
 |         settings, trimmed, origin, original_input_conversion, | 
 |         input_conversion.substr(arraysize(kTrimPrefix) - 1), err); | 
 |   } | 
 |  | 
 |   if (input_conversion == "value") | 
 |     return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err); | 
 |   if (input_conversion == "string") | 
 |     return Value(origin, input); | 
 |   if (input_conversion == "list lines") | 
 |     return ParseList(input, origin, err); | 
 |   if (input_conversion == "scope") | 
 |     return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err); | 
 |   if (input_conversion == "json") | 
 |     return ParseJSON(settings, input, origin, err); | 
 |  | 
 |   *err = Err(original_input_conversion, "Not a valid input_conversion.", | 
 |              "Run gn help input_conversion to see your options."); | 
 |   return Value(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | const char kInputConversion_Help[] = | 
 |     R"(input_conversion: Specifies how to transform input to a variable. | 
 |  | 
 |   input_conversion is an argument to read_file and exec_script that specifies | 
 |   how the result of the read operation should be converted into a variable. | 
 |  | 
 |   "" (the default) | 
 |       Discard the result and return None. | 
 |  | 
 |   "list lines" | 
 |       Return the file contents as a list, with a string for each line. The | 
 |       newlines will not be present in the result. The last line may or may not | 
 |       end in a newline. | 
 |  | 
 |       After splitting, each individual line will be trimmed of whitespace on | 
 |       both ends. | 
 |  | 
 |   "scope" | 
 |       Execute the block as GN code and return a scope with the resulting values | 
 |       in it. If the input was: | 
 |         a = [ "hello.cc", "world.cc" ] | 
 |         b = 26 | 
 |       and you read the result into a variable named "val", then you could | 
 |       access contents the "." operator on "val": | 
 |         sources = val.a | 
 |         some_count = val.b | 
 |  | 
 |   "string" | 
 |       Return the file contents into a single string. | 
 |  | 
 |   "value" | 
 |       Parse the input as if it was a literal rvalue in a buildfile. Examples of | 
 |       typical program output using this mode: | 
 |         [ "foo", "bar" ]     (result will be a list) | 
 |       or | 
 |         "foo bar"            (result will be a string) | 
 |       or | 
 |         5                    (result will be an integer) | 
 |  | 
 |       Note that if the input is empty, the result will be a null value which | 
 |       will produce an error if assigned to a variable. | 
 |  | 
 |   "json" | 
 |       Parse the input as a JSON and convert it to equivalent GN rvalue. The data | 
 |       type mapping is: | 
 |         a string in JSON maps to string in GN | 
 |         an integer in JSON maps to integer in GN | 
 |         a float in JSON is unsupported and will result in an error | 
 |         an object in JSON maps to scope in GN | 
 |         an array in JSON maps to list in GN | 
 |         a boolean in JSON maps to boolean in GN | 
 |         a null in JSON is unsupported and will result in an error | 
 |  | 
 |       Nota that the dictionary keys have to be valid GN identifiers otherwise | 
 |       they will produce an error. | 
 |  | 
 |   "trim ..." | 
 |       Prefixing any of the other transformations with the word "trim" will | 
 |       result in whitespace being trimmed from the beginning and end of the | 
 |       result before processing. | 
 |  | 
 |       Examples: "trim string" or "trim list lines" | 
 |  | 
 |       Note that "trim value" is useless because the value parser skips | 
 |       whitespace anyway. | 
 | )"; | 
 |  | 
 | Value ConvertInputToValue(const Settings* settings, | 
 |                           const std::string& input, | 
 |                           const ParseNode* origin, | 
 |                           const Value& input_conversion_value, | 
 |                           Err* err) { | 
 |   if (input_conversion_value.type() == Value::NONE) | 
 |     return Value();  // Allow null inputs to mean discard the result. | 
 |   if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) | 
 |     return Value(); | 
 |   return DoConvertInputToValue(settings, input, origin, input_conversion_value, | 
 |                                input_conversion_value.string_value(), err); | 
 | } |