main
ts 458 lines 12.2 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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 {CompilerError} from '../CompilerError';
9 import {
10 GeneratedSource,
11 PrunedReactiveScopeBlock,
12 ReactiveFunction,
13 ReactiveScope,
14 ReactiveScopeBlock,
15 ReactiveScopeDependency,
16 ReactiveStatement,
17 ReactiveTerminal,
18 ReactiveValue,
19 } from '../HIR/HIR';
20 import {
21 printFunction,
22 printIdentifier,
23 printInstructionValue,
24 printPlace,
25 printSourceLocation,
26 printType,
27 } from '../HIR/PrintHIR';
28 import {assertExhaustive} from '../Utils/utils';
29
30 export function printReactiveFunctionWithOutlined(
31 fn: ReactiveFunction,
32 ): string {
33 const writer = new Writer();
34 writeReactiveFunction(fn, writer);
35 for (const outlined of fn.env.getOutlinedFunctions()) {
36 writer.writeLine('\nfunction ' + printFunction(outlined.fn));
37 }
38 return writer.complete();
39 }
40
41 export function printReactiveFunction(fn: ReactiveFunction): string {
42 const writer = new Writer();
43 writeReactiveFunction(fn, writer);
44 return writer.complete();
45 }
46
47 function writeReactiveFunction(fn: ReactiveFunction, writer: Writer): void {
48 writer.writeLine(`function ${fn.id !== null ? fn.id : '<unknown>'}(`);
49 writer.indented(() => {
50 for (const param of fn.params) {
51 if (param.kind === 'Identifier') {
52 writer.writeLine(`${printPlace(param)},`);
53 } else {
54 writer.writeLine(`...${printPlace(param.place)},`);
55 }
56 }
57 });
58 writer.writeLine(') {');
59 writeReactiveInstructions(writer, fn.body);
60 writer.writeLine('}');
61 }
62
63 export function printReactiveScopeSummary(scope: ReactiveScope): string {
64 const items = [];
65 // If the scope has a return value it needs a label
66 items.push('scope');
67 items.push(`@${scope.id}`);
68 items.push(`[${scope.range.start}:${scope.range.end}]`);
69 items.push(
70 `dependencies=[${Array.from(scope.dependencies)
71 .map(dep => printDependency(dep))
72 .join(', ')}]`,
73 );
74 items.push(
75 `declarations=[${Array.from(scope.declarations)
76 .map(([, decl]) =>
77 printIdentifier({...decl.identifier, scope: decl.scope}),
78 )
79 .join(', ')}]`,
80 );
81 items.push(
82 `reassignments=[${Array.from(scope.reassignments).map(reassign =>
83 printIdentifier(reassign),
84 )}]`,
85 );
86 if (scope.earlyReturnValue !== null) {
87 items.push(
88 `earlyReturn={id: ${printIdentifier(
89 scope.earlyReturnValue.value,
90 )}, label: ${scope.earlyReturnValue.label}}}`,
91 );
92 }
93 return items.join(' ');
94 }
95
96 export function writeReactiveBlock(
97 writer: Writer,
98 block: ReactiveScopeBlock,
99 ): void {
100 writer.writeLine(`${printReactiveScopeSummary(block.scope)} {`);
101 writeReactiveInstructions(writer, block.instructions);
102 writer.writeLine('}');
103 }
104
105 export function writePrunedScope(
106 writer: Writer,
107 block: PrunedReactiveScopeBlock,
108 ): void {
109 writer.writeLine(`<pruned> ${printReactiveScopeSummary(block.scope)} {`);
110 writeReactiveInstructions(writer, block.instructions);
111 writer.writeLine('}');
112 }
113
114 export function printDependency(dependency: ReactiveScopeDependency): string {
115 const identifier =
116 printIdentifier(dependency.identifier) +
117 printType(dependency.identifier.type);
118 return `${identifier}${dependency.path.map(token => `${token.optional ? '?.' : '.'}${token.property}`).join('')}_${printSourceLocation(dependency.loc)}`;
119 }
120
121 export function printReactiveInstructions(
122 instructions: Array<ReactiveStatement>,
123 ): string {
124 const writer = new Writer();
125 writeReactiveInstructions(writer, instructions);
126 return writer.complete();
127 }
128
129 export function writeReactiveInstructions(
130 writer: Writer,
131 instructions: Array<ReactiveStatement>,
132 ): void {
133 writer.indented(() => {
134 for (const instr of instructions) {
135 writeReactiveInstruction(writer, instr);
136 }
137 });
138 }
139
140 function writeReactiveInstruction(
141 writer: Writer,
142 instr: ReactiveStatement,
143 ): void {
144 switch (instr.kind) {
145 case 'instruction': {
146 const {instruction} = instr;
147 const id = `[${instruction.id}]`;
148
149 if (instruction.lvalue !== null) {
150 writer.write(`${id} ${printPlace(instruction.lvalue)} = `);
151 writeReactiveValue(writer, instruction.value);
152 writer.newline();
153 } else {
154 writer.write(`${id} `);
155 writeReactiveValue(writer, instruction.value);
156 writer.newline();
157 }
158 break;
159 }
160 case 'scope': {
161 writeReactiveBlock(writer, instr);
162 break;
163 }
164 case 'pruned-scope': {
165 writePrunedScope(writer, instr);
166 break;
167 }
168 case 'terminal': {
169 if (instr.label !== null) {
170 writer.write(`bb${instr.label.id}: `);
171 }
172 writeTerminal(writer, instr.terminal);
173 break;
174 }
175 default: {
176 assertExhaustive(
177 instr,
178 `Unexpected terminal kind \`${(instr as any).kind}\``,
179 );
180 }
181 }
182 }
183
184 export function printReactiveValue(value: ReactiveValue): string {
185 const writer = new Writer();
186 writeReactiveValue(writer, value);
187 return writer.complete();
188 }
189
190 function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
191 switch (value.kind) {
192 case 'ConditionalExpression': {
193 writer.writeLine(`Ternary `);
194 writer.indented(() => {
195 writeReactiveValue(writer, value.test);
196 writer.writeLine(`? `);
197 writer.indented(() => {
198 writeReactiveValue(writer, value.consequent);
199 });
200 writer.writeLine(`: `);
201 writer.indented(() => {
202 writeReactiveValue(writer, value.alternate);
203 });
204 });
205 writer.newline();
206 break;
207 }
208 case 'LogicalExpression': {
209 writer.writeLine(`Logical`);
210 writer.indented(() => {
211 writeReactiveValue(writer, value.left);
212 writer.write(`${value.operator} `);
213 writeReactiveValue(writer, value.right);
214 });
215 writer.newline();
216 break;
217 }
218 case 'SequenceExpression': {
219 writer.writeLine(`Sequence`);
220 writer.indented(() => {
221 writer.indented(() => {
222 value.instructions.forEach(instr =>
223 writeReactiveInstruction(writer, {
224 kind: 'instruction',
225 instruction: instr,
226 }),
227 );
228 writer.write(`[${value.id}] `);
229 writeReactiveValue(writer, value.value);
230 });
231 });
232 writer.newline();
233 break;
234 }
235 case 'OptionalExpression': {
236 writer.append(`OptionalExpression optional=${value.optional}`);
237 writer.newline();
238 writer.indented(() => {
239 writeReactiveValue(writer, value.value);
240 });
241 writer.newline();
242 break;
243 }
244 default: {
245 const printed = printInstructionValue(value);
246 const lines = printed.split('\n');
247 if (lines.length === 1) {
248 writer.writeLine(printed);
249 } else {
250 writer.indented(() => {
251 for (const line of lines) {
252 writer.writeLine(line);
253 }
254 });
255 }
256 }
257 }
258 }
259
260 export function printReactiveTerminal(terminal: ReactiveTerminal): string {
261 const writer = new Writer();
262 writeTerminal(writer, terminal);
263 return writer.complete();
264 }
265
266 function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
267 switch (terminal.kind) {
268 case 'break': {
269 const id = terminal.id !== null ? `[${terminal.id}]` : [];
270 writer.writeLine(
271 `${id} break bb${terminal.target} (${terminal.targetKind})`,
272 );
273
274 break;
275 }
276 case 'continue': {
277 const id = `[${terminal.id}]`;
278 writer.writeLine(
279 `${id} continue bb${terminal.target} (${terminal.targetKind})`,
280 );
281 break;
282 }
283 case 'do-while': {
284 writer.writeLine(`[${terminal.id}] do-while {`);
285 writeReactiveInstructions(writer, terminal.loop);
286 writer.writeLine('} (');
287 writer.indented(() => {
288 writeReactiveValue(writer, terminal.test);
289 });
290 writer.writeLine(')');
291 break;
292 }
293 case 'while': {
294 writer.writeLine(`[${terminal.id}] while (`);
295 writer.indented(() => {
296 writeReactiveValue(writer, terminal.test);
297 });
298 writer.writeLine(') {');
299 writeReactiveInstructions(writer, terminal.loop);
300 writer.writeLine('}');
301 break;
302 }
303 case 'if': {
304 const {test, consequent, alternate} = terminal;
305 writer.writeLine(`[${terminal.id}] if (${printPlace(test)}) {`);
306 writeReactiveInstructions(writer, consequent);
307 if (alternate !== null) {
308 writer.writeLine('} else {');
309 writeReactiveInstructions(writer, alternate);
310 }
311 writer.writeLine('}');
312 break;
313 }
314 case 'switch': {
315 writer.writeLine(
316 `[${terminal.id}] switch (${printPlace(terminal.test)}) {`,
317 );
318 writer.indented(() => {
319 for (const case_ of terminal.cases) {
320 let prefix =
321 case_.test !== null ? `case ${printPlace(case_.test)}` : 'default';
322 writer.writeLine(`${prefix}: {`);
323 writer.indented(() => {
324 const block = case_.block;
325 CompilerError.invariant(block != null, {
326 reason: 'Expected case to have a block',
327 loc: case_.test?.loc ?? GeneratedSource,
328 });
329 writeReactiveInstructions(writer, block);
330 });
331 writer.writeLine('}');
332 }
333 });
334 writer.writeLine('}');
335 break;
336 }
337 case 'for': {
338 writer.writeLine(`[${terminal.id}] for (`);
339 writer.indented(() => {
340 writeReactiveValue(writer, terminal.init);
341 writer.writeLine(';');
342 writeReactiveValue(writer, terminal.test);
343 writer.writeLine(';');
344 if (terminal.update !== null) {
345 writeReactiveValue(writer, terminal.update);
346 }
347 });
348 writer.writeLine(') {');
349 writeReactiveInstructions(writer, terminal.loop);
350 writer.writeLine('}');
351 break;
352 }
353 case 'for-of': {
354 writer.writeLine(`[${terminal.id}] for-of (`);
355 writer.indented(() => {
356 writeReactiveValue(writer, terminal.init);
357 writer.writeLine(';');
358 writeReactiveValue(writer, terminal.test);
359 });
360 writer.writeLine(') {');
361 writeReactiveInstructions(writer, terminal.loop);
362 writer.writeLine('}');
363 break;
364 }
365 case 'for-in': {
366 writer.writeLine(`[${terminal.id}] for-in (`);
367 writer.indented(() => {
368 writeReactiveValue(writer, terminal.init);
369 });
370 writer.writeLine(') {');
371 writeReactiveInstructions(writer, terminal.loop);
372 writer.writeLine('}');
373 break;
374 }
375 case 'throw': {
376 writer.writeLine(`[${terminal.id}] throw ${printPlace(terminal.value)}`);
377 break;
378 }
379 case 'return': {
380 writer.writeLine(`[${terminal.id}] return ${printPlace(terminal.value)}`);
381 break;
382 }
383 case 'label': {
384 writer.writeLine('{');
385 writeReactiveInstructions(writer, terminal.block);
386 writer.writeLine('}');
387 break;
388 }
389 case 'try': {
390 writer.writeLine(`[${terminal.id}] try {`);
391 writeReactiveInstructions(writer, terminal.block);
392 writer.write(`} catch `);
393 if (terminal.handlerBinding !== null) {
394 writer.writeLine(`(${printPlace(terminal.handlerBinding)}) {`);
395 } else {
396 writer.writeLine(`{`);
397 }
398 writeReactiveInstructions(writer, terminal.handler);
399 writer.writeLine('}');
400 break;
401 }
402 default:
403 assertExhaustive(
404 terminal,
405 `Unhandled terminal kind \`${(terminal as any).kind}\``,
406 );
407 }
408 }
409
410 export class Writer {
411 #out: Array<string> = [];
412 #line: string;
413 #depth: number;
414
415 constructor({depth}: {depth: number} = {depth: 0}) {
416 this.#depth = Math.max(depth, 0);
417 this.#line = '';
418 }
419
420 complete(): string {
421 const line = this.#line.trimEnd();
422 if (line.length > 0) {
423 this.#out.push(line);
424 }
425 return this.#out.join('\n');
426 }
427
428 append(s: string): void {
429 this.write(s);
430 }
431
432 newline(): void {
433 const line = this.#line.trimEnd();
434 if (line.length > 0) {
435 this.#out.push(line);
436 }
437 this.#line = '';
438 }
439
440 write(s: string): void {
441 if (this.#line.length === 0 && this.#depth > 0) {
442 // indent before writing
443 this.#line = ' '.repeat(this.#depth);
444 }
445 this.#line += s;
446 }
447
448 writeLine(s: string): void {
449 this.write(s);
450 this.newline();
451 }
452
453 indented(f: () => void): void {
454 this.#depth++;
455 f();
456 this.#depth--;
457 }
458 }