blob: 7a4273bbc8bf94f1a982841060bfe82a971286a0 [file] [log] [blame]
// 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.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<Environment, Eval> 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<Environment, Eval> 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<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));
}
}
// 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.updateAndExport(
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<IfStatement.ConditionalStatements> 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 (LoadStatement.Binding binding : node.getBindings()) {
try {
Identifier name = binding.getLocalName();
Identifier declared = binding.getOriginalName();
if (declared.isPrivate() && !node.mayLoadInternalSymbols()) {
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<Statement> 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));
}
}
}