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