// 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.IOException; import java.io.Serializable; import java.util.ArrayList; 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 { /** Enum for distinguishing clause types. */ public enum Kind { FOR, IF } /** * Returns whether this is a For or If clause. * *
This avoids having to rely on reflection, or on checking whether {@link #getLValue} is
* null.
*/
public Kind getKind();
/**
* 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.
*/
void eval(Environment env, OutputCollector collector, int step)
throws EvalException, InterruptedException;
void validate(ValidationEnvironment env, Location loc) 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 LValue getLValue();
/**
* The Expression defined in Clause, i.e. the collection for ForClause and the
* condition for IfClause. This is needed for SyntaxTreeVisitor.
*/
public Expression getExpression();
/** Pretty print to a buffer. */
public void prettyPrint(Appendable buffer) throws IOException;
}
/**
* A for clause in a comprehension, e.g. "for a in b" in the example above.
*/
public static final class ForClause implements Clause {
private final LValue variables;
private final Expression list;
@Override
public Kind getKind() {
return Kind.FOR;
}
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 = collector.getLocation();
Iterable> listValue = EvalUtils.toIterable(listValueObject, loc, env);
EvalUtils.lock(listValueObject, loc);
try {
for (Object listElement : listValue) {
variables.assign(env, loc, listElement);
evalStep(env, collector, step);
}
} finally {
EvalUtils.unlock(listValueObject, loc);
}
}
@Override
public void validate(ValidationEnvironment env, Location loc) throws EvalException {
variables.validate(env, loc);
list.validate(env);
}
@Override
public LValue getLValue() {
return variables;
}
@Override
public Expression getExpression() {
return list;
}
@Override
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append("for ");
variables.prettyPrint(buffer);
buffer.append(" in ");
list.prettyPrint(buffer);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
try {
prettyPrint(builder);
} catch (IOException e) {
// Not possible for StringBuilder.
throw new AssertionError(e);
}
return builder.toString();
}
}
/**
* A if clause in a comprehension, e.g. "if c" in the example above.
*/
public static final class IfClause implements Clause {
private final Expression condition;
@Override
public Kind getKind() {
return Kind.IF;
}
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, Location loc) throws EvalException {
condition.validate(env);
}
@Override
public LValue getLValue() {
return null;
}
@Override
public Expression getExpression() {
return condition;
}
@Override
public void prettyPrint(Appendable buffer) throws IOException {
buffer.append("if ");
condition.prettyPrint(buffer);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
try {
prettyPrint(builder);
} catch (IOException e) {
// Not possible for StringBuilder.
throw new AssertionError(e);
}
return builder.toString();
}
}
/**
* 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 static void evalStep(Environment env, OutputCollector collector, int step)
throws EvalException, InterruptedException {
List