| 60 | } |
| 61 | |
| 62 | function walkImplementation<T extends { nodes?: T[] }>( |
| 63 | ast: T[], |
| 64 | enter: (node: T, ctx: VisitContext<T>) => EnterResult<T> | void = () => WalkAction.Continue, |
| 65 | exit: (node: T, ctx: VisitContext<T>) => ExitResult<T> | void = () => WalkAction.Continue, |
| 66 | ) { |
| 67 | type StackFrame = [nodes: T[], offset: number, parent: Parent<T> | null] |
| 68 | let stack: Stack<StackFrame> | null = { value: [ast, 0, null], prev: null } |
| 69 | |
| 70 | let ctx: VisitContext<T> = { |
| 71 | parent: null, |
| 72 | depth: 0, |
| 73 | index: 0, |
| 74 | siblings: ast, |
| 75 | path() { |
| 76 | let path: T[] = [] |
| 77 | |
| 78 | let frames: Stack<StackFrame> | null = stack |
| 79 | |
| 80 | while (frames) { |
| 81 | let parent = frames.value[2] |
| 82 | if (parent) path.push(parent) |
| 83 | frames = frames.prev |
| 84 | } |
| 85 | |
| 86 | path.reverse() |
| 87 | |
| 88 | return path |
| 89 | }, |
| 90 | } |
| 91 | |
| 92 | while (stack !== null) { |
| 93 | let frame = stack.value |
| 94 | let nodes = frame[0] |
| 95 | let offset = frame[1] |
| 96 | let parent = frame[2] |
| 97 | |
| 98 | class="cm">// Done with this level |
| 99 | if (offset >= nodes.length) { |
| 100 | stack = stack.prev |
| 101 | ctx.depth -= 1 |
| 102 | continue |
| 103 | } |
| 104 | |
| 105 | ctx.parent = parent |
| 106 | ctx.siblings = nodes |
| 107 | |
| 108 | class="cm">// Enter phase (offsets are positive) |
| 109 | if (offset >= 0) { |
| 110 | ctx.index = offset |
| 111 | |
| 112 | let node = nodes[offset] |
| 113 | let result = enter(node, ctx) ?? WalkAction.Continue |
| 114 | |
| 115 | switch (result.kind) { |
| 116 | case WalkKind.Continue: { |
| 117 | if (node.nodes && node.nodes.length > 0) { |
| 118 | ctx.depth += 1 |
| 119 | stack = { |