@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.
- package/dist/capability-matrix.d.ts +15 -0
- package/dist/capability-matrix.js +245 -0
- package/dist/capability-matrix.js.map +1 -0
- package/dist/codegen/body-ts.d.ts +68 -0
- package/dist/codegen/body-ts.js +214 -0
- package/dist/codegen/body-ts.js.map +1 -0
- package/dist/codegen/data-layer.d.ts +1 -1
- package/dist/codegen/data-layer.js +59 -23
- package/dist/codegen/data-layer.js.map +1 -1
- package/dist/codegen/events.js +1 -1
- package/dist/codegen/events.js.map +1 -1
- package/dist/codegen/functions.js +48 -7
- package/dist/codegen/functions.js.map +1 -1
- package/dist/codegen/ground-layer.js +10 -6
- package/dist/codegen/ground-layer.js.map +1 -1
- package/dist/codegen/helpers.d.ts +3 -1
- package/dist/codegen/helpers.js +5 -1
- package/dist/codegen/helpers.js.map +1 -1
- package/dist/codegen/kern-stdlib.d.ts +63 -0
- package/dist/codegen/kern-stdlib.js +160 -0
- package/dist/codegen/kern-stdlib.js.map +1 -0
- package/dist/codegen/machines.js +4 -3
- package/dist/codegen/machines.js.map +1 -1
- package/dist/codegen/modules.d.ts +1 -0
- package/dist/codegen/modules.js +52 -1
- package/dist/codegen/modules.js.map +1 -1
- package/dist/codegen/screens.js +31 -9
- package/dist/codegen/screens.js.map +1 -1
- package/dist/codegen/stdlib-preamble.d.ts +58 -0
- package/dist/codegen/stdlib-preamble.js +271 -0
- package/dist/codegen/stdlib-preamble.js.map +1 -0
- package/dist/codegen/type-system.d.ts +113 -1
- package/dist/codegen/type-system.js +404 -31
- package/dist/codegen/type-system.js.map +1 -1
- package/dist/codegen-core.d.ts +2 -2
- package/dist/codegen-core.js +65 -10
- package/dist/codegen-core.js.map +1 -1
- package/dist/codegen-expression.d.ts +11 -0
- package/dist/codegen-expression.js +199 -0
- package/dist/codegen-expression.js.map +1 -0
- package/dist/concepts.d.ts +3 -3
- package/dist/config.d.ts +16 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -1
- package/dist/decompiler.js +575 -4
- package/dist/decompiler.js.map +1 -1
- package/dist/importer.d.ts +1 -0
- package/dist/importer.js +574 -34
- package/dist/importer.js.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/node-props.d.ts +181 -1
- package/dist/node-props.js.map +1 -1
- package/dist/parser-core.d.ts +7 -1
- package/dist/parser-core.js +33 -7
- package/dist/parser-core.js.map +1 -1
- package/dist/parser-diagnostics.js +8 -0
- package/dist/parser-diagnostics.js.map +1 -1
- package/dist/parser-expression.d.ts +22 -0
- package/dist/parser-expression.js +774 -0
- package/dist/parser-expression.js.map +1 -0
- package/dist/parser-keywords.js +16 -0
- package/dist/parser-keywords.js.map +1 -1
- package/dist/parser-tokenizer.d.ts +5 -3
- package/dist/parser-tokenizer.js +97 -16
- package/dist/parser-tokenizer.js.map +1 -1
- package/dist/parser-validate-effects.d.ts +21 -0
- package/dist/parser-validate-effects.js +188 -0
- package/dist/parser-validate-effects.js.map +1 -0
- package/dist/parser-validate-expressions.d.ts +6 -0
- package/dist/parser-validate-expressions.js +41 -0
- package/dist/parser-validate-expressions.js.map +1 -0
- package/dist/parser-validate-propagation.d.ts +105 -0
- package/dist/parser-validate-propagation.js +684 -0
- package/dist/parser-validate-propagation.js.map +1 -0
- package/dist/parser-validate-union-kind.d.ts +24 -0
- package/dist/parser-validate-union-kind.js +97 -0
- package/dist/parser-validate-union-kind.js.map +1 -0
- package/dist/parser.d.ts +10 -3
- package/dist/parser.js +11 -5
- package/dist/parser.js.map +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +562 -30
- package/dist/schema.js.map +1 -1
- package/dist/semantic-validator.js +24 -13
- package/dist/semantic-validator.js.map +1 -1
- package/dist/spec.d.ts +5 -2
- package/dist/spec.js +36 -2
- package/dist/spec.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/dist/value-ir.d.ts +96 -0
- package/dist/value-ir.js +25 -0
- package/dist/value-ir.js.map +1 -0
- package/package.json +1 -1
package/dist/decompiler.js
CHANGED
|
@@ -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}
|
|
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';
|