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}