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}