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_;
 };