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 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 * Retrieve metadata, searching all element hierarchy. 260 */ 261 public final Object inheritedMetadata(String name) { 262 Object m = this.meta.get(name); 263 if (m != null || this.parent == null || this.parent == this) { 264 return m; 265 } 266 return this.parent.inheritedMetadata(name); 267 } 268 269 /** 270 * Stores a meta-data in this element. 271 * <p> 272 * A meta-data can be any object. The main purpose is to allow visitors or macros to store some generic informations in the IR 273 * that can be used later by other macros or compilation steps (for instance add some Java annotations to the 274 * generated methods). 275 */ 276 public final T metadata(String name, Object value) { 277 if (value == null) { 278 this.meta.remove(name); 279 } else { 280 this.meta.put(name, value); 281 } 282 return self(); 283 } 284 285 protected final RuntimeException cantReplace() { 286 return new UnsupportedOperationException(getClass().getName() + " can't replace elements"); 287 } 288 289 protected final RuntimeException cantReplace(GoloElement<?> original, GoloElement<?> replacement) { 290 return new IllegalArgumentException(this + " can't replace " + original + " with " + replacement); 291 } 292 293 protected final RuntimeException doesNotContain(GoloElement<?> element) { 294 return new NoSuchElementException(element + " not in " + this); 295 } 296 297 protected static final RuntimeException cantConvert(String expected, Object value) { 298 return new ClassCastException(String.format( 299 "expecting a %s but got a %s", 300 expected, 301 value == null ? "null value" : value.getClass().getName())); 302 } 303 304 /** 305 * Replaces this element by the given one in its parent node. 306 * <p> 307 * This method is typically used during the sugar and macros expansion stages to replace the element by a desugarized version, 308 * or the macro call by its result. 309 * 310 * @param newElement the element to replace this one with. 311 * @throws IllegalStateException if this element has no parent. 312 */ 313 public final void replaceInParentBy(GoloElement<?> newElement) { 314 if (newElement == this) { return; } 315 if (this.parent != null) { 316 this.parent.replaceElement(this, newElement); 317 this.parent.makeParentOf(newElement); 318 if (newElement.position == null) { 319 newElement.position = this.position; 320 } 321 if (newElement.documentation == null || newElement.documentation.isEmpty()) { 322 newElement.documentation = this.documentation; 323 } 324 this.setParentNode(null); 325 } else { 326 throw new IllegalStateException("This node has no parent"); 327 } 328 } 329 330 public final String documentation() { 331 return this.documentation; 332 } 333 334 public final T documentation(String doc) { 335 if (doc != null) { 336 this.documentation = doc; 337 } 338 return self(); 339 } 340 341 public final PositionInSourceCode positionInSourceCode() { 342 if (this.position == null && this.parent != null) { 343 return this.parent.positionInSourceCode(); 344 } 345 return PositionInSourceCode.of(this.position); 346 } 347 348 public final T positionInSourceCode(PositionInSourceCode pos) { 349 if (pos != null && pos.isUndefined()) { 350 this.position = null; 351 } else { 352 this.position = pos; 353 } 354 return self(); 355 } 356 357 public final boolean hasPosition() { 358 return position != null || (this.parent != null && this.parent != this && this.parent.hasPosition()); 359 } 360 361 public Optional<ReferenceTable> getLocalReferenceTable() { 362 if (hasParent()) { 363 return this.parent.getLocalReferenceTable(); 364 } 365 return Optional.empty(); 366 } 367 368 /** 369 * Accept the visitor. 370 * <p> 371 * This method should only call the visitor {@code visitXXXX} method. 372 * The children of this node will be visited by the 373 * {@link #walk(GoloIrVisitor)} method. 374 */ 375 public abstract void accept(GoloIrVisitor visitor); 376 377 /** 378 * Walk the visitor through this node children. 379 */ 380 public void walk(GoloIrVisitor visitor) { 381 for (GoloElement<?> e : children()) { 382 e.accept(visitor); 383 } 384 } 385 386 /** 387 * Replace a child. 388 * <p> 389 * Replace {@code original} with {@code newElement} if {@code original} is a child of this node 390 * and type matches. 391 * 392 * @param original the original value to replace. Must be a child of this node 393 * @param newElement the element to replace with. Type must match. 394 * @throws UnsupportedOperationException if this node does not support replacement 395 * @throws NoSuchElementException if {@code original} is not a child of this node 396 * @throws ClassCastException if the type of {@code newElement} does not match 397 * @see #cantReplace() 398 * @see #cantReplace(GoloElement, GoloElement) 399 * @see #doesNotContain(GoloElement) 400 * @see #cantConvert(String, Object) 401 */ 402 protected abstract void replaceElement(GoloElement<?> original, GoloElement<?> newElement); 403 404 private static class RelinkIrVisitor extends AbstractGoloIrVisitor { 405 private final ReferenceTable rt; 406 boolean prune; 407 408 RelinkIrVisitor(ReferenceTable rt) { 409 this.rt = rt; 410 prune = true; 411 } 412 413 @Override 414 public void visitBlock(Block b) { 415 b.getReferenceTable().relink(rt, prune); 416 // We don't walk the subtree since contained blocks are already linked to this, 417 // block, and thus to `rt` by transitivity. 418 } 419 420 @Override 421 public void visitClosureReference(ClosureReference c) { 422 prune = false; 423 c.walk(this); 424 } 425 426 @Override 427 public void visitMacroInvocation(MacroInvocation macroInvocation) { 428 macroInvocation.walk(this); 429 } 430 } 431}