// 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 com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Function; /** * 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 { protected 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; } } public static Eval fromEnvironment(Environment env) { return evalSupplier.apply(env); } public static void setEvalSupplier(Function evalSupplier) { Eval.evalSupplier = evalSupplier; } /** Reset Eval supplier to the default. */ public static void removeCustomEval() { evalSupplier = Eval::new; } // TODO(bazel-team): remove this static state in favor of storing Eval instances in Environment private static Function evalSupplier = Eval::new; private static final FlowException breakException = new FlowException("FlowException - break"); private static final FlowException continueException = new FlowException("FlowException - continue"); /** * This constructor should never be called directly. Call {@link #fromEnvironment(Environment)} * instead. */ protected 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 { execStatements(node.getStatements()); } 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 { execStatements(node.getBlock()); } catch (FlowException ex) { if (ex == breakException) { return; } } } } finally { EvalUtils.unlock(o, node.getLocation()); } } void execDef(FunctionDefStatement node) throws EvalException, InterruptedException { List defaultExpressions = node.getSignature().getDefaultValues(); ArrayList defaultValues = null; if (defaultExpressions != null) { defaultValues = new ArrayList<>(defaultExpressions.size()); for (Expression expr : defaultExpressions) { defaultValues.add(expr.eval(env)); } } // TODO(laurentlb): Could be moved to the Parser or the ValidationEnvironment? FunctionSignature sig = node.getSignature().getSignature(); if (sig.getShape().getMandatoryNamedOnly() > 0) { throw new EvalException(node.getLocation(), "Keyword-only argument is forbidden."); } 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 { ImmutableList thenBlocks = node.getThenBlocks(); // Avoid iterator overhead - most of the time there will be one or few "if"s. for (int i = 0; i < thenBlocks.size(); i++) { IfStatement.ConditionalStatements stmt = thenBlocks.get(i); if (EvalUtils.toBoolean(stmt.getCondition().eval(env))) { exec(stmt); return; } } execStatements(node.getElseBlock()); } void execLoad(LoadStatement node) throws EvalException, InterruptedException { for (Map.Entry entry : node.getSymbolMap().entrySet()) { try { Identifier name = entry.getKey(); Identifier declared = Identifier.of(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 { try { execDispatch(st); } catch (EvalException ex) { throw st.maybeTransformException(ex); } } void execDispatch(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 PASS: break; case RETURN: execReturn((ReturnStatement) st); break; } } private void execStatements(ImmutableList statements) throws EvalException, InterruptedException { // Hot code path, good chance of short lists which don't justify the iterator overhead. for (int i = 0; i < statements.size(); i++) { exec(statements.get(i)); } } }