blob: e0fdbff39ec99fa24fd4e6ebae5bb4ccd8ec8671 [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
17import java.util.ArrayList;
18import java.util.List;
19import java.util.Map;
20
21/**
22 * Evaluation code for the Skylark AST. At the moment, it can execute only statements (and defers to
23 * Expression.eval for evaluating expressions).
24 */
25public class Eval {
26 private final Environment env;
27
28 /** An exception that signals changes in the control flow (e.g. break or continue) */
29 private static class FlowException extends EvalException {
30 FlowException(String message) {
31 super(null, message);
32 }
33
34 @Override
35 public boolean canBeAddedToStackTrace() {
36 return false;
37 }
38 }
39
40 private static final FlowException breakException = new FlowException("FlowException - break");
41 private static final FlowException continueException =
42 new FlowException("FlowException - continue");
43
44 public Eval(Environment env) {
45 this.env = env;
46 }
47
48 void execAssignment(AssignmentStatement node) throws EvalException, InterruptedException {
49 Object rvalue = node.getExpression().eval(env);
50 node.getLValue().assign(rvalue, env, node.getLocation());
51 }
52
53 void execAugmentedAssignment(AugmentedAssignmentStatement node)
54 throws EvalException, InterruptedException {
55 node.getLValue()
56 .assignAugmented(node.getOperator(), node.getExpression(), env, node.getLocation());
57 }
58
59 void execIfBranch(IfStatement.ConditionalStatements node)
60 throws EvalException, InterruptedException {
61 for (Statement stmt : node.getStatements()) {
62 exec(stmt);
63 }
64 }
65
66 void execFor(ForStatement node) throws EvalException, InterruptedException {
67 Object o = node.getCollection().eval(env);
68 Iterable<?> col = EvalUtils.toIterable(o, node.getLocation(), env);
69 EvalUtils.lock(o, node.getLocation());
70 try {
71 for (Object it : col) {
72 node.getVariable().assign(it, env, node.getLocation());
73
74 try {
75 for (Statement stmt : node.getBlock()) {
laurentlb919d1b72017-09-01 20:22:11 +020076 exec(stmt);
laurentlb68f1c682017-08-28 13:45:19 +020077 }
78 } catch (FlowException ex) {
79 if (ex == breakException) {
80 return;
81 }
82 }
83 }
84 } finally {
85 EvalUtils.unlock(o, node.getLocation());
86 }
87 }
88
89 void execDef(FunctionDefStatement node) throws EvalException, InterruptedException {
90 List<Expression> defaultExpressions = node.getSignature().getDefaultValues();
91 ArrayList<Object> defaultValues = null;
92
93 if (defaultExpressions != null) {
94 defaultValues = new ArrayList<>(defaultExpressions.size());
95 for (Expression expr : defaultExpressions) {
96 defaultValues.add(expr.eval(env));
97 }
98 }
99
100 FunctionSignature sig = node.getSignature().getSignature();
brandjon3c161912017-10-05 05:06:05 +0200101 if (env.getSemantics().incompatibleDisallowKeywordOnlyArgs()
laurentlb68f1c682017-08-28 13:45:19 +0200102 && sig.getShape().getMandatoryNamedOnly() > 0) {
103 throw new EvalException(
104 node.getLocation(),
105 "Keyword-only argument is forbidden. You can temporarily disable this "
106 + "error using the flag --incompatible_disallow_keyword_only_args=false");
107 }
108
109 env.update(
110 node.getIdentifier().getName(),
111 new UserDefinedFunction(
112 node.getIdentifier().getName(),
113 node.getIdentifier().getLocation(),
114 FunctionSignature.WithValues.create(sig, defaultValues, /*types=*/ null),
115 node.getStatements(),
116 env.getGlobals()));
117 }
118
119 void execIf(IfStatement node) throws EvalException, InterruptedException {
120 for (IfStatement.ConditionalStatements stmt : node.getThenBlocks()) {
121 if (EvalUtils.toBoolean(stmt.getCondition().eval(env))) {
122 exec(stmt);
123 return;
124 }
125 }
126 for (Statement stmt : node.getElseBlock()) {
127 exec(stmt);
128 }
129 }
130
131 void execLoad(LoadStatement node) throws EvalException, InterruptedException {
brandjon3c161912017-10-05 05:06:05 +0200132 if (env.getSemantics().incompatibleLoadArgumentIsLabel()) {
laurentlb68f1c682017-08-28 13:45:19 +0200133 String s = node.getImport().getValue();
Kevin Gessnerb864db22017-09-06 20:14:49 +0200134 if (!s.startsWith("//") && !s.startsWith(":") && !s.startsWith("@")) {
laurentlb68f1c682017-08-28 13:45:19 +0200135 throw new EvalException(
136 node.getLocation(),
Kevin Gessnerb864db22017-09-06 20:14:49 +0200137 "First argument of 'load' must be a label and start with either '//', ':', or '@'. "
laurentlb68f1c682017-08-28 13:45:19 +0200138 + "Use --incompatible_load_argument_is_label=false to temporarily disable this "
139 + "check.");
140 }
141 }
142
143 for (Map.Entry<Identifier, String> entry : node.getSymbolMap().entrySet()) {
144 try {
145 Identifier name = entry.getKey();
146 Identifier declared = new Identifier(entry.getValue());
147
148 if (declared.isPrivate()) {
149 throw new EvalException(
150 node.getLocation(),
151 "symbol '" + declared.getName() + "' is private and cannot be imported.");
152 }
153 // The key is the original name that was used to define the symbol
154 // in the loaded bzl file.
155 env.importSymbol(node.getImport().getValue(), name, declared.getName());
156 } catch (Environment.LoadFailedException e) {
157 throw new EvalException(node.getLocation(), e.getMessage());
158 }
159 }
160 }
161
162 void execReturn(ReturnStatement node) throws EvalException, InterruptedException {
163 Expression ret = node.getReturnExpression();
164 if (ret == null) {
165 throw new ReturnStatement.ReturnException(node.getLocation(), Runtime.NONE);
166 }
167 throw new ReturnStatement.ReturnException(ret.getLocation(), ret.eval(env));
168 }
169
170 /**
171 * Execute the statement.
172 *
173 * @throws EvalException if execution of the statement could not be completed.
174 * @throws InterruptedException may be thrown in a sub class.
175 */
176 public void exec(Statement st) throws EvalException, InterruptedException {
brandjon95b04672017-09-22 23:33:38 -0400177 try {
178 execDispatch(st);
179 } catch (EvalException ex) {
180 throw st.maybeTransformException(ex);
181 }
182 }
183
184 void execDispatch(Statement st) throws EvalException, InterruptedException {
laurentlb68f1c682017-08-28 13:45:19 +0200185 switch (st.kind()) {
186 case ASSIGNMENT:
187 execAssignment((AssignmentStatement) st);
188 break;
189 case AUGMENTED_ASSIGNMENT:
190 execAugmentedAssignment((AugmentedAssignmentStatement) st);
191 break;
192 case CONDITIONAL:
193 execIfBranch((IfStatement.ConditionalStatements) st);
194 break;
195 case EXPRESSION:
196 ((ExpressionStatement) st).getExpression().eval(env);
197 break;
198 case FLOW:
199 throw ((FlowStatement) st).getKind() == FlowStatement.Kind.BREAK
200 ? breakException
201 : continueException;
202 case FOR:
203 execFor((ForStatement) st);
204 break;
205 case FUNCTION_DEF:
206 execDef((FunctionDefStatement) st);
207 break;
208 case IF:
209 execIf((IfStatement) st);
210 break;
211 case LOAD:
212 execLoad((LoadStatement) st);
213 break;
214 case RETURN:
215 execReturn((ReturnStatement) st);
216 break;
217 }
218 }
219}