Add support for scope subscript

This allows readonly access to scope's variables by a computed string:
key = "foo"
scope = {
  foo = 1
  bar = 2
}
scope[key]

Change-Id: Ib3c8206a505447c51578511fbbcb99d318f59fab
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/7100
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/command_format.cc b/src/gn/command_format.cc
index dafc732..8483b93 100644
--- a/src/gn/command_format.cc
+++ b/src/gn/command_format.cc
@@ -500,7 +500,7 @@
     return result;
 
   if (const AccessorNode* accessor = node->AsAccessor()) {
-    RETURN_IF_SET(SuffixCommentTreeWalk(accessor->index()));
+    RETURN_IF_SET(SuffixCommentTreeWalk(accessor->subscript()));
     RETURN_IF_SET(SuffixCommentTreeWalk(accessor->member()));
   } else if (const BinaryOpNode* binop = node->AsBinaryOp()) {
     RETURN_IF_SET(SuffixCommentTreeWalk(binop->right()));
@@ -706,9 +706,9 @@
       Print(".");
       Expr(accessor->member(), kPrecedenceLowest, std::string());
     } else {
-      CHECK(accessor->index());
+      CHECK(accessor->subscript());
       Print("[");
-      Expr(accessor->index(), kPrecedenceLowest, "]");
+      Expr(accessor->subscript(), kPrecedenceLowest, "]");
     }
   } else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
     CHECK(precedence_.find(binop->op().value()) != precedence_.end());
diff --git a/src/gn/operators.cc b/src/gn/operators.cc
index a960655..3c55a90 100644
--- a/src/gn/operators.cc
+++ b/src/gn/operators.cc
@@ -127,7 +127,7 @@
     return false;
   }
 
