aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
blob: ecc72073fe20c0f45348168418e63a2d1634c892 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// 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();
  }
}