| // Copyright 2014 The Bazel Authors. 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.ImmutableSet; | |
| import com.google.devtools.build.lib.events.Location; | |
| import com.google.devtools.build.lib.util.Preconditions; | |
| import java.io.Serializable; | |
| import java.util.Collection; | |
| /** | |
| * Class representing an LValue. | |
| * It appears in assignment, for loop and comprehensions, e.g. | |
| * lvalue = 2 | |
| * [for lvalue in exp] | |
| * for lvalue in exp: pass | |
| * An LValue can be a simple variable or something more complex like a tuple. | |
| */ | |
| public class LValue implements Serializable { | |
| private final Expression expr; | |
| public LValue(Expression expr) { | |
| this.expr = expr; | |
| } | |
| public Expression getExpression() { | |
| return expr; | |
| } | |
| /** | |
| * Assign a value to an LValue and update the environment. | |
| */ | |
| public void assign(Environment env, Location loc, Object result) | |
| throws EvalException, InterruptedException { | |
| doAssign(env, loc, expr, result); | |
| } | |
| /** | |
| * Evaluate a rhs using a lhs and the operator, then assign to the lhs. | |
| */ | |
| public void assign(Environment env, Location loc, Expression rhs, Operator operator) | |
| throws EvalException, InterruptedException { | |
| if (expr instanceof Identifier) { | |
| Object result = BinaryOperatorExpression.evaluate(operator, expr, rhs, env, loc); | |
| assign(env, loc, (Identifier) expr, result); | |
| return; | |
| } | |
| if (expr instanceof IndexExpression) { | |
| IndexExpression indexExpression = (IndexExpression) expr; | |
| // This object should be evaluated only once | |
| Object evaluatedLhsObject = indexExpression.getObject().eval(env); | |
| Object evaluatedLhs = indexExpression.eval(env, evaluatedLhsObject); | |
| Object key = indexExpression.getKey().eval(env); | |
| Object result = BinaryOperatorExpression.evaluate(operator, evaluatedLhs, rhs, env, loc); | |
| assignItem(env, loc, evaluatedLhsObject, key, result); | |
| return; | |
| } | |
| if (expr instanceof ListLiteral) { | |
| throw new EvalException(loc, "Cannot perform augment assignment on a list literal"); | |
| } | |
| } | |
| /** | |
| * Returns all names bound by this LValue. | |
| * | |
| * Examples: | |
| * <ul> | |
| * <li><{@code x = ...} binds x.</li> | |
| * <li><{@code x, [y,z] = ..} binds x, y, z.</li> | |
| * <li><{@code x[5] = ..} does not bind any names.</li> | |
| * </ul> | |
| */ | |
| public ImmutableSet<String> boundNames() { | |
| ImmutableSet.Builder<String> result = ImmutableSet.builder(); | |
| collectBoundNames(expr, result); | |
| return result.build(); | |
| } | |
| private static void collectBoundNames(Expression lhs, ImmutableSet.Builder<String> result) { | |
| if (lhs instanceof Identifier) { | |
| result.add(((Identifier) lhs).getName()); | |
| return; | |
| } | |
| if (lhs instanceof ListLiteral) { | |
| ListLiteral variables = (ListLiteral) lhs; | |
| for (Expression expression : variables.getElements()) { | |
| collectBoundNames(expression, result); | |
| } | |
| } | |
| } | |
| private static void doAssign( | |
| Environment env, Location loc, Expression lhs, Object result) | |
| throws EvalException, InterruptedException { | |
| if (lhs instanceof Identifier) { | |
| assign(env, loc, (Identifier) lhs, result); | |
| return; | |
| } | |
| if (lhs instanceof ListLiteral) { | |
| ListLiteral variables = (ListLiteral) lhs; | |
| Collection<?> rvalue = EvalUtils.toCollection(result, loc); | |
| int len = variables.getElements().size(); | |
| if (len != rvalue.size()) { | |
| throw new EvalException(loc, String.format( | |
| "lvalue has length %d, but rvalue has has length %d", len, rvalue.size())); | |
| } | |
| int i = 0; | |
| for (Object o : rvalue) { | |
| doAssign(env, loc, variables.getElements().get(i), o); | |
| i++; | |
| } | |
| return; | |
| } | |
| // Support syntax for setting an element in an array, e.g. a[5] = 2 | |
| // TODO: We currently do not allow slices (e.g. a[2:6] = [3]). | |
| if (lhs instanceof IndexExpression) { | |
| IndexExpression expression = (IndexExpression) lhs; | |
| Object key = expression.getKey().eval(env); | |
| Object evaluatedObject = expression.getObject().eval(env); | |
| assignItem(env, loc, evaluatedObject, key, result); | |
| return; | |
| } | |
| throw new EvalException(loc, "cannot assign to '" + lhs + "'"); | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static void assignItem( | |
| Environment env, Location loc, Object o, Object key, Object value) | |
| throws EvalException, InterruptedException { | |
| if (o instanceof SkylarkDict) { | |
| SkylarkDict<Object, Object> dict = (SkylarkDict<Object, Object>) o; | |
| dict.put(key, value, loc, env); | |
| } else if (o instanceof SkylarkList) { | |
| SkylarkList<Object> list = (SkylarkList<Object>) o; | |
| list.set(key, value, loc, env); | |
| } else { | |
| throw new EvalException( | |
| loc, | |
| "can only assign an element in a dictionary or a list, not in a '" | |
| + EvalUtils.getDataTypeName(o) | |
| + "'"); | |
| } | |
| } | |
| /** | |
| * Assign value to a single variable. | |
| */ | |
| private static void assign(Environment env, Location loc, Identifier ident, Object result) | |
| throws EvalException, InterruptedException { | |
| Preconditions.checkNotNull(result, "trying to assign null to %s", ident); | |
| // The variable may have been referenced successfully if a global variable | |
| // with the same name exists. In this case an Exception needs to be thrown. | |
| if (env.isKnownGlobalVariable(ident.getName())) { | |
| throw new EvalException( | |
| loc, | |
| String.format( | |
| "Variable '%s' is referenced before assignment. " | |
| + "The variable is defined in the global scope.", | |
| ident.getName())); | |
| } | |
| env.update(ident.getName(), result); | |
| } | |
| void validate(ValidationEnvironment env, Location loc) throws EvalException { | |
| validate(env, loc, expr); | |
| } | |
| private static void validate(ValidationEnvironment env, Location loc, Expression expr) | |
| throws EvalException { | |
| if (expr instanceof Identifier) { | |
| Identifier ident = (Identifier) expr; | |
| env.declare(ident.getName(), loc); | |
| return; | |
| } | |
| if (expr instanceof ListLiteral) { | |
| for (Expression e : ((ListLiteral) expr).getElements()) { | |
| validate(env, loc, e); | |
| } | |
| return; | |
| } | |
| if (expr instanceof IndexExpression) { | |
| expr.validate(env); | |
| return; | |
| } | |
| throw new EvalException(loc, | |
| "cannot assign to '" + expr + "'"); | |
| } | |
| @Override | |
| public String toString() { | |
| return expr.toString(); | |
| } | |
| } |