aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
blob: b641be321f80510f5e33c74a89e0a9c0be82eee0 (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
// 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 static com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils.append;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.syntax.FlowStatement.FlowException;
import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls;
import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors;
import com.google.devtools.build.lib.syntax.compiler.IntegerVariableIncrease;
import com.google.devtools.build.lib.syntax.compiler.Jump;
import com.google.devtools.build.lib.syntax.compiler.Jump.PrimitiveComparison;
import com.google.devtools.build.lib.syntax.compiler.LabelAdder;
import com.google.devtools.build.lib.syntax.compiler.LoopLabels;
import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
import com.google.devtools.build.lib.syntax.compiler.VariableScope;
import com.google.devtools.build.lib.util.Preconditions;

import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Duplication;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Syntax node for a for loop statement.
 */
public final class ForStatement extends Statement {

  private final LValue variable;
  private final Expression collection;
  private final ImmutableList<Statement> block;

  /**
   * Constructs a for loop statement.
   */
  ForStatement(Expression variable, Expression collection, List<Statement> block) {
    this.variable = new LValue(Preconditions.checkNotNull(variable));
    this.collection = Preconditions.checkNotNull(collection);
    this.block = ImmutableList.copyOf(block);
  }

  public LValue getVariable() {
    return variable;
  }

  /**
   * @return The collection we iterate on, e.g. `col` in `for x in col:`
   */
  public Expression getCollection() {
    return collection;
  }

  public ImmutableList<Statement> block() {
    return block;
  }

  @Override
  public String toString() {
    // TODO(bazel-team): if we want to print the complete statement, the function
    // needs an extra argument to specify indentation level.
    return "for " + variable + " in " + collection + ": ...\n";
  }

  @Override
  void doExec(Environment env) throws EvalException, InterruptedException {
    Object o = collection.eval(env);
    Iterable<?> col = EvalUtils.toIterable(o, getLocation());
    EvalUtils.lock(o, getLocation());
    try {
      for (Object it : col) {
        variable.assign(env, getLocation(), it);

        try {
          for (Statement stmt : block) {
            stmt.exec(env);
          }
        } catch (FlowException ex) {
          if (ex.mustTerminateLoop()) {
            return;
          }
        }
      }
    } finally {
      EvalUtils.unlock(o, getLocation());
    }
  }

  @Override
  public void accept(SyntaxTreeVisitor visitor) {
    visitor.visit(this);
  }

  @Override
  void validate(ValidationEnvironment env) throws EvalException {
    if (env.isTopLevel()) {
      throw new EvalException(getLocation(), "'For' is not allowed as a top level statement");
    }
    env.enterLoop();

    try {
      // TODO(bazel-team): validate variable. Maybe make it temporarily readonly.
      collection.validate(env);
      variable.validate(env, getLocation());

      for (Statement stmt : block) {
        stmt.validate(env);
      }
    } finally {
      env.exitLoop(getLocation());
    }
  }

  @Override
  ByteCodeAppender compile(
      VariableScope scope, Optional<LoopLabels> outerLoopLabels, DebugInfo debugInfo)
      throws EvalException {
    // TODO(bazel-team): Remove obsolete logic for counting size of iterated collection.
    AstAccessors debugAccessors = debugInfo.add(this);
    List<ByteCodeAppender> code = new ArrayList<>();
    InternalVariable originalIterable =
        scope.freshVariable(new TypeDescription.ForLoadedType(Iterable.class));
    InternalVariable iterator =
        scope.freshVariable(new TypeDescription.ForLoadedType(Iterator.class));
    // compute the collection and get it on the stack and transform it to the right type
    code.add(collection.compile(scope, debugInfo));
    append(code, debugAccessors.loadLocation, EvalUtils.toIterable, Duplication.SINGLE);
    // save it for later concurrent modification check
    code.add(originalIterable.store());
    append(
        code,
        ByteCodeMethodCalls.BCImmutableList.copyOf,
        ByteCodeMethodCalls.BCImmutableList.iterator);
    code.add(iterator.store());
    // for counting the size during the loop
    InternalVariable sizeCounterVariable =
        scope.freshVariable(new TypeDescription.ForLoadedType(int.class));
    LabelAdder loopHeader = new LabelAdder();
    LabelAdder loopBody = new LabelAdder();
    LabelAdder breakLoop = new LabelAdder();
    // for passing on the labels for continue/break statements
    Optional<LoopLabels> loopLabels = LoopLabels.of(loopHeader.getLabel(), breakLoop.getLabel());
    append(
        code,
        // initialize loop counter
        IntegerConstant.ZERO);
    code.add(sizeCounterVariable.store());
    append(code, Jump.to(loopHeader), loopBody, iterator.load());
    append(code, ByteCodeMethodCalls.BCIterator.next);
    // store current element into l-value
    code.add(variable.compileAssignment(this, debugAccessors, scope));
    // compile code for the body
    for (Statement statement : block) {
      append(code, new IntegerVariableIncrease(sizeCounterVariable, 1));
      code.add(statement.compile(scope, loopLabels, debugInfo));
    }
    // compile code for the loop header
    append(
        code,
        loopHeader,
        iterator.load(),
        ByteCodeMethodCalls.BCIterator.hasNext,
        // falls through to end of loop if hasNext() was false, otherwise jumps back
        Jump.ifIntOperandToZero(PrimitiveComparison.NOT_EQUAL).to(loopBody),
        breakLoop);
    return ByteCodeUtils.compoundAppender(code);
  }
}