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}