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 org.eclipse.golo.compiler.parser.GoloASTNode; 014 015import java.util.*; 016import java.util.function.Predicate; 017import java.util.stream.Stream; 018 019import org.eclipse.golo.compiler.PositionInSourceCode; 020 021import static java.util.stream.Collectors.toList; 022 023/** 024 * Generic IR tree Element. 025 * <p> 026 * The IR tree represents a more abstract representation of the Golo code. 027 */ 028public abstract class GoloElement<T extends GoloElement<T>> { 029 private GoloElement<?> parent; 030 private String documentation; 031 private PositionInSourceCode position; 032 private final Map<String, Object> meta = new HashMap<>(); 033 034 protected abstract T self(); 035 036 /** 037 * Init the position and documentation from the given AST node. 038 * <p> 039 * 040 * @param node the {@link GoloASTNode} to reference. 041 */ 042 public final T ofAST(GoloASTNode node) { 043 if (node != null) { 044 this.documentation(node.getDocumentation()); 045 this.positionInSourceCode(node.getPositionInSourceCode()); 046 } 047 return self(); 048 } 049 050 private void setParentNode(GoloElement<?> parentElement) { 051 this.parent = parentElement; 052 } 053 054 public final boolean hasParent() { 055 return this.parent != null && this.parent != this; 056 } 057 058 public final GoloElement<?> parent() { 059 return this.parent; 060 } 061 062 /** 063 * Returns the module containing this element. 064 */ 065 public GoloModule enclosingModule() { 066 if (this.parent == null) { 067 return null; 068 } 069 return this.parent.enclosingModule(); 070 } 071 072 /** 073 * Returns the first ancestor of this node being an instance of the given class. 074 * 075 * Note that if the ancestor can be the element itself. 076 */ 077 public <C extends GoloElement<?>> C ancestorOfType(Class<C> cls) { 078 return cls.cast(ancestor(cls::isInstance)); 079 } 080 081 /** 082 * Returns the first ancestor of this node matching the given predicate. 083 * 084 * Note that if the ancestor can be the element itself. 085 */ 086 public GoloElement<?> ancestor(Predicate<GoloElement<?>> predicate) { 087 if (this.parent == null) { 088 return null; 089 } 090 if (predicate.test(this.parent)) { 091 return this.parent; 092 } 093 if (this.parent == this) { 094 // Not tested at the same time as `null` since if `this` matches the predicate it is returned. 095 return null; 096 } 097 return this.parent.ancestor(predicate); 098 } 099 /** 100 * Returns the first ancestor of this node matching the given predicate. 101 * 102 * Note that if the ancestor can be the element itself. 103 */ 104 public GoloElement<?> ancestor(gololang.FunctionReference predicate) { 105 // NOTE: this implementation is required due to some bugs in the implicit conversion of golo function references 106 // into java lambda (see https://github.com/eclipse/golo-lang/issues/277) 107 // When resolved, the `ancestor(Predicate<GoloElement>)` will suffice. 108 return ancestor(predicateWrapper(predicate)); 109 } 110 111 /** 112 * Returns a list of all the descendants of this node matching the given predicate. 113 */ 114 public List<GoloElement<?>> descendants(Predicate<GoloElement<?>> predicate) { 115 return descendants().filter(predicate).collect(toList()); 116 } 117 118 /** 119 * Returns a list of all the descendants of this node matching the given predicate. 120 */ 121 public List<GoloElement<?>> descendants(gololang.FunctionReference predicate) { 122 // NOTE: this implementation is required due to some bugs in the implicit conversion of golo function references 123 // into java lambda (see https://github.com/eclipse/golo-lang/issues/277) 124 // When resolved, the `descendants(Predicate<GoloElement>)` will suffice. 125 return descendants(predicateWrapper(predicate)); 126 } 127 128 public Stream<GoloElement<?>> descendants() { 129 return Stream.concat( 130 children().stream(), 131 children().stream().flatMap(GoloElement::descendants)); 132 } 133 134 private static Predicate<GoloElement<?>> predicateWrapper(gololang.FunctionReference predicate) { 135 return n -> { 136 try { 137 return (Boolean) predicate.invoke(n); 138 } catch (Throwable e) { 139 throw new RuntimeException(e); 140 } 141 }; 142 } 143 144 /** 145 * Returns a list of all the direct children of this node. 146 */ 147 public List<GoloElement<?>> children() { 148 return Collections.emptyList(); 149 } 150 151 /** 152 * Returns a list the direct children of this node matching the given predicate. 153 */ 154 public List<GoloElement<?>> children(Predicate<GoloElement<?>> predicate) { 155 return children().stream().filter(predicate).collect(toList()); 156 } 157 158 /** 159 * Returns a iterable on the direct children of this node matching the given predicate. 160 */ 161 public List<GoloElement<?>> children(gololang.FunctionReference predicate) { 162 // NOTE: this implementation is required due to some bugs in the implicit conversion of golo function references 163 // into java lambda (see https://github.com/eclipse/golo-lang/issues/277) 164 // When resolved, the `children(Predicate<GoloElement>)` will suffice. 165 return children(predicateWrapper(predicate)); 166 } 167 168 /** 169 * Returns the previous sibling of this element. 170 */ 171 public GoloElement<?> previous() { 172 return previous(e -> true); 173 } 174 175 /** 176 * Returns the previous sibling of this element matching the given predicate. 177 */ 178 public GoloElement<?> previous(Predicate<GoloElement<?>> predicate) { 179 if (this.parent == null || this.parent == this) { 180 return null; 181 } 182 GoloElement<?> previous = null; 183 for (GoloElement<?> e : this.parent.children()) { 184 if (e == this) { 185 break; 186 } 187 if (predicate.test(e)) { 188 previous = e; 189 } 190 } 191 return previous; 192 } 193 194 195 /** 196 * Returns the previous sibling of this element matching the given predicate. 197 */ 198 public GoloElement<?> previous(gololang.FunctionReference predicate) { 199 // NOTE: this implementation is required due to some bugs in the implicit conversion of golo function references 200 // into java lambda (see https://github.com/eclipse/golo-lang/issues/277) 201 return previous(predicateWrapper(predicate)); 202 } 203 204 /** 205 * Returns the next sibling of this element. 206 */ 207 public GoloElement<?> next() { 208 return next(e -> true); 209 } 210 211 /** 212 * Returns the next sibling of this element matching the given predicate. 213 */ 214 public GoloElement<?> next(gololang.FunctionReference predicate) { 215 // NOTE: this implementation is required due to some bugs in the implicit conversion of golo function references 216 // into java lambda (see https://github.com/eclipse/golo-lang/issues/277) 217 return next(predicateWrapper(predicate)); 218 } 219 220 /** 221 * Returns the next sibling of this element matching the given predicate. 222 */ 223 public GoloElement<?> next(Predicate<GoloElement<?>> predicate) { 224 if (this.parent == null || this.parent == this) { 225 return null; 226 } 227 boolean found = false; 228 for (GoloElement<?> e : this.parent.children()) { 229 if (found && predicate.test(e)) { 230 return e; 231 } 232 if (e == this) { 233 found = true; 234 } 235 } 236 return null; 237 } 238 239 protected final <C extends GoloElement<?>> C makeParentOf(C childElement) { 240 if (childElement != null && childElement.parent() != this) { 241 ((GoloElement<?>) childElement).setParentNode(this); 242 relinkChild(childElement); 243 } 244 return childElement; 245 } 246 247 private void relinkChild(GoloElement<?> child) { 248 this.getLocalReferenceTable().ifPresent((rt) -> child.accept(new RelinkIrVisitor(rt))); 249 } 250 251 /** 252 * Retrieve a previously stored meta-data. 253 */ 254 public final Object metadata(String name) { 255 return this.meta.get(name); 256 } 257 258 /** 259 * Stores a meta-data in this element. 260 * <p> 261 * A meta-data can be any object. The main purpose is to allow visitors to store some generic informations in the IR 262 * that can be used later by other compilation steps (for instance add some Java annotations to the 263 * generated methods). 264 */ 265 public final T metadata(String name, Object value) { 266 if (value == null) { 267 this.meta.remove(name); 268 } else { 269 this.meta.put(name, value); 270 } 271 return self(); 272 } 273 274 protected final RuntimeException cantReplace() { 275 return new UnsupportedOperationException(getClass().getName() + " can't replace elements"); 276 } 277 278 protected final RuntimeException cantReplace(GoloElement<?> original, GoloElement<?> replacement) { 279 return new IllegalArgumentException(this + " can't replace " + original + " with " + replacement); 280 } 281 282 protected final RuntimeException doesNotContain(GoloElement<?> element) { 283 return new NoSuchElementException(element + " not in " + this); 284 } 285 286 protected static final RuntimeException cantConvert(String expected, Object value) { 287 return new ClassCastException(String.format( 288 "expecting a %s but got a %s", 289 expected, 290 value == null ? "null value" : value.getClass().getName())); 291 } 292 293 /** 294 * Replaces this element by the given one in its parent node. 295 * <p> 296 * This method is typically used during the sugar expansion to replace the element by a desugarized version. 297 * 298 * @param newElement the element to replace this one with. 299 * @throws IllegalStateException if this element has no parent. 300 */ 301 public final void replaceInParentBy(GoloElement<?> newElement) { 302 if (newElement == this) { return; } 303 if (this.parent != null) { 304 this.parent.replaceElement(this, newElement); 305 this.parent.makeParentOf(newElement); 306 if (newElement.position == null) { 307 newElement.position = this.position; 308 } 309 if (newElement.documentation == null || newElement.documentation.isEmpty()) { 310 newElement.documentation = this.documentation; 311 } 312 this.setParentNode(null); 313 } else { 314 throw new IllegalStateException("This node has no parent"); 315 } 316 } 317 318 public final String documentation() { 319 return this.documentation; 320 } 321 322 public final T documentation(String doc) { 323 if (doc != null) { 324 this.documentation = doc; 325 } 326 return self(); 327 } 328 329 public final PositionInSourceCode positionInSourceCode() { 330 if (this.position == null && this.parent != null) { 331 return this.parent.positionInSourceCode(); 332 } 333 return PositionInSourceCode.of(this.position); 334 } 335 336 public final T positionInSourceCode(PositionInSourceCode pos) { 337 if (pos != null && pos.isUndefined()) { 338 this.position = null; 339 } else { 340 this.position = pos; 341 } 342 return self(); 343 } 344 345 public final boolean hasPosition() { 346 return position != null || (this.parent != null && this.parent != this && this.parent.hasPosition()); 347 } 348 349 public Optional<ReferenceTable> getLocalReferenceTable() { 350 if (hasParent()) { 351 return this.parent.getLocalReferenceTable(); 352 } 353 return Optional.empty(); 354 } 355 356 /** 357 * Accept the visitor. 358 * <p> 359 * This method should only call the visitor {@code visitXXXX} method. 360 * The children of this node will be visited by the 361 * {@link #walk(GoloIrVisitor)} method. 362 */ 363 public abstract void accept(GoloIrVisitor visitor); 364 365 /** 366 * Walk the visitor through this node children. 367 */ 368 public void walk(GoloIrVisitor visitor) { 369 for (GoloElement<?> e : children()) { 370 e.accept(visitor); 371 } 372 } 373 374 /** 375 * Replace a child. 376 * <p> 377 * Replace {@code original} with {@code newElement} if {@code original} is a child of this node 378 * and type matches. 379 * 380 * @param original the original value to replace. Must be a child of this node 381 * @param newElement the element to replace with. Type must match. 382 * @throws UnsupportedOperationException if this node does not support replacement 383 * @throws NoSuchElementException if {@code original} is not a child of this node 384 * @throws ClassCastException if the type of {@code newElement} does not match 385 * @see #cantReplace() 386 * @see #cantReplace(GoloElement, GoloElement) 387 * @see #doesNotContain(GoloElement) 388 * @see #cantConvert(String, Object) 389 */ 390 protected abstract void replaceElement(GoloElement<?> original, GoloElement<?> newElement); 391 392 private static class RelinkIrVisitor extends AbstractGoloIrVisitor { 393 private final ReferenceTable rt; 394 boolean prune; 395 396 RelinkIrVisitor(ReferenceTable rt) { 397 this.rt = rt; 398 prune = true; 399 } 400 401 @Override 402 public void visitBlock(Block b) { 403 b.getReferenceTable().relink(rt, prune); 404 // We don't walk the subtree since contained blocks are already linked to this, 405 // block, and thus to `rt` by transitivity. 406 } 407 408 @Override 409 public void visitClosureReference(ClosureReference c) { 410 prune = false; 411 c.walk(this); 412 } 413 } 414}