main
ts 512 lines 14.4 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 {assertExhaustive} from '../Utils/utils';
9 import type {
10 ReactiveBlock,
11 ReactiveFunction,
12 ReactiveInstruction,
13 ReactiveStatement,
14 ReactiveTerminal,
15 ReactiveValue,
16 ReactiveScopeBlock,
17 PrunedReactiveScopeBlock,
18 } from './HIR';
19 import {DebugPrinter} from './DebugPrintHIR';
20
21 export function printDebugReactiveFunction(fn: ReactiveFunction): string {
22 const printer = new ReactiveDebugPrinter();
23 printer.formatReactiveFunction(fn);
24
25 const outlined = fn.env.getOutlinedFunctions();
26 for (let i = 0; i < outlined.length; i++) {
27 const outlinedFn = outlined[i].fn;
28 /*
29 * Only print outlined functions that have been converted to reactive form
30 * (have an array body, not a HIR body with blocks)
31 */
32 if (Array.isArray(outlinedFn.body)) {
33 printer.line('');
34 printer.formatReactiveFunction(outlinedFn as unknown as ReactiveFunction);
35 }
36 }
37
38 printer.line('');
39 printer.line('Environment:');
40 printer.indent();
41 const errors = fn.env.aggregateErrors();
42 printer.formatErrors(errors);
43 printer.dedent();
44
45 return printer.toString();
46 }
47
48 class ReactiveDebugPrinter extends DebugPrinter {
49 formatReactiveFunction(fn: ReactiveFunction): void {
50 this.indent();
51 this.line(`id: ${fn.id !== null ? `"${fn.id}"` : 'null'}`);
52 this.line(
53 `name_hint: ${fn.nameHint !== null ? `"${fn.nameHint}"` : 'null'}`,
54 );
55 this.line(`generator: ${fn.generator}`);
56 this.line(`is_async: ${fn.async}`);
57 this.line(`loc: ${this.formatLoc(fn.loc)}`);
58
59 this.line('params:');
60 this.indent();
61 fn.params.forEach((param, i) => {
62 if (param.kind === 'Identifier') {
63 this.formatPlaceField(`[${i}]`, param);
64 } else {
65 this.line(`[${i}] Spread:`);
66 this.indent();
67 this.formatPlaceField('place', param.place);
68 this.dedent();
69 }
70 });
71 this.dedent();
72
73 this.line('directives:');
74 this.indent();
75 fn.directives.forEach((d, i) => {
76 this.line(`[${i}] "${d}"`);
77 });
78 this.dedent();
79
80 this.line('');
81 this.line('Body:');
82 this.indent();
83 this.formatReactiveBlock(fn.body);
84 this.dedent();
85 this.dedent();
86 }
87
88 formatReactiveBlock(block: ReactiveBlock): void {
89 for (const stmt of block) {
90 this.formatReactiveStatement(stmt);
91 }
92 }
93
94 formatReactiveStatement(stmt: ReactiveStatement): void {
95 switch (stmt.kind) {
96 case 'instruction': {
97 this.formatReactiveInstruction(stmt.instruction);
98 break;
99 }
100 case 'scope': {
101 this.formatReactiveScopeBlock(stmt);
102 break;
103 }
104 case 'pruned-scope': {
105 this.formatPrunedReactiveScopeBlock(stmt);
106 break;
107 }
108 case 'terminal': {
109 this.line('ReactiveTerminalStatement {');
110 this.indent();
111 if (stmt.label !== null) {
112 this.line(
113 `label: { id: bb${stmt.label.id}, implicit: ${stmt.label.implicit} }`,
114 );
115 } else {
116 this.line('label: null');
117 }
118 this.line('terminal:');
119 this.indent();
120 this.formatReactiveTerminal(stmt.terminal);
121 this.dedent();
122 this.dedent();
123 this.line('}');
124 break;
125 }
126 default: {
127 assertExhaustive(
128 stmt,
129 `Unexpected reactive statement kind \`${(stmt as any).kind}\``,
130 );
131 }
132 }
133 }
134
135 formatReactiveInstruction(instr: ReactiveInstruction): void {
136 this.line('ReactiveInstruction {');
137 this.indent();
138 this.line(`id: ${instr.id}`);
139 if (instr.lvalue !== null) {
140 this.formatPlaceField('lvalue', instr.lvalue);
141 } else {
142 this.line('lvalue: null');
143 }
144 this.line('value:');
145 this.indent();
146 this.formatReactiveValue(instr.value);
147 this.dedent();
148 if (instr.effects != null) {
149 this.line('effects:');
150 this.indent();
151 instr.effects.forEach((effect, i) => {
152 this.line(`[${i}] ${this.formatAliasingEffect(effect)}`);
153 });
154 this.dedent();
155 } else {
156 this.line('effects: null');
157 }
158 this.line(`loc: ${this.formatLoc(instr.loc)}`);
159 this.dedent();
160 this.line('}');
161 }
162
163 formatReactiveScopeBlock(block: ReactiveScopeBlock): void {
164 this.line('ReactiveScopeBlock {');
165 this.indent();
166 this.formatScopeField('scope', block.scope);
167 this.line('instructions:');
168 this.indent();
169 this.formatReactiveBlock(block.instructions);
170 this.dedent();
171 this.dedent();
172 this.line('}');
173 }
174
175 formatPrunedReactiveScopeBlock(block: PrunedReactiveScopeBlock): void {
176 this.line('PrunedReactiveScopeBlock {');
177 this.indent();
178 this.formatScopeField('scope', block.scope);
179 this.line('instructions:');
180 this.indent();
181 this.formatReactiveBlock(block.instructions);
182 this.dedent();
183 this.dedent();
184 this.line('}');
185 }
186
187 formatReactiveValue(value: ReactiveValue): void {
188 switch (value.kind) {
189 case 'LogicalExpression': {
190 this.line('LogicalExpression {');
191 this.indent();
192 this.line(`operator: "${value.operator}"`);
193 this.line('left:');
194 this.indent();
195 this.formatReactiveValue(value.left);
196 this.dedent();
197 this.line('right:');
198 this.indent();
199 this.formatReactiveValue(value.right);
200 this.dedent();
201 this.line(`loc: ${this.formatLoc(value.loc)}`);
202 this.dedent();
203 this.line('}');
204 break;
205 }
206 case 'ConditionalExpression': {
207 this.line('ConditionalExpression {');
208 this.indent();
209 this.line('test:');
210 this.indent();
211 this.formatReactiveValue(value.test);
212 this.dedent();
213 this.line('consequent:');
214 this.indent();
215 this.formatReactiveValue(value.consequent);
216 this.dedent();
217 this.line('alternate:');
218 this.indent();
219 this.formatReactiveValue(value.alternate);
220 this.dedent();
221 this.line(`loc: ${this.formatLoc(value.loc)}`);
222 this.dedent();
223 this.line('}');
224 break;
225 }
226 case 'SequenceExpression': {
227 this.line('SequenceExpression {');
228 this.indent();
229 this.line('instructions:');
230 this.indent();
231 value.instructions.forEach((instr, i) => {
232 this.line(`[${i}]:`);
233 this.indent();
234 this.formatReactiveInstruction(instr);
235 this.dedent();
236 });
237 this.dedent();
238 this.line(`id: ${value.id}`);
239 this.line('value:');
240 this.indent();
241 this.formatReactiveValue(value.value);
242 this.dedent();
243 this.line(`loc: ${this.formatLoc(value.loc)}`);
244 this.dedent();
245 this.line('}');
246 break;
247 }
248 case 'OptionalExpression': {
249 this.line('OptionalExpression {');
250 this.indent();
251 this.line(`id: ${value.id}`);
252 this.line('value:');
253 this.indent();
254 this.formatReactiveValue(value.value);
255 this.dedent();
256 this.line(`optional: ${value.optional}`);
257 this.line(`loc: ${this.formatLoc(value.loc)}`);
258 this.dedent();
259 this.line('}');
260 break;
261 }
262 default: {
263 // Base InstructionValue kinds - delegate to existing formatter
264 this.formatInstructionValue(value);
265 break;
266 }
267 }
268 }
269
270 formatReactiveTerminal(terminal: ReactiveTerminal): void {
271 switch (terminal.kind) {
272 case 'break': {
273 this.line('Break {');
274 this.indent();
275 this.line(`target: bb${terminal.target}`);
276 this.line(`id: ${terminal.id}`);
277 this.line(`targetKind: "${terminal.targetKind}"`);
278 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
279 this.dedent();
280 this.line('}');
281 break;
282 }
283 case 'continue': {
284 this.line('Continue {');
285 this.indent();
286 this.line(`target: bb${terminal.target}`);
287 this.line(`id: ${terminal.id}`);
288 this.line(`targetKind: "${terminal.targetKind}"`);
289 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
290 this.dedent();
291 this.line('}');
292 break;
293 }
294 case 'return': {
295 this.line('Return {');
296 this.indent();
297 this.formatPlaceField('value', terminal.value);
298 this.line(`id: ${terminal.id}`);
299 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
300 this.dedent();
301 this.line('}');
302 break;
303 }
304 case 'throw': {
305 this.line('Throw {');
306 this.indent();
307 this.formatPlaceField('value', terminal.value);
308 this.line(`id: ${terminal.id}`);
309 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
310 this.dedent();
311 this.line('}');
312 break;
313 }
314 case 'switch': {
315 this.line('Switch {');
316 this.indent();
317 this.formatPlaceField('test', terminal.test);
318 this.line('cases:');
319 this.indent();
320 terminal.cases.forEach((case_, i) => {
321 this.line(`[${i}] {`);
322 this.indent();
323 if (case_.test !== null) {
324 this.formatPlaceField('test', case_.test);
325 } else {
326 this.line('test: null');
327 }
328 if (case_.block !== undefined) {
329 this.line('block:');
330 this.indent();
331 this.formatReactiveBlock(case_.block);
332 this.dedent();
333 } else {
334 this.line('block: undefined');
335 }
336 this.dedent();
337 this.line('}');
338 });
339 this.dedent();
340 this.line(`id: ${terminal.id}`);
341 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
342 this.dedent();
343 this.line('}');
344 break;
345 }
346 case 'do-while': {
347 this.line('DoWhile {');
348 this.indent();
349 this.line('loop:');
350 this.indent();
351 this.formatReactiveBlock(terminal.loop);
352 this.dedent();
353 this.line('test:');
354 this.indent();
355 this.formatReactiveValue(terminal.test);
356 this.dedent();
357 this.line(`id: ${terminal.id}`);
358 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
359 this.dedent();
360 this.line('}');
361 break;
362 }
363 case 'while': {
364 this.line('While {');
365 this.indent();
366 this.line('test:');
367 this.indent();
368 this.formatReactiveValue(terminal.test);
369 this.dedent();
370 this.line('loop:');
371 this.indent();
372 this.formatReactiveBlock(terminal.loop);
373 this.dedent();
374 this.line(`id: ${terminal.id}`);
375 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
376 this.dedent();
377 this.line('}');
378 break;
379 }
380 case 'for': {
381 this.line('For {');
382 this.indent();
383 this.line('init:');
384 this.indent();
385 this.formatReactiveValue(terminal.init);
386 this.dedent();
387 this.line('test:');
388 this.indent();
389 this.formatReactiveValue(terminal.test);
390 this.dedent();
391 if (terminal.update !== null) {
392 this.line('update:');
393 this.indent();
394 this.formatReactiveValue(terminal.update);
395 this.dedent();
396 } else {
397 this.line('update: null');
398 }
399 this.line('loop:');
400 this.indent();
401 this.formatReactiveBlock(terminal.loop);
402 this.dedent();
403 this.line(`id: ${terminal.id}`);
404 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
405 this.dedent();
406 this.line('}');
407 break;
408 }
409 case 'for-of': {
410 this.line('ForOf {');
411 this.indent();
412 this.line('init:');
413 this.indent();
414 this.formatReactiveValue(terminal.init);
415 this.dedent();
416 this.line('test:');
417 this.indent();
418 this.formatReactiveValue(terminal.test);
419 this.dedent();
420 this.line('loop:');
421 this.indent();
422 this.formatReactiveBlock(terminal.loop);
423 this.dedent();
424 this.line(`id: ${terminal.id}`);
425 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
426 this.dedent();
427 this.line('}');
428 break;
429 }
430 case 'for-in': {
431 this.line('ForIn {');
432 this.indent();
433 this.line('init:');
434 this.indent();
435 this.formatReactiveValue(terminal.init);
436 this.dedent();
437 this.line('loop:');
438 this.indent();
439 this.formatReactiveBlock(terminal.loop);
440 this.dedent();
441 this.line(`id: ${terminal.id}`);
442 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
443 this.dedent();
444 this.line('}');
445 break;
446 }
447 case 'if': {
448 this.line('If {');
449 this.indent();
450 this.formatPlaceField('test', terminal.test);
451 this.line('consequent:');
452 this.indent();
453 this.formatReactiveBlock(terminal.consequent);
454 this.dedent();
455 if (terminal.alternate !== null) {
456 this.line('alternate:');
457 this.indent();
458 this.formatReactiveBlock(terminal.alternate);
459 this.dedent();
460 } else {
461 this.line('alternate: null');
462 }
463 this.line(`id: ${terminal.id}`);
464 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
465 this.dedent();
466 this.line('}');
467 break;
468 }
469 case 'label': {
470 this.line('Label {');
471 this.indent();
472 this.line('block:');
473 this.indent();
474 this.formatReactiveBlock(terminal.block);
475 this.dedent();
476 this.line(`id: ${terminal.id}`);
477 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
478 this.dedent();
479 this.line('}');
480 break;
481 }
482 case 'try': {
483 this.line('Try {');
484 this.indent();
485 this.line('block:');
486 this.indent();
487 this.formatReactiveBlock(terminal.block);
488 this.dedent();
489 if (terminal.handlerBinding !== null) {
490 this.formatPlaceField('handlerBinding', terminal.handlerBinding);
491 } else {
492 this.line('handlerBinding: null');
493 }
494 this.line('handler:');
495 this.indent();
496 this.formatReactiveBlock(terminal.handler);
497 this.dedent();
498 this.line(`id: ${terminal.id}`);
499 this.line(`loc: ${this.formatLoc(terminal.loc)}`);
500 this.dedent();
501 this.line('}');
502 break;
503 }
504 default: {
505 assertExhaustive(
506 terminal,
507 `Unexpected reactive terminal kind \`${(terminal as any).kind}\``,
508 );
509 }
510 }
511 }
512 }