Add "." accessors to GN.
Add support for values referring to a Scope, and add support for the dot operator to read such values out of a scope.
This is not currently used for anything. A followup patch will use this for the template implementation.
[email protected]
Review URL: https://codereview.chromium.org/207233003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@259608 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index badfb0b..2892fbb 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -198,6 +198,7 @@
"ninja_copy_target_writer_unittest.cc",
"ninja_helper_unittest.cc",
"operators_unittest.cc",
+ "parse_tree_unittest.cc",
"parser_unittest.cc",
"path_output_unittest.cc",
"pattern_unittest.cc",
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index fe305c16..7ed031a 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -187,6 +187,7 @@
'ninja_copy_target_writer_unittest.cc',
'ninja_helper_unittest.cc',
'operators_unittest.cc',
+ 'parse_tree_unittest.cc',
'parser_unittest.cc',
'path_output_unittest.cc',
'pattern_unittest.cc',
diff --git a/tools/gn/parse_tree.cc b/tools/gn/parse_tree.cc
index bc50f20..4015fd1f 100644
--- a/tools/gn/parse_tree.cc
+++ b/tools/gn/parse_tree.cc
@@ -53,6 +53,38 @@
}
Value AccessorNode::Execute(Scope* scope, Err* err) const {
+ if (index_)
+ return ExecuteArrayAccess(scope, err);
+ else if (member_)
+ return ExecuteScopeAccess(scope, err);
+ NOTREACHED();
+ return Value();
+}
+
+LocationRange AccessorNode::GetRange() const {
+ if (index_)
+ return LocationRange(base_.location(), index_->GetRange().end());
+ else if (member_)
+ return LocationRange(base_.location(), member_->GetRange().end());
+ NOTREACHED();
+ return LocationRange();
+}
+
+Err AccessorNode::MakeErrorDescribing(const std::string& msg,
+ const std::string& help) const {
+ return Err(GetRange(), msg, help);
+}
+
+void AccessorNode::Print(std::ostream& out, int indent) const {
+ out << IndentFor(indent) << "ACCESSOR\n";
+ out << IndentFor(indent + 1) << base_.value() << "\n";
+ if (index_)
+ index_->Print(out, indent + 1);
+ else if (member_)
+ member_->Print(out, indent + 1);
+}
+
+Value AccessorNode::ExecuteArrayAccess(Scope* scope, Err* err) const {
Value index_value = index_->Execute(scope, err);
if (err->has_error())
return Value();
@@ -91,19 +123,46 @@
return base_value->list_value()[index_sizet];
}
-LocationRange AccessorNode::GetRange() const {
- return LocationRange(base_.location(), index_->GetRange().end());
-}
+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
+ // scope and we can modify it, but it might also be inherited from the
+ // readonly root scope and we can't do used variable tracking on it. (It's
+ // not legal to const cast it away since the root scope will be in readonly
+ // mode and being accessed from multiple threads without locking.) So this
+ // code handles both cases.
+ const Value* result = NULL;
-Err AccessorNode::MakeErrorDescribing(const std::string& msg,
- const std::string& help) const {
- return Err(GetRange(), msg, help);
-}
+ // Look up the value in the scope named by "base_".
+ Value* mutable_base_value = scope->GetMutableValue(base_.value(), true);
+ if (mutable_base_value) {
+ // Common case: base value is mutable so we can track variable accesses
+ // for unused value warnings.
+ if (!mutable_base_value->VerifyTypeIs(Value::SCOPE, err))
+ return Value();
+ result = mutable_base_value->scope_value()->GetValue(
+ member_->value().value(), true);
+ } else {
+ // Fall back to see if the value is on a read-only scope.
+ const Value* const_base_value = scope->GetValue(base_.value(), true);
+ if (const_base_value) {
+ // Read only value, don't try to mark the value access as a "used" one.
+ if (!const_base_value->VerifyTypeIs(Value::SCOPE, err))
+ return Value();
+ result =
+ const_base_value->scope_value()->GetValue(member_->value().value());
+ } else {
+ *err = Err(base_, "Undefined identifier.");
+ return Value();
+ }
+ }
-void AccessorNode::Print(std::ostream& out, int indent) const {
- out << IndentFor(indent) << "ACCESSOR\n";
- out << IndentFor(indent + 1) << base_.value() << "\n";
- index_->Print(out, indent + 1);
+ if (!result) {
+ *err = Err(member_.get(), "No value named \"" +
+ member_->value().value() + "\" in scope \"" + base_.value() + "\"");
+ return Value();
+ }
+ return *result;
}
// BinaryOpNode ---------------------------------------------------------------
diff --git a/tools/gn/parse_tree.h b/tools/gn/parse_tree.h
index cc3550f0..f9ec543 100644
--- a/tools/gn/parse_tree.h
+++ b/tools/gn/parse_tree.h
@@ -63,10 +63,29 @@
// AccessorNode ----------------------------------------------------------------
-// Access an array element.
+// Access an array or scope element.
//
-// If we need to add support for member variables like "variable.len" I was
-// thinking this would also handle that case.
+// Currently, such values are only read-only. In that you can do:
+// a = obj1.a
+// b = obj2[0]
+// But not
+// obj1.a = 5
+// obj2[0] = 6
+//
+// In the current design where the dot operator is used only for templates, we
+// explicitly don't want to allow you to do "invoker.foo = 5", so if we added
+// support for accessors to be lvalues, we would also need to add some concept
+// of a constant scope. Supporting this would also add a lot of complications
+// to the operator= implementation, since some accessors might return values
+// in the const root scope that shouldn't be modified. Without a strong
+// use-case for this, it seems simpler to just disallow it.
+//
+// Additionally, the left-hand-side of the accessor must currently be an
+// identifier. So you can't do things like:
+// function_call()[1]
+// a = b.c.d
+// These are easier to implement if we needed them but given the very limited
+// use cases for this, it hasn't seemed worth the bother.
class AccessorNode : public ParseNode {
public:
AccessorNode();
@@ -80,18 +99,30 @@
const std::string& help = std::string()) const OVERRIDE;
virtual void Print(std::ostream& out, int indent) const OVERRIDE;
- // Base is the thing on the left of the [], currently always required to be
- // an identifier token.
+ // Base is the thing on the left of the [] or dot, currently always required
+ // to be an identifier token.
const Token& base() const { return base_; }
void set_base(const Token& b) { base_ = b; }
- // Index is the expression inside the [].
+ // Index is the expression inside the []. Will be null if member is set.
const ParseNode* index() const { return index_.get(); }
void set_index(scoped_ptr<ParseNode> i) { index_ = i.Pass(); }
+ // The member is the identifier on the right hand side of the dot. Will be
+ // null if the index is set.
+ const IdentifierNode* member() const { return member_.get(); }
+ void set_member(scoped_ptr<IdentifierNode> i) { member_ = i.Pass(); }
+
private:
+ Value ExecuteArrayAccess(Scope* scope, 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.
scoped_ptr<ParseNode> index_;
+ scoped_ptr<IdentifierNode> member_;
DISALLOW_COPY_AND_ASSIGN(AccessorNode);
};
diff --git a/tools/gn/parse_tree_unittest.cc b/tools/gn/parse_tree_unittest.cc
new file mode 100644
index 0000000..8120e224
--- /dev/null
+++ b/tools/gn/parse_tree_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2014 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 "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/input_file.h"
+#include "tools/gn/parse_tree.h"
+#include "tools/gn/scope.h"
+#include "tools/gn/test_with_scope.h"
+
+TEST(ParseTree, Accessor) {
+ TestWithScope setup;
+
+ // Make a pretend parse node with proper tracking that we can blame for the
+ // given value.
+ InputFile input_file(SourceFile("//foo"));
+ Token base_token(Location(&input_file, 1, 1), Token::IDENTIFIER, "a");
+ Token member_token(Location(&input_file, 1, 1), Token::IDENTIFIER, "b");
+
+ AccessorNode accessor;
+ accessor.set_base(base_token);
+
+ scoped_ptr<IdentifierNode> member_identifier(
+ new IdentifierNode(member_token));
+ accessor.set_member(member_identifier.Pass());
+
+ // The access should fail because a is not defined.
+ Err err;
+ Value result = accessor.Execute(setup.scope(), &err);
+ EXPECT_TRUE(err.has_error());
+ EXPECT_EQ(Value::NONE, result.type());
+
+ // Define a as a Scope. It should still fail because b isn't defined.
+ Scope a_scope(setup.scope());
+ err = Err();
+ setup.scope()->SetValue("a", Value(NULL, &a_scope), NULL);
+ result = accessor.Execute(setup.scope(), &err);
+ EXPECT_TRUE(err.has_error());
+ EXPECT_EQ(Value::NONE, result.type());
+
+ // Define b, accessor should succeed now.
+ const int64 kBValue = 42;
+ err = Err();
+ a_scope.SetValue("b", Value(NULL, kBValue), NULL);
+ result = accessor.Execute(setup.scope(), &err);
+ EXPECT_FALSE(err.has_error());
+ ASSERT_EQ(Value::INTEGER, result.type());
+ EXPECT_EQ(kBValue, result.int_value());
+}
diff --git a/tools/gn/parser.cc b/tools/gn/parser.cc
index 6392eedbd..f533542 100644
--- a/tools/gn/parser.cc
+++ b/tools/gn/parser.cc
@@ -19,7 +19,7 @@
// assignment := ident {'=' | '+=' | '-='} expr
enum Precedence {
- PRECEDENCE_ASSIGNMENT = 1,
+ PRECEDENCE_ASSIGNMENT = 1, // Lowest precedence.
PRECEDENCE_OR = 2,
PRECEDENCE_AND = 3,
PRECEDENCE_EQUALITY = 4,
@@ -27,15 +27,16 @@
PRECEDENCE_SUM = 6,
PRECEDENCE_PREFIX = 7,
PRECEDENCE_CALL = 8,
+ PRECEDENCE_DOT = 9, // Highest precedence.
};
-// The top-level for blocks/ifs is still recursive descent, the expression
-// parser is a Pratt parser. The basic idea there is to have the precedences
-// (and associativities) encoded relative to each other and only parse up
-// until you hit something of that precedence. There's a dispatch table in
-// expressions_ at the top of parser.cc that describes how each token
-// dispatches if it's seen as either a prefix or infix operator, and if it's
-// infix, what its precedence is.
+// The top-level for blocks/ifs is recursive descent, the expression parser is
+// a Pratt parser. The basic idea there is to have the precedences (and
+// associativities) encoded relative to each other and only parse up until you
+// hit something of that precedence. There's a dispatch table in expressions_
+// at the top of parser.cc that describes how each token dispatches if it's
+// seen as either a prefix or infix operator, and if it's infix, what its
+// precedence is.
//
// Refs:
// - http://javascript.crockford.com/tdop/tdop.html
@@ -62,6 +63,7 @@
{NULL, &Parser::BinaryOperator, PRECEDENCE_AND}, // BOOLEAN_AND
{NULL, &Parser::BinaryOperator, PRECEDENCE_OR}, // BOOLEAN_OR
{&Parser::Not, NULL, -1}, // BANG
+ {NULL, &Parser::DotOperator, PRECEDENCE_DOT}, // DOT
{&Parser::Group, NULL, -1}, // LEFT_PAREN
{NULL, NULL, -1}, // RIGHT_PAREN
{&Parser::List, &Parser::Subscript, PRECEDENCE_CALL}, // LEFT_BRACKET
@@ -311,7 +313,10 @@
// TODO: Maybe support more complex expressions like a[0][0]. This would
// require work on the evaluator too.
if (left->AsIdentifier() == NULL) {
- *err_ = Err(left.get(), "May only subscript simple identifiers");
+ *err_ = Err(left.get(), "May only subscript identifiers.",
+ "The thing on the left hand side of the [] must be an identifier\n"
+ "and not an expression. If you need this, you'll have to assign the\n"
+ "value to a temporary before subscripting. Sorry.");
return scoped_ptr<ParseNode>();
}
scoped_ptr<ParseNode> value = ParseExpression();
@@ -322,6 +327,30 @@
return accessor.PassAs<ParseNode>();
}
+scoped_ptr<ParseNode> Parser::DotOperator(scoped_ptr<ParseNode> left,
+ Token token) {
+ if (left->AsIdentifier() == NULL) {
+ *err_ = Err(left.get(), "May only use \".\" for identifiers.",
+ "The thing on the left hand side of the dot must be an identifier\n"
+ "and not an expression. If you need this, you'll have to assign the\n"
+ "value to a temporary first. Sorry.");
+ return scoped_ptr<ParseNode>();
+ }
+
+ scoped_ptr<ParseNode> right = ParseExpression(PRECEDENCE_DOT);
+ if (!right || !right->AsIdentifier()) {
+ *err_ = Err(token, "Expected identifier for right-hand-side of \".\"",
+ "Good: a.cookies\nBad: a.42\nLooks good but still bad: a.cookies()");
+ return scoped_ptr<ParseNode>();
+ }
+
+ scoped_ptr<AccessorNode> accessor(new AccessorNode);
+ accessor->set_base(left->AsIdentifier()->value());
+ accessor->set_member(scoped_ptr<IdentifierNode>(
+ static_cast<IdentifierNode*>(right.release())));
+ return accessor.PassAs<ParseNode>();
+}
+
// Does not Consume the start or end token.
scoped_ptr<ListNode> Parser::ParseList(Token::Type stop_before,
bool allow_trailing_comma) {
diff --git a/tools/gn/parser.h b/tools/gn/parser.h
index 76cc71a..407b4720 100644
--- a/tools/gn/parser.h
+++ b/tools/gn/parser.h
@@ -61,6 +61,7 @@
Token token);
scoped_ptr<ParseNode> Assignment(scoped_ptr<ParseNode> left, Token token);
scoped_ptr<ParseNode> Subscript(scoped_ptr<ParseNode> left, Token token);
+ scoped_ptr<ParseNode> DotOperator(scoped_ptr<ParseNode> left, Token token);
// Helper to parse a comma separated list, optionally allowing trailing
// commas (allowed in [] lists, not in function calls).
diff --git a/tools/gn/parser_unittest.cc b/tools/gn/parser_unittest.cc
index 7ef5ff6..adb8fdb4 100644
--- a/tools/gn/parser_unittest.cc
+++ b/tools/gn/parser_unittest.cc
@@ -27,6 +27,8 @@
Err err;
scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
+ if (!result)
+ err.PrintToStdout();
ASSERT_TRUE(result);
std::ostringstream collector;
@@ -215,15 +217,31 @@
}
TEST(Parser, Accessor) {
- DoParserPrintTest("a=b[2]",
+ // Accessor indexing.
+ DoParserPrintTest("a=b[c+2]",
"BLOCK\n"
" BINARY(=)\n"
" IDENTIFIER(a)\n"
" ACCESSOR\n"
" b\n" // AccessorNode is a bit weird in that it holds
// a Token, not a ParseNode for the base.
- " LITERAL(2)\n");
+ " BINARY(+)\n"
+ " IDENTIFIER(c)\n"
+ " LITERAL(2)\n");
DoParserErrorTest("a = b[1][0]", 1, 5);
+
+ // Member accessors.
+ DoParserPrintTest("a=b.c+2",
+ "BLOCK\n"
+ " BINARY(=)\n"
+ " IDENTIFIER(a)\n"
+ " BINARY(+)\n"
+ " ACCESSOR\n"
+ " b\n"
+ " IDENTIFIER(c)\n"
+ " LITERAL(2)\n");
+ DoParserErrorTest("a = b.c.d", 1, 6); // Can't nest accessors (currently).
+ DoParserErrorTest("a.b = 5", 1, 1); // Can't assign to accessors (currently).
}
TEST(Parser, Condition) {
diff --git a/tools/gn/scope.cc b/tools/gn/scope.cc
index 3f714ec3..86f82be 100644
--- a/tools/gn/scope.cc
+++ b/tools/gn/scope.cc
@@ -68,6 +68,22 @@
return NULL;
}
+Value* Scope::GetMutableValue(const base::StringPiece& ident,
+ bool counts_as_used) {
+ // Don't do programatic values, which are not mutable.
+ RecordMap::iterator found = values_.find(ident);
+ if (found != values_.end()) {
+ if (counts_as_used)
+ found->second.used = true;
+ return &found->second.value;
+ }
+
+ // Search in the parent mutable scope, but not const one.
+ if (mutable_containing_)
+ return mutable_containing_->GetMutableValue(ident, counts_as_used);
+ return NULL;
+}
+
Value* Scope::GetValueForcedToCurrentScope(const base::StringPiece& ident,
const ParseNode* set_node) {
RecordMap::iterator found = values_.find(ident);
diff --git a/tools/gn/scope.h b/tools/gn/scope.h
index d4a35ff..bd6722d 100644
--- a/tools/gn/scope.h
+++ b/tools/gn/scope.h
@@ -86,6 +86,30 @@
bool counts_as_used);
const Value* GetValue(const base::StringPiece& ident) const;
+ // Returns the requested value as a mutable one if possible. If the value
+ // is not found in a mutable scope, then returns null. Note that the value
+ // could still exist in a const scope, so GetValue() could still return
+ // non-null in this case.
+ //
+ // Say you have a local scope that then refers to the const root scope from
+ // the master build config. You can't change the values from the master
+ // build config (it's read-only so it can be read from multiple threads
+ // without locking). Read-only operations would work on values from the root
+ // scope, but write operations would only work on values in the derived
+ // scope(s).
+ //
+ // Be careful when calling this. It's not normally correct to modify values,
+ // but you should instead do a new Set each time.
+ //
+ // Consider this code:
+ // a = 5
+ // {
+ // a = 6
+ // }
+ // The 6 should get set on the nested scope rather than modify the value
+ // in the outer one.
+ Value* GetMutableValue(const base::StringPiece& ident, bool counts_as_used);
+
// Same as GetValue, but if the value exists in a parent scope, we'll copy
// it to the current scope. If the return value is non-null, the value is
// guaranteed to be set in the current scope. Generatlly this will be used
diff --git a/tools/gn/scope_unittest.cc b/tools/gn/scope_unittest.cc
index a693957..494b90de 100644
--- a/tools/gn/scope_unittest.cc
+++ b/tools/gn/scope_unittest.cc
@@ -46,3 +46,57 @@
EXPECT_FALSE(err.has_error());
}
}
+
+TEST(Scope, GetMutableValue) {
+ TestWithScope setup;
+
+ // Make a pretend parse node with proper tracking that we can blame for the
+ // given value.
+ InputFile input_file(SourceFile("//foo"));
+ Token assignment_token(Location(&input_file, 1, 1), Token::STRING,
+ "\"hello\"");
+ LiteralNode assignment;
+ assignment.set_value(assignment_token);
+
+ const char kOnConst[] = "on_const";
+ const char kOnMutable1[] = "on_mutable1";
+ const char kOnMutable2[] = "on_mutable2";
+
+ Value value(&assignment, "hello");
+
+ // Create a root scope with one value.
+ Scope root_scope(setup.settings());
+ root_scope.SetValue(kOnConst, value, &assignment);
+
+ // Create a first nested scope with a different value.
+ const Scope* const_root_scope = &root_scope;
+ Scope mutable_scope1(const_root_scope);
+ mutable_scope1.SetValue(kOnMutable1, value, &assignment);
+
+ // Create a second nested scope with a different value.
+ Scope mutable_scope2(&mutable_scope1);
+ mutable_scope2.SetValue(kOnMutable2, value, &assignment);
+
+ // Check getting root scope values.
+ EXPECT_TRUE(mutable_scope2.GetValue(kOnConst, true));
+ EXPECT_FALSE(mutable_scope2.GetMutableValue(kOnConst, true));
+
+ // Test reading a value from scope 1.
+ Value* mutable1_result = mutable_scope2.GetMutableValue(kOnMutable1, false);
+ ASSERT_TRUE(mutable1_result);
+ EXPECT_TRUE(*mutable1_result == value);
+
+ // Make sure CheckForUnusedVars works on scope1 (we didn't mark the value as
+ // used in the previous step).
+ Err err;
+ EXPECT_FALSE(mutable_scope1.CheckForUnusedVars(&err));
+ mutable1_result = mutable_scope2.GetMutableValue(kOnMutable1, true);
+ EXPECT_TRUE(mutable1_result);
+ err = Err();
+ EXPECT_TRUE(mutable_scope1.CheckForUnusedVars(&err));
+
+ // Test reading a value from scope 2.
+ Value* mutable2_result = mutable_scope2.GetMutableValue(kOnMutable2, true);
+ ASSERT_TRUE(mutable2_result);
+ EXPECT_TRUE(*mutable2_result == value);
+}
diff --git a/tools/gn/token.h b/tools/gn/token.h
index 25f176b..69ef2fd 100644
--- a/tools/gn/token.h
+++ b/tools/gn/token.h
@@ -32,6 +32,7 @@
BOOLEAN_AND,
BOOLEAN_OR,
BANG,
+ DOT,
LEFT_PAREN,
RIGHT_PAREN,
diff --git a/tools/gn/tokenizer.cc b/tools/gn/tokenizer.cc
index 8acabdb4..36c9691 100644
--- a/tools/gn/tokenizer.cc
+++ b/tools/gn/tokenizer.cc
@@ -64,6 +64,8 @@
return Token::BOOLEAN_OR;
if (value == "!")
return Token::BANG;
+ if (value == ".")
+ return Token::DOT;
return Token::INVALID;
}
@@ -196,6 +198,8 @@
if (next_char == '}')
return Token::RIGHT_BRACE;
+ if (next_char == '.')
+ return Token::DOT;
if (next_char == ',')
return Token::COMMA;
@@ -283,6 +287,7 @@
case Token::RIGHT_BRACE:
case Token::LEFT_PAREN:
case Token::RIGHT_PAREN:
+ case Token::DOT:
case Token::COMMA:
Advance(); // All are one char.
break;
diff --git a/tools/gn/tokenizer_unittest.cc b/tools/gn/tokenizer_unittest.cc
index b02db901..e8abdf4 100644
--- a/tools/gn/tokenizer_unittest.cc
+++ b/tools/gn/tokenizer_unittest.cc
@@ -99,8 +99,10 @@
{ Token::BANG, "!" },
{ Token::BOOLEAN_OR, "||" },
{ Token::BOOLEAN_AND, "&&" },
+ { Token::DOT, "." },
+ { Token::COMMA, "," },
};
- EXPECT_TRUE(CheckTokenizer("- + = += -= != == < > <= >= ! || &&",
+ EXPECT_TRUE(CheckTokenizer("- + = += -= != == < > <= >= ! || && . ,",
operators));
}
diff --git a/tools/gn/value.cc b/tools/gn/value.cc
index 659ebb9..6bf78da2 100644
--- a/tools/gn/value.cc
+++ b/tools/gn/value.cc
@@ -10,6 +10,7 @@
: type_(NONE),
boolean_value_(false),
int_value_(0),
+ scope_value_(NULL),
origin_(NULL) {
}
@@ -17,6 +18,7 @@
: type_(t),
boolean_value_(false),
int_value_(0),
+ scope_value_(NULL),
origin_(origin) {
}
@@ -24,6 +26,7 @@
: type_(BOOLEAN),
boolean_value_(bool_val),
int_value_(0),
+ scope_value_(NULL),
origin_(origin) {
}
@@ -31,6 +34,7 @@
: type_(INTEGER),
boolean_value_(false),
int_value_(int_val),
+ scope_value_(NULL),
origin_(origin) {
}
@@ -39,6 +43,7 @@
string_value_(),
boolean_value_(false),
int_value_(0),
+ scope_value_(NULL),
origin_(origin) {
string_value_.swap(str_val);
}
@@ -48,6 +53,16 @@
string_value_(str_val),
boolean_value_(false),
int_value_(0),
+ scope_value_(NULL),
+ origin_(origin) {
+}
+
+Value::Value(const ParseNode* origin, Scope* scope)
+ : type_(SCOPE),
+ string_value_(),
+ boolean_value_(false),
+ int_value_(0),
+ scope_value_(scope),
origin_(origin) {
}
@@ -75,6 +90,8 @@
return "string";
case LIST:
return "list";
+ case SCOPE:
+ return "scope";
default:
NOTREACHED();
return "UNKNOWN";
@@ -103,6 +120,8 @@
result.push_back(']');
return result;
}
+ case SCOPE:
+ return std::string("<scope>");
}
return std::string();
}
@@ -134,6 +153,10 @@
return false;
}
return true;
+ case Value::SCOPE:
+ // Its not clear what people mean when comparing scope values, so we test
+ // for scope identity and not contents equality.
+ return scope_value() == other.scope_value();
default:
return false;
}
diff --git a/tools/gn/value.h b/tools/gn/value.h
index 092b852..ee201c4 100644
--- a/tools/gn/value.h
+++ b/tools/gn/value.h
@@ -11,6 +11,7 @@
#include "tools/gn/err.h"
class ParseNode;
+class Scope;
// Represents a variable value in the interpreter.
class Value {
@@ -20,7 +21,8 @@
BOOLEAN,
INTEGER,
STRING,
- LIST
+ LIST,
+ SCOPE
};
Value();
@@ -29,6 +31,8 @@
Value(const ParseNode* origin, int64 int_val);
Value(const ParseNode* origin, std::string str_val);
Value(const ParseNode* origin, const char* str_val);
+ Value(const ParseNode* origin, Scope* scope); // Non-owning ptr.
+ // (must outlive Value.)
~Value();
Type type() const { return type_; }
@@ -80,6 +84,15 @@
return list_value_;
}
+ Scope* scope_value() {
+ DCHECK(type_ == SCOPE);
+ return scope_value_;
+ }
+ const Scope* scope_value() const {
+ DCHECK(type_ == SCOPE);
+ return scope_value_;
+ }
+
// Converts the given value to a string. Returns true if strings should be
// quoted or the ToString of a string should be the string itself.
std::string ToString(bool quote_strings) const;
@@ -98,6 +111,8 @@
bool boolean_value_;
int64 int_value_;
std::vector<Value> list_value_;
+ Scope* scope_value_; // Non-owning.
+
const ParseNode* origin_;
};