Add a new AST node for augmented assignments in Skylark

--
PiperOrigin-RevId: 142438943
MOS_MIGRATED_REVID=142438943
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/AugmentedAssignmentStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/AugmentedAssignmentStatement.java
new file mode 100644
index 0000000..a33482f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/AugmentedAssignmentStatement.java
@@ -0,0 +1,73 @@
+// Copyright 2014 The Bazel Authors. 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.devtools.build.lib.events.Location;
+
+/** Syntax node for an augmented assignment statement. */
+public final class AugmentedAssignmentStatement extends Statement {
+
+  private final Operator operator;
+
+  private final LValue lvalue;
+
+  private final Expression expression;
+
+  /** Constructs an augmented assignment: "lvalue ::= value". */
+  AugmentedAssignmentStatement(Operator operator, Expression lhs, Expression expression) {
+    this.operator = operator;
+    this.lvalue = new LValue(lhs);
+    this.expression = expression;
+  }
+
+  /** Returns the operator of the assignment. */
+  public Operator getOperator() {
+    return operator;
+  }
+
+  /** Returns the LValue of the assignment. */
+  public LValue getLValue() {
+    return lvalue;
+  }
+
+  /** Returns the RHS of the assignment. */
+  public Expression getExpression() {
+    return expression;
+  }
+
+  @Override
+  public String toString() {
+    return String.format("%s %s= %s\n", lvalue, operator, expression);
+  }
+
+  @Override
+  void doExec(Environment env) throws EvalException, InterruptedException {
+    Location loc = getLocation();
+    Object result =
+        BinaryOperatorExpression.evaluate(operator, lvalue.getExpression(), expression, env, loc);
+    lvalue.assign(env, loc, result);
+  }
+
+  @Override
+  public void accept(SyntaxTreeVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override
+  void validate(ValidationEnvironment env) throws EvalException {
+    lvalue.validate(env, getLocation());
+    expression.validate(env);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
index 864f3dc..1e9f023 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
@@ -110,8 +110,13 @@
     }
   }
 
-  @Override
-  Object doEval(Environment env) throws EvalException, InterruptedException {
+  /**
+   * Helper method. Reused from AugmentedAssignmentStatement class which falls back to this method
+   * in most of the cases.
+   */
+  public static Object evaluate(
+      Operator operator, Expression lhs, Expression rhs, Environment env, Location location)
+      throws EvalException, InterruptedException {
     Object lval = lhs.eval(env);
 
     // Short-circuit operators
@@ -135,22 +140,22 @@
 
     switch (operator) {
       case PLUS:
-        return plus(lval, rval, env, getLocation());
+        return plus(lval, rval, env, location);
 
       case PIPE:
-        return pipe(lval, rval, getLocation());
+        return pipe(lval, rval, location);
 
       case MINUS:
-        return minus(lval, rval, getLocation());
+        return minus(lval, rval, location);
 
       case MULT:
-        return mult(lval, rval, env, getLocation());
+        return mult(lval, rval, env, location);
 
       case DIVIDE:
-        return divide(lval, rval, getLocation());
+        return divide(lval, rval, location);
 
       case PERCENT:
-        return percent(lval, rval, getLocation());
+        return percent(lval, rval, location);
 
       case EQUALS_EQUALS:
         return lval.equals(rval);
@@ -159,22 +164,22 @@
         return !lval.equals(rval);
 
       case LESS:
-        return compare(lval, rval, getLocation()) < 0;
+        return compare(lval, rval, location) < 0;
 
       case LESS_EQUALS:
-        return compare(lval, rval, getLocation()) <= 0;
+        return compare(lval, rval, location) <= 0;
 
       case GREATER:
-        return compare(lval, rval, getLocation()) > 0;
+        return compare(lval, rval, location) > 0;
 
       case GREATER_EQUALS:
-        return compare(lval, rval, getLocation()) >= 0;
+        return compare(lval, rval, location) >= 0;
 
       case IN:
-        return in(lval, rval, getLocation());
+        return in(lval, rval, location);
 
       case NOT_IN:
-        return !in(lval, rval, getLocation());
+        return !in(lval, rval, location);
 
       default:
         throw new AssertionError("Unsupported binary operator: " + operator);
@@ -182,6 +187,11 @@
   }
 
   @Override
+  Object doEval(Environment env) throws EvalException, InterruptedException {
+    return evaluate(operator, lhs, rhs, env, getLocation());
+  }
+
+  @Override
   public void accept(SyntaxTreeVisitor visitor) {
     visitor.visit(this);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index 8b28b3e..267808d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -1156,9 +1156,9 @@
   //                  | RETURN expr
   //                  | flow_stmt
   //     assign_stmt ::= expr ('=' | augassign) expr
-  //     augassign ::= ('+=' )
+  //     augassign ::= ('+=' | '-=' | '*=' | '/=' | '%=')
   // Note that these are in Python, but not implemented here (at least for now):
-  // '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' |'<<=' | '>>=' | '**=' | '//='
+  // '&=' | '|=' | '^=' |'<<=' | '>>=' | '**=' | '//='
   // Semantic difference from Python:
   // In Skylark, x += y is simple syntactic sugar for x = x + y.
   // In Python, x += y is more or less equivalent to x = x + y, but if a method is defined
@@ -1185,10 +1185,8 @@
       nextToken();
       Expression operand = parseExpression();
       int end = operand.getLocation().getEndOffset();
-      return setLocation(new AssignmentStatement(expression,
-               setLocation(new BinaryOperatorExpression(
-                   operator, expression, operand), start, end)),
-               start, end);
+      return setLocation(
+          new AugmentedAssignmentStatement(operator, expression, operand), start, end);
     } else {
       return setLocation(new ExpressionStatement(expression), start, expression);
     }
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 d18615c..387268a 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
@@ -15,7 +15,6 @@
 
 import com.google.devtools.build.lib.syntax.DictionaryLiteral.DictionaryEntryLiteral;
 import com.google.devtools.build.lib.syntax.IfStatement.ConditionalStatements;
-
 import java.util.List;
 
 /**
@@ -102,6 +101,11 @@
     visit(node.getExpression());
   }
 
+  public void visit(AugmentedAssignmentStatement node) {
+    visit(node.getLValue());
+    visit(node.getExpression());
+  }
+
   public void visit(ExpressionStatement node) {
     visit(node.getExpression());
   }