blob: ae8af0ae347e93c14359938f1a203974eae8cbeb [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Florian Weikertffd8a5a2015-09-18 11:51:01 +00002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.syntax;
16
17import com.google.common.collect.ImmutableList;
18import com.google.devtools.build.lib.events.Location;
janakr76de1ad2018-03-03 11:12:36 -080019import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
brandjone2ffd5d2017-06-27 18:14:54 +020020import java.io.IOException;
Florian Weikertffd8a5a2015-09-18 11:51:01 +000021import java.io.Serializable;
22import java.util.ArrayList;
Florian Weikertffd8a5a2015-09-18 11:51:01 +000023import java.util.List;
Florian Weikertffd8a5a2015-09-18 11:51:01 +000024import javax.annotation.Nullable;
25
26/**
27 * Base class for list and dict comprehension expressions.
28 *
29 * <p> A comprehension contains one or more clauses, e.g.
30 * [a+d for a in b if c for d in e]
31 * contains three clauses: "for a in b", "if c", "for d in e".
32 * For and If clauses can happen in any order, except that the first one has to be a For.
33 *
34 * <p> The code above can be expanded as:
35 * <pre>
36 * for a in b:
37 * if c:
38 * for d in e:
39 * result.append(a+d)
40 * </pre>
41 * result is initialized to [] (list) or {} (dict) and is the return value of the whole expression.
42 */
43public abstract class AbstractComprehension extends Expression {
44
45 /**
46 * The interface implemented by ForClause and (later) IfClause.
47 * A comprehension consists of one or many Clause.
48 */
49 public interface Clause extends Serializable {
brandjon296cd492017-05-15 16:17:16 +020050
51 /** Enum for distinguishing clause types. */
brandjon990622b2017-07-11 19:56:45 +020052 enum Kind {
brandjon296cd492017-05-15 16:17:16 +020053 FOR,
54 IF
55 }
56
57 /**
58 * Returns whether this is a For or If clause.
59 *
60 * <p>This avoids having to rely on reflection, or on checking whether {@link #getLValue} is
61 * null.
62 */
brandjon990622b2017-07-11 19:56:45 +020063 Kind getKind();
brandjon296cd492017-05-15 16:17:16 +020064
Florian Weikertffd8a5a2015-09-18 11:51:01 +000065 /**
66 * The evaluation of the comprehension is based on recursion. Each clause may
67 * call recursively evalStep (ForClause will call it multiple times, IfClause will
68 * call it zero or one time) which will evaluate the next clause. To know which clause
69 * is the next one, we pass a step argument (it represents the index in the clauses
70 * list). Results are aggregated in the result argument, and are populated by
71 * evalStep.
72 *
73 * @param env environment in which we do the evaluation.
74 * @param collector the aggregated results of the comprehension.
75 * @param step the index of the next clause to evaluate.
76 */
brandjone2ffd5d2017-06-27 18:14:54 +020077 void eval(Environment env, OutputCollector collector, int step)
Florian Weikertffd8a5a2015-09-18 11:51:01 +000078 throws EvalException, InterruptedException;
79
Florian Weikertffd8a5a2015-09-18 11:51:01 +000080 /**
81 * The LValue defined in Clause, i.e. the loop variables for ForClause and null for
82 * IfClause. This is needed for SyntaxTreeVisitor.
83 */
84 @Nullable // for the IfClause
brandjon990622b2017-07-11 19:56:45 +020085 LValue getLValue();
Florian Weikertffd8a5a2015-09-18 11:51:01 +000086
87 /**
88 * The Expression defined in Clause, i.e. the collection for ForClause and the
89 * condition for IfClause. This is needed for SyntaxTreeVisitor.
90 */
brandjon990622b2017-07-11 19:56:45 +020091 Expression getExpression();
brandjone2ffd5d2017-06-27 18:14:54 +020092
93 /** Pretty print to a buffer. */
brandjon990622b2017-07-11 19:56:45 +020094 void prettyPrint(Appendable buffer) throws IOException;
Florian Weikertffd8a5a2015-09-18 11:51:01 +000095 }
96
janakr76de1ad2018-03-03 11:12:36 -080097 /** A for clause in a comprehension, e.g. "for a in b" in the example above. */
98 @AutoCodec
brandjon296cd492017-05-15 16:17:16 +020099 public static final class ForClause implements Clause {
brandjon990622b2017-07-11 19:56:45 +0200100 private final LValue lvalue;
101 private final Expression iterable;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000102
brandjon296cd492017-05-15 16:17:16 +0200103 @Override
104 public Kind getKind() {
105 return Kind.FOR;
106 }
107
brandjon990622b2017-07-11 19:56:45 +0200108 public ForClause(LValue lvalue, Expression iterable) {
109 this.lvalue = lvalue;
110 this.iterable = iterable;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000111 }
112
113 @Override
114 public void eval(Environment env, OutputCollector collector, int step)
115 throws EvalException, InterruptedException {
brandjon990622b2017-07-11 19:56:45 +0200116 Object iterableObject = iterable.eval(env);
brandjon296cd492017-05-15 16:17:16 +0200117 Location loc = collector.getLocation();
brandjon990622b2017-07-11 19:56:45 +0200118 Iterable<?> listValue = EvalUtils.toIterable(iterableObject, loc, env);
119 EvalUtils.lock(iterableObject, loc);
Jon Brandvein65e3ae62016-09-27 19:57:40 +0000120 try {
121 for (Object listElement : listValue) {
brandjon2b51f782017-07-25 21:05:04 +0200122 lvalue.assign(listElement, env, loc);
Jon Brandvein65e3ae62016-09-27 19:57:40 +0000123 evalStep(env, collector, step);
124 }
125 } finally {
brandjon990622b2017-07-11 19:56:45 +0200126 EvalUtils.unlock(iterableObject, loc);
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000127 }
128 }
129
130 @Override
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000131 public LValue getLValue() {
brandjon990622b2017-07-11 19:56:45 +0200132 return lvalue;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000133 }
134
135 @Override
136 public Expression getExpression() {
brandjon990622b2017-07-11 19:56:45 +0200137 return iterable;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000138 }
139
140 @Override
brandjone2ffd5d2017-06-27 18:14:54 +0200141 public void prettyPrint(Appendable buffer) throws IOException {
142 buffer.append("for ");
brandjon990622b2017-07-11 19:56:45 +0200143 lvalue.prettyPrint(buffer);
brandjone2ffd5d2017-06-27 18:14:54 +0200144 buffer.append(" in ");
brandjon990622b2017-07-11 19:56:45 +0200145 iterable.prettyPrint(buffer);
brandjone2ffd5d2017-06-27 18:14:54 +0200146 }
147
148 @Override
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000149 public String toString() {
brandjone2ffd5d2017-06-27 18:14:54 +0200150 StringBuilder builder = new StringBuilder();
151 try {
152 prettyPrint(builder);
153 } catch (IOException e) {
154 // Not possible for StringBuilder.
155 throw new AssertionError(e);
156 }
157 return builder.toString();
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000158 }
159 }
160
janakr76de1ad2018-03-03 11:12:36 -0800161 /** A if clause in a comprehension, e.g. "if c" in the example above. */
162 @AutoCodec
brandjon296cd492017-05-15 16:17:16 +0200163 public static final class IfClause implements Clause {
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000164 private final Expression condition;
165
brandjon296cd492017-05-15 16:17:16 +0200166 @Override
167 public Kind getKind() {
168 return Kind.IF;
169 }
170
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000171 public IfClause(Expression condition) {
172 this.condition = condition;
173 }
174
175 @Override
176 public void eval(Environment env, OutputCollector collector, int step)
177 throws EvalException, InterruptedException {
178 if (EvalUtils.toBoolean(condition.eval(env))) {
179 evalStep(env, collector, step);
180 }
181 }
182
183 @Override
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000184 public LValue getLValue() {
185 return null;
186 }
187
188 @Override
189 public Expression getExpression() {
190 return condition;
191 }
192
193 @Override
brandjone2ffd5d2017-06-27 18:14:54 +0200194 public void prettyPrint(Appendable buffer) throws IOException {
195 buffer.append("if ");
196 condition.prettyPrint(buffer);
197 }
198
199 @Override
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000200 public String toString() {
brandjone2ffd5d2017-06-27 18:14:54 +0200201 StringBuilder builder = new StringBuilder();
202 try {
203 prettyPrint(builder);
204 } catch (IOException e) {
205 // Not possible for StringBuilder.
206 throw new AssertionError(e);
207 }
208 return builder.toString();
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000209 }
210 }
211
212 /**
213 * The output expressions, e.g. "a+d" in the example above. This list has either one (list) or two
214 * (dict) items.
215 */
216 private final ImmutableList<Expression> outputExpressions;
217
brandjon296cd492017-05-15 16:17:16 +0200218 private final ImmutableList<Clause> clauses;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000219
brandjon296cd492017-05-15 16:17:16 +0200220 public AbstractComprehension(List<Clause> clauses, Expression... outputExpressions) {
221 this.clauses = ImmutableList.copyOf(clauses);
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000222 this.outputExpressions = ImmutableList.copyOf(outputExpressions);
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000223 }
224
brandjon296cd492017-05-15 16:17:16 +0200225 protected abstract char openingBracket();
226
227 protected abstract char closingBracket();
228
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000229 public ImmutableList<Expression> getOutputExpressions() {
230 return outputExpressions;
231 }
232
233 @Override
brandjone2ffd5d2017-06-27 18:14:54 +0200234 public void prettyPrint(Appendable buffer) throws IOException {
235 buffer.append(openingBracket());
236 printExpressions(buffer);
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000237 for (Clause clause : clauses) {
brandjone2ffd5d2017-06-27 18:14:54 +0200238 buffer.append(' ');
239 clause.prettyPrint(buffer);
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000240 }
brandjone2ffd5d2017-06-27 18:14:54 +0200241 buffer.append(closingBracket());
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000242 }
243
brandjon296cd492017-05-15 16:17:16 +0200244 /** Base class for comprehension builders. */
245 public abstract static class AbstractBuilder {
246
brandjon990622b2017-07-11 19:56:45 +0200247 protected final List<Clause> clauses = new ArrayList<>();
brandjon296cd492017-05-15 16:17:16 +0200248
brandjon990622b2017-07-11 19:56:45 +0200249 public void addFor(LValue lvalue, Expression iterable) {
250 Clause forClause = new ForClause(lvalue, iterable);
brandjon296cd492017-05-15 16:17:16 +0200251 clauses.add(forClause);
252 }
253
254 public void addIf(Expression condition) {
255 clauses.add(new IfClause(condition));
256 }
257
258 public abstract AbstractComprehension build();
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000259 }
260
brandjon296cd492017-05-15 16:17:16 +0200261 public ImmutableList<Clause> getClauses() {
262 return clauses;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000263 }
264
265 @Override
266 public void accept(SyntaxTreeVisitor visitor) {
267 visitor.visit(this);
268 }
269
270 @Override
laurentlbaf682d12017-08-24 20:32:02 +0200271 public Kind kind() {
272 return Kind.COMPREHENSION;
273 }
274
275 @Override
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000276 Object doEval(Environment env) throws EvalException, InterruptedException {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000277 OutputCollector collector = createCollector(env);
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000278 evalStep(env, collector, 0);
laurentlb9d5c0a02017-06-13 23:08:06 +0200279 Object result = collector.getResult(env);
280
laurentlb9d5c0a02017-06-13 23:08:06 +0200281 // Undefine loop variables (remove them from the environment).
282 // This code is useful for the transition, to make sure no one relies on the old behavior
283 // (where loop variables were leaking).
284 // TODO(laurentlb): Instead of removing variables, we should create them in a nested scope.
285 for (Clause clause : clauses) {
286 // Check if a loop variable conflicts with another local variable.
287 LValue lvalue = clause.getLValue();
288 if (lvalue != null) {
fzaiser37af5d52017-08-28 17:05:54 +0200289 for (Identifier ident : lvalue.boundIdentifiers()) {
290 env.removeLocalBinding(ident.getName());
laurentlb9d5c0a02017-06-13 23:08:06 +0200291 }
292 }
293 }
294 return result;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000295 }
296
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000297 /**
298 * Evaluate the clause indexed by step, or elementExpression. When we evaluate the
299 * comprehension, step is 0 and we evaluate the first clause. Each clause may
300 * recursively call evalStep any number of times. After the last clause,
301 * the output expression(s) is/are evaluated and added to the results.
302 *
303 * <p> In the expanded example above, you can consider that evalStep is equivalent to
304 * evaluating the line number step.
305 */
brandjon296cd492017-05-15 16:17:16 +0200306 private static void evalStep(Environment env, OutputCollector collector, int step)
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000307 throws EvalException, InterruptedException {
brandjon296cd492017-05-15 16:17:16 +0200308 List<Clause> clauses = collector.getClauses();
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000309 if (step >= clauses.size()) {
310 collector.evaluateAndCollect(env);
311 } else {
312 clauses.get(step).eval(env, collector, step + 1);
313 }
314 }
315
brandjone2ffd5d2017-06-27 18:14:54 +0200316 /** Pretty-prints the output expression(s). */
317 protected abstract void printExpressions(Appendable buffer) throws IOException;
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000318
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000319 abstract OutputCollector createCollector(Environment env);
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000320
321 /**
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000322 * Interface for collecting the intermediate output of an {@code AbstractComprehension} and for
323 * providing access to the final results.
324 */
325 interface OutputCollector {
brandjon296cd492017-05-15 16:17:16 +0200326
327 /** Returns the location for the comprehension we are evaluating. */
328 Location getLocation();
329
330 /** Returns the list of clauses for the comprehension we are evaluating. */
331 List<Clause> getClauses();
332
Florian Weikertffd8a5a2015-09-18 11:51:01 +0000333 /**
334 * Evaluates the output expression(s) of the comprehension and collects the result.
335 */
336 void evaluateAndCollect(Environment env) throws EvalException, InterruptedException;
337
338 /**
339 * Returns the final result of the comprehension.
340 */
341 Object getResult(Environment env) throws EvalException;
342 }
343}