Refactoring: Introduce a new class LValue.

This CL is just moving code from AssignmentStatement to a new class. The goal
is to share code dealing with LValues.

LValues can be found here:
 -  lvalue = 2
 -  [for lvalue in exp]
 -  {a: b for lvalue in exp]
 -  for lvalue in exp: pass

The LValue itself can have different forms:
 -  a
 -  a, b
 -  a[0]
 -  a, (b, c)
 -  [a[0], (b, c)]
 -  a[1:5]

Although we may not handle everything, we need to make sure that the same
things can be used in variable assignment and in for loops.

--
MOS_MIGRATED_REVID=89015483
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 3e18132..c8aaefd 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -1239,7 +1239,7 @@
                                                       EventHandler eventHandler) {
     for (Statement stmt : ast.getStatements()) {
       if (stmt instanceof AssignmentStatement) {
-        Expression lvalue = ((AssignmentStatement) stmt).getLValue();
+        Expression lvalue = ((AssignmentStatement) stmt).getLValue().getExpression();
         if (!(lvalue instanceof Ident)) {
           continue;
         }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java
index 0bb5847..f0063d0 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/AssignmentStatement.java
@@ -14,14 +14,12 @@
 
 package com.google.devtools.build.lib.syntax;
 
-import com.google.common.base.Preconditions;
-
 /**
  * Syntax node for an assignment statement.
  */
 public final class AssignmentStatement extends Statement {
 
-  private final Expression lvalue;
+  private final LValue lvalue;
 
   private final Expression expression;
 
@@ -29,14 +27,14 @@
    *  Constructs an assignment: "lvalue := value".
    */
   AssignmentStatement(Expression lvalue, Expression expression) {
-    this.lvalue = lvalue;
+    this.lvalue = new LValue(lvalue);
     this.expression = expression;
   }
 
   /**
    *  Returns the LHS of the assignment.
    */
-  public Expression getLValue() {
+  public LValue getLValue() {
     return lvalue;
   }
 
@@ -54,38 +52,7 @@
 
   @Override
   void exec(Environment env) throws EvalException, InterruptedException {
-    if (!(lvalue instanceof Ident)) {
-      throw new EvalException(getLocation(),
-          "can only assign to variables, not to '" + lvalue + "'");
-    }
-
-    Ident ident = (Ident) lvalue;
-    Object result = expression.eval(env);
-    Preconditions.checkNotNull(result, "result of %s is null", expression);
-
-    if (env.isSkylarkEnabled()) {
-      // The variable may have been referenced successfully if a global variable
-      // with the same name exists. In this case an Exception needs to be thrown.
-      SkylarkEnvironment skylarkEnv = (SkylarkEnvironment) env;
-      if (skylarkEnv.hasBeenReadGlobalVariable(ident.getName())) {
-        throw new EvalException(getLocation(), "Variable '" + ident.getName()
-            + "' is referenced before assignment."
-            + "The variable is defined in the global scope.");
-      }
-      Class<?> variableType = skylarkEnv.getVariableType(ident.getName());
-      Class<?> resultType = EvalUtils.getSkylarkType(result.getClass());
-      if (variableType != null && !variableType.equals(resultType)
-          && !resultType.equals(Environment.NoneType.class)
-          && !variableType.equals(Environment.NoneType.class)) {
-        throw new EvalException(getLocation(), String.format("Incompatible variable types, "
-            + "trying to assign %s (type of %s) to variable %s which is already %s",
-            EvalUtils.prettyPrintValue(result),
-            EvalUtils.getDataTypeName(result),
-            ident.getName(),
-            EvalUtils.getDataTypeNameFromClass(variableType)));
-      }
-    }
-    env.update(ident.getName(), result);
+    lvalue.assign(env, getLocation(), expression);
   }
 
   @Override
@@ -95,14 +62,6 @@
 
   @Override
   void validate(ValidationEnvironment env) throws EvalException {
-    // TODO(bazel-team): Implement other validations.
-    if (lvalue instanceof Ident) {
-      Ident ident = (Ident) lvalue;
-      SkylarkType resultType = expression.validate(env);
-      env.update(ident.getName(), resultType, getLocation());
-    } else {
-      throw new EvalException(getLocation(),
-          "can only assign to variables, not to '" + lvalue + "'");
-    }
+    lvalue.validate(env, getLocation(), expression.validate(env));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LValue.java b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
new file mode 100644
index 0000000..a328118
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
@@ -0,0 +1,97 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.syntax;
+
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.events.Location;
+
+import java.io.Serializable;
+
+/**
+ * Class representing an LValue.
+ * It appears in assignment, for loop and comprehensions, e.g.
+ *    lvalue = 2
+ *    [for lvalue in exp]
+ *    for lvalue in exp: pass
+ * An LValue can be a simple variable or something more complex like a tuple.
+ */
+public class LValue implements Serializable {
+  // Currently, expr can only be an Ident, but we plan to support more.
+  private final Expression expr;
+
+  public LValue(Expression expr) {
+    this.expr = expr;
+  }
+
+  public Expression getExpression() {
+    return expr;
+  }
+
+  /**
+   * Assign a value to an LValue and update the environment.
+   */
+  public void assign(Environment env, Location loc, Expression rvalue)
+      throws EvalException, InterruptedException {
+    if (!(expr instanceof Ident)) {
+      throw new EvalException(loc,
+          "can only assign to variables, not to '" + expr + "'");
+    }
+
+    Ident ident = (Ident) expr;
+    Object result = rvalue.eval(env);
+    Preconditions.checkNotNull(result, "result of %s is null", rvalue);
+
+    if (env.isSkylarkEnabled()) {
+      // The variable may have been referenced successfully if a global variable
+      // with the same name exists. In this case an Exception needs to be thrown.
+      SkylarkEnvironment skylarkEnv = (SkylarkEnvironment) env;
+      if (skylarkEnv.hasBeenReadGlobalVariable(ident.getName())) {
+        throw new EvalException(loc, "Variable '" + ident.getName()
+            + "' is referenced before assignment."
+            + "The variable is defined in the global scope.");
+      }
+      Class<?> variableType = skylarkEnv.getVariableType(ident.getName());
+      Class<?> resultType = EvalUtils.getSkylarkType(result.getClass());
+      if (variableType != null && !variableType.equals(resultType)
+          && !resultType.equals(Environment.NoneType.class)
+          && !variableType.equals(Environment.NoneType.class)) {
+        throw new EvalException(loc, String.format("Incompatible variable types, "
+            + "trying to assign %s (type of %s) to variable %s which is already %s",
+            EvalUtils.prettyPrintValue(result),
+            EvalUtils.getDataTypeName(result),
+            ident.getName(),
+            EvalUtils.getDataTypeNameFromClass(variableType)));
+      }
+    }
+    env.update(ident.getName(), result);
+  }
+
+  void validate(ValidationEnvironment env, Location loc, SkylarkType rvalueType)
+      throws EvalException {
+    // TODO(bazel-team): Implement other validations.
+    if (expr instanceof Ident) {
+      Ident ident = (Ident) expr;
+      env.update(ident.getName(), rvalueType, loc);
+      return;
+    }
+    throw new EvalException(loc,
+        "can only assign to variables, not to '" + expr + "'");
+  }
+
+  @Override
+  public String toString() {
+    return expr.toString();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java b/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java
index 2463d25..439a4b1 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SyntaxTreeVisitor.java
@@ -85,7 +85,7 @@
   }
 
   public void visit(AssignmentStatement node) {
-    visit(node.getLValue());
+    visit(node.getLValue().getExpression());
     visit(node.getExpression());
   }