@kernlang/core 3.3.9 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/capability-matrix.d.ts +15 -0
  2. package/dist/capability-matrix.js +245 -0
  3. package/dist/capability-matrix.js.map +1 -0
  4. package/dist/codegen/body-ts.d.ts +68 -0
  5. package/dist/codegen/body-ts.js +214 -0
  6. package/dist/codegen/body-ts.js.map +1 -0
  7. package/dist/codegen/data-layer.d.ts +1 -1
  8. package/dist/codegen/data-layer.js +59 -23
  9. package/dist/codegen/data-layer.js.map +1 -1
  10. package/dist/codegen/events.js +1 -1
  11. package/dist/codegen/events.js.map +1 -1
  12. package/dist/codegen/functions.js +48 -7
  13. package/dist/codegen/functions.js.map +1 -1
  14. package/dist/codegen/ground-layer.js +10 -6
  15. package/dist/codegen/ground-layer.js.map +1 -1
  16. package/dist/codegen/helpers.d.ts +3 -1
  17. package/dist/codegen/helpers.js +5 -1
  18. package/dist/codegen/helpers.js.map +1 -1
  19. package/dist/codegen/kern-stdlib.d.ts +63 -0
  20. package/dist/codegen/kern-stdlib.js +160 -0
  21. package/dist/codegen/kern-stdlib.js.map +1 -0
  22. package/dist/codegen/machines.js +4 -3
  23. package/dist/codegen/machines.js.map +1 -1
  24. package/dist/codegen/modules.d.ts +1 -0
  25. package/dist/codegen/modules.js +52 -1
  26. package/dist/codegen/modules.js.map +1 -1
  27. package/dist/codegen/screens.js +31 -9
  28. package/dist/codegen/screens.js.map +1 -1
  29. package/dist/codegen/stdlib-preamble.d.ts +58 -0
  30. package/dist/codegen/stdlib-preamble.js +271 -0
  31. package/dist/codegen/stdlib-preamble.js.map +1 -0
  32. package/dist/codegen/type-system.d.ts +113 -1
  33. package/dist/codegen/type-system.js +404 -31
  34. package/dist/codegen/type-system.js.map +1 -1
  35. package/dist/codegen-core.d.ts +2 -2
  36. package/dist/codegen-core.js +65 -10
  37. package/dist/codegen-core.js.map +1 -1
  38. package/dist/codegen-expression.d.ts +11 -0
  39. package/dist/codegen-expression.js +199 -0
  40. package/dist/codegen-expression.js.map +1 -0
  41. package/dist/concepts.d.ts +3 -3
  42. package/dist/config.d.ts +16 -0
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/decompiler.js +575 -4
  46. package/dist/decompiler.js.map +1 -1
  47. package/dist/importer.d.ts +1 -0
  48. package/dist/importer.js +574 -34
  49. package/dist/importer.js.map +1 -1
  50. package/dist/index.d.ts +16 -3
  51. package/dist/index.js +19 -3
  52. package/dist/index.js.map +1 -1
  53. package/dist/node-props.d.ts +181 -1
  54. package/dist/node-props.js.map +1 -1
  55. package/dist/parser-core.d.ts +7 -1
  56. package/dist/parser-core.js +33 -7
  57. package/dist/parser-core.js.map +1 -1
  58. package/dist/parser-diagnostics.js +8 -0
  59. package/dist/parser-diagnostics.js.map +1 -1
  60. package/dist/parser-expression.d.ts +22 -0
  61. package/dist/parser-expression.js +774 -0
  62. package/dist/parser-expression.js.map +1 -0
  63. package/dist/parser-keywords.js +16 -0
  64. package/dist/parser-keywords.js.map +1 -1
  65. package/dist/parser-tokenizer.d.ts +5 -3
  66. package/dist/parser-tokenizer.js +97 -16
  67. package/dist/parser-tokenizer.js.map +1 -1
  68. package/dist/parser-validate-effects.d.ts +21 -0
  69. package/dist/parser-validate-effects.js +188 -0
  70. package/dist/parser-validate-effects.js.map +1 -0
  71. package/dist/parser-validate-expressions.d.ts +6 -0
  72. package/dist/parser-validate-expressions.js +41 -0
  73. package/dist/parser-validate-expressions.js.map +1 -0
  74. package/dist/parser-validate-propagation.d.ts +105 -0
  75. package/dist/parser-validate-propagation.js +684 -0
  76. package/dist/parser-validate-propagation.js.map +1 -0
  77. package/dist/parser-validate-union-kind.d.ts +24 -0
  78. package/dist/parser-validate-union-kind.js +97 -0
  79. package/dist/parser-validate-union-kind.js.map +1 -0
  80. package/dist/parser.d.ts +10 -3
  81. package/dist/parser.js +11 -5
  82. package/dist/parser.js.map +1 -1
  83. package/dist/schema.d.ts +1 -1
  84. package/dist/schema.js +562 -30
  85. package/dist/schema.js.map +1 -1
  86. package/dist/semantic-validator.js +24 -13
  87. package/dist/semantic-validator.js.map +1 -1
  88. package/dist/spec.d.ts +5 -2
  89. package/dist/spec.js +36 -2
  90. package/dist/spec.js.map +1 -1
  91. package/dist/types.d.ts +7 -1
  92. package/dist/types.js +7 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/value-ir.d.ts +96 -0
  95. package/dist/value-ir.js +25 -0
  96. package/dist/value-ir.js.map +1 -0
  97. package/package.json +1 -1
