laurentlb | 68f1c68 | 2017-08-28 13:45:19 +0200 | [diff] [blame] | 1 | // 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 | |
| 15 | package com.google.devtools.build.lib.syntax; |
| 16 | |
| 17 | import java.util.ArrayList; |
| 18 | import java.util.List; |
| 19 | import 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 | */ |
| 25 | public 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()) { |
laurentlb | 919d1b7 | 2017-09-01 20:22:11 +0200 | [diff] [blame] | 76 | exec(stmt); |
laurentlb | 68f1c68 | 2017-08-28 13:45:19 +0200 | [diff] [blame] | 77 | } |
| 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(); |
brandjon | 3c16191 | 2017-10-05 05:06:05 +0200 | [diff] [blame] | 101 | if (env.getSemantics().incompatibleDisallowKeywordOnlyArgs() |
laurentlb | 68f1c68 | 2017-08-28 13:45:19 +0200 | [diff] [blame] | 102 | && 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 { |
brandjon | 3c16191 | 2017-10-05 05:06:05 +0200 | [diff] [blame] | 132 | if (env.getSemantics().incompatibleLoadArgumentIsLabel()) { |
laurentlb | 68f1c68 | 2017-08-28 13:45:19 +0200 | [diff] [blame] | 133 | String s = node.getImport().getValue(); |
Kevin Gessner | b864db2 | 2017-09-06 20:14:49 +0200 | [diff] [blame] | 134 | if (!s.startsWith("//") && !s.startsWith(":") && !s.startsWith("@")) { |
laurentlb | 68f1c68 | 2017-08-28 13:45:19 +0200 | [diff] [blame] | 135 | throw new EvalException( |
| 136 | node.getLocation(), |
Kevin Gessner | b864db2 | 2017-09-06 20:14:49 +0200 | [diff] [blame] | 137 | "First argument of 'load' must be a label and start with either '//', ':', or '@'. " |
laurentlb | 68f1c68 | 2017-08-28 13:45:19 +0200 | [diff] [blame] | 138 | + "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 { |
brandjon | 95b0467 | 2017-09-22 23:33:38 -0400 | [diff] [blame] | 177 | try { |
| 178 | execDispatch(st); |
| 179 | } catch (EvalException ex) { |
| 180 | throw st.maybeTransformException(ex); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | void execDispatch(Statement st) throws EvalException, InterruptedException { |
laurentlb | 68f1c68 | 2017-08-28 13:45:19 +0200 | [diff] [blame] | 185 | 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 | } |