001/*
002 * Copyright (c) 2012-2018 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 final class Block extends ExpressionStatement<Block> implements Iterable<GoloStatement<?>> {
025  private final List<GoloStatement<?>> statements = new LinkedList<>();
026  private ReferenceTable referenceTable;
027  private boolean hasReturn = false;
028
029  Block(ReferenceTable referenceTable) {
030    super();
031    this.referenceTable = requireNonNull(referenceTable);
032  }
033
034  protected Block self() { return this; }
035
036  /**
037   * Create an empty block.
038   */
039  public static Block empty() {
040    return new Block(new ReferenceTable());
041  }
042
043  /**
044   * Block coercion.
045   *
046   * <p> if the argument is {@code null}, returns an empty block, otherwise, tries to convert it into a block, wrapping
047   * it in an empty block if necessary.
048   */
049  public static Block of(Object block) {
050    if (block == null) {
051      return empty();
052    }
053    if (block instanceof Block) {
054      return (Block) block;
055    }
056    if (block instanceof GoloStatement<?>) {
057      return empty().add(block);
058    }
059    throw cantConvert("Block", block);
060  }
061
062  /**
063   * Creates a block containing the given statements.
064   *
065   * <p>If the only argument is already a block, returns it unchanged. If no arguments are given, returns an empty block.
066   *
067   * @param statements the {@link GoloStatement}s composing the block.
068   */
069  public static Block block(Object... statements) {
070    if (statements.length == 1) {
071      return of(statements[0]);
072    }
073    Block block = empty();
074    for (Object st : statements) {
075      block.add(st);
076    }
077    return block;
078  }
079
080  /**
081   * Merge two blocks.
082   *
083   * <p><strong>Warning:</strong>the statements contained in the other block being <em>move</em> to this one and <em>not copied</em>,
084   * the other block references statements but is no more their parent. Therefore, the other block is not
085   * in a consistent state and should be dropped, since no more needed.
086   */
087  public void merge(Block other) {
088    for (GoloStatement<?> innerStatement : other.getStatements()) {
089      this.add(innerStatement);
090    }
091  }
092
093  public ReferenceTable getReferenceTable() {
094    return referenceTable;
095  }
096
097  /**
098   * {@inheritDoc}
099   */
100  @Override
101  public Optional<ReferenceTable> getLocalReferenceTable() {
102    return Optional.ofNullable(referenceTable);
103  }
104
105  /**
106   * Define the reference table of this block.
107   *
108   * <p>This is a builder method.
109   *
110   * @param referenceTable the {@link ReferenceTable} to use in this block.
111   * @return this block
112   */
113  public Block ref(Object referenceTable) {
114    if (referenceTable instanceof ReferenceTable) {
115      this.referenceTable = (ReferenceTable) referenceTable;
116      return this;
117    }
118    throw new IllegalArgumentException("not a reference table");
119  }
120
121  /**
122   * Internal method.
123   */
124  public void internReferenceTable() {
125    this.referenceTable = referenceTable.flatDeepCopy(true);
126  }
127
128  public List<GoloStatement<?>> getStatements() {
129    return unmodifiableList(statements);
130  }
131
132  public Iterator<GoloStatement<?>> iterator() {
133    return statements.iterator();
134  }
135
136  /**
137   * Creates a new block by applying the given function to the elements of this block.
138   */
139  public Block map(FunctionReference fun) throws Throwable {
140    Block res = empty();
141    for (GoloElement<?> elt : this) {
142      res.add(fun.invoke(elt));
143    }
144    return res;
145  }
146
147  /**
148   * Add a statement to this block.
149   *
150   * <p>This is a builder method.
151   *
152   * @param statement the statement to add to this block
153   * @return this block
154   * @throws ClassCastException if the statement can't be converted into a {@link GoloStatement}
155   * @see GoloStatement#of(Object)
156   */
157  public Block add(Object statement) {
158    if (statement != null) {
159      this.addStatement(statements.size(), GoloStatement.of(statement));
160    }
161    return this;
162  }
163
164  private void updateStateWith(GoloStatement<?> statement) {
165    referenceTable.updateFrom(statement);
166    makeParentOf(statement);
167    checkForReturns(statement);
168  }
169
170  private void addStatement(int idx, GoloStatement<?> statement) {
171    statements.add(idx, statement);
172    updateStateWith(statement);
173  }
174
175  public Block prepend(Object statement) {
176    if (statement != null) {
177      this.addStatement(0, GoloStatement.of(statement));
178    }
179    return this;
180  }
181
182  private void setStatement(int idx, GoloStatement<?> statement) {
183    statements.set(idx, statement);
184    updateStateWith(statement);
185  }
186
187  /**
188   * Merge nested blocks with this one if they don't define local variables.
189   */
190  public void flatten() {
191    List<GoloStatement<?>> toFlatten = new ArrayList<>(statements);
192    statements.clear();
193    for (GoloStatement<?> statement : toFlatten) {
194      if (statement instanceof Block) {
195        Block block = (Block) statement;
196        if (block.referenceTable.isEmpty()) {
197          block.flatten();
198          for (GoloStatement<?> s : block.statements) {
199            this.add(s);
200          }
201          continue;
202        }
203      }
204      this.add(statement);
205    }
206  }
207
208  private void checkForReturns(GoloStatement<?> statement) {
209    if (statement instanceof ReturnStatement || statement instanceof ThrowStatement) {
210      hasReturn = true;
211    } else if (statement instanceof ConditionalBranching) {
212      hasReturn = hasReturn || ((ConditionalBranching) statement).returnsFromBothBranches();
213    }
214  }
215
216  public boolean hasReturn() {
217    return hasReturn;
218  }
219
220  public int size() {
221    return statements.size();
222  }
223
224  /**
225   * Checks if this block contains only a return statement.
226   */
227  public boolean hasOnlyReturn() {
228    return statements.size() == 1
229           && statements.get(0) instanceof ReturnStatement
230           && !((ReturnStatement) statements.get(0)).isReturningVoid();
231  }
232
233  /**
234   * Checks if this blocks contains only an expression.
235   */
236  public boolean hasOnlyExpression() {
237    return (statements.size() == 1 && statements.get(0) instanceof ExpressionStatement);
238  }
239
240  @Override
241  public String toString() {
242    return "{" + statements.toString() + "}";
243  }
244
245  public boolean isEmpty() {
246    return statements.isEmpty();
247  }
248
249  /**
250   * {@inheritDoc}
251   */
252  @Override
253  public void accept(GoloIrVisitor visitor) {
254    visitor.visitBlock(this);
255  }
256
257  /**
258   * {@inheritDoc}
259   */
260  @Override
261  public void walk(GoloIrVisitor visitor) {
262    for (LocalReference ref : referenceTable.ownedReferences()) {
263      ref.accept(visitor);
264    }
265    for (GoloStatement<?> statement : statements) {
266      statement.accept(visitor);
267    }
268  }
269
270  /**
271   * {@inheritDoc}
272   */
273  @Override
274  public List<GoloElement<?>> children() {
275    return unmodifiableList(statements);
276  }
277
278  /**
279   * {@inheritDoc}
280   */
281  @Override
282  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
283    if (statements.contains(original) && newElement instanceof GoloStatement) {
284      setStatement(statements.indexOf(original), (GoloStatement) newElement);
285    } else {
286      throw cantReplace(original, newElement);
287    }
288  }
289}