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}