@samitouri / QOS-React-2 / commits / 400bf4588c

[valueblocks] More general visitor for ReactiveFunctions

This adds a truly general-purpose visitor pattern for ReactiveFunction, modeled on what's worked well in Relay Compiler. All types of node that have children get a visitFoo/traverseFoo pair of functions. By default the visitFoo() function delegates to the traverseFoo() function, but the visit variant is meant to be overridden and can delegate to the traverseFoo() variant — this gives you precise control so that you can save/restore state before/after traversing children. Probably the only interesting thing is that visitLValue() does not call visitPlace() by default though it technically could. So far that is making sense in the passes i converted. Note that once all passes are updated to use this, i'll delete the other visitor helpers for ReactiveFunction.

Joe Savona committed Jan 31, 2023 at 13:39 UTC 400bf4588ce80ba6f401a39a5859a1a72ab92fc1
1 file changed +145
compiler/forget/src/ReactiveScopes/visitors.ts
+145
@@ -6,19 +6,164 @@
6 */
7
8 import {
9 + InstructionId,
10 InstructionValue,
11 + LValue,
12 Place,
13 ReactiveBlock,
14 ReactiveFunction,
15 ReactiveInstruction,
16 ReactiveScope,
17 + ReactiveScopeBlock,
18 ReactiveTerminal,
19 + ReactiveTerminalStatement,
20 ReactiveValue,
21 ReactiveValueBlock,
22 } from "../HIR/HIR";
23 import { eachInstructionValueOperand } from "../HIR/visitors";
24 import { assertExhaustive } from "../Utils/utils";
25
26 +export function visitReactiveFunction<TState>(
27 + fn: ReactiveFunction,
28 + visitor: ReactiveFunctionVisitor<TState>,
29 + state: TState
30 +): void {
31 + visitor.visitBlock(fn.body, state);
32 +}
33 +
34 +export class ReactiveFunctionVisitor<TState = void> {
35 + visitID(id: InstructionId, state: TState): void {}
36 + visitLValue(id: InstructionId, lvalue: LValue, state: TState): void {}
37 + visitPlace(id: InstructionId, place: Place, state: TState): void {}
38 +
39 + visitValue(id: InstructionId, value: ReactiveValue, state: TState): void {
40 + this.traverseValue(id, value, state);
41 + }
42 + traverseValue(id: InstructionId, value: ReactiveValue, state: TState): void {
43 + for (const place of eachReactiveValueOperand(value)) {
44 + this.visitPlace(id, place, state);
45 + }
46 + }
47 +
48 + visitInstruction(instruction: ReactiveInstruction, state: TState): void {
49 + this.traverseInstruction(instruction, state);
50 + }
51 + traverseInstruction(instruction: ReactiveInstruction, state: TState): void {
52 + this.visitID(instruction.id, state);
53 + if (instruction.lvalue !== null) {
54 + this.visitLValue(instruction.id, instruction.lvalue, state);
55 + }
56 + this.visitValue(instruction.id, instruction.value, state);
57 + }
58 +
59 + visitTerminal(stmt: ReactiveTerminalStatement, state: TState): void {
60 + this.traverseTerminal(stmt, state);
61 + }
62 + traverseTerminal(stmt: ReactiveTerminalStatement, state: TState): void {
63 + const { terminal } = stmt;
64 + if (terminal.id !== null) {
65 + this.visitID(terminal.id, state);
66 + }
67 + switch (terminal.kind) {
68 + case "break":
69 + case "continue": {
70 + break;
71 + }
72 + case "return": {
73 + if (terminal.value !== null) {
74 + this.visitValue(terminal.id, terminal.value, state);
75 + }
76 + break;
77 + }
78 + case "throw": {
79 + this.visitValue(terminal.id, terminal.value, state);
80 + break;
81 + }
82 + case "for": {
83 + this.visitValueBlock(terminal.init, state);
84 + this.visitValueBlock(terminal.test, state);
85 + this.visitValueBlock(terminal.update, state);
86 + this.visitBlock(terminal.loop, state);
87 + break;
88 + }
89 + case "while": {
90 + this.visitValueBlock(terminal.test, state);
91 + this.visitBlock(terminal.loop, state);
92 + break;
93 + }
94 + case "if": {
95 + this.visitValue(terminal.id, terminal.test, state);
96 + this.visitBlock(terminal.consequent, state);
97 + if (terminal.alternate !== null) {
98 + this.visitBlock(terminal.alternate, state);
99 + }
100 + break;
101 + }
102 + case "switch": {
103 + this.visitValue(terminal.id, terminal.test, state);
104 + for (const case_ of terminal.cases) {
105 + if (case_.test !== null) {
106 + this.visitPlace(terminal.id, case_.test, state);
107 + }
108 + if (case_.block !== undefined) {
109 + this.visitBlock(case_.block, state);
110 + }
111 + }
112 + break;
113 + }
114 + default: {
115 + assertExhaustive(
116 + terminal,
117 + `Unexpected terminal kind '${(terminal as any).kind}'`
118 + );
119 + }
120 + }
121 + }
122 +
123 + visitValueBlock(block: ReactiveValueBlock, state: TState): void {
124 + // NOTE: intentionally bypass calling visitBlock
125 + this.traverseBlock(block.instructions, state);
126 + if (block.last !== null) {
127 + this.visitPlace(block.last.id, block.last.value, state);
128 + }
129 + }
130 +
131 + visitScope(scope: ReactiveScopeBlock, state: TState): void {
132 + this.traverseScope(scope, state);
133 + }
134 + traverseScope(scope: ReactiveScopeBlock, state: TState): void {
135 + this.visitBlock(scope.instructions, state);
136 + }
137 +
138 + visitBlock(block: ReactiveBlock, state: TState): void {
139 + this.traverseBlock(block, state);
140 + }
141 + traverseBlock(block: ReactiveBlock, state: TState): void {
142 + for (const instr of block) {
143 + switch (instr.kind) {
144 + case "instruction": {
145 + this.visitInstruction(instr.instruction, state);
146 + break;
147 + }
148 + case "scope": {
149 + this.visitScope(instr, state);
150 + break;
151 + }
152 + case "terminal": {
153 + this.visitTerminal(instr, state);
154 + break;
155 + }
156 + default: {
157 + assertExhaustive(
158 + instr,
159 + `Unexpected instruction kind '${(instr as any).kind}'`
160 + );
161 + }
162 + }
163 + }
164 + }
165 +}
166 +
167 export function visitFunction(
168 fn: ReactiveFunction,
169 visitors: {