-  if (dest_accessor->index()) {
+  if (dest_accessor->subscript()) {
     // List access with an index.
     if (!base->VerifyTypeIs(Value::LIST, err)) {
       // Errors here will confusingly refer to the variable declaration (since
diff --git a/src/gn/parse_tree.cc b/src/gn/parse_tree.cc
index 8625e45..d4c6290 100644
--- a/src/gn/parse_tree.cc
+++ b/src/gn/parse_tree.cc
@@ -190,8 +190,8 @@
 }
 
 Value AccessorNode::Execute(Scope* scope, Err* err) const {
-  if (index_)
-    return ExecuteArrayAccess(scope, err);
+  if (subscript_)
+    return ExecuteSubscriptAccess(scope, err);
   else if (member_)
     return ExecuteScopeAccess(scope, err);
   NOTREACHED();
@@ -199,8 +199,8 @@
 }
 
 LocationRange AccessorNode::GetRange() const {
-  if (index_)
-    return LocationRange(base_.location(), index_->GetRange().end());
+  if (subscript_)
+    return LocationRange(base_.location(), subscript_->GetRange().end());
   else if (member_)
     return LocationRange(base_.location(), member_->GetRange().end());
   NOTREACHED();
@@ -215,23 +215,35 @@
 base::Value AccessorNode::GetJSONNode() const {
   base::Value dict(CreateJSONNode("ACCESSOR", base_.value()));
   base::Value child(base::Value::Type::LIST);
-  if (index_)
-    child.GetList().push_back(index_->GetJSONNode());
+  if (subscript_)
+    child.GetList().push_back(subscript_->GetJSONNode());
   else if (member_)
     child.GetList().push_back(member_->GetJSONNode());
   dict.SetKey(kJsonNodeChild, std::move(child));
   return dict;
 }
 
-Value AccessorNode::ExecuteArrayAccess(Scope* scope, Err* err) const {
+Value AccessorNode::ExecuteSubscriptAccess(Scope* scope, Err* err) const {
   const Value* base_value = scope->GetValue(base_.value(), true);
   if (!base_value) {
     *err = MakeErrorDescribing("Undefined identifier.");
     return Value();
   }
-  if (!base_value->VerifyTypeIs(Value::LIST, err))
+  if (base_value->type() == Value::LIST) {
+    return ExecuteArrayAccess(scope, base_value, err);
+  } else if (base_value->type() == Value::SCOPE) {
+    return ExecuteScopeSubscriptAccess(scope, base_value, err);
+  } else {
+    *err = MakeErrorDescribing(
+        std::string("Expecting either a list or a scope for subscript, got ") +
+        Value::DescribeType(base_value->type()) + ".");
     return Value();
+  }
+}
 
+Value AccessorNode::ExecuteArrayAccess(Scope* scope,
+                                       const Value* base_value,
+                                       Err* err) const {
   size_t index = 0;
   if (!ComputeAndValidateListIndex(scope, base_value->list_value().size(),
                                    &index, err))
@@ -239,6 +251,24 @@
   return base_value->list_value()[index];
 }
 
+Value AccessorNode::ExecuteScopeSubscriptAccess(Scope* scope,
+                                                const Value* base_value,
+                                                Err* err) const {
+  Value key_value = subscript_->Execute(scope, err);
+  if (err->has_error())
+    return Value();
+  if (!key_value.VerifyTypeIs(Value::STRING, err))
+    return Value();
+  const Value* result =
+      base_value->scope_value()->GetValue(key_value.string_value());
+  if (!result) {
+    *err =
+        Err(subscript_.get(), "No value named \"" + key_value.string_value() +
+                                  "\" in scope \"" + base_.value() + "\"");
+  }
+  return *result;
+}
+
 Value AccessorNode::ExecuteScopeAccess(Scope* scope, Err* err) const {
   // We jump through some hoops here since ideally a.b will count "b" as
   // accessed in the given scope. The value "a" might be in some normal nested
@@ -292,7 +322,7 @@
                                                size_t max_len,
                                                size_t* computed_index,
                                                Err* err) const {
-  Value index_value = index_->Execute(scope, err);
+  Value index_value = subscript_->Execute(scope, err);
   if (err->has_error())
     return false;
   if (!index_value.VerifyTypeIs(Value::INTEGER, err))
@@ -300,19 +330,19 @@
 
   int64_t index_int = index_value.int_value();
   if (index_int < 0) {
-    *err = Err(index_->GetRange(), "Negative array subscript.",
+    *err = Err(subscript_->GetRange(), "Negative array subscript.",
                "You gave me " + base::Int64ToString(index_int) + ".");
     return false;
   }
   if (max_len == 0) {
-    *err = Err(index_->GetRange(), "Array subscript out of range.",
+    *err = Err(subscript_->GetRange(), "Array subscript out of range.",
                "You gave me " + base::Int64ToString(index_int) + " but the " +
                    "array has no elements.");
     return false;
   }
   size_t index_sizet = static_cast<size_t>(index_int);
   if (index_sizet >= max_len) {
-    *err = Err(index_->GetRange(), "Array subscript out of range.",
+    *err = Err(subscript_->GetRange(), "Array subscript out of range.",
                "You gave me " + base::Int64ToString(index_int) +
                    " but I was expecting something from 0 to " +
                    base::NumberToString(max_len - 1) + ", inclusive.");
diff --git a/src/gn/parse_tree.h b/src/gn/parse_tree.h
index 19f0887..13fa7d1 100644
--- a/src/gn/parse_tree.h
+++ b/src/gn/parse_tree.h
@@ -167,9 +167,11 @@
   const Token& base() const { return base_; }
   void set_base(const Token& b) { base_ = b; }
 
-  // Index is the expression inside the []. Will be null if member is set.
-  const ParseNode* index() const { return index_.get(); }
-  void set_index(std::unique_ptr<ParseNode> i) { index_ = std::move(i); }
+  // Subscript is the expression inside the []. Will be null if member is set.
+  const ParseNode* subscript() const { return subscript_.get(); }
+  void set_subscript(std::unique_ptr<ParseNode> key) {
+    subscript_ = std::move(key);
+  }
 
   // The member is the identifier on the right hand side of the dot. Will be
   // null if the index is set.
@@ -188,14 +190,20 @@
                                    Err* err) const;
 
  private:
-  Value ExecuteArrayAccess(Scope* scope, Err* err) const;
+  Value ExecuteSubscriptAccess(Scope* scope, Err* err) const;
+  Value ExecuteArrayAccess(Scope* scope,
+                           const Value* base_value,
+                           Err* err) const;
+  Value ExecuteScopeSubscriptAccess(Scope* scope,
+                                    const Value* base_value,
+                                    Err* err) const;
   Value ExecuteScopeAccess(Scope* scope, Err* err) const;
 
   Token base_;
 
   // Either index or member will be set according to what type of access this
   // is.
-  std::unique_ptr<ParseNode> index_;
+  std::unique_ptr<ParseNode> subscript_;
   std::unique_ptr<IdentifierNode> member_;
 
   DISALLOW_COPY_AND_ASSIGN(AccessorNode);
diff --git a/src/gn/parse_tree_unittest.cc b/src/gn/parse_tree_unittest.cc
index b2d19b2..e366b54 100644
--- a/src/gn/parse_tree_unittest.cc
+++ b/src/gn/parse_tree_unittest.cc
@@ -57,6 +57,50 @@
   EXPECT_EQ(kBValue, result.int_value());
 }
 
+TEST(ParseTree, SubscriptedAccess) {
+  TestWithScope setup;
+  Err err;
+  TestParseInput values(
+      "list = [ 2, 3 ]\n"
+      "scope = {\n"
+      "  foo = 5\n"
+      "  bar = 8\n"
+      "}\n"
+      "bar_key = \"bar\""
+      "second_element_idx = 1");
+  values.parsed()->Execute(setup.scope(), &err);
+
+  EXPECT_FALSE(err.has_error());
+
+  Value first = setup.ExecuteExpression("list[0]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(first.type(), Value::INTEGER);
+  EXPECT_EQ(first.int_value(), 2);
+
+  Value second = setup.ExecuteExpression("list[second_element_idx]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(second.type(), Value::INTEGER);
+  EXPECT_EQ(second.int_value(), 3);
+
+  Value foo = setup.ExecuteExpression("scope[\"foo\"]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(foo.type(), Value::INTEGER);
+  EXPECT_EQ(foo.int_value(), 5);
+
+  Value bar = setup.ExecuteExpression("scope[bar_key]", &err);
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(bar.type(), Value::INTEGER);
+  EXPECT_EQ(bar.int_value(), 8);
+
+  Value invalid1 = setup.ExecuteExpression("scope[second_element_idx]", &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ(invalid1.type(), Value::NONE);
+
+  Value invalid2 = setup.ExecuteExpression("list[bar_key]", &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ(invalid2.type(), Value::NONE);
+}
+
 TEST(ParseTree, BlockUnusedVars) {
   TestWithScope setup;
 
diff --git a/src/gn/parser.cc b/src/gn/parser.cc
index 0e761f2..833e590 100644
--- a/src/gn/parser.cc
+++ b/src/gn/parser.cc
@@ -608,7 +608,7 @@
   Consume(Token::RIGHT_BRACKET, "Expecting ']' after subscript.");
   std::unique_ptr<AccessorNode> accessor = std::make_unique<AccessorNode>();
   accessor->set_base(left->AsIdentifier()->value());
-  accessor->set_index(std::move(value));
+  accessor->set_subscript(std::move(value));
   return std::move(accessor);
 }
 
@@ -785,7 +785,7 @@
     pre->push_back(root);
 
     if (const AccessorNode* accessor = root->AsAccessor()) {
-      TraverseOrder(accessor->index(), pre, post);
+      TraverseOrder(accessor->subscript(), pre, post);
       TraverseOrder(accessor->member(), pre, post);
     } else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
       TraverseOrder(binop->left(), pre, post);
diff --git a/src/gn/test_with_scope.cc b/src/gn/test_with_scope.cc
index dda4b2a..70d1612 100644
--- a/src/gn/test_with_scope.cc
+++ b/src/gn/test_with_scope.cc
@@ -68,6 +68,22 @@
   return true;
 }
 
+Value TestWithScope::ExecuteExpression(const std::string& expr, Err* err) {
+  InputFile input_file(SourceFile("//test"));
+  input_file.SetContents(expr);
+
+  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, err);
+  if (err->has_error()) {
+    return Value();
+  }
+  std::unique_ptr<ParseNode> node = Parser::ParseExpression(tokens, err);
+  if (err->has_error()) {
+    return Value();
+  }
+
+  return node->Execute(&scope_, err);
+}
+
 // static
 void TestWithScope::SetupToolchain(Toolchain* toolchain, bool use_toc) {
   Err err;
diff --git a/src/gn/test_with_scope.h b/src/gn/test_with_scope.h
index 6292b6c..19854db 100644
--- a/src/gn/test_with_scope.h
+++ b/src/gn/test_with_scope.h
@@ -53,6 +53,8 @@
   // just blindly resolves all targets in order).
   bool ExecuteSnippet(const std::string& str, Err* err);
 
+  Value ExecuteExpression(const std::string& expr, Err* err);
+
   // Fills in the tools for the given toolchain with reasonable default values.
   // The toolchain in this object will be automatically set up with this
   // function, it is exposed to allow tests to get the same functionality for