@@ -16,6 +16,27 @@ function expandVal(val) {
16
16
  */
17
17
  export function decompile(root) {
18
18
  const lines = [];
19
+ function isExpr(value) {
20
+ return typeof value === 'object' && value !== null && value.__expr === true;
21
+ }
22
+ function renderScalarProp(propName, raw, quoted = []) {
23
+ if (isExpr(raw))
24
+ return `${propName}={{${raw.code}}}`;
25
+ if (typeof raw === 'boolean' || typeof raw === 'number')
26
+ return `${propName}=${String(raw)}`;
27
+ const s = String(raw);
28
+ const wasQuoted = quoted.includes(propName);
29
+ const safeBare = !wasQuoted && s !== '' && /^[\w.-]+$/.test(s);
30
+ return `${propName}=${safeBare ? s : JSON.stringify(s)}`;
31
+ }
32
+ function pushHandler(node, indent) {
33
+ const code = String(node.props?.code || '');
34
+ lines.push(`${indent}handler <<<`);
35
+ for (const line of code.split('\n')) {
36
+ lines.push(`${indent} ${line}`);
37
+ }
38
+ lines.push(`${indent}>>>`);
39
+ }
19
40
  function render(node, indent) {
20
41
  if (!node.type) {
21
42
  lines.push(`${indent}[unknown node]`);
@@ -25,6 +46,51 @@ export function decompile(root) {
25
46
  // Canonical-grammar cases — emit re-parseable KERN. Other node types
26
47
  // still fall through to the debug-shape serializer below; make them
27
48
  // canonical in a follow-up PR.
49
+ if (node.type === 'type') {
50
+ renderType(node, indent);
51
+ return;
52
+ }
53
+ if (node.type === 'interface') {
54
+ renderInterface(node, indent);
55
+ return;
56
+ }
57
+ if (node.type === 'enum') {
58
+ renderEnum(node, indent);
59
+ return;
60
+ }
61
+ if (node.type === 'class' || node.type === 'service') {
62
+ renderClassLike(node, indent);
63
+ return;
64
+ }
65
+ if (node.type === 'fn' ||
66
+ node.type === 'method' ||
67
+ node.type === 'constructor' ||
68
+ node.type === 'getter' ||
69
+ node.type === 'setter' ||
70
+ node.type === 'overload') {
71
+ renderCallable(node, indent);
72
+ return;
73
+ }
74
+ if (node.type === 'const') {
75
+ renderConst(node, indent);
76
+ return;
77
+ }
78
+ if (node.type === 'member') {
79
+ renderMember(node, indent);
80
+ return;
81
+ }
82
+ if (node.type === 'indexer') {
83
+ renderIndexer(node, indent);
84
+ return;
85
+ }
86
+ if (node.type === 'handler') {
87
+ pushHandler(node, indent);
88
+ return;
89
+ }
90
+ if (node.type === 'doc') {
91
+ renderDoc(node, indent);
92
+ return;
93
+ }
28
94
  if (node.type === 'each') {
29
95
  renderEach(node, indent);
30
96
  return;
@@ -33,6 +99,39 @@ export function decompile(root) {
33
99
  renderLet(node, indent);
34
100
  return;
35
101
  }
102
+ if (node.type === 'field') {
103
+ renderField(node, indent);
104
+ return;
105
+ }
106
+ if (node.type === 'param') {
107
+ renderParam(node, indent);
108
+ return;
109
+ }
110
+ if (node.type === 'destructure') {
111
+ renderDestructure(node, indent);
112
+ return;
113
+ }
114
+ if (node.type === 'binding' || node.type === 'element') {
115
+ // Standalone render path — only hit when these appear outside a
116
+ // `destructure` parent. Inside a parent, `renderDestructure` handles
117
+ // them inline.
118
+ renderDestructureChild(node, indent);
119
+ return;
120
+ }
121
+ if (node.type === 'mapLit') {
122
+ renderMapLit(node, indent);
123
+ return;
124
+ }
125
+ if (node.type === 'setLit') {
126
+ renderSetLit(node, indent);
127
+ return;
128
+ }
129
+ if (node.type === 'mapEntry' || node.type === 'setItem') {
130
+ // Children of mapLit/setLit — handled inline by their parent renderer.
131
+ // Standalone render path covers the orphan case for completeness.
132
+ renderMapSetChild(node, indent);
133
+ return;
134
+ }
36
135
  const name = props.name || '';
37
136
  const type = node.type.charAt(0).toUpperCase() + node.type.slice(1);
38
137
  // Style description
@@ -66,15 +165,181 @@ export function decompile(root) {
66
165
  }
67
166
  }
68
167
  }
168
+ function renderType(node, indent) {
169
+ const props = node.props || {};
170
+ const quoted = node.__quotedProps ?? [];
171
+ const name = props.name || 'UnknownType';
172
+ const parts = [`type name=${name}`];
173
+ if (props.generics !== undefined)
174
+ parts.push(renderScalarProp('generics', props.generics, quoted));
175
+ if (props.values !== undefined)
176
+ parts.push(renderScalarProp('values', props.values, quoted));
177
+ if (props.alias !== undefined)
178
+ parts.push(renderScalarProp('alias', props.alias, quoted));
179
+ if (props.export === false || props.export === 'false')
180
+ parts.push('export=false');
181
+ lines.push(`${indent}${parts.join(' ')}`);
182
+ for (const child of node.children || [])
183
+ render(child, `${indent} `);
184
+ }
185
+ function renderInterface(node, indent) {
186
+ const props = node.props || {};
187
+ const quoted = node.__quotedProps ?? [];
188
+ const name = props.name || 'UnknownInterface';
189
+ const parts = [`interface name=${name}`];
190
+ if (props.generics !== undefined)
191
+ parts.push(renderScalarProp('generics', props.generics, quoted));
192
+ if (props.extends !== undefined)
193
+ parts.push(renderScalarProp('extends', props.extends, quoted));
194
+ if (props.export === false || props.export === 'false')
195
+ parts.push('export=false');
196
+ lines.push(`${indent}${parts.join(' ')}`);
197
+ for (const child of node.children || [])
198
+ render(child, `${indent} `);
199
+ }
200
+ function renderEnum(node, indent) {
201
+ const props = node.props || {};
202
+ const quoted = node.__quotedProps ?? [];
203
+ const name = props.name || 'UnknownEnum';
204
+ const parts = [`enum name=${name}`];
205
+ if (props.values !== undefined)
206
+ parts.push(renderScalarProp('values', props.values, quoted));
207
+ if (props.const === true || props.const === 'true')
208
+ parts.push('const=true');
209
+ if (props.export === false || props.export === 'false')
210
+ parts.push('export=false');
211
+ lines.push(`${indent}${parts.join(' ')}`);
212
+ for (const child of node.children || [])
213
+ render(child, `${indent} `);
214
+ }
215
+ function renderMember(node, indent) {
216
+ const props = node.props || {};
217
+ const quoted = node.__quotedProps ?? [];
218
+ const name = props.name || 'member';
219
+ const parts = [`member name=${name}`];
220
+ if (props.value !== undefined)
221
+ parts.push(renderScalarProp('value', props.value, quoted));
222
+ lines.push(`${indent}${parts.join(' ')}`);
223
+ }
224
+ function renderIndexer(node, indent) {
225
+ const props = node.props || {};
226
+ const quoted = node.__quotedProps ?? [];
227
+ const parts = ['indexer'];
228
+ if (props.keyName !== undefined)
229
+ parts.push(renderScalarProp('keyName', props.keyName, quoted));
230
+ if (props.keyType !== undefined)
231
+ parts.push(renderScalarProp('keyType', props.keyType, quoted));
232
+ if (props.type !== undefined)
233
+ parts.push(renderScalarProp('type', props.type, quoted));
234
+ if (props.readonly === true || props.readonly === 'true')
235
+ parts.push('readonly=true');
236
+ lines.push(`${indent}${parts.join(' ')}`);
237
+ }
238
+ function renderClassLike(node, indent) {
239
+ const props = node.props || {};
240
+ const quoted = node.__quotedProps ?? [];
241
+ const name = props.name || (node.type === 'service' ? 'UnknownService' : 'UnknownClass');
242
+ const parts = [`${node.type} name=${name}`];
243
+ if (props.generics !== undefined)
244
+ parts.push(renderScalarProp('generics', props.generics, quoted));
245
+ if (props.extends !== undefined)
246
+ parts.push(renderScalarProp('extends', props.extends, quoted));
247
+ if (props.implements !== undefined)
248
+ parts.push(renderScalarProp('implements', props.implements, quoted));
249
+ if (props.abstract === true || props.abstract === 'true')
250
+ parts.push('abstract=true');
251
+ if (props.export === false || props.export === 'false')
252
+ parts.push('export=false');
253
+ lines.push(`${indent}${parts.join(' ')}`);
254
+ for (const child of node.children || [])
255
+ render(child, `${indent} `);
256
+ }
257
+ function renderCallable(node, indent) {
258
+ const props = node.props || {};
259
+ const quoted = node.__quotedProps ?? [];
260
+ const parts = [];
261
+ if (node.type === 'constructor') {
262
+ parts.push('constructor');
263
+ }
264
+ else if (node.type === 'overload') {
265
+ parts.push('overload');
266
+ }
267
+ else {
268
+ parts.push(`${node.type} name=${props.name || node.type}`);
269
+ }
270
+ if (props.generics !== undefined)
271
+ parts.push(renderScalarProp('generics', props.generics, quoted));
272
+ if (props.params !== undefined)
273
+ parts.push(renderScalarProp('params', props.params, quoted));
274
+ if (props.returns !== undefined)
275
+ parts.push(renderScalarProp('returns', props.returns, quoted));
276
+ if (props.async === true || props.async === 'true')
277
+ parts.push('async=true');
278
+ if (props.stream === true || props.stream === 'true')
279
+ parts.push('stream=true');
280
+ if (props.generator === true || props.generator === 'true')
281
+ parts.push('generator=true');
282
+ if (props.static === true || props.static === 'true')
283
+ parts.push('static=true');
284
+ if (props.private === true || props.private === 'true')
285
+ parts.push('private=true');
286
+ if (props.export === false || props.export === 'false')
287
+ parts.push('export=false');
288
+ if (props.expr !== undefined)
289
+ parts.push(renderScalarProp('expr', props.expr, quoted));
290
+ lines.push(`${indent}${parts.join(' ')}`);
291
+ for (const child of node.children || [])
292
+ render(child, `${indent} `);
293
+ }
294
+ function renderConst(node, indent) {
295
+ const props = node.props || {};
296
+ const quoted = node.__quotedProps ?? [];
297
+ const name = props.name || 'unknownConst';
298
+ const parts = [`const name=${name}`];
299
+ if (props.type !== undefined)
300
+ parts.push(renderScalarProp('type', props.type, quoted));
301
+ if (props.value !== undefined)
302
+ parts.push(renderScalarProp('value', props.value, quoted));
303
+ if (props.export === false || props.export === 'false')
304
+ parts.push('export=false');
305
+ lines.push(`${indent}${parts.join(' ')}`);
306
+ for (const child of node.children || [])
307
+ render(child, `${indent} `);
308
+ }
309
+ function renderDoc(node, indent) {
310
+ const text = String(node.props?.text || node.props?.code || '');
311
+ if (!text.includes('\n')) {
312
+ lines.push(`${indent}doc text=${JSON.stringify(text)}`);
313
+ return;
314
+ }
315
+ lines.push(`${indent}doc <<<`);
316
+ for (const line of text.split('\n')) {
317
+ lines.push(`${indent} ${line}`);
318
+ }
319
+ lines.push(`${indent}>>>`);
320
+ }
69
321
  function renderLet(node, indent) {
70
322
  const props = node.props || {};
71
323
  const name = props.name || 'binding';
324
+ // Codex hold #3: prefer `value=` if the let node carries one (slice 3a).
325
+ // Without this, a let authored with `value=42` would round-trip to
326
+ // `expr=""` and lose its assignment entirely.
327
+ const rawValue = props.value;
72
328
  const rawExpr = props.expr;
73
- const expr = rawExpr && typeof rawExpr === 'object' && rawExpr.__expr
74
- ? rawExpr.code
75
- : rawExpr || '';
76
329
  const t = props.type;
77
- const parts = [`let name=${name}`, `expr=${JSON.stringify(expr)}`];
330
+ const parts = [`let name=${name}`];
331
+ if (rawValue !== undefined) {
332
+ const valueText = typeof rawValue === 'object' && rawValue.__expr
333
+ ? `{{${rawValue.code}}}`
334
+ : JSON.stringify(rawValue);
335
+ parts.push(`value=${valueText}`);
336
+ }
337
+ else {
338
+ const expr = rawExpr && typeof rawExpr === 'object' && rawExpr.__expr
339
+ ? rawExpr.code
340
+ : rawExpr || '';
341
+ parts.push(`expr=${JSON.stringify(expr)}`);
342
+ }
78
343
  if (t)
79
344
  parts.push(`type=${t}`);
80
345
  lines.push(`${indent}${parts.join(' ')}`);
@@ -85,6 +350,312 @@ export function decompile(root) {
85
350
  }
86
351
  }
87
352
  }
353
+ function renderField(node, indent) {
354
+ // Slice 3b: emit `field` re-parseably so canonical `value={{...}}` forms
355
+ // survive the IR → text round-trip. Without this, the generic JSON.stringify
356
+ // path would emit `value={"__expr":true,"code":"foo()"}` for any class field
357
+ // imported from TS — un-re-parseable.
358
+ //
359
+ // String prop emission honours __quotedProps so a bare `value=42` (numeric
360
+ // literal) round-trips as bare and codegens to `42`, whereas a quoted
361
+ // `value="42"` round-trips quoted and codegens to `"42"` (string literal).
362
+ // Without this distinction, all bare values would gain spurious quotes on
363
+ // every decompile + re-parse cycle.
364
+ const props = node.props || {};
365
+ const quoted = node.__quotedProps ?? [];
366
+ const name = props.name || 'field';
367
+ const parts = [`field name=${name}`];
368
+ function renderStringProp(propName, raw) {
369
+ if (typeof raw === 'object' && raw.__expr) {
370
+ return `${propName}={{${raw.code}}}`;
371
+ }
372
+ const s = raw;
373
+ // Bare-emit only when the source was unquoted AND the value matches a
374
+ // strict whitelist of identifier-shape characters (alphanumeric, `_`,
375
+ // `.`, `-`). Codex hold #2: a permissive blacklist (e.g. `/[\s=]/`)
376
+ // would emit values like `'draft'|'done'` or `{id:string}` bare, which
377
+ // the parser then truncates at the embedded quote or treats as a
378
+ // style block. The whitelist covers numeric literals, identifiers, and
379
+ // dotted member chains — the cases ValueIR canonicalises — and forces
380
+ // JSON.stringify on anything else (type unions, object shorthands,
381
+ // strings with punctuation, etc.).
382
+ const wasQuoted = quoted.includes(propName);
383
+ const safeBare = !wasQuoted && s !== '' && /^[\w.-]+$/.test(s);
384
+ return `${propName}=${safeBare ? s : JSON.stringify(s)}`;
385
+ }
386
+ const t = props.type;
387
+ if (t !== undefined)
388
+ parts.push(renderStringProp('type', t));
389
+ const opt = props.optional;
390
+ if (opt === true || opt === 'true')
391
+ parts.push('optional=true');
392
+ const priv = props.private;
393
+ if (priv === true || priv === 'true')
394
+ parts.push('private=true');
395
+ const ro = props.readonly;
396
+ if (ro === true || ro === 'true')
397
+ parts.push('readonly=true');
398
+ const stat = props.static;
399
+ if (stat === true || stat === 'true')
400
+ parts.push('static=true');
401
+ const rawValue = props.value;
402
+ const rawDefault = props.default;
403
+ if (rawValue !== undefined) {
404
+ parts.push(renderStringProp('value', rawValue));
405
+ }
406
+ else if (rawDefault !== undefined) {
407
+ parts.push(renderStringProp('default', rawDefault));
408
+ }
409
+ lines.push(`${indent}${parts.join(' ')}`);
410
+ if (node.children) {
411
+ for (const child of node.children) {
412
+ render(child, `${indent} `);
413
+ }
414
+ }
415
+ }
416
+ function renderParam(node, indent) {
417
+ // Slice 3c: emit `param` re-parseably so canonical `value={{...}}` forms
418
+ // (and ValueIR-canonicalised bare values) survive IR → text round-trip.
419
+ // Mirrors renderField — same __quotedProps-aware bare/quoted policy.
420
+ //
421
+ // `param` is used in two contexts: (a) MCP tool/resource/prompt params
422
+ // (description/required/min/max apply); (b) fn/method/constructor
423
+ // parameter defaults via slice 3c (value/default apply). Both share this
424
+ // emitter so a node round-trips correctly regardless of parent context.
425
+ const props = node.props || {};
426
+ const quoted = node.__quotedProps ?? [];
427
+ // Slice 3c-extension #3: destructured params omit `name=`; the LHS pattern
428
+ // is encoded in `binding`/`element` children. Detect by child presence so
429
+ // round-trips of importer-emitted destructured params don't gain a bogus
430
+ // `name=param` attribute that would re-parse to a `param name="param"`.
431
+ const hasDestructure = (node.children ?? []).some((c) => c.type === 'binding' || c.type === 'element');
432
+ const name = props.name;
433
+ const parts = ['param'];
434
+ if (!hasDestructure && name)
435
+ parts[0] = `param name=${name}`;
436
+ else if (!hasDestructure)
437
+ parts[0] = 'param name=param';
438
+ function renderStringProp(propName, raw) {
439
+ if (typeof raw === 'object' && raw.__expr) {
440
+ return `${propName}={{${raw.code}}}`;
441
+ }
442
+ const s = raw;
443
+ // Bare-emit only when source was unquoted AND value matches the
444
+ // identifier-shape whitelist (mirrors renderField — see Codex hold #2).
445
+ const wasQuoted = quoted.includes(propName);
446
+ const safeBare = !wasQuoted && s !== '' && /^[\w.-]+$/.test(s);
447
+ return `${propName}=${safeBare ? s : JSON.stringify(s)}`;
448
+ }
449
+ const t = props.type;
450
+ if (t !== undefined)
451
+ parts.push(renderStringProp('type', t));
452
+ const required = props.required;
453
+ if (required === true || required === 'true')
454
+ parts.push('required=true');
455
+ // Slice 3c-extension: TS-style optional `?` round-trips via `optional=true`.
456
+ const optional = props.optional;
457
+ if (optional === true || optional === 'true')
458
+ parts.push('optional=true');
459
+ // Slice 3c-extension: TS-style variadic `...` round-trips via `variadic=true`.
460
+ const variadic = props.variadic;
461
+ if (variadic === true || variadic === 'true')
462
+ parts.push('variadic=true');
463
+ const rawValue = props.value;
464
+ const rawDefault = props.default;
465
+ if (rawValue !== undefined) {
466
+ parts.push(renderStringProp('value', rawValue));
467
+ }
468
+ else if (rawDefault !== undefined) {
469
+ parts.push(renderStringProp('default', rawDefault));
470
+ }
471
+ const description = props.description;
472
+ if (typeof description === 'string')
473
+ parts.push(`description=${JSON.stringify(description)}`);
474
+ const min = props.min;
475
+ if (min !== undefined)
476
+ parts.push(`min=${min}`);
477
+ const max = props.max;
478
+ if (max !== undefined)
479
+ parts.push(`max=${max}`);
480
+ lines.push(`${indent}${parts.join(' ')}`);
481
+ if (node.children) {
482
+ for (const child of node.children) {
483
+ render(child, `${indent} `);
484
+ }
485
+ }
486
+ }
487
+ function renderDestructure(node, indent) {
488
+ // Slice 3d: emit `destructure` re-parseably so structured `binding`/
489
+ // `element` children plus the `expr={{...}}` escape hatch survive
490
+ // IR → text round-trip. Mirrors renderParam's __quotedProps-aware
491
+ // bare-vs-quoted policy on `source`.
492
+ const props = node.props || {};
493
+ const quoted = node.__quotedProps ?? [];
494
+ const parts = ['destructure'];
495
+ // Escape-hatch path: raw statement carried verbatim. When present, all
496
+ // other props are ignored (codegen also ignores them — see generateDestructure).
497
+ const rawExpr = props.expr;
498
+ if (rawExpr !== undefined) {
499
+ if (typeof rawExpr === 'object' && rawExpr.__expr) {
500
+ parts.push(`expr={{${rawExpr.code}}}`);
501
+ }
502
+ else {
503
+ parts.push(`expr=${JSON.stringify(rawExpr)}`);
504
+ }
505
+ lines.push(`${indent}${parts.join(' ')}`);
506
+ return;
507
+ }
508
+ const kind = props.kind;
509
+ if (kind && kind !== 'const')
510
+ parts.push(`kind=${kind}`);
511
+ const t = props.type;
512
+ if (t !== undefined) {
513
+ const wasQuoted = quoted.includes('type');
514
+ const safeBare = !wasQuoted && /^[\w.<>[\]|&,\s-]+$/.test(t) && !/\s/.test(t.trim());
515
+ parts.push(`type=${safeBare ? t : JSON.stringify(t)}`);
516
+ }
517
+ const rawSource = props.source;
518
+ if (rawSource !== undefined) {
519
+ if (typeof rawSource === 'object' && rawSource.__expr) {
520
+ parts.push(`source={{${rawSource.code}}}`);
521
+ }
522
+ else {
523
+ const s = rawSource;
524
+ const wasQuoted = quoted.includes('source');
525
+ const safeBare = !wasQuoted && /^[\w.-]+$/.test(s);
526
+ parts.push(`source=${safeBare ? s : JSON.stringify(s)}`);
527
+ }
528
+ }
529
+ const exported = props.export;
530
+ if (exported === true || exported === 'true')
531
+ parts.push('export=true');
532
+ lines.push(`${indent}${parts.join(' ')}`);
533
+ if (node.children) {
534
+ for (const child of node.children) {
535
+ renderDestructureChild(child, `${indent} `);
536
+ }
537
+ }
538
+ }
539
+ function renderDestructureChild(node, indent) {
540
+ const props = node.props || {};
541
+ const name = props.name || '?';
542
+ if (node.type === 'binding') {
543
+ const parts = [`binding name=${name}`];
544
+ const key = props.key;
545
+ if (key)
546
+ parts.push(`key=${key}`);
547
+ lines.push(`${indent}${parts.join(' ')}`);
548
+ return;
549
+ }
550
+ if (node.type === 'element') {
551
+ const parts = [`element name=${name}`];
552
+ const idx = props.index;
553
+ if (idx !== undefined)
554
+ parts.push(`index=${idx}`);
555
+ lines.push(`${indent}${parts.join(' ')}`);
556
+ return;
557
+ }
558
+ // Unknown — should never hit; fall through to generic render.
559
+ render(node, indent);
560
+ }
561
+ function renderMapLit(node, indent) {
562
+ // Slice 3e: emit `mapLit` re-parseably with `mapEntry` children inline.
563
+ // Mirrors renderDestructure escape-hatch + __quotedProps policy.
564
+ const props = node.props || {};
565
+ const quoted = node.__quotedProps ?? [];
566
+ const name = props.name || 'unknownMap';
567
+ const parts = [`mapLit name=${name}`];
568
+ // Escape-hatch path — raw statement carried verbatim. Other props ignored.
569
+ const rawExpr = props.expr;
570
+ if (rawExpr !== undefined) {
571
+ const exprText = typeof rawExpr === 'object' && rawExpr.__expr
572
+ ? `{{${rawExpr.code}}}`
573
+ : JSON.stringify(rawExpr);
574
+ lines.push(`${indent}mapLit name=${name} expr=${exprText}`);
575
+ return;
576
+ }
577
+ const t = props.type;
578
+ if (t !== undefined) {
579
+ const wasQuoted = quoted.includes('type');
580
+ const safeBare = !wasQuoted && /^[\w.<>[\]|&,\s-]+$/.test(t) && !/\s/.test(t.trim());
581
+ parts.push(`type=${safeBare ? t : JSON.stringify(t)}`);
582
+ }
583
+ const kind = props.kind;
584
+ if (kind && kind !== 'const')
585
+ parts.push(`kind=${kind}`);
586
+ if (props.export === true || props.export === 'true')
587
+ parts.push('export=true');
588
+ lines.push(`${indent}${parts.join(' ')}`);
589
+ if (node.children) {
590
+ for (const child of node.children) {
591
+ renderMapSetChild(child, `${indent} `);
592
+ }
593
+ }
594
+ }
595
+ function renderSetLit(node, indent) {
596
+ const props = node.props || {};
597
+ const quoted = node.__quotedProps ?? [];
598
+ const name = props.name || 'unknownSet';
599
+ const parts = [`setLit name=${name}`];
600
+ const rawExpr = props.expr;
601
+ if (rawExpr !== undefined) {
602
+ const exprText = typeof rawExpr === 'object' && rawExpr.__expr
603
+ ? `{{${rawExpr.code}}}`
604
+ : JSON.stringify(rawExpr);
605
+ lines.push(`${indent}setLit name=${name} expr=${exprText}`);
606
+ return;
607
+ }
608
+ const t = props.type;
609
+ if (t !== undefined) {
610
+ const wasQuoted = quoted.includes('type');
611
+ const safeBare = !wasQuoted && /^[\w.<>[\]|&,\s-]+$/.test(t) && !/\s/.test(t.trim());
612
+ parts.push(`type=${safeBare ? t : JSON.stringify(t)}`);
613
+ }
614
+ const kind = props.kind;
615
+ if (kind && kind !== 'const')
616
+ parts.push(`kind=${kind}`);
617
+ if (props.export === true || props.export === 'true')
618
+ parts.push('export=true');
619
+ lines.push(`${indent}${parts.join(' ')}`);
620
+ if (node.children) {
621
+ for (const child of node.children) {
622
+ renderMapSetChild(child, `${indent} `);
623
+ }
624
+ }
625
+ }
626
+ function renderMapSetChild(node, indent) {
627
+ const props = node.props || {};
628
+ const quoted = node.__quotedProps ?? [];
629
+ function renderExprProp(propName, raw) {
630
+ if (typeof raw === 'object' && raw !== null && raw.__expr) {
631
+ return `${propName}={{${raw.code}}}`;
632
+ }
633
+ const s = raw;
634
+ const wasQuoted = quoted.includes(propName);
635
+ const safeBare = !wasQuoted && typeof s === 'string' && s !== '' && /^[\w.-]+$/.test(s);
636
+ return `${propName}=${safeBare ? s : JSON.stringify(s)}`;
637
+ }
638
+ if (node.type === 'mapEntry') {
639
+ const key = props.key;
640
+ const val = props.value;
641
+ const parts = ['mapEntry'];
642
+ if (key !== undefined)
643
+ parts.push(renderExprProp('key', key));
644
+ if (val !== undefined)
645
+ parts.push(renderExprProp('value', val));
646
+ lines.push(`${indent}${parts.join(' ')}`);
647
+ return;
648
+ }
649
+ if (node.type === 'setItem') {
650
+ const val = props.value;
651
+ const parts = ['setItem'];
652
+ if (val !== undefined)
653
+ parts.push(renderExprProp('value', val));
654
+ lines.push(`${indent}${parts.join(' ')}`);
655
+ return;
656
+ }
657
+ render(node, indent);
658
+ }
88
659
  function renderEach(node, indent) {
89
660
  const props = node.props || {};
90
661
  const name = props.name || 'item';