blob: 3199952d82d8917e70956dbcd0da165b0cc20e7d [file] [log] [blame]
laurentlb68f1c682017-08-28 13:45:19 +02001// Copyright 2017 The Bazel Authors. All rights reserved.
2//
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
michajlo5f394752017-10-06 23:51:10 +020017import com.google.common.collect.ImmutableList;
Googlerade53272019-07-01 07:29:11 -070018import com.google.devtools.build.lib.events.Location;
laurentlb68f1c682017-08-28 13:45:19 +020019import java.util.ArrayList;
Googlerade53272019-07-01 07:29:11 -070020import java.util.Collection;
laurentlb68f1c682017-08-28 13:45:19 +020021import java.util.List;
Googler29eafdf2018-05-23 12:32:07 -070022import java.util.function.Function;
laurentlb68f1c682017-08-28 13:45:19 +020023
24/**
25 * Evaluation code for the Skylark AST. At the moment, it can execute only statements (and defers to
26 * Expression.eval for evaluating expressions).
27 */
28public class Eval {
Googler29eafdf2018-05-23 12:32:07 -070029 protected final Environment env;
Googler5d72d4e2019-06-30 21:10:44 -070030
31 /** An exception that signals changes in the control flow (e.g. break or continue) */
32 private static class FlowException extends EvalException {
33 FlowException(String message) {
34 super(null, message);
35 }
36
37 @Override
38 public boolean canBeAddedToStackTrace() {
39 return false;
40 }
41 }
laurentlb68f1c682017-08-28 13:45:19 +020042
Googler29eafdf2018-05-23 12:32:07 -070043 public static Eval fromEnvironment(Environment env) {
44 return evalSupplier.apply(env);
45 }
46
47 public static void setEvalSupplier(Function<Environment, Eval> evalSupplier) {
48 Eval.evalSupplier = evalSupplier;
49 }
50
51 /** Reset Eval supplier to the default. */
52 public static void removeCustomEval() {
53 evalSupplier = Eval::new;
54 }
55
56 // TODO(bazel-team): remove this static state in favor of storing Eval instances in Environment
57 private static Function<Environment, Eval> evalSupplier = Eval::new;
58
Googler5d72d4e2019-06-30 21:10:44 -070059 private static final FlowException breakException = new FlowException("FlowException - break");
60 private static final FlowException continueException =
61 new FlowException("FlowException - continue");
62
Googler29eafdf2018-05-23 12:32:07 -070063 /**
64 * This constructor should never be called directly. Call {@link #fromEnvironment(Environment)}
65 * instead.
66 */
67 protected Eval(Environment env) {
laurentlb68f1c682017-08-28 13:45:19 +020068 this.env = env;
69 }
70
71 void execAssignment(AssignmentStatement node) throws EvalException, InterruptedException {
Googlerade53272019-07-01 07:29:11 -070072 Object rvalue = node.getRHS().eval(env);
73 assign(node.getLHS(), rvalue, env, node.getLocation());
laurentlb68f1c682017-08-28 13:45:19 +020074 }
75
76 void execAugmentedAssignment(AugmentedAssignmentStatement node)
77 throws EvalException, InterruptedException {
Googlerade53272019-07-01 07:29:11 -070078 assignAugmented(node.getLHS(), node.getOperator(), node.getRHS(), env, node.getLocation());
laurentlb68f1c682017-08-28 13:45:19 +020079 }
80
Googler5d72d4e2019-06-30 21:10:44 -070081 void execIfBranch(IfStatement.ConditionalStatements node)
laurentlb68f1c682017-08-28 13:45:19 +020082 throws EvalException, InterruptedException {
Googler5d72d4e2019-06-30 21:10:44 -070083 execStatements(node.getStatements());
laurentlb68f1c682017-08-28 13:45:19 +020084 }
85
Googler5d72d4e2019-06-30 21:10:44 -070086 void execFor(ForStatement node) throws EvalException, InterruptedException {
laurentlb68f1c682017-08-28 13:45:19 +020087 Object o = node.getCollection().eval(env);
88 Iterable<?> col = EvalUtils.toIterable(o, node.getLocation(), env);
89 EvalUtils.lock(o, node.getLocation());
90 try {
91 for (Object it : col) {
Googlerade53272019-07-01 07:29:11 -070092 assign(node.getLHS(), it, env, node.getLocation());
laurentlb68f1c682017-08-28 13:45:19 +020093
Googler5d72d4e2019-06-30 21:10:44 -070094 try {
95 execStatements(node.getBlock());
96 } catch (FlowException ex) {
97 if (ex == breakException) {
98 return;
99 }
laurentlb68f1c682017-08-28 13:45:19 +0200100 }
101 }
102 } finally {
103 EvalUtils.unlock(o, node.getLocation());
104 }
105 }
106
107 void execDef(FunctionDefStatement node) throws EvalException, InterruptedException {
108 List<Expression> defaultExpressions = node.getSignature().getDefaultValues();
109 ArrayList<Object> defaultValues = null;
110
111 if (defaultExpressions != null) {
112 defaultValues = new ArrayList<>(defaultExpressions.size());
113 for (Expression expr : defaultExpressions) {
114 defaultValues.add(expr.eval(env));
115 }
116 }
117
laurentlb2195b1c2018-02-16 04:14:46 -0800118 // TODO(laurentlb): Could be moved to the Parser or the ValidationEnvironment?
laurentlb68f1c682017-08-28 13:45:19 +0200119 FunctionSignature sig = node.getSignature().getSignature();
laurentlb2195b1c2018-02-16 04:14:46 -0800120 if (sig.getShape().getMandatoryNamedOnly() > 0) {
121 throw new EvalException(node.getLocation(), "Keyword-only argument is forbidden.");
laurentlb68f1c682017-08-28 13:45:19 +0200122 }
123
laurentlb9d179e12018-09-27 08:15:42 -0700124 env.updateAndExport(
laurentlb68f1c682017-08-28 13:45:19 +0200125 node.getIdentifier().getName(),
126 new UserDefinedFunction(
127 node.getIdentifier().getName(),
128 node.getIdentifier().getLocation(),
129 FunctionSignature.WithValues.create(sig, defaultValues, /*types=*/ null),
130 node.getStatements(),
131 env.getGlobals()));
132 }
133
Googler5d72d4e2019-06-30 21:10:44 -0700134 void execIf(IfStatement node) throws EvalException, InterruptedException {
michajloae9a8812018-02-26 09:19:11 -0800135 ImmutableList<IfStatement.ConditionalStatements> thenBlocks = node.getThenBlocks();
136 // Avoid iterator overhead - most of the time there will be one or few "if"s.
137 for (int i = 0; i < thenBlocks.size(); i++) {
138 IfStatement.ConditionalStatements stmt = thenBlocks.get(i);
laurentlb68f1c682017-08-28 13:45:19 +0200139 if (EvalUtils.toBoolean(stmt.getCondition().eval(env))) {
Googler5d72d4e2019-06-30 21:10:44 -0700140 exec(stmt);
141 return;
laurentlb68f1c682017-08-28 13:45:19 +0200142 }
143 }
Googler5d72d4e2019-06-30 21:10:44 -0700144 execStatements(node.getElseBlock());
laurentlb68f1c682017-08-28 13:45:19 +0200145 }
146
147 void execLoad(LoadStatement node) throws EvalException, InterruptedException {
laurentlb14c0f402018-11-09 13:59:34 -0800148 for (LoadStatement.Binding binding : node.getBindings()) {
laurentlb68f1c682017-08-28 13:45:19 +0200149 try {
laurentlb14c0f402018-11-09 13:59:34 -0800150 Identifier name = binding.getLocalName();
151 Identifier declared = binding.getOriginalName();
laurentlb68f1c682017-08-28 13:45:19 +0200152
Klaus Aehlig9b31fbd2019-02-07 03:27:57 -0800153 if (declared.isPrivate() && !node.mayLoadInternalSymbols()) {
laurentlb68f1c682017-08-28 13:45:19 +0200154 throw new EvalException(
155 node.getLocation(),
156 "symbol '" + declared.getName() + "' is private and cannot be imported.");
157 }
158 // The key is the original name that was used to define the symbol
159 // in the loaded bzl file.
160 env.importSymbol(node.getImport().getValue(), name, declared.getName());
161 } catch (Environment.LoadFailedException e) {
162 throw new EvalException(node.getLocation(), e.getMessage());
163 }
164 }
165 }
166
Googler5d72d4e2019-06-30 21:10:44 -0700167 void execReturn(ReturnStatement node) throws EvalException, InterruptedException {
laurentlb68f1c682017-08-28 13:45:19 +0200168 Expression ret = node.getReturnExpression();
Googler5d72d4e2019-06-30 21:10:44 -0700169 if (ret == null) {
170 throw new ReturnStatement.ReturnException(node.getLocation(), Runtime.NONE);
laurentlb68f1c682017-08-28 13:45:19 +0200171 }
Googler5d72d4e2019-06-30 21:10:44 -0700172 throw new ReturnStatement.ReturnException(ret.getLocation(), ret.eval(env));
laurentlb68f1c682017-08-28 13:45:19 +0200173 }
174
175 /**
176 * Execute the statement.
177 *
178 * @throws EvalException if execution of the statement could not be completed.
179 * @throws InterruptedException may be thrown in a sub class.
180 */
Googler5d72d4e2019-06-30 21:10:44 -0700181 public void exec(Statement st) throws EvalException, InterruptedException {
brandjon95b04672017-09-22 23:33:38 -0400182 try {
Googler5d72d4e2019-06-30 21:10:44 -0700183 execDispatch(st);
brandjon95b04672017-09-22 23:33:38 -0400184 } catch (EvalException ex) {
185 throw st.maybeTransformException(ex);
186 }
187 }
188
Googler5d72d4e2019-06-30 21:10:44 -0700189 void execDispatch(Statement st) throws EvalException, InterruptedException {
laurentlb68f1c682017-08-28 13:45:19 +0200190 switch (st.kind()) {
191 case ASSIGNMENT:
192 execAssignment((AssignmentStatement) st);
Googler5d72d4e2019-06-30 21:10:44 -0700193 break;
laurentlb68f1c682017-08-28 13:45:19 +0200194 case AUGMENTED_ASSIGNMENT:
195 execAugmentedAssignment((AugmentedAssignmentStatement) st);
Googler5d72d4e2019-06-30 21:10:44 -0700196 break;
laurentlb68f1c682017-08-28 13:45:19 +0200197 case CONDITIONAL:
Googler5d72d4e2019-06-30 21:10:44 -0700198 execIfBranch((IfStatement.ConditionalStatements) st);
199 break;
laurentlb68f1c682017-08-28 13:45:19 +0200200 case EXPRESSION:
201 ((ExpressionStatement) st).getExpression().eval(env);
Googler5d72d4e2019-06-30 21:10:44 -0700202 break;
laurentlb68f1c682017-08-28 13:45:19 +0200203 case FLOW:
Googler5d72d4e2019-06-30 21:10:44 -0700204 throw ((FlowStatement) st).getKind() == FlowStatement.Kind.BREAK
205 ? breakException
206 : continueException;
laurentlb68f1c682017-08-28 13:45:19 +0200207 case FOR:
Googler5d72d4e2019-06-30 21:10:44 -0700208 execFor((ForStatement) st);
209 break;
laurentlb68f1c682017-08-28 13:45:19 +0200210 case FUNCTION_DEF:
211 execDef((FunctionDefStatement) st);
Googler5d72d4e2019-06-30 21:10:44 -0700212 break;
laurentlb68f1c682017-08-28 13:45:19 +0200213 case IF:
Googler5d72d4e2019-06-30 21:10:44 -0700214 execIf((IfStatement) st);
215 break;
laurentlb68f1c682017-08-28 13:45:19 +0200216 case LOAD:
217 execLoad((LoadStatement) st);
Googler5d72d4e2019-06-30 21:10:44 -0700218 break;
219 case PASS:
220 break;
laurentlb68f1c682017-08-28 13:45:19 +0200221 case RETURN:
Googler5d72d4e2019-06-30 21:10:44 -0700222 execReturn((ReturnStatement) st);
223 break;
laurentlb68f1c682017-08-28 13:45:19 +0200224 }
225 }
michajlo5f394752017-10-06 23:51:10 +0200226
Googler5d72d4e2019-06-30 21:10:44 -0700227 private void execStatements(ImmutableList<Statement> statements)
michajlo5f394752017-10-06 23:51:10 +0200228 throws EvalException, InterruptedException {
229 // Hot code path, good chance of short lists which don't justify the iterator overhead.
230 for (int i = 0; i < statements.size(); i++) {
Googler5d72d4e2019-06-30 21:10:44 -0700231 exec(statements.get(i));
michajlo5f394752017-10-06 23:51:10 +0200232 }
michajlo5f394752017-10-06 23:51:10 +0200233 }
Googlerade53272019-07-01 07:29:11 -0700234
235 /**
236 * Updates the environment bindings, and possibly mutates objects, so as to assign the given value
237 * to the given expression. The expression must be valid for an {@code LValue}.
238 */
239 // TODO(adonovan): make this a private instance method once all Expression.eval methods move here.
240 static void assign(Expression expr, Object value, Environment env, Location loc)
241 throws EvalException, InterruptedException {
242 if (expr instanceof Identifier) {
243 assignIdentifier((Identifier) expr, value, env);
244 } else if (expr instanceof IndexExpression) {
245 Object object = ((IndexExpression) expr).getObject().eval(env);
246 Object key = ((IndexExpression) expr).getKey().eval(env);
247 assignItem(object, key, value, env, loc);
248 } else if (expr instanceof ListLiteral) {
249 ListLiteral list = (ListLiteral) expr;
250 assignList(list, value, env, loc);
251 } else {
252 // Not possible for validated ASTs.
253 throw new EvalException(loc, "cannot assign to '" + expr + "'");
254 }
255 }
256
257 /** Binds a variable to the given value in the environment. */
258 private static void assignIdentifier(Identifier ident, Object value, Environment env)
259 throws EvalException {
260 env.updateAndExport(ident.getName(), value);
261 }
262
263 /**
264 * Adds or changes an object-key-value relationship for a list or dict.
265 *
266 * <p>For a list, the key is an in-range index. For a dict, it is a hashable value.
267 *
268 * @throws EvalException if the object is not a list or dict
269 */
270 @SuppressWarnings("unchecked")
271 private static void assignItem(
272 Object object, Object key, Object value, Environment env, Location loc) throws EvalException {
273 if (object instanceof SkylarkDict) {
274 SkylarkDict<Object, Object> dict = (SkylarkDict<Object, Object>) object;
275 dict.put(key, value, loc, env);
276 } else if (object instanceof SkylarkList.MutableList) {
277 SkylarkList.MutableList<Object> list = (SkylarkList.MutableList<Object>) object;
278 int index = EvalUtils.getSequenceIndex(key, list.size(), loc);
279 list.set(index, value, loc, env.mutability());
280 } else {
281 throw new EvalException(
282 loc,
283 "can only assign an element in a dictionary or a list, not in a '"
284 + EvalUtils.getDataTypeName(object)
285 + "'");
286 }
287 }
288
289 /**
290 * Recursively assigns an iterable value to a list literal.
291 *
292 * @throws EvalException if the list literal has length 0, or if the value is not an iterable of
293 * matching length
294 */
295 private static void assignList(ListLiteral list, Object value, Environment env, Location loc)
296 throws EvalException, InterruptedException {
297 Collection<?> collection = EvalUtils.toCollection(value, loc, env);
298 int len = list.getElements().size();
299 if (len == 0) {
300 throw new EvalException(
301 loc, "lists or tuples on the left-hand side of assignments must have at least one item");
302 }
303 if (len != collection.size()) {
304 throw new EvalException(
305 loc,
306 String.format(
307 "assignment length mismatch: left-hand side has length %d, but right-hand side"
308 + " evaluates to value of length %d",
309 len, collection.size()));
310 }
311 int i = 0;
312 for (Object item : collection) {
313 assign(list.getElements().get(i), item, env, loc);
314 i++;
315 }
316 }
317
318 /**
319 * Evaluates an augmented assignment that mutates this {@code LValue} with the given right-hand
320 * side's value.
321 *
322 * <p>The left-hand side expression is evaluated only once, even when it is an {@link
323 * IndexExpression}. The left-hand side is evaluated before the right-hand side to match Python's
324 * behavior (hence why the right-hand side is passed as an expression rather than as an evaluated
325 * value).
326 */
327 private static void assignAugmented(
Googler6748ccb2019-07-01 10:22:59 -0700328 Expression expr, TokenKind op, Expression rhs, Environment env, Location loc)
Googlerade53272019-07-01 07:29:11 -0700329 throws EvalException, InterruptedException {
330 if (expr instanceof Identifier) {
331 Object result =
Googler6748ccb2019-07-01 10:22:59 -0700332 BinaryOperatorExpression.evaluateAugmented(op, expr.eval(env), rhs.eval(env), env, loc);
Googlerade53272019-07-01 07:29:11 -0700333 assignIdentifier((Identifier) expr, result, env);
334 } else if (expr instanceof IndexExpression) {
335 IndexExpression indexExpression = (IndexExpression) expr;
336 // The object and key should be evaluated only once, so we don't use expr.eval().
337 Object object = indexExpression.getObject().eval(env);
338 Object key = indexExpression.getKey().eval(env);
339 Object oldValue = IndexExpression.evaluate(object, key, env, loc);
340 // Evaluate rhs after lhs.
341 Object rhsValue = rhs.eval(env);
Googler6748ccb2019-07-01 10:22:59 -0700342 Object result = BinaryOperatorExpression.evaluateAugmented(op, oldValue, rhsValue, env, loc);
Googlerade53272019-07-01 07:29:11 -0700343 assignItem(object, key, result, env, loc);
344 } else if (expr instanceof ListLiteral) {
345 throw new EvalException(loc, "cannot perform augmented assignment on a list literal");
346 } else {
347 // Not possible for validated ASTs.
348 throw new EvalException(loc, "cannot perform augmented assignment on '" + expr + "'");
349 }
350 }
laurentlb68f1c682017-08-28 13:45:19 +0200351}