1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import invariant from "invariant";
9
+import {
10
+ BlockId,
11
+ HIRFunction,
12
+ Instruction,
13
+ InstructionId,
14
+ InstructionValue,
15
+ Place,
16
+ ReactiveBlock,
17
+ ReactiveFunction,
18
+ ReactiveStatement,
19
+ ReactiveTerminal,
20
+ ReactiveValueBlock,
21
+} from "../HIR/HIR";
22
+import { BlockTerminal, Visitor, visitTree } from "../HIR/HIRTreeVisitor";
23
+import { assertExhaustive } from "../Utils/utils";
24
+
25
+export function buildReactiveFunction(fn: HIRFunction): ReactiveFunction {
26
+ const builder = new ReactiveFunctionBuilder();
27
+ const body = visitTree(fn, builder);
28
+ invariant(body != null, "Expected a root block");
29
+ return {
30
+ loc: fn.loc,
31
+ id: fn.id,
32
+ params: fn.params,
33
+ generator: fn.generator,
34
+ async: fn.async,
35
+ body,
36
+ };
37
+}
38
+
39
+class Builder {
40
+ #instructions: ReactiveBlock = [];
41
+
42
+ append(item: ReactiveStatement, label: BlockId | undefined): void {
43
+ if (label !== undefined) {
44
+ invariant(item.kind === "terminal", "Only terminals may have a label");
45
+ item.label = label;
46
+ }
47
+ this.#instructions.push(item);
48
+ }
49
+
50
+ complete(): ReactiveBlock {
51
+ return this.#instructions;
52
+ }
53
+}
54
+
55
+class ReactiveFunctionBuilder
56
+ implements
57
+ Visitor<
58
+ Builder,
59
+ ReactiveBlock,
60
+ ReactiveValueBlock,
61
+ ReactiveValueBlock,
62
+ InstructionValue | ReactiveValueBlock,
63
+ ReactiveStatement,
64
+ { test: InstructionValue | null; block: ReactiveBlock }
65
+ >
66
+{
67
+ enterBlock(): Builder {
68
+ return new Builder();
69
+ }
70
+ appendBlock(
71
+ block: Builder,
72
+ item: ReactiveStatement,
73
+ label?: BlockId | undefined
74
+ ): void {
75
+ block.append(item, label);
76
+ }
77
+ leaveBlock(block: Builder): ReactiveBlock {
78
+ return block.complete();
79
+ }
80
+
81
+ enterValueBlock(block: Builder): ReactiveValueBlock {
82
+ return {
83
+ kind: "value-block",
84
+ instructions: [],
85
+ value: null,
86
+ };
87
+ }
88
+ appendValueBlock(block: ReactiveValueBlock, item: ReactiveStatement): void {
89
+ block.instructions.push(item);
90
+ }
91
+ leaveValueBlock(
92
+ block: ReactiveValueBlock,
93
+ value: InstructionValue | ReactiveValueBlock | null
94
+ ): InstructionValue | ReactiveValueBlock {
95
+ if (value !== null) {
96
+ invariant(
97
+ value.kind !== "value-block",
98
+ "Expected value block to end in a value"
99
+ );
100
+ block.value = value;
101
+ }
102
+ return block;
103
+ }
104
+
105
+ enterInitBlock(block: Builder): ReactiveValueBlock {
106
+ return this.enterValueBlock(block);
107
+ }
108
+ appendInitBlock(block: ReactiveValueBlock, item: ReactiveStatement): void {
109
+ this.appendValueBlock(block, item);
110
+ }
111
+ leaveInitBlock(block: ReactiveValueBlock): ReactiveValueBlock {
112
+ return block;
113
+ }
114
+
115
+ visitValue(
116
+ value: InstructionValue,
117
+ id: InstructionId
118
+ ): InstructionValue | ReactiveValueBlock {
119
+ return value;
120
+ }
121
+ visitInstruction(
122
+ instruction: Instruction,
123
+ value: InstructionValue | ReactiveValueBlock
124
+ ): ReactiveStatement {
125
+ return { kind: "instruction", instruction };
126
+ }
127
+ visitTerminalId(id: InstructionId): void {}
128
+ visitImplicitTerminal(): ReactiveStatement | null {
129
+ return null;
130
+ }
131
+ visitTerminal(
132
+ terminal: BlockTerminal<
133
+ ReactiveValueBlock,
134
+ InstructionValue | ReactiveValueBlock,
135
+ ReactiveBlock,
136
+ { test: InstructionValue | null; block: ReactiveBlock }
137
+ >
138
+ ): ReactiveStatement {
139
+ let result: ReactiveTerminal;
140
+ switch (terminal.kind) {
141
+ case "break": {
142
+ result = { kind: "break", label: terminal.label, id: terminal.id };
143
+ break;
144
+ }
145
+ case "continue": {
146
+ result = { kind: "continue", label: terminal.label, id: terminal.id };
147
+ break;
148
+ }
149
+ case "for": {
150
+ const { test, update } = terminal;
151
+ result = {
152
+ kind: "for",
153
+ init: terminal.init,
154
+ test: terminal.test as ReactiveValueBlock,
155
+ update: terminal.update as ReactiveValueBlock,
156
+ loop: terminal.loop,
157
+ id: terminal.id,
158
+ };
159
+ break;
160
+ }
161
+ case "if": {
162
+ result = {
163
+ kind: "if",
164
+ test: terminal.test as Place,
165
+ consequent: terminal.consequent,
166
+ alternate: terminal.alternate,
167
+ id: terminal.id,
168
+ };
169
+ break;
170
+ }
171
+ case "return": {
172
+ const value = terminal.value;
173
+ if (value !== null && value.kind !== "Identifier") {
174
+ invariant(false, "Expected return to be a Place");
175
+ }
176
+ result = { kind: "return", value, id: terminal.id };
177
+ break;
178
+ }
179
+ case "switch": {
180
+ result = {
181
+ kind: "switch",
182
+ test: terminal.test as Place,
183
+ cases: terminal.cases as Array<{
184
+ test: Place | null;
185
+ block: ReactiveBlock | void;
186
+ }>,
187
+ id: terminal.id,
188
+ };
189
+ break;
190
+ }
191
+ case "throw": {
192
+ result = {
193
+ kind: "throw",
194
+ value: terminal.value as Place,
195
+ id: terminal.id,
196
+ };
197
+ break;
198
+ }
199
+ case "while": {
200
+ result = {
201
+ kind: "while",
202
+ test: terminal.test as ReactiveValueBlock,
203
+ loop: terminal.loop,
204
+ id: terminal.id,
205
+ };
206
+ break;
207
+ }
208
+ default: {
209
+ assertExhaustive(
210
+ terminal,
211
+ `Unexpected terminal kind '${(terminal as any).kind}'`
212
+ );
213
+ }
214
+ }
215
+ return {
216
+ kind: "terminal",
217
+ terminal: result,
218
+ label: null,
219
+ };
220
+ }
221
+ visitCase(
222
+ test: InstructionValue | ReactiveValueBlock | null,
223
+ block: ReactiveBlock
224
+ ): { test: InstructionValue | null; block: ReactiveBlock } {
225
+ if (test !== null && test.kind !== "Identifier") {
226
+ invariant(false, "Expected a Place");
227
+ }
228
+ return { test, block };
229
+ }
230
+}