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}