aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java286
1 files changed, 286 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java b/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java
new file mode 100644
index 0000000000..2bbed64cd6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java
@@ -0,0 +1,286 @@
+// Copyright 2014 Google Inc. 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.
+ *
+ * <p> 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.
+ *
+ * <p> The code above can be expanded as:
+ * <pre>
+ * for a in b:
+ * if c:
+ * for d in e:
+ * result.append(a+d)
+ * </pre>
+ * 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);
+ for (Object listElement : listValue) {
+ variables.assign(env, loc, listElement);
+ evalStep(env, collector, step);
+ }
+ }
+
+ @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<Expression> outputExpressions;
+
+ private final List<Clause> clauses;
+ private final char openingBracket;
+ private final char closingBracket;
+
+ public AbstractComprehension(
+ char openingBracket, char closingBracket, Expression... outputExpressions) {
+ clauses = new ArrayList<>();
+ this.outputExpressions = ImmutableList.copyOf(outputExpressions);
+ this.openingBracket = openingBracket;
+ this.closingBracket = closingBracket;
+ }
+
+ public ImmutableList<Expression> getOutputExpressions() {
+ return outputExpressions;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(openingBracket).append(printExpressions());
+ for (Clause clause : clauses) {
+ sb.append(' ').append(clause.toString());
+ }
+ sb.append(closingBracket);
+ return sb.toString();
+ }
+
+ /**
+ * Add a new ForClause to the comprehension. This is used only by the parser and must
+ * not be called once AST is complete.
+ * TODO(bazel-team): Remove this side-effect. Clauses should be passed to the constructor
+ * instead.
+ */
+ void addFor(Expression loopVar, Expression listExpression) {
+ Clause forClause = new ForClause(new LValue(loopVar), listExpression);
+ clauses.add(forClause);
+ }
+
+ /**
+ * Add a new ForClause to the comprehension.
+ * TODO(bazel-team): Remove this side-effect.
+ */
+ void addIf(Expression condition) {
+ clauses.add(new IfClause(condition));
+ }
+
+ public List<Clause> getClauses() {
+ return Collections.unmodifiableList(clauses);
+ }
+
+ @Override
+ public void accept(SyntaxTreeVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ Object doEval(Environment env) throws EvalException, InterruptedException {
+ OutputCollector collector = createCollector();
+ evalStep(env, collector, 0);
+ return collector.getResult(env);
+ }
+
+ @Override
+ void validate(ValidationEnvironment env) throws EvalException {
+ for (Clause clause : clauses) {
+ clause.validate(env);
+ }
+ // Clauses have to be validated before expressions in order to introduce the variable names.
+ for (Expression expr : outputExpressions) {
+ expr.validate(env);
+ }
+ }
+
+ /**
+ * Evaluate the clause indexed by step, or elementExpression. When we evaluate the
+ * comprehension, step is 0 and we evaluate the first clause. Each clause may
+ * recursively call evalStep any number of times. After the last clause,
+ * the output expression(s) is/are evaluated and added to the results.
+ *
+ * <p> 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();
+
+ /**
+ * 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;
+ }
+}