001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package gololang.ir;
012
013import java.util.*;
014import gololang.FunctionReference;
015
016import static java.util.Collections.unmodifiableList;
017import static java.util.Objects.requireNonNull;
018
019/**
020 * Represent a block, that is a sequence of statements.
021 *
022 * <p>A block defines a scope, and as such maintains its local reference table.
023 */
024public class Block extends ExpressionStatement<Block> implements Iterable<GoloStatement<?>> {
025
026  private static final Block NULL = new Block() {
027    @Override
028    public void accept(GoloIrVisitor visitor) { }
029
030    @Override
031    public void merge(Block other) { throw new UnsupportedOperationException(); }
032
033    @Override
034    public Block ref(Object o) { return this; }
035
036    @Override
037    public void internReferenceTable() {}
038
039    @Override
040    public List<GoloStatement<?>> getStatements() { return Collections.emptyList(); }
041
042    @Override
043    public Iterator<GoloStatement<?>> iterator() { return Collections.emptyIterator(); }
044
045    @Override
046    public Block map(FunctionReference fun) throws Throwable { return this; }
047
048    @Override
049    public Block add(Object statement) { throw new UnsupportedOperationException(); }
050
051    @Override
052    public Block prepend(Object statement) { throw new UnsupportedOperationException(); }
053
054    @Override
055    public void flatten() {}
056
057    @Override
058    public int size() { return 0; }
059
060    @Override
061    public boolean hasOnlyReturn() { return false; }
062
063    @Override
064    public boolean hasOnlyExpression() { return false; }
065
066    @Override
067    public String toString() { return "{NULL}"; }
068
069    @Override
070    public boolean isEmpty() { return true; }
071
072    @Override
073    public void walk(GoloIrVisitor visitor) {}
074
075    @Override
076    public List<GoloElement<?>> children() { return Collections.emptyList(); }
077
078    @Override
079    protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
080      throw new UnsupportedOperationException();
081    }
082  };
083
084  private final List<GoloStatement<?>> statements;
085  private ReferenceTable referenceTable;
086  private boolean hasReturn = false;
087
088  private Block() {
089    super();
090    statements = Collections.emptyList();
091  }
092
093  Block(ReferenceTable referenceTable) {
094    super();
095    this.referenceTable = requireNonNull(referenceTable);
096    this.statements = new LinkedList<>();
097  }
098
099  protected Block self() { return this; }
100
101  /**
102   * Create an empty block.
103   */
104  public static Block empty() {
105    return new Block(new ReferenceTable());
106  }
107
108  /**
109   * Return the null block.
110   *
111   * Null Object that does nothing.
112   */
113  public static Block nullBlock() {
114    return NULL;
115  }
116
117  /**
118   * Block coercion.
119   *
120   * <p> if the argument is {@code null}, returns an empty block, otherwise, tries to convert it into a block, wrapping
121   * it in an empty block if necessary.
122   */
123  public static Block of(Object block) {
124    if (block == null) {
125      return empty();
126    }
127    if (block instanceof Block) {
128      return (Block) block;
129    }
130    if (block instanceof GoloStatement<?>) {
131      return empty().add(block);
132    }
133    throw cantConvert("Block", block);
134  }
135
136  /**
137   * Creates a block containing the given statements.
138   *
139   * <p>If the only argument is already a block, returns it unchanged. If no arguments are given, returns an empty block.
140   *
141   * @param statements the {@link GoloStatement}s composing the block.
142   */
143  public static Block block(Object... statements) {
144    if (statements.length == 1) {
145      return of(statements[0]);
146    }
147    Block block = empty();
148    for (Object st : statements) {
149      block.add(st);
150    }
151    return block;
152  }
153
154  /**
155   * Merge two blocks.
156   *
157   * <p><strong>Warning:</strong>the statements contained in the other block being <em>move</em> to this one and <em>not copied</em>,
158   * the other block references statements but is no more their parent. Therefore, the other block is not
159   * in a consistent state and should be dropped, since no more needed.
160   */
161  public void merge(Block other) {
162    for (GoloStatement<?> innerStatement : other.getStatements()) {
163      this.add(innerStatement);
164    }
165  }
166
167  public ReferenceTable getReferenceTable() {
168    return referenceTable;
169  }
170
171  /**
172   * {@inheritDoc}
173   */
174  @Override
175  public Optional<ReferenceTable> getLocalReferenceTable() {
176    return Optional.ofNullable(referenceTable);
177  }
178
179  /**
180   * Define the reference table of this block.
181   *
182   * <p>This is a builder method.
183   *
184   * @param referenceTable the {@link ReferenceTable} to use in this block.
185   * @return this block
186   */
187  public Block ref(Object referenceTable) {
188    if (referenceTable instanceof ReferenceTable) {
189      this.referenceTable = (ReferenceTable) referenceTable;
190      return this;
191    }
192    throw new IllegalArgumentException("not a reference table");
193  }
194
195  /**
196   * Internal method.
197   */
198  public void internReferenceTable() {
199    this.referenceTable = referenceTable.flatDeepCopy(true);
200  }
201
202  public List<GoloStatement<?>> getStatements() {
203    return unmodifiableList(statements);
204  }
205
206  public Iterator<GoloStatement<?>> iterator() {
207    return statements.iterator();
208  }
209
210  /**
211   * Creates a new block by applying the given function to the elements of this block.
212   */
213  public Block map(FunctionReference fun) throws Throwable {
214    Block res = empty();
215    for (GoloElement<?> elt : this) {
216      res.add(fun.invoke(elt));
217    }
218    return res;
219  }
220
221  /**
222   * Add a statement to this block.
223   *
224   * <p>This is a builder method.
225   *
226   * @param statement the statement to add to this block
227   * @return this block
228   * @throws ClassCastException if the statement can't be converted into a {@link GoloStatement}
229   * @see GoloStatement#of(Object)
230   */
231  public Block add(Object statement) {
232    if (statement != null) {
233      this.addStatement(statements.size(), GoloStatement.of(statement));
234    }
235    return this;
236  }
237
238  private void updateStateWith(GoloStatement<?> statement) {
239    referenceTable.updateFrom(statement);
240    makeParentOf(statement);
241    checkForReturns(statement);
242  }
243
244  private void addStatement(int idx, GoloStatement<?> statement) {
245    statements.add(idx, statement);
246    updateStateWith(statement);
247  }
248
249  public Block prepend(Object statement) {
250    if (statement != null) {
251      this.addStatement(0, GoloStatement.of(statement));
252    }
253    return this;
254  }
255
256  private void setStatement(int idx, GoloStatement<?> statement) {
257    statements.set(idx, statement);
258    updateStateWith(statement);
259  }
260
261  /**
262   * Merge nested blocks with this one if they don't define local variables.
263   */
264  public void flatten() {
265    List<GoloStatement<?>> toFlatten = new ArrayList<>(statements);
266    statements.clear();
267    for (GoloStatement<?> statement : toFlatten) {
268      if (statement instanceof Block) {
269        Block block = (Block) statement;
270        if (block.referenceTable.isEmpty()) {
271          block.flatten();
272          for (GoloStatement<?> s : block.statements) {
273            this.add(s);
274          }
275          continue;
276        }
277      }
278      this.add(statement);
279    }
280  }
281
282  private void checkForReturns(GoloStatement<?> statement) {
283    if (statement instanceof ReturnStatement || statement instanceof ThrowStatement) {
284      hasReturn = true;
285    } else if (statement instanceof ConditionalBranching) {
286      hasReturn = hasReturn || ((ConditionalBranching) statement).returnsFromBothBranches();
287    } else if (statement instanceof LoopStatement) {
288      hasReturn = hasReturn || ((LoopStatement) statement).getBlock().hasReturn();
289    } else if (statement instanceof TryCatchFinally) {
290      hasReturn = hasReturn || ((TryCatchFinally) statement).hasReturn();
291    }
292  }
293
294  public boolean hasReturn() {
295    return hasReturn;
296  }
297
298  public int size() {
299    return statements.size();
300  }
301
302  /**
303   * Checks if this block contains only a return statement.
304   */
305  public boolean hasOnlyReturn() {
306    return statements.size() == 1
307           && statements.get(0) instanceof ReturnStatement
308           && !((ReturnStatement) statements.get(0)).isReturningVoid();
309  }
310
311  /**
312   * Checks if this blocks contains only an expression.
313   */
314  public boolean hasOnlyExpression() {
315    return (statements.size() == 1 && statements.get(0) instanceof ExpressionStatement);
316  }
317
318  @Override
319  public String toString() {
320    return "{" + statements.toString() + "}";
321  }
322
323  public boolean isEmpty() {
324    return statements.isEmpty();
325  }
326
327  /**
328   * {@inheritDoc}
329   */
330  @Override
331  public void accept(GoloIrVisitor visitor) {
332    visitor.visitBlock(this);
333  }
334
335  /**
336   * {@inheritDoc}
337   */
338  @Override
339  public void walk(GoloIrVisitor visitor) {
340    for (LocalReference ref : referenceTable.ownedReferences()) {
341      ref.accept(visitor);
342    }
343    for (GoloStatement<?> statement : statements) {
344      statement.accept(visitor);
345    }
346  }
347
348  /**
349   * {@inheritDoc}
350   */
351  @Override
352  public List<GoloElement<?>> children() {
353    return unmodifiableList(statements);
354  }
355
356  /**
357   * {@inheritDoc}
358   */
359  @Override
360  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
361    if (statements.contains(original) && newElement instanceof GoloStatement) {
362      setStatement(statements.indexOf(original), (GoloStatement) newElement);
363    } else {
364      throw cantReplace(original, newElement);
365    }
366  }
367}