main
mjs 1,015 lines 33.8 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 /**
9 * Debug ReactiveFunction printer for the Rust port testing infrastructure.
10 *
11 * Custom printer that walks the ReactiveFunction tree structure and prints
12 * every field of every scope, instruction, terminal, and reactive value node.
13 *
14 * This does NOT delegate to printReactiveFunctionWithOutlined() — it is a
15 * standalone walker that produces a detailed, deterministic representation
16 * suitable for cross-compiler comparison between the TS and Rust implementations.
17 *
18 * @param {Function} _printReactiveFunctionWithOutlined - Unused (kept for API compat)
19 * @param {object} reactiveFunction - The ReactiveFunction to print
20 * @returns {string} The debug representation
21 */
22 export function debugPrintReactive(
23 _printReactiveFunctionWithOutlined,
24 reactiveFunction
25 ) {
26 const outlined = [];
27 const result = printReactiveFunction(reactiveFunction, 0, outlined);
28 const parts = [result];
29 for (let i = 0; i < outlined.length; i++) {
30 parts.push(printReactiveFunction(outlined[i], i + 1, outlined));
31 }
32 return parts.join("\n\n");
33 }
34
35 // ---------------------------------------------------------------------------
36 // Helpers
37 // ---------------------------------------------------------------------------
38
39 function ind(depth) {
40 return " ".repeat(depth);
41 }
42
43 function formatLoc(loc) {
44 if (loc == null || typeof loc === "symbol") {
45 return "generated";
46 }
47 return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`;
48 }
49
50 function formatEffect(effect) {
51 return String(effect);
52 }
53
54 function formatType(type) {
55 if (type == null) return "Type";
56 switch (type.kind) {
57 case "Type":
58 return "Type";
59 case "Primitive":
60 return "Primitive";
61 case "Object":
62 return type.shapeId != null ? `Object<${type.shapeId}>` : "Object";
63 case "Function": {
64 const ret = formatType(type.return);
65 const base =
66 type.shapeId != null ? `Function<${type.shapeId}>` : "Function";
67 return ret !== "Type" ? `${base}():${ret}` : base;
68 }
69 case "Poly":
70 return "Poly";
71 case "Phi": {
72 const ops = type.operands.map(formatType).join(", ");
73 return `Phi(${ops})`;
74 }
75 case "Property": {
76 const objType = formatType(type.objectType);
77 return `Property(${objType}.${type.objectName})`;
78 }
79 case "ObjectMethod":
80 return "ObjectMethod";
81 default:
82 return "Type";
83 }
84 }
85
86 function formatIdentifierName(name) {
87 if (name == null) return "null";
88 if (typeof name === "object" && name.value != null) {
89 return JSON.stringify(name.value);
90 }
91 return JSON.stringify(String(name));
92 }
93
94 function formatMutableRange(range) {
95 if (range == null) return "[0:0]";
96 return `[${range.start}:${range.end}]`;
97 }
98
99 function formatScopeId(scope) {
100 if (scope == null) return "null";
101 return `@${scope.id}`;
102 }
103
104 function formatDeclarationId(id) {
105 if (id == null) return "null";
106 return String(id);
107 }
108
109 // ---------------------------------------------------------------------------
110 // Place printing
111 // ---------------------------------------------------------------------------
112
113 function printPlaceInline(place, depth) {
114 const id = place.identifier;
115 return [
116 `${ind(depth)}Place {`,
117 `${ind(depth + 1)}identifier: $${id.id}`,
118 `${ind(depth + 1)}effect: ${formatEffect(place.effect)}`,
119 `${ind(depth + 1)}reactive: ${place.reactive}`,
120 `${ind(depth + 1)}loc: ${formatLoc(place.loc)}`,
121 `${ind(depth)}}`,
122 ].join("\n");
123 }
124
125 // ---------------------------------------------------------------------------
126 // Object property key
127 // ---------------------------------------------------------------------------
128
129 function printObjectPropertyKey(key) {
130 switch (key.kind) {
131 case "identifier":
132 return key.name;
133 case "string":
134 return `"${key.name}"`;
135 case "computed":
136 return `[$${key.name.identifier.id}]`;
137 case "number":
138 return String(key.name);
139 default:
140 return String(key.name ?? key.kind);
141 }
142 }
143
144 function printPlaceOrSpread(ps) {
145 if (ps.kind === "Identifier") return `$${ps.identifier.id}`;
146 if (ps.kind === "Spread") return `...$${ps.place.identifier.id}`;
147 return "<hole>";
148 }
149
150 function printPattern(pattern) {
151 switch (pattern.kind) {
152 case "ArrayPattern":
153 return `[${pattern.items.map((item) => (item.kind === "Hole" ? "<hole>" : item.kind === "Spread" ? `...$${item.place.identifier.id}` : `$${item.identifier.id}`)).join(", ")}]`;
154 case "ObjectPattern":
155 return `{${pattern.properties.map((p) => p.kind === "Spread" ? `...$${p.place.identifier.id}` : `${printObjectPropertyKey(p.key)}: $${p.place.identifier.id}`).join(", ")}}`;
156 default:
157 return String(pattern);
158 }
159 }
160
161 // ---------------------------------------------------------------------------
162 // InstructionValue printing (shared with HIR printer)
163 // ---------------------------------------------------------------------------
164
165 function printInstructionValueFields(value, depth) {
166 const d = depth;
167 const lines = [];
168 const kind = value.kind;
169
170 switch (kind) {
171 case "LoadLocal":
172 lines.push(`${ind(d)}LoadLocal {`);
173 lines.push(`${ind(d + 1)}place: $${value.place.identifier.id}`);
174 lines.push(`${ind(d)}}`);
175 break;
176 case "LoadContext":
177 lines.push(`${ind(d)}LoadContext {`);
178 lines.push(`${ind(d + 1)}place: $${value.place.identifier.id}`);
179 lines.push(`${ind(d)}}`);
180 break;
181 case "DeclareLocal":
182 lines.push(`${ind(d)}DeclareLocal {`);
183 lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
184 lines.push(
185 `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
186 );
187 lines.push(`${ind(d)}}`);
188 break;
189 case "DeclareContext":
190 lines.push(`${ind(d)}DeclareContext {`);
191 lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
192 lines.push(
193 `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
194 );
195 lines.push(`${ind(d)}}`);
196 break;
197 case "StoreLocal":
198 lines.push(`${ind(d)}StoreLocal {`);
199 lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
200 lines.push(
201 `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
202 );
203 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
204 lines.push(`${ind(d)}}`);
205 break;
206 case "StoreContext":
207 lines.push(`${ind(d)}StoreContext {`);
208 lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
209 lines.push(
210 `${ind(d + 1)}lvalue.place: $${value.lvalue.place.identifier.id}`
211 );
212 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
213 lines.push(`${ind(d)}}`);
214 break;
215 case "Destructure":
216 lines.push(`${ind(d)}Destructure {`);
217 lines.push(`${ind(d + 1)}lvalue.kind: ${value.lvalue.kind}`);
218 lines.push(
219 `${ind(d + 1)}lvalue.pattern: ${printPattern(value.lvalue.pattern)}`
220 );
221 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
222 lines.push(`${ind(d)}}`);
223 break;
224 case "Primitive":
225 lines.push(`${ind(d)}Primitive {`);
226 lines.push(
227 `${ind(d + 1)}value: ${value.value === undefined ? "undefined" : JSON.stringify(value.value)}`
228 );
229 lines.push(`${ind(d)}}`);
230 break;
231 case "JSXText":
232 lines.push(`${ind(d)}JSXText {`);
233 lines.push(`${ind(d + 1)}value: ${JSON.stringify(value.value)}`);
234 lines.push(`${ind(d)}}`);
235 break;
236 case "BinaryExpression":
237 lines.push(`${ind(d)}BinaryExpression {`);
238 lines.push(`${ind(d + 1)}operator: ${value.operator}`);
239 lines.push(`${ind(d + 1)}left: $${value.left.identifier.id}`);
240 lines.push(`${ind(d + 1)}right: $${value.right.identifier.id}`);
241 lines.push(`${ind(d)}}`);
242 break;
243 case "UnaryExpression":
244 lines.push(`${ind(d)}UnaryExpression {`);
245 lines.push(`${ind(d + 1)}operator: ${value.operator}`);
246 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
247 lines.push(`${ind(d)}}`);
248 break;
249 case "CallExpression":
250 lines.push(`${ind(d)}CallExpression {`);
251 lines.push(`${ind(d + 1)}callee: $${value.callee.identifier.id}`);
252 lines.push(
253 `${ind(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]`
254 );
255 lines.push(`${ind(d)}}`);
256 break;
257 case "MethodCall":
258 lines.push(`${ind(d)}MethodCall {`);
259 lines.push(`${ind(d + 1)}receiver: $${value.receiver.identifier.id}`);
260 lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`);
261 lines.push(
262 `${ind(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]`
263 );
264 lines.push(`${ind(d)}}`);
265 break;
266 case "NewExpression":
267 lines.push(`${ind(d)}NewExpression {`);
268 lines.push(`${ind(d + 1)}callee: $${value.callee.identifier.id}`);
269 lines.push(
270 `${ind(d + 1)}args: [${value.args.map(printPlaceOrSpread).join(", ")}]`
271 );
272 lines.push(`${ind(d)}}`);
273 break;
274 case "ObjectExpression":
275 lines.push(`${ind(d)}ObjectExpression {`);
276 if (value.properties != null) {
277 lines.push(`${ind(d + 1)}properties:`);
278 for (const prop of value.properties) {
279 if (prop.kind === "ObjectProperty") {
280 lines.push(
281 `${ind(d + 2)}${printObjectPropertyKey(prop.key)}: $${prop.place.identifier.id}`
282 );
283 } else {
284 lines.push(`${ind(d + 2)}...$${prop.place.identifier.id}`);
285 }
286 }
287 } else {
288 lines.push(`${ind(d + 1)}properties: null`);
289 }
290 lines.push(`${ind(d)}}`);
291 break;
292 case "ArrayExpression":
293 lines.push(`${ind(d)}ArrayExpression {`);
294 lines.push(
295 `${ind(d + 1)}elements: [${value.elements.map((e) => (e.kind === "Hole" ? "<hole>" : e.kind === "Spread" ? `...$${e.place.identifier.id}` : `$${e.identifier.id}`)).join(", ")}]`
296 );
297 lines.push(`${ind(d)}}`);
298 break;
299 case "PropertyLoad":
300 lines.push(`${ind(d)}PropertyLoad {`);
301 lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`);
302 lines.push(`${ind(d + 1)}property: ${value.property}`);
303 lines.push(`${ind(d)}}`);
304 break;
305 case "PropertyStore":
306 lines.push(`${ind(d)}PropertyStore {`);
307 lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`);
308 lines.push(`${ind(d + 1)}property: ${value.property}`);
309 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
310 lines.push(`${ind(d)}}`);
311 break;
312 case "PropertyDelete":
313 lines.push(`${ind(d)}PropertyDelete {`);
314 lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`);
315 lines.push(`${ind(d + 1)}property: ${value.property}`);
316 lines.push(`${ind(d)}}`);
317 break;
318 case "ComputedLoad":
319 lines.push(`${ind(d)}ComputedLoad {`);
320 lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`);
321 lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`);
322 lines.push(`${ind(d)}}`);
323 break;
324 case "ComputedStore":
325 lines.push(`${ind(d)}ComputedStore {`);
326 lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`);
327 lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`);
328 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
329 lines.push(`${ind(d)}}`);
330 break;
331 case "ComputedDelete":
332 lines.push(`${ind(d)}ComputedDelete {`);
333 lines.push(`${ind(d + 1)}object: $${value.object.identifier.id}`);
334 lines.push(`${ind(d + 1)}property: $${value.property.identifier.id}`);
335 lines.push(`${ind(d)}}`);
336 break;
337 case "LoadGlobal": {
338 lines.push(`${ind(d)}LoadGlobal {`);
339 const b = value.binding;
340 lines.push(`${ind(d + 1)}binding.kind: ${b.kind}`);
341 lines.push(`${ind(d + 1)}binding.name: ${b.name}`);
342 if (b.module != null) {
343 lines.push(`${ind(d + 1)}binding.module: ${b.module}`);
344 }
345 if (b.imported != null) {
346 lines.push(`${ind(d + 1)}binding.imported: ${b.imported}`);
347 }
348 lines.push(`${ind(d)}}`);
349 break;
350 }
351 case "StoreGlobal":
352 lines.push(`${ind(d)}StoreGlobal {`);
353 lines.push(`${ind(d + 1)}name: ${value.name}`);
354 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
355 lines.push(`${ind(d)}}`);
356 break;
357 case "TypeCastExpression":
358 lines.push(`${ind(d)}TypeCastExpression {`);
359 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
360 lines.push(`${ind(d + 1)}type: ${formatType(value.type)}`);
361 lines.push(`${ind(d)}}`);
362 break;
363 case "JsxExpression": {
364 lines.push(`${ind(d)}JsxExpression {`);
365 if (value.tag.kind === "Identifier") {
366 lines.push(`${ind(d + 1)}tag: $${value.tag.identifier.id}`);
367 } else {
368 lines.push(`${ind(d + 1)}tag: "${value.tag.name}"`);
369 }
370 lines.push(`${ind(d + 1)}props:`);
371 for (const attr of value.props) {
372 if (attr.kind === "JsxAttribute") {
373 lines.push(
374 `${ind(d + 2)}${attr.name}: $${attr.place.identifier.id}`
375 );
376 } else {
377 lines.push(`${ind(d + 2)}...$${attr.argument.identifier.id}`);
378 }
379 }
380 if (value.children != null) {
381 lines.push(
382 `${ind(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]`
383 );
384 } else {
385 lines.push(`${ind(d + 1)}children: null`);
386 }
387 lines.push(`${ind(d)}}`);
388 break;
389 }
390 case "JsxFragment":
391 lines.push(`${ind(d)}JsxFragment {`);
392 lines.push(
393 `${ind(d + 1)}children: [${value.children.map((c) => `$${c.identifier.id}`).join(", ")}]`
394 );
395 lines.push(`${ind(d)}}`);
396 break;
397 case "FunctionExpression":
398 case "ObjectMethod": {
399 const label =
400 kind === "FunctionExpression" ? "FunctionExpression" : "ObjectMethod";
401 lines.push(`${ind(d)}${label} {`);
402 if (kind === "FunctionExpression") {
403 lines.push(
404 `${ind(d + 1)}name: ${value.name != null ? JSON.stringify(value.name) : "null"}`
405 );
406 }
407 lines.push(
408 `${ind(d + 1)}loweredFunc.id: ${value.loweredFunc.func.id ?? "null"}`
409 );
410 const ctx = value.loweredFunc.func.context;
411 lines.push(
412 `${ind(d + 1)}context: [${ctx.map((c) => `$${c.identifier.id}`).join(", ")}]`
413 );
414 const ae = value.loweredFunc.func.aliasingEffects;
415 lines.push(
416 `${ind(d + 1)}aliasingEffects: ${ae != null ? `[${ae.length} effects]` : "null"}`
417 );
418 lines.push(`${ind(d)}}`);
419 break;
420 }
421 case "TaggedTemplateExpression":
422 lines.push(`${ind(d)}TaggedTemplateExpression {`);
423 lines.push(`${ind(d + 1)}tag: $${value.tag.identifier.id}`);
424 lines.push(
425 `${ind(d + 1)}value.raw: ${JSON.stringify(value.value.raw)}`
426 );
427 lines.push(`${ind(d)}}`);
428 break;
429 case "TemplateLiteral":
430 lines.push(`${ind(d)}TemplateLiteral {`);
431 lines.push(
432 `${ind(d + 1)}quasis: [${value.quasis.map((q) => JSON.stringify(q.raw)).join(", ")}]`
433 );
434 lines.push(
435 `${ind(d + 1)}subexprs: [${value.subexprs.map((s) => `$${s.identifier.id}`).join(", ")}]`
436 );
437 lines.push(`${ind(d)}}`);
438 break;
439 case "RegExpLiteral":
440 lines.push(`${ind(d)}RegExpLiteral {`);
441 lines.push(`${ind(d + 1)}pattern: ${value.pattern}`);
442 lines.push(`${ind(d + 1)}flags: ${value.flags}`);
443 lines.push(`${ind(d)}}`);
444 break;
445 case "MetaProperty":
446 lines.push(`${ind(d)}MetaProperty {`);
447 lines.push(`${ind(d + 1)}meta: ${value.meta}`);
448 lines.push(`${ind(d + 1)}property: ${value.property}`);
449 lines.push(`${ind(d)}}`);
450 break;
451 case "Await":
452 lines.push(`${ind(d)}Await {`);
453 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
454 lines.push(`${ind(d)}}`);
455 break;
456 case "GetIterator":
457 lines.push(`${ind(d)}GetIterator {`);
458 lines.push(
459 `${ind(d + 1)}collection: $${value.collection.identifier.id}`
460 );
461 lines.push(`${ind(d)}}`);
462 break;
463 case "IteratorNext":
464 lines.push(`${ind(d)}IteratorNext {`);
465 lines.push(`${ind(d + 1)}iterator: $${value.iterator.identifier.id}`);
466 lines.push(
467 `${ind(d + 1)}collection: $${value.collection.identifier.id}`
468 );
469 lines.push(`${ind(d)}}`);
470 break;
471 case "NextPropertyOf":
472 lines.push(`${ind(d)}NextPropertyOf {`);
473 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
474 lines.push(`${ind(d)}}`);
475 break;
476 case "PostfixUpdate":
477 lines.push(`${ind(d)}PostfixUpdate {`);
478 lines.push(`${ind(d + 1)}lvalue: $${value.lvalue.identifier.id}`);
479 lines.push(`${ind(d + 1)}operation: ${value.operation}`);
480 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
481 lines.push(`${ind(d)}}`);
482 break;
483 case "PrefixUpdate":
484 lines.push(`${ind(d)}PrefixUpdate {`);
485 lines.push(`${ind(d + 1)}lvalue: $${value.lvalue.identifier.id}`);
486 lines.push(`${ind(d + 1)}operation: ${value.operation}`);
487 lines.push(`${ind(d + 1)}value: $${value.value.identifier.id}`);
488 lines.push(`${ind(d)}}`);
489 break;
490 case "Debugger":
491 lines.push(`${ind(d)}Debugger {}`);
492 break;
493 case "StartMemoize":
494 lines.push(`${ind(d)}StartMemoize {`);
495 lines.push(`${ind(d + 1)}manualMemoId: ${value.manualMemoId}`);
496 lines.push(
497 `${ind(d + 1)}deps: ${value.deps != null ? `[${value.deps.length} deps]` : "null"}`
498 );
499 lines.push(`${ind(d)}}`);
500 break;
501 case "FinishMemoize":
502 lines.push(`${ind(d)}FinishMemoize {`);
503 lines.push(`${ind(d + 1)}manualMemoId: ${value.manualMemoId}`);
504 lines.push(`${ind(d + 1)}decl: $${value.decl.identifier.id}`);
505 lines.push(`${ind(d + 1)}pruned: ${value.pruned === true}`);
506 lines.push(`${ind(d)}}`);
507 break;
508 case "UnsupportedNode":
509 lines.push(`${ind(d)}UnsupportedNode {`);
510 lines.push(
511 `${ind(d + 1)}type: ${value.node != null ? value.node.type : "unknown"}`
512 );
513 lines.push(`${ind(d)}}`);
514 break;
515 default:
516 lines.push(`${ind(d)}${kind} {}`);
517 break;
518 }
519 return lines.join("\n");
520 }
521
522 // ---------------------------------------------------------------------------
523 // Reactive value printing (tree-structured values)
524 // ---------------------------------------------------------------------------
525
526 function printReactiveValue(value, depth, outlinedCollector) {
527 const d = depth;
528 const lines = [];
529
530 switch (value.kind) {
531 case "LogicalExpression":
532 lines.push(`${ind(d)}LogicalExpression {`);
533 lines.push(`${ind(d + 1)}operator: ${value.operator}`);
534 lines.push(`${ind(d + 1)}left:`);
535 lines.push(printReactiveValue(value.left, d + 2, outlinedCollector));
536 lines.push(`${ind(d + 1)}right:`);
537 lines.push(printReactiveValue(value.right, d + 2, outlinedCollector));
538 lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`);
539 lines.push(`${ind(d)}}`);
540 break;
541 case "ConditionalExpression":
542 lines.push(`${ind(d)}ConditionalExpression {`);
543 lines.push(`${ind(d + 1)}test:`);
544 lines.push(printReactiveValue(value.test, d + 2, outlinedCollector));
545 lines.push(`${ind(d + 1)}consequent:`);
546 lines.push(
547 printReactiveValue(value.consequent, d + 2, outlinedCollector)
548 );
549 lines.push(`${ind(d + 1)}alternate:`);
550 lines.push(
551 printReactiveValue(value.alternate, d + 2, outlinedCollector)
552 );
553 lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`);
554 lines.push(`${ind(d)}}`);
555 break;
556 case "SequenceExpression":
557 lines.push(`${ind(d)}SequenceExpression {`);
558 lines.push(`${ind(d + 1)}id: ${value.id}`);
559 lines.push(`${ind(d + 1)}instructions:`);
560 for (const instr of value.instructions) {
561 lines.push(printReactiveInstruction(instr, d + 2, outlinedCollector));
562 }
563 lines.push(`${ind(d + 1)}value:`);
564 lines.push(printReactiveValue(value.value, d + 2, outlinedCollector));
565 lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`);
566 lines.push(`${ind(d)}}`);
567 break;
568 case "OptionalExpression":
569 lines.push(`${ind(d)}OptionalExpression {`);
570 lines.push(`${ind(d + 1)}id: ${value.id}`);
571 lines.push(`${ind(d + 1)}optional: ${value.optional}`);
572 lines.push(`${ind(d + 1)}value:`);
573 lines.push(printReactiveValue(value.value, d + 2, outlinedCollector));
574 lines.push(`${ind(d + 1)}loc: ${formatLoc(value.loc)}`);
575 lines.push(`${ind(d)}}`);
576 break;
577 default:
578 // Plain InstructionValue
579 lines.push(printInstructionValueFields(value, d));
580 break;
581 }
582 return lines.join("\n");
583 }
584
585 // ---------------------------------------------------------------------------
586 // Reactive instruction printing
587 // ---------------------------------------------------------------------------
588
589 function printReactiveInstruction(instr, depth, outlinedCollector) {
590 const lines = [];
591 lines.push(`${ind(depth)}[${instr.id}] ReactiveInstruction {`);
592 const d = depth + 1;
593 lines.push(`${ind(d)}id: ${instr.id}`);
594 // lvalue
595 if (instr.lvalue != null) {
596 lines.push(`${ind(d)}lvalue:`);
597 lines.push(printPlaceInline(instr.lvalue, d + 1));
598 } else {
599 lines.push(`${ind(d)}lvalue: null`);
600 }
601 // value
602 lines.push(`${ind(d)}value:`);
603 lines.push(printReactiveValue(instr.value, d + 1, outlinedCollector));
604 // Collect outlined functions
605 collectOutlinedFromValue(instr.value, outlinedCollector);
606 // effects
607 if (instr.effects != null) {
608 lines.push(`${ind(d)}effects: [${instr.effects.length} effects]`);
609 } else {
610 lines.push(`${ind(d)}effects: null`);
611 }
612 lines.push(`${ind(d)}loc: ${formatLoc(instr.loc)}`);
613 lines.push(`${ind(depth)}}`);
614 return lines.join("\n");
615 }
616
617 // ---------------------------------------------------------------------------
618 // Reactive scope printing
619 // ---------------------------------------------------------------------------
620
621 function printReactiveScopeDetails(scope, depth) {
622 const lines = [];
623 const d = depth;
624 lines.push(`${ind(d)}scope @${scope.id} {`);
625 lines.push(`${ind(d + 1)}id: ${scope.id}`);
626 lines.push(
627 `${ind(d + 1)}range: ${formatMutableRange(scope.range)}`
628 );
629 // dependencies
630 const deps = [...scope.dependencies];
631 lines.push(`${ind(d + 1)}dependencies: [${deps.length}]`);
632 for (const dep of deps) {
633 const path = dep.path
634 .map((p) => `${p.optional ? "?." : "."}${p.property}`)
635 .join("");
636 lines.push(
637 `${ind(d + 2)}$${dep.identifier.id}${path} (reactive=${dep.reactive})`
638 );
639 }
640 // declarations
641 const decls = [...scope.declarations].sort((a, b) => a[0] - b[0]);
642 lines.push(`${ind(d + 1)}declarations: [${decls.length}]`);
643 for (const [id, decl] of decls) {
644 lines.push(`${ind(d + 2)}$${id}: $${decl.identifier.id}`);
645 }
646 // reassignments
647 const reassigns = [...scope.reassignments];
648 lines.push(`${ind(d + 1)}reassignments: [${reassigns.length}]`);
649 for (const ident of reassigns) {
650 lines.push(`${ind(d + 2)}$${ident.id}`);
651 }
652 // earlyReturnValue
653 if (scope.earlyReturnValue != null) {
654 lines.push(`${ind(d + 1)}earlyReturnValue:`);
655 lines.push(
656 `${ind(d + 2)}value: $${scope.earlyReturnValue.value.id}`
657 );
658 lines.push(
659 `${ind(d + 2)}label: bb${scope.earlyReturnValue.label}`
660 );
661 } else {
662 lines.push(`${ind(d + 1)}earlyReturnValue: null`);
663 }
664 // merged
665 const merged = [...scope.merged];
666 if (merged.length > 0) {
667 lines.push(
668 `${ind(d + 1)}merged: [${merged.map((m) => `@${m}`).join(", ")}]`
669 );
670 } else {
671 lines.push(`${ind(d + 1)}merged: []`);
672 }
673 lines.push(`${ind(d + 1)}loc: ${formatLoc(scope.loc)}`);
674 lines.push(`${ind(d)}}`);
675 return lines.join("\n");
676 }
677
678 // ---------------------------------------------------------------------------
679 // Reactive terminal printing
680 // ---------------------------------------------------------------------------
681
682 function printReactiveTerminal(terminal, depth, outlinedCollector) {
683 const lines = [];
684 const d = depth;
685 const kind = terminal.kind;
686
687 lines.push(`${ind(d)}${reactiveTerminalName(kind)} {`);
688 lines.push(`${ind(d + 1)}id: ${terminal.id}`);
689
690 switch (kind) {
691 case "break":
692 lines.push(`${ind(d + 1)}target: bb${terminal.target}`);
693 lines.push(`${ind(d + 1)}targetKind: ${terminal.targetKind}`);
694 break;
695 case "continue":
696 lines.push(`${ind(d + 1)}target: bb${terminal.target}`);
697 lines.push(`${ind(d + 1)}targetKind: ${terminal.targetKind}`);
698 break;
699 case "return":
700 lines.push(`${ind(d + 1)}value:`);
701 lines.push(printPlaceInline(terminal.value, d + 2));
702 break;
703 case "throw":
704 lines.push(`${ind(d + 1)}value:`);
705 lines.push(printPlaceInline(terminal.value, d + 2));
706 break;
707 case "if":
708 lines.push(`${ind(d + 1)}test:`);
709 lines.push(printPlaceInline(terminal.test, d + 2));
710 lines.push(`${ind(d + 1)}consequent:`);
711 lines.push(
712 printReactiveBlock(terminal.consequent, d + 2, outlinedCollector)
713 );
714 if (terminal.alternate != null) {
715 lines.push(`${ind(d + 1)}alternate:`);
716 lines.push(
717 printReactiveBlock(terminal.alternate, d + 2, outlinedCollector)
718 );
719 } else {
720 lines.push(`${ind(d + 1)}alternate: null`);
721 }
722 break;
723 case "switch":
724 lines.push(`${ind(d + 1)}test:`);
725 lines.push(printPlaceInline(terminal.test, d + 2));
726 lines.push(`${ind(d + 1)}cases:`);
727 for (const c of terminal.cases) {
728 if (c.test != null) {
729 lines.push(`${ind(d + 2)}case $${c.test.identifier.id}:`);
730 } else {
731 lines.push(`${ind(d + 2)}default:`);
732 }
733 if (c.block != null) {
734 lines.push(printReactiveBlock(c.block, d + 3, outlinedCollector));
735 } else {
736 lines.push(`${ind(d + 3)}(empty)`);
737 }
738 }
739 break;
740 case "do-while":
741 lines.push(`${ind(d + 1)}loop:`);
742 lines.push(
743 printReactiveBlock(terminal.loop, d + 2, outlinedCollector)
744 );
745 lines.push(`${ind(d + 1)}test:`);
746 lines.push(
747 printReactiveValue(terminal.test, d + 2, outlinedCollector)
748 );
749 break;
750 case "while":
751 lines.push(`${ind(d + 1)}test:`);
752 lines.push(
753 printReactiveValue(terminal.test, d + 2, outlinedCollector)
754 );
755 lines.push(`${ind(d + 1)}loop:`);
756 lines.push(
757 printReactiveBlock(terminal.loop, d + 2, outlinedCollector)
758 );
759 break;
760 case "for":
761 lines.push(`${ind(d + 1)}init:`);
762 lines.push(
763 printReactiveValue(terminal.init, d + 2, outlinedCollector)
764 );
765 lines.push(`${ind(d + 1)}test:`);
766 lines.push(
767 printReactiveValue(terminal.test, d + 2, outlinedCollector)
768 );
769 if (terminal.update != null) {
770 lines.push(`${ind(d + 1)}update:`);
771 lines.push(
772 printReactiveValue(terminal.update, d + 2, outlinedCollector)
773 );
774 } else {
775 lines.push(`${ind(d + 1)}update: null`);
776 }
777 lines.push(`${ind(d + 1)}loop:`);
778 lines.push(
779 printReactiveBlock(terminal.loop, d + 2, outlinedCollector)
780 );
781 break;
782 case "for-of":
783 lines.push(`${ind(d + 1)}init:`);
784 lines.push(
785 printReactiveValue(terminal.init, d + 2, outlinedCollector)
786 );
787 lines.push(`${ind(d + 1)}test:`);
788 lines.push(
789 printReactiveValue(terminal.test, d + 2, outlinedCollector)
790 );
791 lines.push(`${ind(d + 1)}loop:`);
792 lines.push(
793 printReactiveBlock(terminal.loop, d + 2, outlinedCollector)
794 );
795 break;
796 case "for-in":
797 lines.push(`${ind(d + 1)}init:`);
798 lines.push(
799 printReactiveValue(terminal.init, d + 2, outlinedCollector)
800 );
801 lines.push(`${ind(d + 1)}loop:`);
802 lines.push(
803 printReactiveBlock(terminal.loop, d + 2, outlinedCollector)
804 );
805 break;
806 case "label":
807 lines.push(`${ind(d + 1)}block:`);
808 lines.push(
809 printReactiveBlock(terminal.block, d + 2, outlinedCollector)
810 );
811 break;
812 case "try":
813 lines.push(`${ind(d + 1)}block:`);
814 lines.push(
815 printReactiveBlock(terminal.block, d + 2, outlinedCollector)
816 );
817 if (terminal.handlerBinding != null) {
818 lines.push(`${ind(d + 1)}handlerBinding:`);
819 lines.push(printPlaceInline(terminal.handlerBinding, d + 2));
820 } else {
821 lines.push(`${ind(d + 1)}handlerBinding: null`);
822 }
823 lines.push(`${ind(d + 1)}handler:`);
824 lines.push(
825 printReactiveBlock(terminal.handler, d + 2, outlinedCollector)
826 );
827 break;
828 default:
829 break;
830 }
831
832 lines.push(`${ind(d + 1)}loc: ${formatLoc(terminal.loc)}`);
833 lines.push(`${ind(d)}}`);
834 return lines.join("\n");
835 }
836
837 function reactiveTerminalName(kind) {
838 const names = {
839 break: "Break",
840 continue: "Continue",
841 return: "Return",
842 throw: "Throw",
843 if: "If",
844 switch: "Switch",
845 "do-while": "DoWhile",
846 while: "While",
847 for: "For",
848 "for-of": "ForOf",
849 "for-in": "ForIn",
850 label: "Label",
851 try: "Try",
852 };
853 return names[kind] ?? kind;
854 }
855
856 // ---------------------------------------------------------------------------
857 // Reactive block printing (array of ReactiveStatements)
858 // ---------------------------------------------------------------------------
859
860 function printReactiveBlock(block, depth, outlinedCollector) {
861 if (block == null || block.length === 0) {
862 return `${ind(depth)}(empty block)`;
863 }
864 const lines = [];
865 for (const stmt of block) {
866 lines.push(printReactiveStatement(stmt, depth, outlinedCollector));
867 }
868 return lines.join("\n");
869 }
870
871 function printReactiveStatement(stmt, depth, outlinedCollector) {
872 const lines = [];
873 const d = depth;
874
875 switch (stmt.kind) {
876 case "instruction":
877 lines.push(
878 printReactiveInstruction(stmt.instruction, d, outlinedCollector)
879 );
880 break;
881 case "scope":
882 lines.push(`${ind(d)}ReactiveScopeBlock {`);
883 lines.push(printReactiveScopeDetails(stmt.scope, d + 1));
884 lines.push(`${ind(d + 1)}instructions:`);
885 lines.push(
886 printReactiveBlock(stmt.instructions, d + 2, outlinedCollector)
887 );
888 lines.push(`${ind(d)}}`);
889 break;
890 case "pruned-scope":
891 lines.push(`${ind(d)}PrunedReactiveScopeBlock {`);
892 lines.push(printReactiveScopeDetails(stmt.scope, d + 1));
893 lines.push(`${ind(d + 1)}instructions:`);
894 lines.push(
895 printReactiveBlock(stmt.instructions, d + 2, outlinedCollector)
896 );
897 lines.push(`${ind(d)}}`);
898 break;
899 case "terminal":
900 if (stmt.label != null) {
901 lines.push(
902 `${ind(d)}label bb${stmt.label.id} (implicit=${stmt.label.implicit}):`
903 );
904 }
905 lines.push(
906 printReactiveTerminal(stmt.terminal, d, outlinedCollector)
907 );
908 break;
909 default:
910 lines.push(`${ind(d)}Unknown statement kind: ${stmt.kind}`);
911 break;
912 }
913
914 return lines.join("\n");
915 }
916
917 // ---------------------------------------------------------------------------
918 // Collect outlined functions from reactive values
919 // ---------------------------------------------------------------------------
920
921 function collectOutlinedFromValue(value, collector) {
922 if (value == null) return;
923 if (
924 value.kind === "FunctionExpression" ||
925 value.kind === "ObjectMethod"
926 ) {
927 // The loweredFunc in reactive context points to an HIRFunction
928 // which in turn has a reactive body after BuildReactiveFunction.
929 // But outlined functions are collected from env, so we just track
930 // them for the main function printer.
931 }
932 // For reactive compound values, recurse
933 if (value.kind === "SequenceExpression") {
934 for (const instr of value.instructions) {
935 collectOutlinedFromValue(instr.value, collector);
936 }
937 collectOutlinedFromValue(value.value, collector);
938 } else if (value.kind === "LogicalExpression") {
939 collectOutlinedFromValue(value.left, collector);
940 collectOutlinedFromValue(value.right, collector);
941 } else if (value.kind === "ConditionalExpression") {
942 collectOutlinedFromValue(value.test, collector);
943 collectOutlinedFromValue(value.consequent, collector);
944 collectOutlinedFromValue(value.alternate, collector);
945 } else if (value.kind === "OptionalExpression") {
946 collectOutlinedFromValue(value.value, collector);
947 }
948 }
949
950 // ---------------------------------------------------------------------------
951 // Main function printer
952 // ---------------------------------------------------------------------------
953
954 function printReactiveFunction(fn, functionIndex, outlinedCollector) {
955 const lines = [];
956 const d0 = 0;
957 const d1 = 1;
958 const d2 = 2;
959
960 lines.push(`${ind(d0)}ReactiveFunction #${functionIndex}:`);
961
962 // id
963 lines.push(
964 `${ind(d1)}id: ${fn.id != null ? JSON.stringify(fn.id) : "null"}`
965 );
966
967 // nameHint
968 lines.push(
969 `${ind(d1)}nameHint: ${fn.nameHint != null ? JSON.stringify(fn.nameHint) : "null"}`
970 );
971
972 // params
973 lines.push(`${ind(d1)}params:`);
974 for (let i = 0; i < fn.params.length; i++) {
975 const param = fn.params[i];
976 if (param.kind === "Identifier") {
977 lines.push(`${ind(d2)}[${i}]`);
978 lines.push(printPlaceInline(param, d2 + 1));
979 } else {
980 lines.push(`${ind(d2)}[${i}] ...`);
981 lines.push(printPlaceInline(param.place, d2 + 1));
982 }
983 }
984
985 // generator / async
986 lines.push(`${ind(d1)}generator: ${fn.generator}`);
987 lines.push(`${ind(d1)}async: ${fn.async}`);
988
989 // directives
990 if (fn.directives.length > 0) {
991 lines.push(
992 `${ind(d1)}directives: [${fn.directives.map((d) => JSON.stringify(d)).join(", ")}]`
993 );
994 } else {
995 lines.push(`${ind(d1)}directives: []`);
996 }
997
998 // loc
999 lines.push(`${ind(d1)}loc: ${formatLoc(fn.loc)}`);
1000
1001 // body
1002 lines.push("");
1003 lines.push(`${ind(d1)}body:`);
1004 lines.push(printReactiveBlock(fn.body, d2, outlinedCollector));
1005
1006 // Outlined functions from env
1007 if (fn.env != null && typeof fn.env.getOutlinedFunctions === "function") {
1008 const outlinedFns = fn.env.getOutlinedFunctions();
1009 for (const outlined of outlinedFns) {
1010 outlinedCollector.push(outlined.fn);
1011 }
1012 }
1013
1014 return lines.join("\n");
1015 }