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