Move Statement.exec methods to a separate class.

RELNOTES: None.
PiperOrigin-RevId: 166689144
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
new file mode 100644
index 0000000..d4c6edb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
@@ -0,0 +1,211 @@
+// Copyright 2017 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 java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Evaluation code for the Skylark AST. At the moment, it can execute only statements (and defers to
+ * Expression.eval for evaluating expressions).
+ */
+public class Eval {
+  private final Environment env;
+
+  /** An exception that signals changes in the control flow (e.g. break or continue) */
+  private static class FlowException extends EvalException {
+    FlowException(String message) {
+      super(null, message);
+    }
+
+    @Override
+    public boolean canBeAddedToStackTrace() {
+      return false;
+    }
+  }
+
+  private static final FlowException breakException = new FlowException("FlowException - break");
+  private static final FlowException continueException =
+      new FlowException("FlowException - continue");
+
+  public Eval(Environment env) {
+    this.env = env;
+  }
+
+  void execAssignment(AssignmentStatement node) throws EvalException, InterruptedException {
+    Object rvalue = node.getExpression().eval(env);
+    node.getLValue().assign(rvalue, env, node.getLocation());
+  }
+
+  void execAugmentedAssignment(AugmentedAssignmentStatement node)
+      throws EvalException, InterruptedException {
+    node.getLValue()
+        .assignAugmented(node.getOperator(), node.getExpression(), env, node.getLocation());
+  }
+
+  void execIfBranch(IfStatement.ConditionalStatements node)
+      throws EvalException, InterruptedException {
+    for (Statement stmt : node.getStatements()) {
+      exec(stmt);
+    }
+  }
+
+  void execFor(ForStatement node) throws EvalException, InterruptedException {
+    Object o = node.getCollection().eval(env);
+    Iterable<?> col = EvalUtils.toIterable(o, node.getLocation(), env);
+    EvalUtils.lock(o, node.getLocation());
+    try {
+      for (Object it : col) {
+        node.getVariable().assign(it, env, node.getLocation());
+
+        try {
+          for (Statement stmt : node.getBlock()) {
+            stmt.exec(env);
+          }
+        } catch (FlowException ex) {
+          if (ex == breakException) {
+            return;
+          }
+        }
+      }
+    } finally {
+      EvalUtils.unlock(o, node.getLocation());
+    }
+  }
+
+  void execDef(FunctionDefStatement node) throws EvalException, InterruptedException {
+    List<Expression> defaultExpressions = node.getSignature().getDefaultValues();
+    ArrayList<Object> defaultValues = null;
+
+    if (defaultExpressions != null) {
+      defaultValues = new ArrayList<>(defaultExpressions.size());
+      for (Expression expr : defaultExpressions) {
+        defaultValues.add(expr.eval(env));
+      }
+    }
+
+    FunctionSignature sig = node.getSignature().getSignature();
+    if (env.getSemantics().incompatibleDisallowKeywordOnlyArgs
+        && sig.getShape().getMandatoryNamedOnly() > 0) {
+      throw new EvalException(
+          node.getLocation(),
+          "Keyword-only argument is forbidden. You can temporarily disable this "
+              + "error using the flag --incompatible_disallow_keyword_only_args=false");
+    }
+
+    env.update(
+        node.getIdentifier().getName(),
+        new UserDefinedFunction(
+            node.getIdentifier().getName(),
+            node.getIdentifier().getLocation(),
+            FunctionSignature.WithValues.create(sig, defaultValues, /*types=*/ null),
+            node.getStatements(),
+            env.getGlobals()));
+  }
+
+  void execIf(IfStatement node) throws EvalException, InterruptedException {
+    for (IfStatement.ConditionalStatements stmt : node.getThenBlocks()) {
+      if (EvalUtils.toBoolean(stmt.getCondition().eval(env))) {
+        exec(stmt);
+        return;
+      }
+    }
+    for (Statement stmt : node.getElseBlock()) {
+      exec(stmt);
+    }
+  }
+
+  void execLoad(LoadStatement node) throws EvalException, InterruptedException {
+    if (env.getSemantics().incompatibleLoadArgumentIsLabel) {
+      String s = node.getImport().getValue();
+      if (!s.startsWith("//") && !s.startsWith(":")) {
+        throw new EvalException(
+            node.getLocation(),
+            "First argument of 'load' must be a label and start with either '//' or ':'. "
+                + "Use --incompatible_load_argument_is_label=false to temporarily disable this "
+                + "check.");
+      }
+    }
+
+    for (Map.Entry<Identifier, String> entry : node.getSymbolMap().entrySet()) {
+      try {
+        Identifier name = entry.getKey();
+        Identifier declared = new Identifier(entry.getValue());
+
+        if (declared.isPrivate()) {
+          throw new EvalException(
+              node.getLocation(),
+              "symbol '" + declared.getName() + "' is private and cannot be imported.");
+        }
+        // The key is the original name that was used to define the symbol
+        // in the loaded bzl file.
+        env.importSymbol(node.getImport().getValue(), name, declared.getName());
+      } catch (Environment.LoadFailedException e) {
+        throw new EvalException(node.getLocation(), e.getMessage());
+      }
+    }
+  }
+
+  void execReturn(ReturnStatement node) throws EvalException, InterruptedException {
+    Expression ret = node.getReturnExpression();
+    if (ret == null) {
+      throw new ReturnStatement.ReturnException(node.getLocation(), Runtime.NONE);
+    }
+    throw new ReturnStatement.ReturnException(ret.getLocation(), ret.eval(env));
+  }
+
+  /**
+   * Execute the statement.
+   *
+   * @throws EvalException if execution of the statement could not be completed.
+   * @throws InterruptedException may be thrown in a sub class.
+   */
+  public void exec(Statement st) throws EvalException, InterruptedException {
+    switch (st.kind()) {
+      case ASSIGNMENT:
+        execAssignment((AssignmentStatement) st);
+        break;
+      case AUGMENTED_ASSIGNMENT:
+        execAugmentedAssignment((AugmentedAssignmentStatement) st);
+        break;
+      case CONDITIONAL:
+        execIfBranch((IfStatement.ConditionalStatements) st);
+        break;
+      case EXPRESSION:
+        ((ExpressionStatement) st).getExpression().eval(env);
+        break;
+      case FLOW:
+        throw ((FlowStatement) st).getKind() == FlowStatement.Kind.BREAK
+            ? breakException
+            : continueException;
+      case FOR:
+        execFor((ForStatement) st);
+        break;
+      case FUNCTION_DEF:
+        execDef((FunctionDefStatement) st);
+        break;
+      case IF:
+        execIf((IfStatement) st);
+        break;
+      case LOAD:
+        execLoad((LoadStatement) st);
+        break;
+      case RETURN:
+        execReturn((ReturnStatement) st);
+        break;
+    }
+  }
+}