// 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.common.collect.ImmutableList; import com.google.devtools.build.lib.events.Location; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; /** * Base class for list and dict comprehension expressions. * *
A comprehension contains one or more clauses, e.g. * [a+d for a in b if c for d in e] * contains three clauses: "for a in b", "if c", "for d in e". * For and If clauses can happen in any order, except that the first one has to be a For. * *
The code above can be expanded as: *
* for a in b: * if c: * for d in e: * result.append(a+d) ** result is initialized to [] (list) or {} (dict) and is the return value of the whole expression. */ public abstract class AbstractComprehension extends Expression { /** * The interface implemented by ForClause and (later) IfClause. * A comprehension consists of one or many Clause. */ public interface Clause extends Serializable { /** * The evaluation of the comprehension is based on recursion. Each clause may * call recursively evalStep (ForClause will call it multiple times, IfClause will * call it zero or one time) which will evaluate the next clause. To know which clause * is the next one, we pass a step argument (it represents the index in the clauses * list). Results are aggregated in the result argument, and are populated by * evalStep. * * @param env environment in which we do the evaluation. * @param collector the aggregated results of the comprehension. * @param step the index of the next clause to evaluate. */ abstract void eval(Environment env, OutputCollector collector, int step) throws EvalException, InterruptedException; abstract void validate(ValidationEnvironment env) throws EvalException; /** * The LValue defined in Clause, i.e. the loop variables for ForClause and null for * IfClause. This is needed for SyntaxTreeVisitor. */ @Nullable // for the IfClause public abstract LValue getLValue(); /** * The Expression defined in Clause, i.e. the collection for ForClause and the * condition for IfClause. This is needed for SyntaxTreeVisitor. */ public abstract Expression getExpression(); } /** * A for clause in a comprehension, e.g. "for a in b" in the example above. */ public final class ForClause implements Clause { private final LValue variables; private final Expression list; public ForClause(LValue variables, Expression list) { this.variables = variables; this.list = list; } @Override public void eval(Environment env, OutputCollector collector, int step) throws EvalException, InterruptedException { Object listValueObject = list.eval(env); Location loc = getLocation(); Iterable> listValue = EvalUtils.toIterable(listValueObject, loc); EvalUtils.lock(listValueObject, getLocation()); try { for (Object listElement : listValue) { variables.assign(env, loc, listElement); evalStep(env, collector, step); } } finally { EvalUtils.unlock(listValueObject, getLocation()); } } @Override public void validate(ValidationEnvironment env) throws EvalException { variables.validate(env, getLocation()); list.validate(env); } @Override public LValue getLValue() { return variables; } @Override public Expression getExpression() { return list; } @Override public String toString() { return Printer.format("for %s in %r", variables.toString(), list); } } /** * A if clause in a comprehension, e.g. "if c" in the example above. */ public final class IfClause implements Clause { private final Expression condition; public IfClause(Expression condition) { this.condition = condition; } @Override public void eval(Environment env, OutputCollector collector, int step) throws EvalException, InterruptedException { if (EvalUtils.toBoolean(condition.eval(env))) { evalStep(env, collector, step); } } @Override public void validate(ValidationEnvironment env) throws EvalException { condition.validate(env); } @Override public LValue getLValue() { return null; } @Override public Expression getExpression() { return condition; } @Override public String toString() { return String.format("if %s", condition); } } /** * The output expressions, e.g. "a+d" in the example above. This list has either one (list) or two * (dict) items. */ private final ImmutableList
In the expanded example above, you can consider that evalStep is equivalent to * evaluating the line number step. */ private void evalStep(Environment env, OutputCollector collector, int step) throws EvalException, InterruptedException { if (step >= clauses.size()) { collector.evaluateAndCollect(env); } else { clauses.get(step).eval(env, collector, step + 1); } } /** * Returns a {@link String} representation of the output expression(s). */ abstract String printExpressions(); abstract OutputCollector createCollector(Environment env); /** * Interface for collecting the intermediate output of an {@code AbstractComprehension} and for * providing access to the final results. */ interface OutputCollector { /** * Evaluates the output expression(s) of the comprehension and collects the result. */ void evaluateAndCollect(Environment env) throws EvalException, InterruptedException; /** * Returns the final result of the comprehension. */ Object getResult(Environment env) throws EvalException; } }