main
ts 281 lines 6.97 KB
Raw
1 import {
2 Place,
3 ReactiveScopeDependency,
4 Identifier,
5 makeInstructionId,
6 InstructionKind,
7 GeneratedSource,
8 BlockId,
9 makeTemporaryIdentifier,
10 Effect,
11 GotoVariant,
12 HIR,
13 } from './HIR';
14 import {CompilerError} from '../CompilerError';
15 import {Environment} from './Environment';
16 import HIRBuilder from './HIRBuilder';
17 import {lowerValueToTemporary} from './BuildHIR';
18
19 type DependencyInstructions = {
20 place: Place;
21 value: HIR;
22 exitBlockId: BlockId;
23 };
24
25 export function buildDependencyInstructions(
26 dep: ReactiveScopeDependency,
27 env: Environment,
28 ): DependencyInstructions {
29 const builder = new HIRBuilder(env, {
30 entryBlockKind: 'value',
31 });
32 let dependencyValue: Identifier;
33 if (dep.path.every(path => !path.optional)) {
34 dependencyValue = writeNonOptionalDependency(dep, env, builder);
35 } else {
36 dependencyValue = writeOptionalDependency(dep, builder, null);
37 }
38
39 const exitBlockId = builder.terminate(
40 {
41 kind: 'unsupported',
42 loc: GeneratedSource,
43 id: makeInstructionId(0),
44 },
45 null,
46 );
47 return {
48 place: {
49 kind: 'Identifier',
50 identifier: dependencyValue,
51 effect: Effect.Freeze,
52 reactive: dep.reactive,
53 loc: GeneratedSource,
54 },
55 value: builder.build(),
56 exitBlockId,
57 };
58 }
59
60 /**
61 * Write instructions for a simple dependency (without optional chains)
62 */
63 function writeNonOptionalDependency(
64 dep: ReactiveScopeDependency,
65 env: Environment,
66 builder: HIRBuilder,
67 ): Identifier {
68 const loc = dep.identifier.loc;
69 let curr: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc);
70 builder.push({
71 lvalue: {
72 identifier: curr,
73 kind: 'Identifier',
74 effect: Effect.Mutate,
75 reactive: dep.reactive,
76 loc,
77 },
78 value: {
79 kind: 'LoadLocal',
80 place: {
81 identifier: dep.identifier,
82 kind: 'Identifier',
83 effect: Effect.Freeze,
84 reactive: dep.reactive,
85 loc,
86 },
87 loc,
88 },
89 id: makeInstructionId(1),
90 loc: loc,
91 effects: null,
92 });
93
94 /**
95 * Iteratively build up dependency instructions by reading from the last written
96 * instruction.
97 */
98 for (const path of dep.path) {
99 const next = makeTemporaryIdentifier(env.nextIdentifierId, loc);
100 builder.push({
101 lvalue: {
102 identifier: next,
103 kind: 'Identifier',
104 effect: Effect.Mutate,
105 reactive: dep.reactive,
106 loc,
107 },
108 value: {
109 kind: 'PropertyLoad',
110 object: {
111 identifier: curr,
112 kind: 'Identifier',
113 effect: Effect.Freeze,
114 reactive: dep.reactive,
115 loc,
116 },
117 property: path.property,
118 loc,
119 },
120 id: makeInstructionId(1),
121 loc: loc,
122 effects: null,
123 });
124 curr = next;
125 }
126 return curr;
127 }
128
129 /**
130 * Write a dependency into optional blocks.
131 *
132 * e.g. `a.b?.c.d` is written to an optional block that tests `a.b` and
133 * conditionally evaluates `c.d`.
134 */
135 function writeOptionalDependency(
136 dep: ReactiveScopeDependency,
137 builder: HIRBuilder,
138 parentAlternate: BlockId | null,
139 ): Identifier {
140 const env = builder.environment;
141 /**
142 * Reserve an identifier which will be used to store the result of this
143 * dependency.
144 */
145 const dependencyValue: Place = {
146 kind: 'Identifier',
147 identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource),
148 effect: Effect.Mutate,
149 reactive: dep.reactive,
150 loc: GeneratedSource,
151 };
152
153 /**
154 * Reserve a block which is the fallthrough (and transitive successor) of this
155 * optional chain.
156 */
157 const continuationBlock = builder.reserve(builder.currentBlockKind());
158 let alternate;
159 if (parentAlternate != null) {
160 alternate = parentAlternate;
161 } else {
162 /**
163 * If an outermost alternate block has not been reserved, write one
164 *
165 * $N = Primitive undefined
166 * $M = StoreLocal $OptionalResult = $N
167 * goto fallthrough
168 */
169 alternate = builder.enter('value', () => {
170 const temp = lowerValueToTemporary(builder, {
171 kind: 'Primitive',
172 value: undefined,
173 loc: GeneratedSource,
174 });
175 lowerValueToTemporary(builder, {
176 kind: 'StoreLocal',
177 lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}},
178 value: {...temp},
179 type: null,
180 loc: GeneratedSource,
181 });
182 return {
183 kind: 'goto',
184 variant: GotoVariant.Break,
185 block: continuationBlock.id,
186 id: makeInstructionId(0),
187 loc: GeneratedSource,
188 };
189 });
190 }
191
192 // Reserve the consequent block, which is the successor of the test block
193 const consequent = builder.reserve('value');
194
195 let testIdentifier: Identifier | null = null;
196 const testBlock = builder.enter('value', () => {
197 const testDependency = {
198 ...dep,
199 path: dep.path.slice(0, dep.path.length - 1),
200 };
201 const firstOptional = dep.path.findIndex(path => path.optional);
202 CompilerError.invariant(firstOptional !== -1, {
203 reason:
204 '[ScopeDependencyUtils] Internal invariant broken: expected optional path',
205 loc: dep.identifier.loc,
206 });
207 if (firstOptional === dep.path.length - 1) {
208 // Base case: the test block is simple
209 testIdentifier = writeNonOptionalDependency(testDependency, env, builder);
210 } else {
211 // Otherwise, the test block is a nested optional chain
212 testIdentifier = writeOptionalDependency(
213 testDependency,
214 builder,
215 alternate,
216 );
217 }
218
219 return {
220 kind: 'branch',
221 test: {
222 identifier: testIdentifier,
223 effect: Effect.Freeze,
224 kind: 'Identifier',
225 loc: GeneratedSource,
226 reactive: dep.reactive,
227 },
228 consequent: consequent.id,
229 alternate,
230 id: makeInstructionId(0),
231 loc: GeneratedSource,
232 fallthrough: continuationBlock.id,
233 };
234 });
235
236 builder.enterReserved(consequent, () => {
237 CompilerError.invariant(testIdentifier !== null, {
238 reason: 'Satisfy type checker',
239 loc: GeneratedSource,
240 });
241
242 lowerValueToTemporary(builder, {
243 kind: 'StoreLocal',
244 lvalue: {kind: InstructionKind.Const, place: {...dependencyValue}},
245 value: lowerValueToTemporary(builder, {
246 kind: 'PropertyLoad',
247 object: {
248 identifier: testIdentifier,
249 kind: 'Identifier',
250 effect: Effect.Freeze,
251 reactive: dep.reactive,
252 loc: GeneratedSource,
253 },
254 property: dep.path.at(-1)!.property,
255 loc: GeneratedSource,
256 }),
257 type: null,
258 loc: GeneratedSource,
259 });
260 return {
261 kind: 'goto',
262 variant: GotoVariant.Break,
263 block: continuationBlock.id,
264 id: makeInstructionId(0),
265 loc: GeneratedSource,
266 };
267 });
268 builder.terminateWithContinuation(
269 {
270 kind: 'optional',
271 optional: dep.path.at(-1)!.optional,
272 test: testBlock,
273 fallthrough: continuationBlock.id,
274 id: makeInstructionId(0),
275 loc: GeneratedSource,
276 },
277 continuationBlock,
278 );
279
280 return dependencyValue.identifier;
281 }