001package gololang.ir;
002
003import gololang.FunctionReference;
004import java.util.*;
005import java.util.function.BiFunction;
006
007public final class Visitors {
008  private Visitors() {
009    throw new UnsupportedOperationException();
010  }
011
012  /**
013   * Define how to walk the tree.
014   */
015  enum Walk {
016    /**
017     * Walk the tree <em>before</em> dealing with the current element.
018     */
019    PREFIX,
020
021    /**
022     * Walk the tree <em>after</em> dealing with the current element.
023     */
024    POSTFIX,
025
026    /**
027     * Walk the tree twice, <em>before and after</em>dealing with the current element.
028     */
029    BOTH,
030
031    /**
032     * Don't walk the tree.
033     * <p>
034     * This allows the user to choose if and when to walk the tree.
035     */
036    NONE
037  }
038
039  /**
040   * A special adapter class to ease the creation of IR visitors in Golo.
041   * <p>
042   * The visitor will apply a given function to elements in the IR tree, accumulating returned values.
043   * <p>
044   * Three kinds of functions are valid:<ul>
045   * <li>unary functions will be called with the current element only, the returned value is stored in the accumulator;
046   * <li>binary functions will be called with the current accumulator and the current element, the returned value is
047   * stored in the accumulator; this is the more natural version, similar to a <em>reduce</em>;
048   * <li>ternary functions will be called with the visitor itself, the accumulator, and the current element, the
049   * returned value is stored in the accumulator if not {@code null}.
050   * </ul>
051   *
052   * This last version is more complicated, but gives full access to the user on how to walk the tree and change the
053   * accumulator.
054   */
055  public static abstract class DispatchIrVisitor implements GoloIrVisitor, BiFunction<Object, GoloElement<?>, Object> {
056
057    private Object accumulator;
058    private Walk walk;
059
060    protected abstract FunctionReference dispatchFunction(GoloElement<?> elt);
061
062    private void dispatch(GoloElement<?> elt) {
063      if (walk == Walk.PREFIX || walk == Walk.BOTH) {
064        elt.walk(this);
065      }
066      FunctionReference f = dispatchFunction(elt);
067      if (f != null) {
068        try {
069          switch (f.arity()) {
070            case 1:
071              accumulator = f.invoke(elt);
072              break;
073            case 2:
074              accumulator = f.invoke(accumulator, elt);
075              break;
076            case 3:
077              Object result = f.invoke(this, accumulator, elt);
078              if (result != null) {
079                accumulator = result;
080              }
081              break;
082            default:
083              throw new IllegalArgumentException("The dispatched function must have an arity of 1, 2 or 3");
084          }
085        } catch (RuntimeException e) {
086          throw e;
087        } catch (Throwable t) {
088          throw new RuntimeException(t);
089        }
090      }
091      if (walk == Walk.POSTFIX || walk == Walk.BOTH) {
092        elt.walk(this);
093      }
094    }
095
096    private DispatchIrVisitor(Walk walk) {
097      this.walk = walk;
098    }
099
100    /**
101     * @return the value of the accumulator.
102     */
103    public Object accumulator() {
104      return accumulator;
105    }
106
107    /**
108     * Initialize the accumulator.
109     */
110    public DispatchIrVisitor accumulator(Object acc) {
111      this.accumulator = acc;
112      return this;
113    }
114
115    /**
116     * Function-like use of the visitor.
117     * <p>
118     * Initialize the accumulator with the given value, visit the given element, and returns the final value of the
119     * accumulator.
120     *
121     * @param acc the initial value for the accumulator
122     * @param elt the IR element to visit
123     * @return the final value of the accumulator
124     */
125    @Override
126    public Object apply(Object acc, GoloElement<?> elt) {
127      accumulator(acc);
128      elt.accept(this);
129      return accumulator();
130    }
131
132    @Override
133    public void visitModule(GoloModule elt) {
134      dispatch(elt);
135    }
136
137    @Override
138    public void visitModuleImport(ModuleImport elt) {
139      dispatch(elt);
140    }
141
142    @Override
143    public void visitStruct(Struct elt) {
144      dispatch(elt);
145    }
146
147    @Override
148    public void visitUnion(Union elt) {
149      dispatch(elt);
150    }
151
152    @Override
153    public void visitUnionValue(UnionValue elt) {
154      dispatch(elt);
155    }
156
157    @Override
158    public void visitAugmentation(Augmentation elt) {
159      dispatch(elt);
160    }
161
162    @Override
163    public void visitNamedAugmentation(NamedAugmentation elt) {
164      dispatch(elt);
165    }
166
167    @Override
168    public void visitFunction(GoloFunction elt) {
169      dispatch(elt);
170    }
171
172    @Override
173    public void visitDecorator(Decorator elt) {
174      dispatch(elt);
175    }
176
177    @Override
178    public void visitBlock(Block elt) {
179      dispatch(elt);
180    }
181
182    @Override
183    public void visitConstantStatement(ConstantStatement elt) {
184      dispatch(elt);
185    }
186
187    @Override
188    public void visitReturnStatement(ReturnStatement elt) {
189      dispatch(elt);
190    }
191
192    @Override
193    public void visitFunctionInvocation(FunctionInvocation elt) {
194      dispatch(elt);
195    }
196
197    @Override
198    public void visitMethodInvocation(MethodInvocation elt) {
199      dispatch(elt);
200    }
201
202    @Override
203    public void visitAssignmentStatement(AssignmentStatement elt) {
204      dispatch(elt);
205    }
206
207    @Override
208    public void visitDestructuringAssignment(DestructuringAssignment elt) {
209      dispatch(elt);
210    }
211
212    @Override
213    public void visitReferenceLookup(ReferenceLookup elt) {
214      dispatch(elt);
215    }
216
217    @Override
218    public void visitConditionalBranching(ConditionalBranching elt) {
219      dispatch(elt);
220    }
221
222    @Override
223    public void visitBinaryOperation(BinaryOperation elt) {
224      dispatch(elt);
225    }
226
227    @Override
228    public void visitUnaryOperation(UnaryOperation elt) {
229      dispatch(elt);
230    }
231
232    @Override
233    public void visitLoopStatement(LoopStatement elt) {
234      dispatch(elt);
235    }
236
237    @Override
238    public void visitForEachLoopStatement(ForEachLoopStatement elt) {
239      dispatch(elt);
240    }
241
242    @Override
243    public void visitCaseStatement(CaseStatement elt) {
244      dispatch(elt);
245    }
246
247    @Override
248    public void visitMatchExpression(MatchExpression elt) {
249      dispatch(elt);
250    }
251
252    @Override
253    public void visitWhenClause(WhenClause<?> elt) {
254      dispatch(elt);
255    }
256
257    @Override
258    public void visitThrowStatement(ThrowStatement elt) {
259      dispatch(elt);
260    }
261
262    @Override
263    public void visitTryCatchFinally(TryCatchFinally elt) {
264      dispatch(elt);
265    }
266
267    @Override
268    public void visitClosureReference(ClosureReference elt) {
269      dispatch(elt);
270    }
271
272    @Override
273    public void visitLoopBreakFlowStatement(LoopBreakFlowStatement elt) {
274      dispatch(elt);
275    }
276
277    @Override
278    public void visitCollectionLiteral(CollectionLiteral elt) {
279      dispatch(elt);
280    }
281
282    @Override
283    public void visitCollectionComprehension(CollectionComprehension elt) {
284      dispatch(elt);
285    }
286
287    @Override
288    public void visitNamedArgument(NamedArgument elt) {
289      dispatch(elt);
290    }
291
292    @Override
293    public void visitLocalReference(LocalReference elt) {
294      dispatch(elt);
295    }
296
297    @Override
298    public void visitMember(Member elt) {
299      dispatch(elt);
300    }
301
302    @Override
303    public void visitNoop(Noop elt) {
304      dispatch(elt);
305    }
306
307    @Override
308    public void visitToplevelElements(ToplevelElements elt) {
309      dispatch(elt);
310    }
311  }
312
313  /**
314   * Creates a visitor from a map of functions.
315   * <p>
316   * This is the same as {@code visitor(functions, null, Walk.PREFIX)}.
317   */
318  public static GoloIrVisitor visitor(Map<Class<? extends GoloElement<?>>, FunctionReference> functions) {
319    return visitor(functions, null, Walk.PREFIX);
320  }
321
322
323  /**
324   * Creates a visitor from a map of functions.
325   * <p>
326   * The resulting visitor will use the function corresponding to the class of the element to visit. If the class is
327   * not in the map, the default function will be used. If the function is {@code null}, no action will be taken for
328   * the node, but the subtree will be walked if required, according to the {@code walk} parameter.
329   * <p>
330   * For instance, the function:
331   * <pre class="listing"><code class="lang-golo" data-lang="golo">
332   * function numberOfAssignement = |irTree| -> Visitors.visitor!(
333   *   map[
334   *     [AssignmentStatement.class, |acc, elt| -> acc + 1],
335   *     [DestructuringAssignment.class, |acc, elt| -> acc + elt: referencesCount()]
336   *   ],
337   *   |acc, elt| -> acc,
338   *   Visitors$Walk.PREFIX())
339   *   : apply(0, irTree)
340   * </code></pre>
341   * counts the number of assignment in an IR tree.
342   *
343   * @param functions a map from IR classes to the golo functions
344   * @param defaultFunction the function to apply if the class of the element is not in the map.
345   * @param walk when to walk the tree
346   */
347  public static DispatchIrVisitor visitor(Map<Class<? extends GoloElement<?>>, FunctionReference> functions, FunctionReference defaultFunction, Walk walk) {
348    return new DispatchIrVisitor(walk) {
349      @Override
350      protected FunctionReference dispatchFunction(GoloElement<?> elt) {
351        return functions.getOrDefault(elt.getClass(), defaultFunction);
352      }
353    };
354  }
355
356  /**
357   * Creates a visitor from a unique function.
358   * <p>
359   * The resulting visitor will use the given function on each element. It is therefore the function responsibility to match on the element type if necessary.
360   * <p>
361   * For instance, the function:
362   * <pre class="listing"><code class="lang-golo" data-lang="golo">
363   * function numberOfAssignement = |irTree| -> Visitors.visitor!(|acc, elt| -> match {
364   *     when elt oftype AssignmentStatement.class then acc + 1
365   *     when elt oftype DestructuringAssignment.class then acc + elt: referencesCount()
366   *     otherwise acc
367   *   }, Visitors$Walk.PREFIX())
368   *   : apply(0, irTree)
369   * </code></pre>
370   * counts the number of assignment in an IR tree.
371   *
372   * @param fun the golo function to apply to elements in the tree
373   * @param walk when to walk the tree
374   */
375  public static DispatchIrVisitor visitor(FunctionReference fun, Walk walk) {
376    return new DispatchIrVisitor(walk) {
377      @Override
378      protected FunctionReference dispatchFunction(GoloElement<?> elt) {
379        return fun;
380      }
381    };
382  }
383
384  /**
385   * Creates a visitor from a unique function.
386   * <p>
387   * This is the same as {@code visitor(function, Walk.PREFIX)}.
388   */
389  public static GoloIrVisitor visitor(FunctionReference function) {
390    return visitor(function, Walk.PREFIX);
391  }
392
393
394}