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}