@scriptc/compiler 0.0.0 → 0.0.2
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/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5949 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10102 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +65 -0
- package/dist/coverage/report.js +238 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6286 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +486 -0
- package/dist/frontend/lowering/lower-containers.js +6345 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8111 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1074 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6257 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2255 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +67 -0
- package/dist/frontend/shared.js +241 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +429 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4885 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- package/index.js +0 -2
|
@@ -0,0 +1,1587 @@
|
|
|
1
|
+
import { DYN_HANDLE_KINDS, isRefCounted, typeEquals, typeKey } from "../../ir/nodes.js";
|
|
2
|
+
import { cDecl, cStringLiteral, cType, elemAccess, releaseCallC, retainCallC, vAdapters } from "./emit-types.js";
|
|
3
|
+
import { mangleField, mangleRecordNew, mangleRecordStruct } from "../mangle.js";
|
|
4
|
+
import { OVERFLOW_MEMBER } from "./emit-shapes.js";
|
|
5
|
+
/** Short human description of a dynCheck target for error messages
|
|
6
|
+
* ("expected number | string at $.items[2]"). Records read as "object" —
|
|
7
|
+
* the path already says where; the full shape would bloat messages. */
|
|
8
|
+
export function dynDesc(E, t) {
|
|
9
|
+
switch (t.kind) {
|
|
10
|
+
case "f64":
|
|
11
|
+
return "number";
|
|
12
|
+
case "string":
|
|
13
|
+
return "string";
|
|
14
|
+
case "bool":
|
|
15
|
+
return "boolean";
|
|
16
|
+
case "record":
|
|
17
|
+
// Tuples read as "array": that IS the JSON representation the
|
|
18
|
+
// caller must supply (arity failures carry their own message).
|
|
19
|
+
return E.recordsById.get(t.shapeId)?.tuple ? "array" : "object";
|
|
20
|
+
case "array":
|
|
21
|
+
return "array";
|
|
22
|
+
case "nullT":
|
|
23
|
+
return "null";
|
|
24
|
+
// Reachable through optional-flavored record fields ("expected
|
|
25
|
+
// string | undefined at $.a" — honest: the fix is a string value OR
|
|
26
|
+
// omitting the key).
|
|
27
|
+
case "undefinedT":
|
|
28
|
+
return "undefined";
|
|
29
|
+
// dyn fields accept any DOM value — only reachable in messages as a
|
|
30
|
+
// sibling of a failing field, never as the failure itself.
|
|
31
|
+
case "dyn":
|
|
32
|
+
return "unknown";
|
|
33
|
+
case "bytes":
|
|
34
|
+
return "Uint8Array";
|
|
35
|
+
// The %Error extraction (an instanceof-Error narrow / cast on an
|
|
36
|
+
// unknown value): the failure names the class, like caughtCheck's.
|
|
37
|
+
case "object":
|
|
38
|
+
return t.className.replace(/^%/, "");
|
|
39
|
+
case "union": {
|
|
40
|
+
const def = E.unionsById.get(t.unionId);
|
|
41
|
+
if (!def)
|
|
42
|
+
throw new Error(`emitter bug: dynDesc of unknown union ${t.unionId}`);
|
|
43
|
+
return def.arms.map((a) => E.dynDesc(a)).join(" | ");
|
|
44
|
+
}
|
|
45
|
+
// Function targets (the checked-dynamic function boundary): the
|
|
46
|
+
// failure names the expected callability, not the full signature —
|
|
47
|
+
// the path already says where.
|
|
48
|
+
case "func":
|
|
49
|
+
return "function";
|
|
50
|
+
default: {
|
|
51
|
+
// Runtime HANDLE targets name the class ("expected
|
|
52
|
+
// IncomingMessage at $, got string").
|
|
53
|
+
const h = DYN_HANDLE_KINDS.get(t.kind);
|
|
54
|
+
if (h)
|
|
55
|
+
return h.cls;
|
|
56
|
+
throw new Error(`emitter bug: dynDesc of non-JSON type ${t.kind}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** The per-union ToBoolean helper (interned per unionId): switch on the
|
|
61
|
+
* runtime tag — unit arms false, f64 arms 0/NaN-falsy, string arms
|
|
62
|
+
* empty-falsy, bool arms by payload, ref arms always true (JS objects),
|
|
63
|
+
* jsval arms answered by the engine. Borrows the operand. */
|
|
64
|
+
export function unionTruthyHelper(E, unionId) {
|
|
65
|
+
const existing = E.unionTruthyFns.get(unionId);
|
|
66
|
+
if (existing)
|
|
67
|
+
return existing;
|
|
68
|
+
const def = E.unionsById.get(unionId);
|
|
69
|
+
if (!def)
|
|
70
|
+
throw new Error(`emitter bug: truthiness of unknown union ${unionId}`);
|
|
71
|
+
const name = `sc_ut_${E.unionTruthyFns.size}`;
|
|
72
|
+
E.unionTruthyFns.set(unionId, name);
|
|
73
|
+
const sig = `static bool ${name}(ScrUnion *v)`;
|
|
74
|
+
E.walkerProtos.push(`${sig}; /* ToBoolean ${unionId} */`);
|
|
75
|
+
const d = [`${sig} { /* ToBoolean ${unionId} */`, ` switch (v->tag) {`];
|
|
76
|
+
def.arms.forEach((arm, i) => {
|
|
77
|
+
switch (arm.kind) {
|
|
78
|
+
case "undefinedT":
|
|
79
|
+
case "nullT":
|
|
80
|
+
d.push(` case ${i}: return false;`);
|
|
81
|
+
break;
|
|
82
|
+
case "f64":
|
|
83
|
+
d.push(` case ${i}: { double x = scr_union_get_f64(v); return x == x && x != 0; }`);
|
|
84
|
+
break;
|
|
85
|
+
case "bool":
|
|
86
|
+
d.push(` case ${i}: return scr_union_get_bool(v);`);
|
|
87
|
+
break;
|
|
88
|
+
case "string":
|
|
89
|
+
d.push(` case ${i}: return ((ScrStr *)scr_union_peek(v))->len != 0;`);
|
|
90
|
+
break;
|
|
91
|
+
case "jsval":
|
|
92
|
+
d.push(` case ${i}: return scr_jsval_truthy((ScrJsval *)scr_union_peek(v)) != 0;`);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
// Arrays, maps, sets, records, objects, functions, promises,
|
|
96
|
+
// regexes, ...: JS objects are always truthy — [] and {} included.
|
|
97
|
+
d.push(` case ${i}: return true; /* ${arm.kind} */`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
d.push(` default: fputs("scriptc: internal error: invalid union tag\\n", stderr); abort();`, ` }`, `}`, ``);
|
|
101
|
+
E.walkerDefs.push(...d);
|
|
102
|
+
return name;
|
|
103
|
+
}
|
|
104
|
+
/** The per-union strict-equality helper (interned per unionId):
|
|
105
|
+
* different tags are never equal; equal tags compare the arm values —
|
|
106
|
+
* unit arms equal, f64 with C == (NaN !== NaN, +0 === -0), strings by
|
|
107
|
+
* bytes, bools by value, ref arms by POINTER identity (JS object
|
|
108
|
+
* equality). Borrows both operands. */
|
|
109
|
+
export function unionEqHelper(E, unionId) {
|
|
110
|
+
const existing = E.unionEqFns.get(unionId);
|
|
111
|
+
if (existing)
|
|
112
|
+
return existing;
|
|
113
|
+
const def = E.unionsById.get(unionId);
|
|
114
|
+
if (!def)
|
|
115
|
+
throw new Error(`emitter bug: equality of unknown union ${unionId}`);
|
|
116
|
+
const name = `sc_ue_${E.unionEqFns.size}`;
|
|
117
|
+
E.unionEqFns.set(unionId, name);
|
|
118
|
+
const sig = `static bool ${name}(ScrUnion *a, ScrUnion *b)`;
|
|
119
|
+
E.walkerProtos.push(`${sig}; /* strict equality ${unionId} */`);
|
|
120
|
+
const d = [
|
|
121
|
+
`${sig} { /* strict equality ${unionId} */`,
|
|
122
|
+
` if (a->tag != b->tag) return false;`,
|
|
123
|
+
` switch (a->tag) {`,
|
|
124
|
+
];
|
|
125
|
+
def.arms.forEach((arm, i) => {
|
|
126
|
+
switch (arm.kind) {
|
|
127
|
+
case "undefinedT":
|
|
128
|
+
case "nullT":
|
|
129
|
+
d.push(` case ${i}: return true;`);
|
|
130
|
+
break;
|
|
131
|
+
case "f64":
|
|
132
|
+
d.push(` case ${i}: return scr_union_get_f64(a) == scr_union_get_f64(b);`);
|
|
133
|
+
break;
|
|
134
|
+
case "bool":
|
|
135
|
+
d.push(` case ${i}: return scr_union_get_bool(a) == scr_union_get_bool(b);`);
|
|
136
|
+
break;
|
|
137
|
+
case "string":
|
|
138
|
+
d.push(` case ${i}: return scr_str_eq((ScrStr *)scr_union_peek(a), (ScrStr *)scr_union_peek(b));`);
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
// Ref arms: pointer identity, exactly JS object equality.
|
|
142
|
+
d.push(` case ${i}: return scr_union_peek(a) == scr_union_peek(b); /* ${arm.kind} */`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
d.push(` default: fputs("scriptc: internal error: invalid union tag\\n", stderr); abort();`, ` }`, `}`, ``);
|
|
146
|
+
E.walkerDefs.push(...d);
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
/** The per-union ToString helper (interned per unionId): switch on the
|
|
150
|
+
* runtime tag — unit arms return the interned "undefined"/"null" texts,
|
|
151
|
+
* string arms retain the payload, f64/bool arms go through the JS-exact
|
|
152
|
+
* formatters (String(x) semantics). Ref arms never arrive (the frontend
|
|
153
|
+
* fences unions with object arms from string conversion — JS would print
|
|
154
|
+
* "[object Object]"). Borrows the operand; the result is owned (+1). */
|
|
155
|
+
export function unionToStrHelper(E, unionId) {
|
|
156
|
+
const existing = E.unionToStrFns.get(unionId);
|
|
157
|
+
if (existing)
|
|
158
|
+
return existing;
|
|
159
|
+
const def = E.unionsById.get(unionId);
|
|
160
|
+
if (!def)
|
|
161
|
+
throw new Error(`emitter bug: ToString of unknown union ${unionId}`);
|
|
162
|
+
const name = `sc_us_${E.unionToStrFns.size}`;
|
|
163
|
+
E.unionToStrFns.set(unionId, name);
|
|
164
|
+
const sig = `static ScrStr *${name}(ScrUnion *v)`;
|
|
165
|
+
E.walkerProtos.push(`${sig}; /* ToString ${unionId} */`);
|
|
166
|
+
const d = [`${sig} { /* ToString ${unionId} */`, ` switch (v->tag) {`];
|
|
167
|
+
def.arms.forEach((arm, i) => {
|
|
168
|
+
switch (arm.kind) {
|
|
169
|
+
case "undefinedT":
|
|
170
|
+
case "nullT": {
|
|
171
|
+
const lit = E.internLiteral(arm.kind === "undefinedT" ? "undefined" : "null");
|
|
172
|
+
d.push(` case ${i}: return scr_str_retain((ScrStr *)&${lit});`);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case "string":
|
|
176
|
+
d.push(` case ${i}: return scr_str_retain((ScrStr *)scr_union_peek(v));`);
|
|
177
|
+
break;
|
|
178
|
+
case "f64":
|
|
179
|
+
d.push(` case ${i}: return scr_f64_to_scrstr(scr_union_get_f64(v));`);
|
|
180
|
+
break;
|
|
181
|
+
case "bool":
|
|
182
|
+
d.push(` case ${i}: return scr_bool_to_scrstr(scr_union_get_bool(v));`);
|
|
183
|
+
break;
|
|
184
|
+
case "bytes": {
|
|
185
|
+
// Buffer.toString() IS the utf8 decode (Node's default
|
|
186
|
+
// encoding) — the `Buffer | string` chunk idiom.
|
|
187
|
+
const enc = E.internLiteral("utf8");
|
|
188
|
+
d.push(` case ${i}: return scr_bytes_to_str((ScrBytes *)scr_union_peek(v), (ScrStr *)&${enc});`);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
default:
|
|
192
|
+
throw new Error(`emitter bug: ToString of union with a ${arm.kind} arm (frontend fences these)`);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
d.push(` default: fputs("scriptc: internal error: invalid union tag\\n", stderr); abort();`, ` }`, `}`, ``);
|
|
196
|
+
E.walkerDefs.push(...d);
|
|
197
|
+
return name;
|
|
198
|
+
}
|
|
199
|
+
/** The per-union array-join helper (interned per unionId): Array.prototype
|
|
200
|
+
* .join over union elements — undefined/null arms print EMPTY (exactly
|
|
201
|
+
* JS's join, which silences only the nullish elements), every other arm
|
|
202
|
+
* goes through the per-union ToString walker (string arms verbatim,
|
|
203
|
+
* f64/bool via the JS-exact formatters). The frontend admits only unions
|
|
204
|
+
* of f64/string/bool/unit arms here. Borrows the array and separator;
|
|
205
|
+
* the result is owned (+1). */
|
|
206
|
+
export function unionJoinHelper(E, unionId) {
|
|
207
|
+
const existing = E.unionJoinFns.get(unionId);
|
|
208
|
+
if (existing)
|
|
209
|
+
return existing;
|
|
210
|
+
const def = E.unionsById.get(unionId);
|
|
211
|
+
if (!def)
|
|
212
|
+
throw new Error(`emitter bug: join of unknown union ${unionId}`);
|
|
213
|
+
const name = `sc_uj_${E.unionJoinFns.size}`;
|
|
214
|
+
E.unionJoinFns.set(unionId, name);
|
|
215
|
+
const toStr = unionToStrHelper(E, unionId);
|
|
216
|
+
const unitTags = def.arms.flatMap((a, i) => a.kind === "undefinedT" || a.kind === "nullT" ? [i] : []);
|
|
217
|
+
const sig = `static ScrStr *${name}(ScrArr *a, ScrStr *sep)`;
|
|
218
|
+
E.walkerProtos.push(`${sig}; /* join ${unionId} */`);
|
|
219
|
+
E.walkerDefs.push(`${sig} { /* Array#join over ${unionId}: nullish arms print empty */`, ` ScrJsonBuf b;`, ` scr_jb_init(&b);`, ` size_t n = (size_t)scr_arr_len(a);`, ` for (size_t i = 0; i < n; i++) {`, ` if (i) for (size_t j = 0; j < sep->len; j++) scr_jb_putc(&b, sep->data[j]);`, ` ScrUnion *u = (ScrUnion *)scr_arr_get_ref(a, (double)i);`, ...(unitTags.length > 0
|
|
220
|
+
? [` if (${unitTags.map((t) => `u->tag == ${t}`).join(" || ")}) { scr_union_release(u); continue; }`]
|
|
221
|
+
: []), ` ScrStr *s = ${toStr}(u);`, ` for (size_t j = 0; j < s->len; j++) scr_jb_putc(&b, s->data[j]);`, ` scr_str_release(s);`, ` scr_union_release(u);`, ` }`, ` return scr_jb_finish(&b);`, `}`, ``);
|
|
222
|
+
return name;
|
|
223
|
+
}
|
|
224
|
+
/** The dyn ToString helper pair (interned once, type-independent):
|
|
225
|
+
* Node's String() over a DOM value — "undefined"/"null" texts, bools,
|
|
226
|
+
* JS-exact number formatting (NaN/Infinity spelled out — NOT the JSON
|
|
227
|
+
* null), strings verbatim, arrays via Array.prototype.toString (join
|
|
228
|
+
* with ","; null/undefined ELEMENTS print empty, nested arrays flatten
|
|
229
|
+
* through the recursion — JS-exact), plain objects as
|
|
230
|
+
* "[object Object]". The operand is borrowed; the result is owned (+1). */
|
|
231
|
+
export function dynToStrHelper(E) {
|
|
232
|
+
if (E.dynToStrFn)
|
|
233
|
+
return E.dynToStrFn;
|
|
234
|
+
const name = "sc_ds";
|
|
235
|
+
E.dynToStrFn = name;
|
|
236
|
+
E.walkerProtos.push(`static void ${name}_buf(ScrJsonBuf *b, const ScrDyn *d); /* String(unknown) walker */`, `static ScrStr *${name}(const ScrDyn *d); /* String(unknown) */`);
|
|
237
|
+
E.walkerDefs.push(`static void ${name}_buf(ScrJsonBuf *b, const ScrDyn *d) { /* String(unknown), recursive */`, ` switch (d->kind) {`, ` case SCR_DYN_UNDEF: scr_jb_puts(b, "undefined"); break;`, ` case SCR_DYN_NULL: scr_jb_puts(b, "null"); break;`, ` case SCR_DYN_BOOL: scr_jb_puts(b, d->v.b ? "true" : "false"); break;`, ` case SCR_DYN_NUM: {`, ` ScrStr *s = scr_f64_to_scrstr(d->v.num); /* String(n): NaN/Infinity spelled out, not JSON null */`, ` for (size_t i = 0; i < s->len; i++) scr_jb_putc(b, s->data[i]);`, ` scr_str_release(s);`, ` break;`, ` }`, ` case SCR_DYN_STR:`, ` for (size_t i = 0; i < d->v.str->len; i++) scr_jb_putc(b, d->v.str->data[i]);`, ` break;`, ` case SCR_DYN_ARR:`, ` /* Array.prototype.toString: join(",") — null/undefined ELEMENTS`, ` * print empty (unlike top level), nested arrays flatten. */`, ` for (size_t i = 0; i < d->v.arr.len; i++) {`, ` if (i > 0) scr_jb_putc(b, ',');`, ` const ScrDyn *e = d->v.arr.items[i];`, ` if (e->kind == SCR_DYN_UNDEF || e->kind == SCR_DYN_NULL) continue;`, ` ${name}_buf(b, e);`, ` }`, ` break;`, ` case SCR_DYN_OBJ:`, ` if (scr_dyn_obj_get(d, "%error", 6)) {`, ` /* The DOM's error encoding (caughtToDyn): Error.prototype`, ` * .toString over the encoded name/message — Node's String(err),`, ` * which carries no stack either. */`, ` const ScrDyn *en = scr_dyn_obj_get(d, "name", 4);`, ` const ScrDyn *em = scr_dyn_obj_get(d, "message", 7);`, ` const ScrStr *ens = (en && en->kind == SCR_DYN_STR) ? en->v.str : NULL;`, ` const ScrStr *ems = (em && em->kind == SCR_DYN_STR) ? em->v.str : NULL;`, ` if (ens) for (size_t i = 0; i < ens->len; i++) scr_jb_putc(b, ens->data[i]);`, ` if (ens && ens->len && ems && ems->len) scr_jb_puts(b, ": ");`, ` if (ems) for (size_t i = 0; i < ems->len; i++) scr_jb_putc(b, ems->data[i]);`, ` break;`, ` }`, ` scr_jb_puts(b, "[object Object]");`, ` break;`, ` case SCR_DYN_BYTES: {`, ` /* Buffer-flavored values (stream chunks) coerce utf8 (Node's`, ` * Buffer.toString); plain Uint8Array joins its elements. */`, ` if (d->buffer) {`, ` ScrStr *enc = scr_str_new("utf8", 4);`, ` ScrStr *txt = scr_bytes_to_str(d->v.bytes, enc);`, ` scr_str_release(enc);`, ` for (size_t i = 0; i < txt->len; i++) scr_jb_putc(b, txt->data[i]);`, ` scr_str_release(txt);`, ` break;`, ` }`, ` for (size_t i = 0; i < d->v.bytes->len; i++) {`, ` if (i > 0) scr_jb_putc(b, ',');`, ` char n[16];`, ` snprintf(n, sizeof n, "%u", (unsigned)d->v.bytes->data[i]);`, ` scr_jb_puts(b, n);`, ` }`, ` break;`, ` }`, ` case SCR_DYN_FUNC:`, ` /* Function.prototype.toString: the SOURCE is gone in a compiled`, ` * program, so the native-code form — exactly what JS engines`, ` * print for their own non-JS functions. */`, ` scr_jb_puts(b, "function ");`, ` if (d->v.fn.name) scr_jb_puts(b, d->v.fn.name);`, ` scr_jb_puts(b, "() { [native code] }");`, ` break;`, ` case SCR_DYN_HANDLE:`, ` /* Object.prototype.toString — Node's String() over these`, ` * classes (IncomingMessage/ServerResponse/Socket). */`, ` scr_jb_puts(b, "[object Object]");`, ` break;`, ` case SCR_DYN_PROMISE:`, ` /* Object.prototype.toString with the Promise @@toStringTag. */`, ` scr_jb_puts(b, "[object Promise]");`, ` break;`, ` }`, `}`, `static ScrStr *${name}(const ScrDyn *d) { /* String(unknown) -> owned (+1) */`, ` if (d->kind == SCR_DYN_STR) return scr_str_retain(d->v.str);`, ` ScrJsonBuf b;`, ` scr_jb_init(&b);`, ` ${name}_buf(&b, d);`, ` return scr_jb_finish(&b);`, `}`, ``);
|
|
238
|
+
return name;
|
|
239
|
+
}
|
|
240
|
+
/** The caught→dyn converter (interned once, type-independent): a catch
|
|
241
|
+
* binding flowing into an `unknown` slot — the typed→unknown deep-copy
|
|
242
|
+
* stance over the exception snapshot's runtime kind. Scalar payloads
|
|
243
|
+
* convert exactly; an Error-family OBJ payload becomes the DOM's error
|
|
244
|
+
* encoding — the reserved "%error" marker plus name/message (and code
|
|
245
|
+
* when stamped), so `instanceof Error`, the %Error extraction, and
|
|
246
|
+
* String() answer like Node; every other payload (REF — records, arrays,
|
|
247
|
+
* closures, unions — and non-Error hierarchy objects, type-erased at
|
|
248
|
+
* runtime) becomes an EMPTY DOM object (SEMANTICS.md 67). Borrows the
|
|
249
|
+
* snapshot; the result is a fresh tree (+1). Never throws. */
|
|
250
|
+
export function caughtToDynHelper(E) {
|
|
251
|
+
if (E.caughtToDynFn)
|
|
252
|
+
return E.caughtToDynFn;
|
|
253
|
+
const name = "sc_cd";
|
|
254
|
+
E.caughtToDynFn = name;
|
|
255
|
+
const sig = `static ScrDyn *${name}(const ScrCaught *c)`;
|
|
256
|
+
E.walkerProtos.push(`${sig}; /* caught -> unknown */`);
|
|
257
|
+
E.walkerDefs.push(`${sig} { /* caught -> unknown (+1, fresh tree) */`, ` switch (c->kind) {`, ` case SCR_EXC_F64: return scr_dyn_new_num(c->f64);`, ` case SCR_EXC_BOOL: return scr_dyn_new_bool(c->b);`, ` case SCR_EXC_STR: return scr_dyn_new_str((ScrStr *)c->payload); /* _new_str retains */`, ` case SCR_EXC_OBJ:`, ` if (scr_error_is(c->payload)) {`, ` /* The identity-cached crossing (scr_json.c): the SAME error`, ` * instance boxes to ONE DOM node however it crosses, so a`, ` * caught dyn compares reference-equal to the thrown error's`, ` * other boxings, like Node. */`, ` return scr_dyn_from_error((const ScrError *)c->payload);`, ` }`, ` /* FALLTHROUGH: a non-Error hierarchy object is type-erased */`, ` case SCR_EXC_REF:`, ` /* A thrown DOM value passes back BY REFERENCE (identity with`, ` * every other holder of the node — the traced-throw shape). */`, ` if (c->retain_fn == scr_dyn_retain_v) return scr_dyn_retain((ScrDyn *)c->payload);`, ` /* FALLTHROUGH */`, ` default:`, ` /* non-dyn REF and non-Error objects: the "[object Object]"`, ` * approximation — truthy, typeof "object", fields unreadable. */`, ` return scr_dyn_new_obj();`, ` }`, `}`, ``);
|
|
258
|
+
return name;
|
|
259
|
+
}
|
|
260
|
+
/** The pretty-print re-indenter (interned once, type-independent):
|
|
261
|
+
* `JSON.stringify(v, null, space)` output as a REWRITE of the compact
|
|
262
|
+
* serializer text — Node's gap algorithm exactly. Structural '{'/'[' open
|
|
263
|
+
* a newline + one-deeper indent (unless immediately closed: `{}` and `[]`
|
|
264
|
+
* stay inline, like Node), '}'/']' close onto their own line at the outer
|
|
265
|
+
* depth, ',' breaks the line at the current depth, and the key ':' gains
|
|
266
|
+
* one space. String state (with escape skipping) keeps literal braces,
|
|
267
|
+
* commas, and colons inside JSON strings untouched. Compact input is
|
|
268
|
+
* BORROWED; the result is a fresh string (+1). An empty indent (space 0,
|
|
269
|
+
* "", null) never reaches here — the frontend drops the property. */
|
|
270
|
+
export function jsonIndentHelper(E) {
|
|
271
|
+
if (E.jsonIndentFn)
|
|
272
|
+
return E.jsonIndentFn;
|
|
273
|
+
const name = "sc_ji";
|
|
274
|
+
E.jsonIndentFn = name;
|
|
275
|
+
const sig = `static ScrStr *${name}(ScrStr *compact, const char *indent, size_t ilen)`;
|
|
276
|
+
E.walkerProtos.push(`${sig}; /* stringify space re-indent */`);
|
|
277
|
+
E.walkerDefs.push(`${sig} { /* stringify space re-indent (Node's gap algorithm) */`, ` ScrJsonBuf b;`, ` scr_jb_init(&b);`, ` const char *s = compact->data;`, ` size_t n = compact->len;`, ` size_t depth = 0;`, ` bool instr = false;`, `#define SCR_JI_INDENT() do { \\`, ` scr_jb_putc(&b, '\\n'); \\`, ` for (size_t td = 0; td < depth; td++) \\`, ` for (size_t tk = 0; tk < ilen; tk++) scr_jb_putc(&b, indent[tk]); \\`, ` } while (0)`, ` for (size_t i = 0; i < n; i++) {`, ` char c = s[i];`, ` if (instr) {`, ` scr_jb_putc(&b, c);`, ` if (c == '\\\\' && i + 1 < n) { scr_jb_putc(&b, s[++i]); }`, ` else if (c == '"') instr = false;`, ` continue;`, ` }`, ` switch (c) {`, ` case '"': instr = true; scr_jb_putc(&b, c); break;`, ` case '{': case '[':`, ` scr_jb_putc(&b, c);`, ` if (i + 1 < n && s[i + 1] == (c == '{' ? '}' : ']')) {`, ` scr_jb_putc(&b, s[++i]); /* empty {} / [] stay inline, like Node */`, ` break;`, ` }`, ` depth++;`, ` SCR_JI_INDENT();`, ` break;`, ` case '}': case ']':`, ` depth--;`, ` SCR_JI_INDENT();`, ` scr_jb_putc(&b, c);`, ` break;`, ` case ',':`, ` scr_jb_putc(&b, ',');`, ` SCR_JI_INDENT();`, ` break;`, ` case ':':`, ` scr_jb_putc(&b, ':');`, ` scr_jb_putc(&b, ' ');`, ` break;`, ` default:`, ` scr_jb_putc(&b, c);`, ` }`, ` }`, `#undef SCR_JI_INDENT`, ` return scr_jb_finish(&b);`, `}`, ``);
|
|
278
|
+
return name;
|
|
279
|
+
}
|
|
280
|
+
export function jsonWriteHelper(E, t) {
|
|
281
|
+
const key = typeKey(t);
|
|
282
|
+
const existing = E.jsonWriters.get(key);
|
|
283
|
+
if (existing)
|
|
284
|
+
return existing;
|
|
285
|
+
const name = `sc_jw_${E.jsonWriters.size}`;
|
|
286
|
+
E.jsonWriters.set(key, name);
|
|
287
|
+
const sig = `static void ${name}(ScrJsonBuf *b, ${cDecl(t, "v")})`;
|
|
288
|
+
E.walkerProtos.push(`${sig}; /* stringify ${key} */`);
|
|
289
|
+
const d = [`${sig} { /* stringify ${key} */`];
|
|
290
|
+
switch (t.kind) {
|
|
291
|
+
case "f64":
|
|
292
|
+
d.push(` scr_jb_put_f64(b, v); /* NaN/Infinity -> null, -0 -> 0, like JS */`);
|
|
293
|
+
break;
|
|
294
|
+
case "bool":
|
|
295
|
+
d.push(` scr_jb_puts(b, v ? "true" : "false");`);
|
|
296
|
+
break;
|
|
297
|
+
case "string":
|
|
298
|
+
d.push(` scr_jb_put_json_str(b, v);`);
|
|
299
|
+
break;
|
|
300
|
+
case "record": {
|
|
301
|
+
const shape = E.recordsById.get(t.shapeId);
|
|
302
|
+
if (!shape)
|
|
303
|
+
throw new Error(`emitter bug: jsonStringify of unknown shape ${t.shapeId}`);
|
|
304
|
+
// A tuple serializes as a JSON ARRAY in index order — JS-exact
|
|
305
|
+
// (JSON.stringify(["a", 1]) is `["a",1]`). Every position is
|
|
306
|
+
// required, so labels/commas are static like all-required records.
|
|
307
|
+
if (shape.tuple) {
|
|
308
|
+
const byIndex = [...shape.fields].sort((a, b) => Number(a.name) - Number(b.name));
|
|
309
|
+
d.push(` scr_jb_putc(b, '[');`);
|
|
310
|
+
byIndex.forEach((f, i) => {
|
|
311
|
+
if (i > 0)
|
|
312
|
+
d.push(` scr_jb_putc(b, ',');`);
|
|
313
|
+
d.push(` ${E.jsonWriteHelper(f.type)}(b, v->${mangleField(f.name)});`);
|
|
314
|
+
});
|
|
315
|
+
d.push(` scr_jb_putc(b, ']');`);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
// Fields serialize in DECLARED order — JS insertion order, exactly
|
|
319
|
+
// Node for objects constructed in declaration order (SEMANTICS.md
|
|
320
|
+
// 36 documents the divergence when they are not) — never the
|
|
321
|
+
// canonical (sorted) struct order. declaredOrder may omit internal
|
|
322
|
+
// '%'-fields (Dirent's %dtype): those are hidden from JSON exactly
|
|
323
|
+
// like every other key-order surface. Shapes interned WITHOUT an
|
|
324
|
+
// order (the CJS export-table literal, accessor-narrowed JS
|
|
325
|
+
// shapes) keep the canonical order — their construction paths
|
|
326
|
+
// document the divergence.
|
|
327
|
+
const order = shape.declaredOrder ?? shape.fields.map((f) => f.name);
|
|
328
|
+
const inOrder = new Set(order);
|
|
329
|
+
if (shape.fields.some((f) => !inOrder.has(f.name) && !f.name.startsWith("%"))) {
|
|
330
|
+
throw new Error(`emitter bug: declaredOrder of shape ${t.shapeId} omits a non-internal field`);
|
|
331
|
+
}
|
|
332
|
+
const byName = new Map(shape.fields.map((f) => [f.name, f]));
|
|
333
|
+
const emitFields = order.map((n) => byName.get(n)).filter((f) => f !== undefined);
|
|
334
|
+
// Optional-flavored fields (undefined-armed unions) DROP from the
|
|
335
|
+
// output while they hold the undefined arm — exactly Node — so as
|
|
336
|
+
// soon as one exists, comma placement turns dynamic (a `first`
|
|
337
|
+
// flag); all-required shapes keep the static prefix labels. An
|
|
338
|
+
// overflow portion forces the dynamic path too (entry count is
|
|
339
|
+
// runtime state).
|
|
340
|
+
const droppable = emitFields.some((f) => E.undefinedArmTag(f.type) >= 0) || !!shape.indexValue;
|
|
341
|
+
d.push(` scr_jb_putc(b, '{');`);
|
|
342
|
+
if (!droppable) {
|
|
343
|
+
emitFields.forEach((f, i) => {
|
|
344
|
+
const label = cStringLiteral(Buffer.from(`${i > 0 ? "," : ""}"${f.name}":`, "utf8"));
|
|
345
|
+
d.push(` scr_jb_puts(b, ${label});`);
|
|
346
|
+
// Refcounted fields are BORROWED straight off the struct (the
|
|
347
|
+
// record itself is borrowed for the whole call).
|
|
348
|
+
d.push(` ${E.jsonWriteHelper(f.type)}(b, v->${mangleField(f.name)});`);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
d.push(` bool first = true;`);
|
|
353
|
+
for (const f of emitFields) {
|
|
354
|
+
const label = cStringLiteral(Buffer.from(`"${f.name}":`, "utf8"));
|
|
355
|
+
const utag = E.undefinedArmTag(f.type);
|
|
356
|
+
const pad = utag >= 0 ? " " : " ";
|
|
357
|
+
if (utag >= 0) {
|
|
358
|
+
d.push(` if (v->${mangleField(f.name)}->tag != ${utag}) { /* undefined-valued field: dropped, like Node */`);
|
|
359
|
+
}
|
|
360
|
+
d.push(`${pad}if (!first) scr_jb_putc(b, ',');`);
|
|
361
|
+
d.push(`${pad}first = false;`);
|
|
362
|
+
d.push(`${pad}scr_jb_puts(b, ${label});`);
|
|
363
|
+
d.push(`${pad}${E.jsonWriteHelper(f.type)}(b, v->${mangleField(f.name)});`);
|
|
364
|
+
if (utag >= 0)
|
|
365
|
+
d.push(` }`);
|
|
366
|
+
}
|
|
367
|
+
// Overflow entries follow the declared fields, themselves in JS
|
|
368
|
+
// OWN-KEY order — integer-like keys ascending first, then the
|
|
369
|
+
// rest in insertion order (scr_map_keys_js_order, the same
|
|
370
|
+
// enumeration Object.keys answers; number-keyed signatures make
|
|
371
|
+
// integer-like overflow keys ordinary). SEMANTICS.md documents
|
|
372
|
+
// the declared-canonical-then-overflow divergence from JS's one
|
|
373
|
+
// interleaved key order. Keys escape like any JSON string;
|
|
374
|
+
// undefined-valued entries drop, exactly the optional-field rule.
|
|
375
|
+
if (shape.indexValue) {
|
|
376
|
+
const iv = shape.indexValue;
|
|
377
|
+
const m = `v->${OVERFLOW_MEMBER}`;
|
|
378
|
+
d.push(` {`);
|
|
379
|
+
d.push(` ScrArr *ks = scr_map_keys_js_order(${m});`);
|
|
380
|
+
d.push(` for (size_t i = 0; i < ks->len; i++) {`);
|
|
381
|
+
d.push(` ScrStr *k = (ScrStr *)scr_arr_get_ref(ks, (double)i);`);
|
|
382
|
+
if (iv.kind === "f64" || iv.kind === "bool") {
|
|
383
|
+
d.push(` ${cDecl(iv, "e")} = 0;`);
|
|
384
|
+
d.push(` scr_map_get_str_${iv.kind === "f64" ? "f64" : "bool"}(${m}, k, &e);`);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
d.push(` ${cDecl(iv, "e")} = (${cType(iv).trim()})scr_map_get_str_ref(${m}, k);`);
|
|
388
|
+
}
|
|
389
|
+
const skipUndef = iv.kind === "dyn"
|
|
390
|
+
? `e->kind == SCR_DYN_UNDEF`
|
|
391
|
+
: E.undefinedArmTag(iv) >= 0
|
|
392
|
+
? `e->tag == ${E.undefinedArmTag(iv)}`
|
|
393
|
+
: null;
|
|
394
|
+
if (skipUndef) {
|
|
395
|
+
d.push(` if (${skipUndef}) { /* undefined-valued entry: dropped, like Node */`);
|
|
396
|
+
d.push(` ${releaseCallC(iv, "e")};`);
|
|
397
|
+
d.push(` scr_str_release(k);`);
|
|
398
|
+
d.push(` continue;`);
|
|
399
|
+
d.push(` }`);
|
|
400
|
+
}
|
|
401
|
+
d.push(` if (!first) scr_jb_putc(b, ',');`);
|
|
402
|
+
d.push(` first = false;`);
|
|
403
|
+
d.push(` scr_jb_put_json_str(b, k);`);
|
|
404
|
+
d.push(` scr_jb_putc(b, ':');`);
|
|
405
|
+
d.push(` ${E.jsonWriteHelper(iv)}(b, e);`);
|
|
406
|
+
d.push(` scr_str_release(k);`);
|
|
407
|
+
if (isRefCounted(iv))
|
|
408
|
+
d.push(` ${releaseCallC(iv, "e")};`);
|
|
409
|
+
d.push(` }`);
|
|
410
|
+
d.push(` scr_arr_release(ks);`);
|
|
411
|
+
d.push(` }`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
d.push(` scr_jb_putc(b, '}');`);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
case "dyn":
|
|
418
|
+
// Overflow values under an `unknown` index signature: the DOM
|
|
419
|
+
// serializes itself (runtime walker) — object members holding
|
|
420
|
+
// undefined drop, array slots holding undefined print null,
|
|
421
|
+
// exactly Node. Bare JSON.stringify of dyn stays frontend-fenced;
|
|
422
|
+
// this writer is reachable only through overflow entries.
|
|
423
|
+
d.push(` scr_jb_put_dyn(b, v);`);
|
|
424
|
+
break;
|
|
425
|
+
case "array": {
|
|
426
|
+
const elem = t.elem;
|
|
427
|
+
const w = E.jsonWriteHelper(elem);
|
|
428
|
+
d.push(` scr_jb_putc(b, '[');`);
|
|
429
|
+
d.push(` for (size_t i = 0; i < v->len; i++) {`);
|
|
430
|
+
d.push(` if (i > 0) scr_jb_putc(b, ',');`);
|
|
431
|
+
if (elem.kind === "f64") {
|
|
432
|
+
d.push(` ${w}(b, scr_arr_get_f64(v, (double)i));`);
|
|
433
|
+
}
|
|
434
|
+
else if (elem.kind === "bool") {
|
|
435
|
+
d.push(` ${w}(b, scr_arr_get_bool(v, (double)i));`);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
// _get_ref returns +1; release after writing.
|
|
439
|
+
d.push(` ${cDecl(elem, "e")} = (${cType(elem).trim()})scr_arr_get_ref(v, (double)i);`);
|
|
440
|
+
d.push(` ${w}(b, e);`);
|
|
441
|
+
d.push(` ${releaseCallC(elem, "e")};`);
|
|
442
|
+
}
|
|
443
|
+
d.push(` }`);
|
|
444
|
+
d.push(` scr_jb_putc(b, ']');`);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
case "union": {
|
|
448
|
+
const def = E.unionsById.get(t.unionId);
|
|
449
|
+
if (!def)
|
|
450
|
+
throw new Error(`emitter bug: jsonStringify of unknown union ${t.unionId}`);
|
|
451
|
+
d.push(` switch (v->tag) {`);
|
|
452
|
+
def.arms.forEach((arm, i) => {
|
|
453
|
+
if (arm.kind === "nullT") {
|
|
454
|
+
// Payload-less arm: JSON.stringify(null) is the text `null`,
|
|
455
|
+
// exactly like Node.
|
|
456
|
+
d.push(` case ${i}: scr_jb_puts(b, "null"); break;`);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (arm.kind === "undefinedT") {
|
|
460
|
+
// Reachable only as a record FIELD's serializer (bare
|
|
461
|
+
// undefined-armed unions are fenced from stringify), and the
|
|
462
|
+
// record writer drops the field while it holds this tag before
|
|
463
|
+
// calling — so the tag can never arrive here.
|
|
464
|
+
d.push(` case ${i}: /* undefined arm: the field dropped at the record level */`);
|
|
465
|
+
d.push(` fputs("scriptc: internal error: stringify reached an undefined arm\\n", stderr); abort();`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const w = E.jsonWriteHelper(arm);
|
|
469
|
+
if (arm.kind === "f64") {
|
|
470
|
+
d.push(` case ${i}: ${w}(b, scr_union_get_f64(v)); break;`);
|
|
471
|
+
}
|
|
472
|
+
else if (arm.kind === "bool") {
|
|
473
|
+
d.push(` case ${i}: ${w}(b, scr_union_get_bool(v)); break;`);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
// Payload is BORROWED out of the box for the write.
|
|
477
|
+
d.push(` case ${i}: ${w}(b, (${cType(arm).trim()})scr_union_peek(v)); break;`);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
d.push(` default: fputs("scriptc: internal error: invalid union tag\\n", stderr); abort();`);
|
|
481
|
+
d.push(` }`);
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
default:
|
|
485
|
+
throw new Error(`emitter bug: jsonStringify of non-JSON type ${t.kind}`);
|
|
486
|
+
}
|
|
487
|
+
d.push(`}`, ``);
|
|
488
|
+
E.walkerDefs.push(...d);
|
|
489
|
+
return name;
|
|
490
|
+
}
|
|
491
|
+
/** The emitted match predicate for one dynCheck target type:
|
|
492
|
+
* `static bool sc_dm_<n>(const ScrDyn *d)` — does this DOM fit T?
|
|
493
|
+
* Never throws, builds nothing; union builders use it to try arms in
|
|
494
|
+
* canonical order (first FULL match wins). Records are width-tolerant
|
|
495
|
+
* here too: extra keys are ignored, only declared fields are examined. */
|
|
496
|
+
export function dynMatchHelper(E, t) {
|
|
497
|
+
const key = typeKey(t);
|
|
498
|
+
const existing = E.dynMatchers.get(key);
|
|
499
|
+
if (existing)
|
|
500
|
+
return existing;
|
|
501
|
+
const name = `sc_dm_${E.dynMatchers.size}`;
|
|
502
|
+
E.dynMatchers.set(key, name);
|
|
503
|
+
const sig = `static bool ${name}(const ScrDyn *d)`;
|
|
504
|
+
E.walkerProtos.push(`${sig}; /* matches ${key} */`);
|
|
505
|
+
const d = [`${sig} { /* matches ${key} */`];
|
|
506
|
+
switch (t.kind) {
|
|
507
|
+
case "f64":
|
|
508
|
+
d.push(` return d->kind == SCR_DYN_NUM;`);
|
|
509
|
+
break;
|
|
510
|
+
case "string":
|
|
511
|
+
d.push(` return d->kind == SCR_DYN_STR;`);
|
|
512
|
+
break;
|
|
513
|
+
case "bool":
|
|
514
|
+
d.push(` return d->kind == SCR_DYN_BOOL;`);
|
|
515
|
+
break;
|
|
516
|
+
case "nullT":
|
|
517
|
+
// JSON null matches exactly the nullT arm (reachable only as a
|
|
518
|
+
// union-arm matcher — bare null is not a valid dynCheck target).
|
|
519
|
+
d.push(` return d->kind == SCR_DYN_NULL;`);
|
|
520
|
+
break;
|
|
521
|
+
case "undefinedT":
|
|
522
|
+
// JSON text never parses to undefined, but the DOM can HOLD it now
|
|
523
|
+
// (overflow reads/conversions under `unknown` index signatures):
|
|
524
|
+
// the undefined DOM value matches exactly the undefined arm.
|
|
525
|
+
d.push(` return d->kind == SCR_DYN_UNDEF;`);
|
|
526
|
+
break;
|
|
527
|
+
case "dyn":
|
|
528
|
+
// An `unknown` target: every DOM value fits, undefined included.
|
|
529
|
+
d.push(` (void)d;`);
|
|
530
|
+
d.push(` return true;`);
|
|
531
|
+
break;
|
|
532
|
+
case "bytes":
|
|
533
|
+
// A Uint8Array target (the DOM carries u8 payloads only).
|
|
534
|
+
if (t.elem !== "u8")
|
|
535
|
+
throw new Error(`emitter bug: dynMatch of bytes<${t.elem}>`);
|
|
536
|
+
d.push(` return d->kind == SCR_DYN_BYTES;`);
|
|
537
|
+
break;
|
|
538
|
+
case "record": {
|
|
539
|
+
const shape = E.recordsById.get(t.shapeId);
|
|
540
|
+
if (!shape)
|
|
541
|
+
throw new Error(`emitter bug: dynCheck of unknown shape ${t.shapeId}`);
|
|
542
|
+
// A tuple matches a JSON ARRAY of EXACTLY its arity whose elements
|
|
543
|
+
// match positionally (arity is part of the type — width tolerance
|
|
544
|
+
// is an object-key concept and does not apply).
|
|
545
|
+
if (shape.tuple) {
|
|
546
|
+
const byIndex = [...shape.fields].sort((a, b) => Number(a.name) - Number(b.name));
|
|
547
|
+
d.push(` if (d->kind != SCR_DYN_ARR || d->v.arr.len != ${byIndex.length}) return false;`);
|
|
548
|
+
byIndex.forEach((f, i) => {
|
|
549
|
+
d.push(` if (!${E.dynMatchHelper(f.type)}(d->v.arr.items[${i}])) return false;`);
|
|
550
|
+
});
|
|
551
|
+
d.push(` return true;`);
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
d.push(` if (d->kind != SCR_DYN_OBJ) return false;`);
|
|
555
|
+
// dyn ('unknown') fields match ANY value, present or missing (a
|
|
556
|
+
// missing key builds the undefined DOM value) — no test to emit.
|
|
557
|
+
if (shape.fields.some((f) => f.type.kind !== "dyn"))
|
|
558
|
+
d.push(` const ScrDyn *m;`);
|
|
559
|
+
for (const f of shape.fields) {
|
|
560
|
+
if (f.type.kind === "dyn")
|
|
561
|
+
continue;
|
|
562
|
+
const keyLit = cStringLiteral(Buffer.from(f.name, "utf8"));
|
|
563
|
+
const keyLen = Buffer.byteLength(f.name, "utf8");
|
|
564
|
+
d.push(` m = scr_dyn_obj_get(d, ${keyLit}, ${keyLen});`);
|
|
565
|
+
if (E.undefinedArmTag(f.type) >= 0) {
|
|
566
|
+
// Optional-flavored field: a MISSING key is the undefined arm
|
|
567
|
+
// (a match); a PRESENT key must fit the union as usual.
|
|
568
|
+
d.push(` if (m && !${E.dynMatchHelper(f.type)}(m)) return false;`);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
d.push(` if (!m || !${E.dynMatchHelper(f.type)}(m)) return false;`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Index-signature shapes: UNDECLARED keys must fit the overflow
|
|
575
|
+
// value type (the builder CAPTURES them — width tolerance became
|
|
576
|
+
// width capture). A dyn value type accepts anything; concrete
|
|
577
|
+
// types check every extra entry.
|
|
578
|
+
if (shape.indexValue && shape.indexValue.kind !== "dyn") {
|
|
579
|
+
const skip = shape.fields
|
|
580
|
+
.map((f) => {
|
|
581
|
+
const keyLit = cStringLiteral(Buffer.from(f.name, "utf8"));
|
|
582
|
+
const keyLen = Buffer.byteLength(f.name, "utf8");
|
|
583
|
+
return `(e->key_len == ${keyLen} && memcmp(e->key, ${keyLit}, ${keyLen}) == 0)`;
|
|
584
|
+
})
|
|
585
|
+
.join(" || ");
|
|
586
|
+
d.push(` for (size_t i = 0; i < d->v.obj.len; i++) {`);
|
|
587
|
+
d.push(` const ScrDynEntry *e = &d->v.obj.entries[i];`);
|
|
588
|
+
if (shape.fields.length > 0)
|
|
589
|
+
d.push(` if (${skip}) continue;`);
|
|
590
|
+
d.push(` if (!${E.dynMatchHelper(shape.indexValue)}(e->value)) return false;`);
|
|
591
|
+
d.push(` }`);
|
|
592
|
+
}
|
|
593
|
+
d.push(` return true;`);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case "array": {
|
|
597
|
+
const m = E.dynMatchHelper(t.elem);
|
|
598
|
+
d.push(` if (d->kind != SCR_DYN_ARR) return false;`);
|
|
599
|
+
d.push(` for (size_t i = 0; i < d->v.arr.len; i++) {`);
|
|
600
|
+
d.push(` if (!${m}(d->v.arr.items[i])) return false;`);
|
|
601
|
+
d.push(` }`);
|
|
602
|
+
d.push(` return true;`);
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case "union": {
|
|
606
|
+
const def = E.unionsById.get(t.unionId);
|
|
607
|
+
if (!def)
|
|
608
|
+
throw new Error(`emitter bug: dynCheck of unknown union ${t.unionId}`);
|
|
609
|
+
// The undefined arm participates: parsed JSON never contains
|
|
610
|
+
// undefined (a MISSING record key was the arm's only source), but
|
|
611
|
+
// the DOM can hold the undefined value now — overflow entries
|
|
612
|
+
// under `unknown` index signatures — and it matches exactly the
|
|
613
|
+
// undefined arm.
|
|
614
|
+
const arms = def.arms.map((a) => `${E.dynMatchHelper(a)}(d)`);
|
|
615
|
+
d.push(` return ${arms.join(" || ")};`);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
default:
|
|
619
|
+
throw new Error(`emitter bug: dynMatch of non-JSON type ${t.kind}`);
|
|
620
|
+
}
|
|
621
|
+
d.push(`}`, ``);
|
|
622
|
+
E.walkerDefs.push(...d);
|
|
623
|
+
return name;
|
|
624
|
+
}
|
|
625
|
+
/** The emitted keyed read on a dyn DOM value:
|
|
626
|
+
* `static ScrDyn *sc_dyn_key_get(ScrDyn *d, ScrStr *k, bool opt)` —
|
|
627
|
+
* OBJ answers the member (+1) or the undefined singleton (own-property
|
|
628
|
+
* answer); ARR answers `length` and canonical in-range indices, STR
|
|
629
|
+
* answers `length` (UTF-16-exact via scr_str_utf16_len), both
|
|
630
|
+
* undefined otherwise; NUM/BOOL/BYTES answer undefined. undefined/null
|
|
631
|
+
* receivers THROW the catchable Node-shaped TypeError, or answer the
|
|
632
|
+
* undefined singleton when `opt` is set (a `?.` step). Borrows d and k;
|
|
633
|
+
* the result is +1. Interned once (the memo key can never collide with
|
|
634
|
+
* a typeKey — no IR type spells "%"). */
|
|
635
|
+
/** RequireObjectCoercible with V8's destructuring TypeError (the
|
|
636
|
+
* dynDestrCheck node): nullish throws "Cannot destructure 'SPELL' as it
|
|
637
|
+
* is undefined." (or "…null."), the property form "Cannot destructure
|
|
638
|
+
* property 'PROP' of 'SPELL' …" when firstProp is non-NULL — V8's exact
|
|
639
|
+
* strings, source spelling included. Non-nullish values pass silently. */
|
|
640
|
+
export function dynDestrCheckHelper(E) {
|
|
641
|
+
const memoKey = "%dynDestrCheck";
|
|
642
|
+
const existing = E.dynBuilders.get(memoKey);
|
|
643
|
+
if (existing)
|
|
644
|
+
return existing;
|
|
645
|
+
const name = "sc_dyn_destr_check";
|
|
646
|
+
E.dynBuilders.set(memoKey, name);
|
|
647
|
+
const sig = `static void ${name}(const ScrDyn *d, const char *spell, const char *firstProp)`;
|
|
648
|
+
E.walkerProtos.push(`${sig}; /* destructuring RequireObjectCoercible */`);
|
|
649
|
+
E.walkerDefs.push(`${sig} { /* destructuring RequireObjectCoercible */`, ` if (d->kind != SCR_DYN_UNDEF && d->kind != SCR_DYN_NULL) return;`, ` ScrJsonBuf b;`, ` scr_jb_init(&b);`, ` if (firstProp) {`, ` scr_jb_puts(&b, "Cannot destructure property '");`, ` scr_jb_puts(&b, firstProp);`, ` scr_jb_puts(&b, "' of '");`, ` scr_jb_puts(&b, spell);`, ` scr_jb_puts(&b, "' as it is ");`, ` } else {`, ` scr_jb_puts(&b, "Cannot destructure '");`, ` scr_jb_puts(&b, spell);`, ` scr_jb_puts(&b, "' as it is ");`, ` }`, ` scr_jb_puts(&b, d->kind == SCR_DYN_UNDEF ? "undefined." : "null.");`, ` scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));`, `}`, ``);
|
|
650
|
+
return name;
|
|
651
|
+
}
|
|
652
|
+
/** GetIterator + first-N steps over a DOM value (the dynIterN node), as
|
|
653
|
+
* array destructuring sees it: arrays step by index, strings by CODE
|
|
654
|
+
* POINT (the string iterator — astral chars arrive unsplit), Buffers by
|
|
655
|
+
* byte; everything else throws V8's exact not-iterable TypeError
|
|
656
|
+
* ("undefined is not iterable …", "object null is not iterable …",
|
|
657
|
+
* "number 1 is not iterable …", "boolean true …", "object …",
|
|
658
|
+
* "function …" — each with the "(cannot read property
|
|
659
|
+
* Symbol(Symbol.iterator))" tail). Returns a fresh +1 DOM array of
|
|
660
|
+
* exactly n elements, undefined-padded past the end. */
|
|
661
|
+
export function dynIterNHelper(E) {
|
|
662
|
+
const memoKey = "%dynIterN";
|
|
663
|
+
const existing = E.dynBuilders.get(memoKey);
|
|
664
|
+
if (existing)
|
|
665
|
+
return existing;
|
|
666
|
+
const name = "sc_dyn_iter_n";
|
|
667
|
+
E.dynBuilders.set(memoKey, name);
|
|
668
|
+
const sig = `static ScrDyn *${name}(const ScrDyn *d, size_t n)`;
|
|
669
|
+
E.walkerProtos.push(`${sig}; /* destructuring GetIterator + N steps */`);
|
|
670
|
+
E.walkerDefs.push(`${sig} { /* destructuring GetIterator + N steps */`, ` if (d->kind != SCR_DYN_ARR && d->kind != SCR_DYN_STR && d->kind != SCR_DYN_BYTES) {`, ` ScrJsonBuf b;`, ` scr_jb_init(&b);`, ` switch (d->kind) {`, ` case SCR_DYN_UNDEF: scr_jb_puts(&b, "undefined"); break;`, ` case SCR_DYN_NULL: scr_jb_puts(&b, "object null"); break;`, ` case SCR_DYN_BOOL: scr_jb_puts(&b, d->v.b ? "boolean true" : "boolean false"); break;`, ` case SCR_DYN_NUM: {`, ` scr_jb_puts(&b, "number ");`, ` ScrStr *s = scr_f64_to_scrstr(d->v.num);`, ` for (size_t i = 0; i < s->len; i++) scr_jb_putc(&b, s->data[i]);`, ` scr_str_release(s);`, ` break;`, ` }`, ` case SCR_DYN_FUNC: scr_jb_puts(&b, "function"); break;`, ` default: scr_jb_puts(&b, "object"); break;`, ` }`, ` scr_jb_puts(&b, " is not iterable (cannot read property Symbol(Symbol.iterator))");`, ` scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));`, ` return NULL;`, ` }`, ` ScrDyn *out = scr_dyn_new_arr();`, ` for (size_t i = 0; i < n; i++) {`, ` ScrDyn *item = NULL;`, ` if (d->kind == SCR_DYN_ARR) {`, ` item = i < d->v.arr.len ? scr_dyn_retain(d->v.arr.items[i]) : scr_dyn_retain(scr_dyn_undefined());`, ` } else if (d->kind == SCR_DYN_BYTES) {`, ` item = i < d->v.bytes->len ? scr_dyn_new_num((double)d->v.bytes->data[i]) : scr_dyn_retain(scr_dyn_undefined());`, ` } else {`, ` /* String iteration: whole code POINTS (astral chars arrive`, ` * unsplit — the string iterator, not charAt). */`, ` double len = scr_str_utf16_len(d->v.str);`, ` double at = 0;`, ` for (size_t step = 0; step < i && at < len; step++) {`, ` ScrStr *cp = scr_str_cp_at(d->v.str, at);`, ` at += scr_str_utf16_len(cp);`, ` scr_str_release(cp);`, ` }`, ` if (at < len) {`, ` ScrStr *cp = scr_str_cp_at(d->v.str, at);`, ` item = scr_dyn_new_str(cp);`, ` scr_str_release(cp);`, ` } else {`, ` item = scr_dyn_retain(scr_dyn_undefined());`, ` }`, ` }`, ` scr_dyn_arr_push(out, item); /* push takes ownership */`, ` }`, ` return out;`, `}`, ``);
|
|
671
|
+
return name;
|
|
672
|
+
}
|
|
673
|
+
export function dynKeyGetHelper(E) {
|
|
674
|
+
const memoKey = "%dynKeyGet";
|
|
675
|
+
const existing = E.dynBuilders.get(memoKey);
|
|
676
|
+
if (existing)
|
|
677
|
+
return existing;
|
|
678
|
+
const name = "sc_dyn_key_get";
|
|
679
|
+
E.dynBuilders.set(memoKey, name);
|
|
680
|
+
const sig = `static ScrDyn *${name}(ScrDyn *d, ScrStr *k, bool opt)`;
|
|
681
|
+
E.walkerProtos.push(`${sig}; /* d[k] on dyn */`);
|
|
682
|
+
const d = [`${sig} { /* d[k] on dyn */`];
|
|
683
|
+
d.push(` if (d->kind == SCR_DYN_UNDEF || d->kind == SCR_DYN_NULL) {`);
|
|
684
|
+
d.push(` if (opt) return scr_dyn_retain(scr_dyn_undefined());`);
|
|
685
|
+
d.push(` const char *base = d->kind == SCR_DYN_UNDEF`);
|
|
686
|
+
d.push(` ? "Cannot read properties of undefined (reading '"`);
|
|
687
|
+
d.push(` : "Cannot read properties of null (reading '";`);
|
|
688
|
+
d.push(` ScrStr *head = scr_str_new(base, strlen(base));`);
|
|
689
|
+
d.push(` ScrStr *withKey = scr_str_concat(head, k);`);
|
|
690
|
+
d.push(` scr_str_release(head);`);
|
|
691
|
+
d.push(` ScrStr *tail = scr_str_new("')", 2);`);
|
|
692
|
+
d.push(` ScrStr *msg = scr_str_concat(withKey, tail);`);
|
|
693
|
+
d.push(` scr_str_release(withKey);`);
|
|
694
|
+
d.push(` scr_str_release(tail);`);
|
|
695
|
+
d.push(` scr_throw_error(SCR_ERR_TYPE, msg); /* takes ownership */`);
|
|
696
|
+
d.push(` return NULL;`);
|
|
697
|
+
d.push(` }`);
|
|
698
|
+
d.push(` if (d->kind == SCR_DYN_OBJ) {`);
|
|
699
|
+
d.push(` ScrDyn *m = scr_dyn_obj_get(d, k->data, k->len);`);
|
|
700
|
+
d.push(` return scr_dyn_retain(m ? m : scr_dyn_undefined());`);
|
|
701
|
+
d.push(` }`);
|
|
702
|
+
d.push(` if (d->kind == SCR_DYN_HANDLE) {`);
|
|
703
|
+
d.push(` /* Native handles: the tag's modeled properties (req.url,`);
|
|
704
|
+
d.push(` * res.statusCode, ...) answer boxed values through the`);
|
|
705
|
+
d.push(` * installed ops; the rest is the undefined singleton or the`);
|
|
706
|
+
d.push(` * loud not-supported ladder (scr_json.c). */`);
|
|
707
|
+
d.push(` return scr_dyn_handle_key_get(d, k);`);
|
|
708
|
+
d.push(` }`);
|
|
709
|
+
d.push(` if (d->kind == SCR_DYN_BYTES) {`);
|
|
710
|
+
d.push(` /* Buffer-shaped dyn (a stream's 'data' chunk in the JS lane):`);
|
|
711
|
+
d.push(` * .length and canonical-index byte reads answer like Node. */`);
|
|
712
|
+
d.push(` if (k->len == 6 && memcmp(k->data, "length", 6) == 0) {`);
|
|
713
|
+
d.push(` return scr_dyn_new_num((double)d->v.bytes->len);`);
|
|
714
|
+
d.push(` }`);
|
|
715
|
+
d.push(` if (k->len > 0 && !(k->len > 1 && k->data[0] == '0')) {`);
|
|
716
|
+
d.push(` size_t idx = 0; bool digits = true;`);
|
|
717
|
+
d.push(` for (size_t i = 0; i < k->len; i++) {`);
|
|
718
|
+
d.push(` if (k->data[i] < '0' || k->data[i] > '9' || idx > (SIZE_MAX - 9) / 10) { digits = false; break; }`);
|
|
719
|
+
d.push(` idx = idx * 10 + (size_t)(k->data[i] - '0');`);
|
|
720
|
+
d.push(` }`);
|
|
721
|
+
d.push(` if (digits && idx < d->v.bytes->len) return scr_dyn_new_num((double)d->v.bytes->data[idx]);`);
|
|
722
|
+
d.push(` }`);
|
|
723
|
+
d.push(` }`);
|
|
724
|
+
d.push(` if (d->kind == SCR_DYN_FUNC) {`);
|
|
725
|
+
d.push(` /* own props (defineProperties writes), then name/length —`);
|
|
726
|
+
d.push(` * the function-instance members test/common copies. */`);
|
|
727
|
+
d.push(` ScrDyn *m = scr_dyn_fn_get(d, k->data, k->len);`);
|
|
728
|
+
d.push(` if (m) return m;`);
|
|
729
|
+
d.push(` }`);
|
|
730
|
+
d.push(` if (d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_STR) {`);
|
|
731
|
+
d.push(` if (k->len == 6 && memcmp(k->data, "length", 6) == 0) {`);
|
|
732
|
+
d.push(` return scr_dyn_new_num(d->kind == SCR_DYN_ARR ? (double)d->v.arr.len : scr_str_utf16_len(d->v.str));`);
|
|
733
|
+
d.push(` }`);
|
|
734
|
+
d.push(` if (k->len > 0 && !(k->len > 1 && k->data[0] == '0')) {`);
|
|
735
|
+
d.push(` /* canonical index: digits only, no leading zero — array`);
|
|
736
|
+
d.push(` * elements, and the string's 1-code-unit char ("abc"[1] is`);
|
|
737
|
+
d.push(` * "b" in JS; out of range reads as an absent key). */`);
|
|
738
|
+
d.push(` size_t idx = 0; bool digits = true;`);
|
|
739
|
+
d.push(` for (size_t i = 0; i < k->len; i++) {`);
|
|
740
|
+
d.push(` if (k->data[i] < '0' || k->data[i] > '9' || idx > (SIZE_MAX - 9) / 10) { digits = false; break; }`);
|
|
741
|
+
d.push(` idx = idx * 10 + (size_t)(k->data[i] - '0');`);
|
|
742
|
+
d.push(` }`);
|
|
743
|
+
d.push(` if (digits && d->kind == SCR_DYN_ARR && idx < d->v.arr.len) return scr_dyn_retain(d->v.arr.items[idx]);`);
|
|
744
|
+
d.push(` if (digits && d->kind == SCR_DYN_STR && (double)idx < scr_str_utf16_len(d->v.str)) {`);
|
|
745
|
+
d.push(` ScrStr *c = scr_str_char_at(d->v.str, (double)idx);`);
|
|
746
|
+
d.push(` ScrDyn *r = scr_dyn_new_str(c);`);
|
|
747
|
+
d.push(` scr_str_release(c);`);
|
|
748
|
+
d.push(` return r;`);
|
|
749
|
+
d.push(` }`);
|
|
750
|
+
d.push(` }`);
|
|
751
|
+
d.push(` }`);
|
|
752
|
+
d.push(` return scr_dyn_retain(scr_dyn_undefined());`);
|
|
753
|
+
d.push(`}`, ``);
|
|
754
|
+
E.walkerDefs.push(...d);
|
|
755
|
+
return name;
|
|
756
|
+
}
|
|
757
|
+
/** The emitted checked builder for one dynCheck target type:
|
|
758
|
+
* `static T sc_dc_<n>(const ScrDyn *d, const ScrDynPath *path)` —
|
|
759
|
+
* validate the DOM against T and BUILD the typed value (+1), or throw a
|
|
760
|
+
* catchable, path-annotated TypeError-shaped string through the exception
|
|
761
|
+
* cell and return a dummy (0/false/NULL) with the pending flag set.
|
|
762
|
+
* Recursive calls propagate a pending exception by releasing the
|
|
763
|
+
* partially-built value and returning — the same pending-check discipline
|
|
764
|
+
* emitted function bodies follow. */
|
|
765
|
+
export function dynCheckHelper(E, t) {
|
|
766
|
+
const key = typeKey(t);
|
|
767
|
+
const existing = E.dynBuilders.get(key);
|
|
768
|
+
if (existing)
|
|
769
|
+
return existing;
|
|
770
|
+
const name = `sc_dc_${E.dynBuilders.size}`;
|
|
771
|
+
E.dynBuilders.set(key, name);
|
|
772
|
+
const sig = `static ${cType(t)}${cType(t).endsWith("*") ? "" : " "}${name}(const ScrDyn *d, const ScrDynPath *path)`;
|
|
773
|
+
E.walkerProtos.push(`${sig}; /* check ${key} */`);
|
|
774
|
+
const want = cStringLiteral(Buffer.from(E.dynDesc(t), "utf8"));
|
|
775
|
+
const d = [`${sig} { /* check ${key} */`];
|
|
776
|
+
switch (t.kind) {
|
|
777
|
+
case "f64":
|
|
778
|
+
d.push(` if (d->kind != SCR_DYN_NUM) { scr_dyn_check_fail(path, ${want}, d); return 0; }`);
|
|
779
|
+
d.push(` return d->v.num;`);
|
|
780
|
+
break;
|
|
781
|
+
case "bool":
|
|
782
|
+
d.push(` if (d->kind != SCR_DYN_BOOL) { scr_dyn_check_fail(path, ${want}, d); return false; }`);
|
|
783
|
+
d.push(` return d->v.b;`);
|
|
784
|
+
break;
|
|
785
|
+
case "string":
|
|
786
|
+
d.push(` if (d->kind != SCR_DYN_STR) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
|
787
|
+
d.push(` return scr_str_retain(d->v.str);`);
|
|
788
|
+
break;
|
|
789
|
+
case "dyn":
|
|
790
|
+
// An `unknown` slot (a dyn record field): the DOM subtree passes
|
|
791
|
+
// through as-is — nothing to validate, nothing to build.
|
|
792
|
+
d.push(` (void)path;`);
|
|
793
|
+
d.push(` return scr_dyn_retain((ScrDyn *)d);`);
|
|
794
|
+
break;
|
|
795
|
+
case "bytes":
|
|
796
|
+
// `u as Uint8Array`: kind check, then a fresh COPY out (the
|
|
797
|
+
// boundary's aliasing stance in both directions).
|
|
798
|
+
if (t.elem !== "u8")
|
|
799
|
+
throw new Error(`emitter bug: dynCheck of bytes<${t.elem}>`);
|
|
800
|
+
d.push(` if (d->kind != SCR_DYN_BYTES) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
|
801
|
+
d.push(` return scr_dyn_bytes_copy_out(d);`);
|
|
802
|
+
break;
|
|
803
|
+
case "object":
|
|
804
|
+
// The %Error extraction (an instanceof-Error narrow on unknown):
|
|
805
|
+
// validate the DOM's error encoding — the reserved "%error" marker
|
|
806
|
+
// caughtToDyn builds — and extract through the runtime's IDENTITY
|
|
807
|
+
// CACHE (scr_error_from_dyn): a DOM error that came from a runtime
|
|
808
|
+
// ScrError answers that very instance, so out-and-back crossings
|
|
809
|
+
// compare reference-equal (the tracing suite's shape); alien
|
|
810
|
+
// %error objects rebuild once and cache the pair.
|
|
811
|
+
if (t.className !== "%Error") {
|
|
812
|
+
throw new Error(`emitter bug: dynCheck of class ${t.className} (only %Error extracts from the DOM)`);
|
|
813
|
+
}
|
|
814
|
+
d.push(` if (d->kind != SCR_DYN_OBJ || !scr_dyn_obj_get(d, "%error", 6)) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
|
815
|
+
d.push(` return scr_error_from_dyn(d);`);
|
|
816
|
+
break;
|
|
817
|
+
case "record": {
|
|
818
|
+
const shape = E.recordsById.get(t.shapeId);
|
|
819
|
+
if (!shape)
|
|
820
|
+
throw new Error(`emitter bug: dynCheck of unknown shape ${t.shapeId}`);
|
|
821
|
+
const rel = (v) => releaseCallC(t, v);
|
|
822
|
+
// Tuple targets: a JSON ARRAY of exactly the arity, validated and
|
|
823
|
+
// extracted positionally with index paths ("$.pairs[3][1]").
|
|
824
|
+
if (shape.tuple) {
|
|
825
|
+
const byIndex = [...shape.fields].sort((a, b) => Number(a.name) - Number(b.name));
|
|
826
|
+
const arityWant = cStringLiteral(Buffer.from(`array of length ${byIndex.length}`, "utf8"));
|
|
827
|
+
d.push(` if (d->kind != SCR_DYN_ARR) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
|
828
|
+
d.push(` if (d->v.arr.len != ${byIndex.length}) { scr_dyn_check_fail(path, ${arityWant}, d); return NULL; }`);
|
|
829
|
+
d.push(` ${cDecl(t, "r")} = ${mangleRecordNew(t.shapeId)}();`);
|
|
830
|
+
byIndex.forEach((f, i) => {
|
|
831
|
+
d.push(` {`);
|
|
832
|
+
d.push(` ScrDynPath p = { path, NULL, ${i} };`);
|
|
833
|
+
d.push(` r->${mangleField(f.name)} = ${E.dynCheckHelper(f.type)}(d->v.arr.items[${i}], &p);`);
|
|
834
|
+
d.push(` if (scr_exc_pending()) { ${rel("r")}; return NULL; }`);
|
|
835
|
+
d.push(` }`);
|
|
836
|
+
});
|
|
837
|
+
d.push(` return r;`);
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
d.push(` if (d->kind != SCR_DYN_OBJ) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
|
841
|
+
d.push(` ${cDecl(t, "r")} = ${mangleRecordNew(t.shapeId)}();`);
|
|
842
|
+
for (const f of shape.fields) {
|
|
843
|
+
const keyLit = cStringLiteral(Buffer.from(f.name, "utf8"));
|
|
844
|
+
const keyLen = Buffer.byteLength(f.name, "utf8");
|
|
845
|
+
const fieldWant = cStringLiteral(Buffer.from(E.dynDesc(f.type), "utf8"));
|
|
846
|
+
// Width tolerance: only declared fields are looked up — extra JSON
|
|
847
|
+
// keys are simply never examined (check-and-extract, not shape
|
|
848
|
+
// equality). A missing field fails as "got undefined" — except
|
|
849
|
+
// optional-flavored fields (undefined-armed unions), where the
|
|
850
|
+
// missing key IS the undefined arm: the interned immortal
|
|
851
|
+
// instance, no retain owed (rc == SIZE_MAX).
|
|
852
|
+
const utag = f.type.kind === "union" ? E.undefinedArmTag(f.type) : -1;
|
|
853
|
+
d.push(` {`);
|
|
854
|
+
d.push(` ScrDynPath p = { path, ${keyLit}, 0 };`);
|
|
855
|
+
d.push(` const ScrDyn *m = scr_dyn_obj_get(d, ${keyLit}, ${keyLen});`);
|
|
856
|
+
if (f.type.kind === "dyn") {
|
|
857
|
+
// An `unknown` field: a present key passes through, a missing
|
|
858
|
+
// one IS the undefined DOM value (JS's missing-property read).
|
|
859
|
+
d.push(` (void)p;`);
|
|
860
|
+
d.push(` r->${mangleField(f.name)} = scr_dyn_retain(m ? (ScrDyn *)m : scr_dyn_undefined());`);
|
|
861
|
+
}
|
|
862
|
+
else if (utag >= 0 && f.type.kind === "union") {
|
|
863
|
+
const unit = E.unitInstanceRef(f.type.unionId, utag);
|
|
864
|
+
d.push(` if (!m) {`);
|
|
865
|
+
d.push(` r->${mangleField(f.name)} = ${unit}; /* absent key -> the undefined arm */`);
|
|
866
|
+
d.push(` } else {`);
|
|
867
|
+
d.push(` r->${mangleField(f.name)} = ${E.dynCheckHelper(f.type)}(m, &p);`);
|
|
868
|
+
d.push(` if (scr_exc_pending()) { ${rel("r")}; return NULL; }`);
|
|
869
|
+
d.push(` }`);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
d.push(` if (!m) { scr_dyn_check_fail(&p, ${fieldWant}, NULL); ${rel("r")}; return NULL; }`);
|
|
873
|
+
d.push(` r->${mangleField(f.name)} = ${E.dynCheckHelper(f.type)}(m, &p);`);
|
|
874
|
+
d.push(` if (scr_exc_pending()) { ${rel("r")}; return NULL; }`);
|
|
875
|
+
}
|
|
876
|
+
d.push(` }`);
|
|
877
|
+
}
|
|
878
|
+
// Index-signature shapes CAPTURE undeclared keys into the overflow
|
|
879
|
+
// map (width tolerance became width capture — plain shapes above
|
|
880
|
+
// keep ignoring extras). dyn value types retain the DOM subtree
|
|
881
|
+
// as-is; concrete types validate each entry with its key path.
|
|
882
|
+
if (shape.indexValue) {
|
|
883
|
+
const iv = shape.indexValue;
|
|
884
|
+
const skip = shape.fields
|
|
885
|
+
.map((f) => {
|
|
886
|
+
const keyLit = cStringLiteral(Buffer.from(f.name, "utf8"));
|
|
887
|
+
const keyLen = Buffer.byteLength(f.name, "utf8");
|
|
888
|
+
return `(e->key_len == ${keyLen} && memcmp(e->key, ${keyLit}, ${keyLen}) == 0)`;
|
|
889
|
+
})
|
|
890
|
+
.join(" || ");
|
|
891
|
+
d.push(` for (size_t i = 0; i < d->v.obj.len; i++) {`);
|
|
892
|
+
d.push(` const ScrDynEntry *e = &d->v.obj.entries[i];`);
|
|
893
|
+
if (shape.fields.length > 0)
|
|
894
|
+
d.push(` if (${skip}) continue;`);
|
|
895
|
+
if (iv.kind === "dyn") {
|
|
896
|
+
d.push(` ${cDecl(iv, "ev")} = scr_dyn_retain(e->value);`);
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
d.push(` ScrDynPath p = { path, e->key, 0 };`);
|
|
900
|
+
d.push(` ${cDecl(iv, "ev")} = ${E.dynCheckHelper(iv)}(e->value, &p);`);
|
|
901
|
+
d.push(` if (scr_exc_pending()) { ${rel("r")}; return NULL; }`);
|
|
902
|
+
}
|
|
903
|
+
d.push(` ScrStr *ek = scr_str_new(e->key, e->key_len);`);
|
|
904
|
+
if (iv.kind === "f64" || iv.kind === "bool") {
|
|
905
|
+
d.push(` scr_map_set_str_${iv.kind === "f64" ? "f64" : "bool"}(r->${OVERFLOW_MEMBER}, ek, ev);`);
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
d.push(` scr_map_set_str_ref(r->${OVERFLOW_MEMBER}, ek, ev);`);
|
|
909
|
+
}
|
|
910
|
+
d.push(` scr_str_release(ek);`);
|
|
911
|
+
d.push(` }`);
|
|
912
|
+
}
|
|
913
|
+
d.push(` return r;`);
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
case "array": {
|
|
917
|
+
const elem = t.elem;
|
|
918
|
+
const c = E.dynCheckHelper(elem);
|
|
919
|
+
d.push(` if (d->kind != SCR_DYN_ARR) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
|
920
|
+
d.push(` ScrArr *a = ${E.arrNewC(elem, "d->v.arr.len")};`);
|
|
921
|
+
d.push(` for (size_t i = 0; i < d->v.arr.len; i++) {`);
|
|
922
|
+
d.push(` ScrDynPath p = { path, NULL, i };`);
|
|
923
|
+
d.push(` ${cDecl(elem, "e")} = ${c}(d->v.arr.items[i], &p);`);
|
|
924
|
+
d.push(` if (scr_exc_pending()) { scr_arr_release(a); return NULL; }`);
|
|
925
|
+
d.push(` scr_arr_push_${elemAccess(elem)}(a, e);`);
|
|
926
|
+
d.push(` }`);
|
|
927
|
+
d.push(` return a;`);
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
case "union": {
|
|
931
|
+
const def = E.unionsById.get(t.unionId);
|
|
932
|
+
if (!def)
|
|
933
|
+
throw new Error(`emitter bug: dynCheck of unknown union ${t.unionId}`);
|
|
934
|
+
// Arms in CANONICAL order, first FULL match wins (discriminated
|
|
935
|
+
// unions disambiguate naturally: the arm whose declared fields all
|
|
936
|
+
// fit). The matched arm's builder can no longer fail.
|
|
937
|
+
def.arms.forEach((arm, i) => {
|
|
938
|
+
const m = E.dynMatchHelper(arm);
|
|
939
|
+
if (arm.kind === "undefinedT") {
|
|
940
|
+
// Parsed JSON never matches here (no undefined in JSON text —
|
|
941
|
+
// a MISSING record key builds this arm in the record builder
|
|
942
|
+
// above), but the undefined DOM value can arrive from
|
|
943
|
+
// `unknown` index-signature overflows and builds the interned
|
|
944
|
+
// unit instance exactly like null.
|
|
945
|
+
d.push(` if (${m}(d)) {`);
|
|
946
|
+
d.push(` return ${E.unitInstanceRef(t.unionId, i)};`);
|
|
947
|
+
d.push(` }`);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (arm.kind === "nullT") {
|
|
951
|
+
// A matched unit arm builds nothing: the result is THE interned
|
|
952
|
+
// immortal instance (rc == SIZE_MAX — RC entry points and the
|
|
953
|
+
// collector both skip it, so no retain is owed).
|
|
954
|
+
d.push(` if (${m}(d)) {`);
|
|
955
|
+
d.push(` return ${E.unitInstanceRef(t.unionId, i)};`);
|
|
956
|
+
d.push(` }`);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
const c = E.dynCheckHelper(arm);
|
|
960
|
+
d.push(` if (${m}(d)) {`);
|
|
961
|
+
if (arm.kind === "f64") {
|
|
962
|
+
d.push(` return scr_union_new_f64(${i}, ${c}(d, path));`);
|
|
963
|
+
}
|
|
964
|
+
else if (arm.kind === "bool") {
|
|
965
|
+
d.push(` return scr_union_new_bool(${i}, ${c}(d, path));`);
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
const rc = vAdapters(arm);
|
|
969
|
+
d.push(` return scr_union_new_ref(${i}, ${c}(d, path), &${rc.retain}, &${rc.release}, ${E.traceArgC(arm)});`);
|
|
970
|
+
}
|
|
971
|
+
d.push(` }`);
|
|
972
|
+
});
|
|
973
|
+
d.push(` scr_dyn_check_fail(path, ${want}, d);`);
|
|
974
|
+
d.push(` return NULL;`);
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
case "func": {
|
|
978
|
+
// The checked-dynamic function boundary, OUT direction: a
|
|
979
|
+
// non-function kind fails like any dynCheck; an IDENTICAL boxed
|
|
980
|
+
// signature (the interned typeKey — same key ⇔ same IR type ⇔
|
|
981
|
+
// same ABI) unwraps the closure directly (identity preserved:
|
|
982
|
+
// `mustCall(fn)` handed back to a slot of fn's own type IS fn's
|
|
983
|
+
// wrapper, retained); anything else wraps in the per-target
|
|
984
|
+
// adapter closure, whose caps[0] obj-box owns the dyn value
|
|
985
|
+
// (untraced — cycles through dyn never collect, SEMANTICS.md).
|
|
986
|
+
const adapter = dynFuncAdapterHelper(E, t);
|
|
987
|
+
const sigLit = cStringLiteral(Buffer.from(key, "utf8"));
|
|
988
|
+
d.push(` if (d->kind != SCR_DYN_FUNC) { scr_dyn_check_fail(path, ${want}, d); return NULL; }`);
|
|
989
|
+
d.push(` if (strcmp(d->v.fn.sig, ${sigLit}) == 0) return scr_closure_retain(d->v.fn.clo);`);
|
|
990
|
+
d.push(` {`);
|
|
991
|
+
d.push(` ScrClosure *a = scr_closure_new((void *)&${adapter}, 1);`);
|
|
992
|
+
d.push(` a->caps[0] = scr_box_new_obj(&scr_dyn_retain_v, &scr_dyn_release_v, NULL);`);
|
|
993
|
+
d.push(` scr_box_set_ref(a->caps[0], scr_dyn_retain((ScrDyn *)d));`);
|
|
994
|
+
d.push(` return a;`);
|
|
995
|
+
d.push(` }`);
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
default: {
|
|
999
|
+
// Runtime HANDLE targets: a tag-checked reference unwrap (+1 —
|
|
1000
|
+
// identity, no copy; the runtime throws the path-annotated
|
|
1001
|
+
// TypeError on any other kind or tag).
|
|
1002
|
+
const h = DYN_HANDLE_KINDS.get(t.kind);
|
|
1003
|
+
if (h) {
|
|
1004
|
+
d.push(` return (${cType(t).trim()})scr_dyn_handle_unbox(d, ${h.tag}, path, ${want});`);
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
throw new Error(`emitter bug: dynCheck of non-JSON type ${t.kind}`);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
d.push(`}`, ``);
|
|
1011
|
+
E.walkerDefs.push(...d);
|
|
1012
|
+
return name;
|
|
1013
|
+
}
|
|
1014
|
+
/** The emitted static→DOM converter for one type:
|
|
1015
|
+
* `static ScrDyn *sc_td_<n>(<T> v)` — build a fresh dyn DOM value from a
|
|
1016
|
+
* static one (+1), DEEP-COPYING composites (a dyn value never aliases
|
|
1017
|
+
* static storage — the jsMarshal stance). Domain: the JSON-safe kinds
|
|
1018
|
+
* plus undefined-armed unions (the undefined arm becomes the undefined
|
|
1019
|
+
* DOM singleton) and index-signature records (whose overflow entries
|
|
1020
|
+
* copy over). The operand is borrowed. Never throws. */
|
|
1021
|
+
export function toDynHelper(E, t) {
|
|
1022
|
+
const key = typeKey(t);
|
|
1023
|
+
const existing = E.toDynFns.get(key);
|
|
1024
|
+
if (existing)
|
|
1025
|
+
return existing;
|
|
1026
|
+
const name = `sc_td_${E.toDynFns.size}`;
|
|
1027
|
+
E.toDynFns.set(key, name);
|
|
1028
|
+
const sig = `static ScrDyn *${name}(${cDecl(t, "v")})`;
|
|
1029
|
+
E.walkerProtos.push(`${sig}; /* to-dyn ${key} */`);
|
|
1030
|
+
const d = [`${sig} { /* to-dyn ${key} */`];
|
|
1031
|
+
switch (t.kind) {
|
|
1032
|
+
case "f64":
|
|
1033
|
+
d.push(` return scr_dyn_new_num(v);`);
|
|
1034
|
+
break;
|
|
1035
|
+
case "bool":
|
|
1036
|
+
d.push(` return scr_dyn_new_bool(v);`);
|
|
1037
|
+
break;
|
|
1038
|
+
case "string":
|
|
1039
|
+
d.push(` return scr_dyn_new_str(v); /* retains v */`);
|
|
1040
|
+
break;
|
|
1041
|
+
case "undefinedT":
|
|
1042
|
+
// A bare unit field (`x: undefined` in a record — the tls
|
|
1043
|
+
// options-record's explicit-absent member): the DOM unit value.
|
|
1044
|
+
d.push(` (void)v; return scr_dyn_retain(scr_dyn_undefined());`);
|
|
1045
|
+
break;
|
|
1046
|
+
case "nullT":
|
|
1047
|
+
d.push(` (void)v; return scr_dyn_new_null();`);
|
|
1048
|
+
break;
|
|
1049
|
+
case "object":
|
|
1050
|
+
// %Error only (canConvertToDyn's gate): the DOM's error encoding.
|
|
1051
|
+
if (t.className !== "%Error") {
|
|
1052
|
+
throw new Error(`emitter bug: to-dyn of class ${t.className}`);
|
|
1053
|
+
}
|
|
1054
|
+
d.push(` return scr_dyn_from_error(v);`);
|
|
1055
|
+
break;
|
|
1056
|
+
case "dyn":
|
|
1057
|
+
// A dyn member of a converting composite (a dyn record field): the
|
|
1058
|
+
// DOM value passes through by reference — already a DOM, already
|
|
1059
|
+
// immutable-through-copies.
|
|
1060
|
+
d.push(` return scr_dyn_retain(v);`);
|
|
1061
|
+
break;
|
|
1062
|
+
case "bytes":
|
|
1063
|
+
// bytes<u8> → the DOM's bytes kind, payload COPIED (the boundary
|
|
1064
|
+
// stance; stdin chunks into unknown-typed helpers).
|
|
1065
|
+
if (t.elem !== "u8")
|
|
1066
|
+
throw new Error(`emitter bug: to-dyn of bytes<${t.elem}>`);
|
|
1067
|
+
d.push(` return scr_dyn_new_bytes_copy(v);`);
|
|
1068
|
+
break;
|
|
1069
|
+
case "record": {
|
|
1070
|
+
const shape = E.recordsById.get(t.shapeId);
|
|
1071
|
+
if (!shape)
|
|
1072
|
+
throw new Error(`emitter bug: to-dyn of unknown shape ${t.shapeId}`);
|
|
1073
|
+
if (shape.tuple) {
|
|
1074
|
+
// A tuple converts as the JSON ARRAY it is everywhere else.
|
|
1075
|
+
const byIndex = [...shape.fields].sort((a, b) => Number(a.name) - Number(b.name));
|
|
1076
|
+
d.push(` ScrDyn *d = scr_dyn_new_arr();`);
|
|
1077
|
+
for (const f of byIndex) {
|
|
1078
|
+
d.push(` scr_dyn_arr_push(d, ${E.toDynHelper(f.type)}(v->${mangleField(f.name)}));`);
|
|
1079
|
+
}
|
|
1080
|
+
d.push(` return d;`);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
d.push(` ScrDyn *d = scr_dyn_new_obj();`);
|
|
1084
|
+
// Keys insert in DECLARED order — the DOM object's insertion order
|
|
1085
|
+
// is observable (Object.keys/for-in over checked-dynamic values,
|
|
1086
|
+
// dyn JSON), so it must be JS's (SEMANTICS.md 36's stance, same as
|
|
1087
|
+
// the JSON writer above). Internal '%'-fields declaredOrder omits
|
|
1088
|
+
// still enter the DOM (after the visible keys) so a
|
|
1089
|
+
// record→dyn→record round trip keeps their data.
|
|
1090
|
+
{
|
|
1091
|
+
const byName = new Map(shape.fields.map((f) => [f.name, f]));
|
|
1092
|
+
const order = shape.declaredOrder ?? shape.fields.map((f) => f.name);
|
|
1093
|
+
const inOrder = new Set(order);
|
|
1094
|
+
const dynFields = [
|
|
1095
|
+
...order.map((n) => byName.get(n)).filter((f) => f !== undefined),
|
|
1096
|
+
...shape.fields.filter((f) => !inOrder.has(f.name)),
|
|
1097
|
+
];
|
|
1098
|
+
for (const f of dynFields) {
|
|
1099
|
+
const keyLit = cStringLiteral(Buffer.from(f.name, "utf8"));
|
|
1100
|
+
const keyLen = Buffer.byteLength(f.name, "utf8");
|
|
1101
|
+
d.push(` scr_dyn_obj_set(d, ${keyLit}, ${keyLen}, ${E.toDynHelper(f.type)}(v->${mangleField(f.name)}));`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (shape.indexValue) {
|
|
1105
|
+
const iv = shape.indexValue;
|
|
1106
|
+
const m = `v->${OVERFLOW_MEMBER}`;
|
|
1107
|
+
// JS OWN-KEY order (integer-like keys ascending first) — the same
|
|
1108
|
+
// enumeration Object.keys and the JSON writer answer.
|
|
1109
|
+
d.push(` {`);
|
|
1110
|
+
d.push(` ScrArr *ks = scr_map_keys_js_order(${m});`);
|
|
1111
|
+
d.push(` for (size_t i = 0; i < ks->len; i++) {`);
|
|
1112
|
+
d.push(` ScrStr *k = (ScrStr *)scr_arr_get_ref(ks, (double)i);`);
|
|
1113
|
+
if (iv.kind === "f64" || iv.kind === "bool") {
|
|
1114
|
+
d.push(` ${cDecl(iv, "sv")} = 0;`);
|
|
1115
|
+
d.push(` scr_map_get_str_${iv.kind === "f64" ? "f64" : "bool"}(${m}, k, &sv);`);
|
|
1116
|
+
d.push(` scr_dyn_obj_set(d, k->data, k->len, scr_dyn_new_${iv.kind === "f64" ? "num" : "bool"}(sv));`);
|
|
1117
|
+
}
|
|
1118
|
+
else if (iv.kind === "dyn") {
|
|
1119
|
+
// get_str_ref returns +1 — exactly the ownership obj_set takes.
|
|
1120
|
+
d.push(` scr_dyn_obj_set(d, k->data, k->len, (ScrDyn *)scr_map_get_str_ref(${m}, k));`);
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
d.push(` ${cDecl(iv, "e")} = (${cType(iv).trim()})scr_map_get_str_ref(${m}, k);`);
|
|
1124
|
+
d.push(` scr_dyn_obj_set(d, k->data, k->len, ${E.toDynHelper(iv)}(e));`);
|
|
1125
|
+
d.push(` ${releaseCallC(iv, "e")};`);
|
|
1126
|
+
}
|
|
1127
|
+
d.push(` scr_str_release(k);`);
|
|
1128
|
+
d.push(` }`);
|
|
1129
|
+
d.push(` scr_arr_release(ks);`);
|
|
1130
|
+
d.push(` }`);
|
|
1131
|
+
}
|
|
1132
|
+
d.push(` return d;`);
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
case "array": {
|
|
1136
|
+
const elem = t.elem;
|
|
1137
|
+
d.push(` ScrDyn *d = scr_dyn_new_arr();`);
|
|
1138
|
+
d.push(` for (size_t i = 0; i < v->len; i++) {`);
|
|
1139
|
+
if (elem.kind === "f64") {
|
|
1140
|
+
d.push(` scr_dyn_arr_push(d, scr_dyn_new_num(scr_arr_get_f64(v, (double)i)));`);
|
|
1141
|
+
}
|
|
1142
|
+
else if (elem.kind === "bool") {
|
|
1143
|
+
d.push(` scr_dyn_arr_push(d, scr_dyn_new_bool(scr_arr_get_bool(v, (double)i)));`);
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
d.push(` ${cDecl(elem, "e")} = (${cType(elem).trim()})scr_arr_get_ref(v, (double)i);`);
|
|
1147
|
+
d.push(` scr_dyn_arr_push(d, ${E.toDynHelper(elem)}(e));`);
|
|
1148
|
+
d.push(` ${releaseCallC(elem, "e")};`);
|
|
1149
|
+
}
|
|
1150
|
+
d.push(` }`);
|
|
1151
|
+
d.push(` return d;`);
|
|
1152
|
+
break;
|
|
1153
|
+
}
|
|
1154
|
+
case "union": {
|
|
1155
|
+
const def = E.unionsById.get(t.unionId);
|
|
1156
|
+
if (!def)
|
|
1157
|
+
throw new Error(`emitter bug: to-dyn of unknown union ${t.unionId}`);
|
|
1158
|
+
d.push(` switch (v->tag) {`);
|
|
1159
|
+
def.arms.forEach((arm, i) => {
|
|
1160
|
+
if (arm.kind === "undefinedT") {
|
|
1161
|
+
d.push(` case ${i}: return scr_dyn_retain(scr_dyn_undefined());`);
|
|
1162
|
+
}
|
|
1163
|
+
else if (arm.kind === "nullT") {
|
|
1164
|
+
d.push(` case ${i}: return scr_dyn_new_null();`);
|
|
1165
|
+
}
|
|
1166
|
+
else if (arm.kind === "f64") {
|
|
1167
|
+
d.push(` case ${i}: return scr_dyn_new_num(scr_union_get_f64(v));`);
|
|
1168
|
+
}
|
|
1169
|
+
else if (arm.kind === "bool") {
|
|
1170
|
+
d.push(` case ${i}: return scr_dyn_new_bool(scr_union_get_bool(v));`);
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
d.push(` case ${i}: return ${E.toDynHelper(arm)}((${cType(arm).trim()})scr_union_peek(v));`);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
d.push(` default: fputs("scriptc: internal error: invalid union tag\\n", stderr); abort();`);
|
|
1177
|
+
d.push(` }`);
|
|
1178
|
+
break;
|
|
1179
|
+
}
|
|
1180
|
+
case "promise": {
|
|
1181
|
+
// Promises box by REFERENCE (SCR_DYN_PROMISE — identity is the
|
|
1182
|
+
// promise): promise<dyn> carries its ScrPromise directly (the
|
|
1183
|
+
// payload is already a DOM value); any other inner boxes an
|
|
1184
|
+
// ADAPTER promise whose settle callback converts the payload
|
|
1185
|
+
// (rejections copy raw inside the runtime's cb-waiter machinery).
|
|
1186
|
+
if (t.inner.kind === "dyn") {
|
|
1187
|
+
d.push(` return scr_dyn_new_promise(v);`);
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
d.push(` return scr_dyn_new_promise_adapting(v, &${promiseDynAdapterHelper(E, t.inner)});`);
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
default: {
|
|
1194
|
+
// Runtime HANDLE kinds box by REFERENCE (identity — no copy):
|
|
1195
|
+
// scr_dyn_new_handle retains the borrowed operand through the
|
|
1196
|
+
// tag's installed ops.
|
|
1197
|
+
const h = DYN_HANDLE_KINDS.get(t.kind);
|
|
1198
|
+
if (h) {
|
|
1199
|
+
d.push(` return scr_dyn_new_handle(v, ${h.tag});`);
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
throw new Error(`emitter bug: to-dyn of unsupported type ${t.kind}`);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
d.push(`}`, ``);
|
|
1206
|
+
E.walkerDefs.push(...d);
|
|
1207
|
+
return name;
|
|
1208
|
+
}
|
|
1209
|
+
/** The DOM-promise settle adapter for one fulfillment payload type:
|
|
1210
|
+
* `static void sc_pda_<n>(ScrPromise *dst, ScrPromise *src)` — read
|
|
1211
|
+
* src's fulfilled payload by its compile-time kind, convert it to a DOM
|
|
1212
|
+
* value, and fulfill dst with it (scr_dyn_new_promise_adapting's
|
|
1213
|
+
* callback; rejections never reach an adapter — the runtime copies them
|
|
1214
|
+
* raw). Interned per inner typeKey. */
|
|
1215
|
+
export function promiseDynAdapterHelper(E, inner) {
|
|
1216
|
+
const key = typeKey(inner);
|
|
1217
|
+
const existing = E.promiseDynAdapters.get(key);
|
|
1218
|
+
if (existing)
|
|
1219
|
+
return existing;
|
|
1220
|
+
const name = `sc_pda_${E.promiseDynAdapters.size}`;
|
|
1221
|
+
E.promiseDynAdapters.set(key, name);
|
|
1222
|
+
const sig = `static void ${name}(ScrPromise *dst, ScrPromise *src)`;
|
|
1223
|
+
E.walkerProtos.push(`${sig}; /* dyn-box settle adapter for promise<${key}> */`);
|
|
1224
|
+
const d = [`${sig} { /* dyn-box settle adapter for promise<${key}> */`];
|
|
1225
|
+
const fulfill = (expr) => ` scr_promise_fulfill_ref(dst, ${expr}, scr_dyn_retain_v, scr_dyn_release_v, NULL);`;
|
|
1226
|
+
switch (inner.kind) {
|
|
1227
|
+
case "void":
|
|
1228
|
+
case "undefinedT":
|
|
1229
|
+
d.push(` (void)src;`);
|
|
1230
|
+
d.push(fulfill(`scr_dyn_retain(scr_dyn_undefined())`));
|
|
1231
|
+
break;
|
|
1232
|
+
case "nullT":
|
|
1233
|
+
d.push(` (void)src;`);
|
|
1234
|
+
d.push(fulfill(`scr_dyn_new_null()`));
|
|
1235
|
+
break;
|
|
1236
|
+
case "f64":
|
|
1237
|
+
d.push(fulfill(`scr_dyn_new_num(scr_promise_payload_f64(src))`));
|
|
1238
|
+
break;
|
|
1239
|
+
case "bool":
|
|
1240
|
+
d.push(fulfill(`scr_dyn_new_bool(scr_promise_payload_bool(src))`));
|
|
1241
|
+
break;
|
|
1242
|
+
case "string":
|
|
1243
|
+
d.push(` ScrStr *s = scr_promise_payload_str(src);`);
|
|
1244
|
+
d.push(` ScrDyn *dv = scr_dyn_new_str(s); /* retains s */`);
|
|
1245
|
+
d.push(` scr_str_release(s);`);
|
|
1246
|
+
d.push(fulfill(`dv`));
|
|
1247
|
+
break;
|
|
1248
|
+
default: {
|
|
1249
|
+
// Ref-payload inners (records, arrays, bytes, %Error, unions,
|
|
1250
|
+
// nested promises, handles): extract (+1 via the stored retain),
|
|
1251
|
+
// convert through the shared to-dyn spelling, release the extract.
|
|
1252
|
+
d.push(` ${cDecl(inner, "pv")} = (${cType(inner).trim()})scr_promise_payload_ref(src);`);
|
|
1253
|
+
d.push(` ScrDyn *dv = ${toDynExprC(E, inner, "pv")};`);
|
|
1254
|
+
if (isRefCounted(inner))
|
|
1255
|
+
d.push(` ${releaseCallC(inner, "pv")};`);
|
|
1256
|
+
d.push(fulfill(`dv`));
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
d.push(`}`, ``);
|
|
1261
|
+
E.walkerDefs.push(...d);
|
|
1262
|
+
return name;
|
|
1263
|
+
}
|
|
1264
|
+
/* ── the checked-dynamic function boundary (nodes.ts) ─────────────────
|
|
1265
|
+
* Three interned per-signature helpers:
|
|
1266
|
+
*
|
|
1267
|
+
* sc_dfk_<n> — the CALL THUNK a dyn-boxed closure carries: validate
|
|
1268
|
+
* each dyn argument into the declared param type (JS arity: extras
|
|
1269
|
+
* ignored, missing args are the undefined DOM value — a param type
|
|
1270
|
+
* the undefined value fails checks throws the path-annotated
|
|
1271
|
+
* TypeError), call through the closure, convert the result back to a
|
|
1272
|
+
* DOM value (+1).
|
|
1273
|
+
*
|
|
1274
|
+
* sc_dfb_<n> — the BOX builder dynFrom emits: a fresh SCR_DYN_FUNC
|
|
1275
|
+
* node carrying the retained closure, the thunk, the arity, the
|
|
1276
|
+
* interned signature key (typeKey — dynCheck's exact-unwrap fast
|
|
1277
|
+
* path), and the best-effort name.
|
|
1278
|
+
*
|
|
1279
|
+
* sc_dfa_<n> — the ADAPTER a func-targeted dynCheck wraps a
|
|
1280
|
+
* NON-identical boxed signature in: a closure of the TARGET type
|
|
1281
|
+
* whose body converts each typed argument INTO dyn, calls the boxed
|
|
1282
|
+
* thunk, and validates the dyn result into the target's return type.
|
|
1283
|
+
*/
|
|
1284
|
+
/** The dyn argument spelling of one static value (thunk results, adapter
|
|
1285
|
+
* arguments): dyn passes through (+1 via retain — callers own the
|
|
1286
|
+
* result uniformly), funcs box (anonymous — the static name is gone by
|
|
1287
|
+
* the time a value flows through an adapter; SEMANTICS.md), everything
|
|
1288
|
+
* else rides the toDyn converter. `expr` is BORROWED in every arm. */
|
|
1289
|
+
function toDynExprC(E, t, expr) {
|
|
1290
|
+
if (t.kind === "dyn")
|
|
1291
|
+
return `scr_dyn_retain(${expr})`;
|
|
1292
|
+
if (t.kind === "func")
|
|
1293
|
+
return `${E.dynFuncBoxHelper(t)}(${expr}, NULL)`;
|
|
1294
|
+
return `${E.toDynHelper(t)}(${expr})`;
|
|
1295
|
+
}
|
|
1296
|
+
/** The call thunk for one closure signature (see the block comment). */
|
|
1297
|
+
export function dynFuncThunkHelper(E, t) {
|
|
1298
|
+
const key = typeKey(t);
|
|
1299
|
+
const existing = E.dynFuncThunks.get(key);
|
|
1300
|
+
if (existing)
|
|
1301
|
+
return existing;
|
|
1302
|
+
const name = `sc_dfk_${E.dynFuncThunks.size}`;
|
|
1303
|
+
E.dynFuncThunks.set(key, name);
|
|
1304
|
+
const sig = `static ScrDyn *${name}(ScrClosure *c, ScrDyn *const *args, size_t argc)`;
|
|
1305
|
+
E.walkerProtos.push(`${sig}; /* dyn call thunk for ${key} */`);
|
|
1306
|
+
const d = [`${sig} { /* dyn call thunk for ${key} */`];
|
|
1307
|
+
if (t.params.length === 0)
|
|
1308
|
+
d.push(` (void)args;`);
|
|
1309
|
+
d.push(` (void)argc;`);
|
|
1310
|
+
t.params.forEach((p, i) => {
|
|
1311
|
+
// JS arity: a missing argument IS the undefined DOM value; the
|
|
1312
|
+
// param's own check decides whether that flies (dyn params take
|
|
1313
|
+
// anything; a number param throws the catchable TypeError).
|
|
1314
|
+
d.push(` ${cDecl(p, `a${i}`)};`);
|
|
1315
|
+
d.push(` {`);
|
|
1316
|
+
d.push(` const ScrDyn *ad = ${i} < argc ? args[${i}] : scr_dyn_undefined();`);
|
|
1317
|
+
if (p.kind === "dyn") {
|
|
1318
|
+
d.push(` a${i} = scr_dyn_retain((ScrDyn *)ad);`);
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
d.push(` ScrDynPath pp = { NULL, NULL, ${i} };`);
|
|
1322
|
+
d.push(` a${i} = ${E.dynCheckHelper(p)}(ad, &pp);`);
|
|
1323
|
+
const undo = t.params
|
|
1324
|
+
.slice(0, i)
|
|
1325
|
+
.flatMap((q, j) => (isRefCounted(q) ? [`${releaseCallC(q, `a${j}`)};`] : []));
|
|
1326
|
+
d.push(` if (scr_exc_pending()) { ${undo.join(" ")}${undo.length > 0 ? " " : ""}return NULL; }`);
|
|
1327
|
+
}
|
|
1328
|
+
d.push(` }`);
|
|
1329
|
+
});
|
|
1330
|
+
// VARIADIC (rest-marked) signatures: one extra trailing DOM-array
|
|
1331
|
+
// param carries the call's arguments from index params.length on —
|
|
1332
|
+
// the mustCall wrapper's `arguments`, a JS `...args`. Built fresh per
|
|
1333
|
+
// call (+1, moved into the callee like every param).
|
|
1334
|
+
if (t.rest) {
|
|
1335
|
+
d.push(` ScrDyn *rest = scr_dyn_new_arr();`);
|
|
1336
|
+
d.push(` for (size_t ri = ${t.params.length}; ri < argc; ri++) {`);
|
|
1337
|
+
d.push(` scr_dyn_arr_push(rest, scr_dyn_retain((ScrDyn *)args[ri]));`);
|
|
1338
|
+
d.push(` }`);
|
|
1339
|
+
}
|
|
1340
|
+
// The closure CONSUMES its params (+1 each moved in — exactly what the
|
|
1341
|
+
// builders above returned).
|
|
1342
|
+
const castParams = ["ScrClosure *", ...t.params.map((p) => cType(p).trim()), ...(t.rest ? ["ScrDyn *"] : [])].join(", ");
|
|
1343
|
+
const call = `((${cType(t.ret).trim()} (*)(${castParams}))c->fn)(${["c", ...t.params.map((_, i) => `a${i}`), ...(t.rest ? ["rest"] : [])].join(", ")})`;
|
|
1344
|
+
if (t.ret.kind === "void") {
|
|
1345
|
+
d.push(` ${call};`);
|
|
1346
|
+
d.push(` if (scr_exc_pending()) return NULL;`);
|
|
1347
|
+
d.push(` return scr_dyn_retain(scr_dyn_undefined());`);
|
|
1348
|
+
}
|
|
1349
|
+
else if (t.ret.kind === "dyn") {
|
|
1350
|
+
d.push(` ScrDyn *r = ${call};`);
|
|
1351
|
+
d.push(` if (scr_exc_pending()) return NULL;`);
|
|
1352
|
+
d.push(` return r;`);
|
|
1353
|
+
}
|
|
1354
|
+
else {
|
|
1355
|
+
d.push(` ${cDecl(t.ret, "r")} = ${call};`);
|
|
1356
|
+
d.push(` if (scr_exc_pending()) return NULL;`);
|
|
1357
|
+
d.push(` ScrDyn *out = ${toDynExprC(E, t.ret, "r")};`);
|
|
1358
|
+
if (isRefCounted(t.ret))
|
|
1359
|
+
d.push(` ${releaseCallC(t.ret, "r")};`);
|
|
1360
|
+
d.push(` return out;`);
|
|
1361
|
+
}
|
|
1362
|
+
d.push(`}`, ``);
|
|
1363
|
+
E.walkerDefs.push(...d);
|
|
1364
|
+
return name;
|
|
1365
|
+
}
|
|
1366
|
+
/** The box builder dynFrom emits for one closure signature. */
|
|
1367
|
+
export function dynFuncBoxHelper(E, t) {
|
|
1368
|
+
const key = typeKey(t);
|
|
1369
|
+
const existing = E.dynFuncBoxes.get(key);
|
|
1370
|
+
if (existing)
|
|
1371
|
+
return existing;
|
|
1372
|
+
const name = `sc_dfb_${E.dynFuncBoxes.size}`;
|
|
1373
|
+
E.dynFuncBoxes.set(key, name);
|
|
1374
|
+
const sig = `static ScrDyn *${name}(ScrClosure *v, const char *fname)`;
|
|
1375
|
+
E.walkerProtos.push(`${sig}; /* box ${key} into dyn */`);
|
|
1376
|
+
const thunk = dynFuncThunkHelper(E, t);
|
|
1377
|
+
const sigLit = cStringLiteral(Buffer.from(key, "utf8"));
|
|
1378
|
+
E.walkerDefs.push(`${sig} { /* box ${key} into dyn */`, ` return scr_dyn_new_func(scr_closure_retain(v), &${thunk}, ${t.params.length}, ${sigLit}, fname);`, `}`, ``);
|
|
1379
|
+
return name;
|
|
1380
|
+
}
|
|
1381
|
+
/** The adapter closure body for one TARGET signature: the emitted C
|
|
1382
|
+
* function a func-targeted dynCheck wraps a non-identical boxed
|
|
1383
|
+
* signature in. caps[0] is an untraced obj-box owning the dyn function
|
|
1384
|
+
* value. */
|
|
1385
|
+
export function dynFuncAdapterHelper(E, t) {
|
|
1386
|
+
const key = typeKey(t);
|
|
1387
|
+
const existing = E.dynFuncAdapters.get(key);
|
|
1388
|
+
if (existing)
|
|
1389
|
+
return existing;
|
|
1390
|
+
const name = `sc_dfa_${E.dynFuncAdapters.size}`;
|
|
1391
|
+
E.dynFuncAdapters.set(key, name);
|
|
1392
|
+
const params = ["ScrClosure *sc_env", ...t.params.map((p, i) => cDecl(p, `a${i}`))].join(", ");
|
|
1393
|
+
const sig = `static ${cType(t.ret)}${cType(t.ret).endsWith("*") ? "" : " "}${name}(${params})`;
|
|
1394
|
+
E.walkerProtos.push(`${sig}; /* dyn fn adapter to ${key} */`);
|
|
1395
|
+
const dummy = t.ret.kind === "void" ? "" : t.ret.kind === "f64" ? "0" : t.ret.kind === "bool" ? "false" : "NULL";
|
|
1396
|
+
const d = [`${sig} { /* dyn fn adapter to ${key} */`];
|
|
1397
|
+
d.push(` ScrDyn *sc_fn = (ScrDyn *)scr_box_get_ref(sc_env->caps[0]); /* +1 */`);
|
|
1398
|
+
// The adapter OWNS its params (closure ABI); each converts to a dyn
|
|
1399
|
+
// argument (toDynExprC borrows) and releases.
|
|
1400
|
+
if (t.params.length > 0) {
|
|
1401
|
+
d.push(` ScrDyn *sc_args[${t.params.length}];`);
|
|
1402
|
+
t.params.forEach((p, i) => {
|
|
1403
|
+
d.push(` sc_args[${i}] = ${toDynExprC(E, p, `a${i}`)};`);
|
|
1404
|
+
if (isRefCounted(p))
|
|
1405
|
+
d.push(` ${releaseCallC(p, `a${i}`)};`);
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
// The kind is FUNC by construction (the dynCheck that built this
|
|
1409
|
+
// adapter tested it), so `what` is unreachable — spelled anyway.
|
|
1410
|
+
d.push(` ScrDyn *sc_r = scr_dyn_call(sc_fn, ${t.params.length > 0 ? "sc_args" : "NULL"}, ${t.params.length}, "value");`);
|
|
1411
|
+
d.push(` scr_dyn_release(sc_fn);`);
|
|
1412
|
+
t.params.forEach((_, i) => d.push(` scr_dyn_release(sc_args[${i}]);`));
|
|
1413
|
+
d.push(` if (scr_exc_pending()) return ${dummy};`.replace("return ;", "return;"));
|
|
1414
|
+
if (t.ret.kind === "void") {
|
|
1415
|
+
d.push(` scr_dyn_release(sc_r);`);
|
|
1416
|
+
d.push(` return;`);
|
|
1417
|
+
}
|
|
1418
|
+
else if (t.ret.kind === "dyn") {
|
|
1419
|
+
d.push(` return sc_r;`);
|
|
1420
|
+
}
|
|
1421
|
+
else {
|
|
1422
|
+
// Validate the dyn result into the target's return type — a lying
|
|
1423
|
+
// wrapper throws the catchable TypeError here (path "$"), exactly
|
|
1424
|
+
// the dynCheck stance; the dummy result rides the pending unwind.
|
|
1425
|
+
d.push(` ${cDecl(t.ret, "out")} = ${E.dynCheckHelper(t.ret)}(sc_r, NULL);`);
|
|
1426
|
+
d.push(` scr_dyn_release(sc_r);`);
|
|
1427
|
+
d.push(` return out;`);
|
|
1428
|
+
}
|
|
1429
|
+
d.push(`}`, ``);
|
|
1430
|
+
E.walkerDefs.push(...d);
|
|
1431
|
+
return name;
|
|
1432
|
+
}
|
|
1433
|
+
/** The emitted dynamic-keyed READ helper for one (shape, result type):
|
|
1434
|
+
* `static <T> sc_rkg_<n>(<struct> *r, ScrStr *k)` — declared fields
|
|
1435
|
+
* answer first (string comparisons in canonical order; the frontend
|
|
1436
|
+
* proved each surfaces as T: identity, a to-dyn conversion, or an
|
|
1437
|
+
* arm-into-union wrap), then the overflow map on index-signature shapes.
|
|
1438
|
+
* A MISSING key yields the undefined DOM value (T dyn), the undefined
|
|
1439
|
+
* arm (T undefined-armed union), or TRAPS with the key text (the array
|
|
1440
|
+
* OOB policy — the checker claimed T and no undefined is representable).
|
|
1441
|
+
* Borrows both; refcounted results are owned (+1). */
|
|
1442
|
+
export function recordKeyGetHelper(E, shapeId, t, overflowOnly = false) {
|
|
1443
|
+
const key = `${shapeId}|${typeKey(t)}${overflowOnly ? "|ovf" : ""}`;
|
|
1444
|
+
const existing = E.recordKeyGetFns.get(key);
|
|
1445
|
+
if (existing)
|
|
1446
|
+
return existing;
|
|
1447
|
+
const shape = E.recordsById.get(shapeId);
|
|
1448
|
+
if (!shape)
|
|
1449
|
+
throw new Error(`emitter bug: keyed read of unknown shape ${shapeId}`);
|
|
1450
|
+
const name = `sc_rkg_${E.recordKeyGetFns.size}`;
|
|
1451
|
+
E.recordKeyGetFns.set(key, name);
|
|
1452
|
+
const struct = mangleRecordStruct(shapeId);
|
|
1453
|
+
const sig = `static ${cType(t)}${cType(t).endsWith("*") ? "" : " "}${name}(${struct} *r, ScrStr *k)`;
|
|
1454
|
+
E.walkerProtos.push(`${sig}; /* r[k] on ${shapeId} as ${typeKey(t)} */`);
|
|
1455
|
+
const d = [`${sig} { /* r[k] on ${shapeId} as ${typeKey(t)} */`];
|
|
1456
|
+
// How a value of type `vt` (a field, or the overflow hit) surfaces as T.
|
|
1457
|
+
const surface = (vt, expr, owned) => {
|
|
1458
|
+
if (typeEquals(vt, t)) {
|
|
1459
|
+
return owned || !isRefCounted(vt) ? expr : retainCallC(vt, expr);
|
|
1460
|
+
}
|
|
1461
|
+
if (t.kind === "dyn") {
|
|
1462
|
+
// toDyn borrows — an owned operand would leak; callers pass borrowed.
|
|
1463
|
+
return `${E.toDynHelper(vt)}(${expr})`;
|
|
1464
|
+
}
|
|
1465
|
+
if (t.kind === "union") {
|
|
1466
|
+
const def = E.unionsById.get(t.unionId);
|
|
1467
|
+
const tag = def?.arms.findIndex((a) => typeEquals(a, vt)) ?? -1;
|
|
1468
|
+
if (tag < 0)
|
|
1469
|
+
throw new Error(`emitter bug: keyed read arm for ${typeKey(vt)} in ${typeKey(t)}`);
|
|
1470
|
+
if (vt.kind === "f64")
|
|
1471
|
+
return `scr_union_new_f64(${tag}, ${expr})`;
|
|
1472
|
+
if (vt.kind === "bool")
|
|
1473
|
+
return `scr_union_new_bool(${tag}, ${expr})`;
|
|
1474
|
+
const rc = vAdapters(vt);
|
|
1475
|
+
const payload = owned ? expr : retainCallC(vt, expr);
|
|
1476
|
+
return `scr_union_new_ref(${tag}, ${payload}, &${rc.retain}, &${rc.release}, ${E.traceArgC(vt)})`;
|
|
1477
|
+
}
|
|
1478
|
+
throw new Error(`emitter bug: keyed read cannot surface ${typeKey(vt)} as ${typeKey(t)}`);
|
|
1479
|
+
};
|
|
1480
|
+
// overflowOnly (a literal key naming no declared field): the declared
|
|
1481
|
+
// switch can never hit — only the overflow answers.
|
|
1482
|
+
for (const f of overflowOnly ? [] : shape.fields) {
|
|
1483
|
+
const lit = E.internLiteral(f.name);
|
|
1484
|
+
d.push(` if (scr_str_eq(k, (ScrStr *)&${lit})) { /* ${f.name} */`);
|
|
1485
|
+
d.push(` return ${surface(f.type, `r->${mangleField(f.name)}`, false)};`);
|
|
1486
|
+
d.push(` }`);
|
|
1487
|
+
}
|
|
1488
|
+
const iv = shape.indexValue;
|
|
1489
|
+
if (iv) {
|
|
1490
|
+
const m = `r->${OVERFLOW_MEMBER}`;
|
|
1491
|
+
if (iv.kind === "f64" || iv.kind === "bool") {
|
|
1492
|
+
const acc = iv.kind === "f64" ? "f64" : "bool";
|
|
1493
|
+
d.push(` ${cDecl(iv, "hit")};`);
|
|
1494
|
+
d.push(` if (scr_map_get_str_${acc}(${m}, k, &hit)) {`);
|
|
1495
|
+
d.push(` return ${surface(iv, "hit", true)};`);
|
|
1496
|
+
d.push(` }`);
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
d.push(` ${cDecl(iv, "hit")} = (${cType(iv).trim()})scr_map_get_str_ref(${m}, k);`);
|
|
1500
|
+
d.push(` if (hit) {`);
|
|
1501
|
+
if (typeEquals(iv, t)) {
|
|
1502
|
+
d.push(` return hit; /* get returned +1 */`);
|
|
1503
|
+
}
|
|
1504
|
+
else if (t.kind === "union") {
|
|
1505
|
+
d.push(` return ${surface(iv, "hit", true)};`);
|
|
1506
|
+
}
|
|
1507
|
+
else {
|
|
1508
|
+
throw new Error(`emitter bug: keyed read overflow ${typeKey(iv)} as ${typeKey(t)}`);
|
|
1509
|
+
}
|
|
1510
|
+
d.push(` }`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
// The miss path.
|
|
1514
|
+
if (t.kind === "dyn") {
|
|
1515
|
+
d.push(` return scr_dyn_retain(scr_dyn_undefined());`);
|
|
1516
|
+
}
|
|
1517
|
+
else if (t.kind === "union" && E.undefinedArmTag(t) >= 0) {
|
|
1518
|
+
d.push(` return ${E.unitInstanceRef(t.unionId, E.undefinedArmTag(t))};`);
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
// JS would answer undefined; T has no way to say it (the checker
|
|
1522
|
+
// claimed T without noUncheckedIndexedAccess) — trap like an array
|
|
1523
|
+
// OOB read instead of corrupting a typed slot (SEMANTICS.md).
|
|
1524
|
+
d.push(` fprintf(stderr, "scriptc: TypeError: record has no key '%.*s' (typed '${E.dynDesc(t)}' — no undefined is representable)\\n", (int)k->len, k->data);`);
|
|
1525
|
+
d.push(` abort();`);
|
|
1526
|
+
}
|
|
1527
|
+
d.push(`}`, ``);
|
|
1528
|
+
E.walkerDefs.push(...d);
|
|
1529
|
+
return name;
|
|
1530
|
+
}
|
|
1531
|
+
/** The emitted dynamic-keyed WRITE helper for one index-signature shape:
|
|
1532
|
+
* `static void sc_rks_<n>(<struct> *r, ScrStr *k, <V> v)` — a declared
|
|
1533
|
+
* key writes THROUGH to the struct slot (dyn values validate against the
|
|
1534
|
+
* field's type first via the dynCheck walker: a mismatch throws the
|
|
1535
|
+
* catchable TypeError and leaves the field untouched — JS would store
|
|
1536
|
+
* anything, the documented divergence; typed values store directly),
|
|
1537
|
+
* an undeclared key inserts/replaces in the overflow map. Borrows r and
|
|
1538
|
+
* k; OWNS v (+1 moves in — released on the validation-failure path). */
|
|
1539
|
+
export function recordKeySetHelper(E, shapeId) {
|
|
1540
|
+
const existing = E.recordKeySetFns.get(shapeId);
|
|
1541
|
+
if (existing)
|
|
1542
|
+
return existing;
|
|
1543
|
+
const shape = E.recordsById.get(shapeId);
|
|
1544
|
+
if (!shape?.indexValue)
|
|
1545
|
+
throw new Error(`emitter bug: keyed write on non-overflow shape ${shapeId}`);
|
|
1546
|
+
const iv = shape.indexValue;
|
|
1547
|
+
const name = `sc_rks_${E.recordKeySetFns.size}`;
|
|
1548
|
+
E.recordKeySetFns.set(shapeId, name);
|
|
1549
|
+
const struct = mangleRecordStruct(shapeId);
|
|
1550
|
+
const sig = `static void ${name}(${struct} *r, ScrStr *k, ${cDecl(iv, "v")})`;
|
|
1551
|
+
E.walkerProtos.push(`${sig}; /* r[k] = v on ${shapeId} */`);
|
|
1552
|
+
const d = [`${sig} { /* r[k] = v on ${shapeId} */`];
|
|
1553
|
+
for (const f of shape.fields) {
|
|
1554
|
+
const lit = E.internLiteral(f.name);
|
|
1555
|
+
const member = `r->${mangleField(f.name)}`;
|
|
1556
|
+
d.push(` if (scr_str_eq(k, (ScrStr *)&${lit})) { /* ${f.name} */`);
|
|
1557
|
+
if (iv.kind === "dyn") {
|
|
1558
|
+
const keyLit = cStringLiteral(Buffer.from(f.name, "utf8"));
|
|
1559
|
+
d.push(` ScrDynPath p = { NULL, ${keyLit}, 0 };`);
|
|
1560
|
+
d.push(` ${cDecl(f.type, "nv")} = ${E.dynCheckHelper(f.type)}(v, &p);`);
|
|
1561
|
+
d.push(` scr_dyn_release(v);`);
|
|
1562
|
+
d.push(` if (scr_exc_pending()) return; /* mismatched write: TypeError, field untouched */`);
|
|
1563
|
+
d.push(` ${isRefCounted(f.type) ? `if (${member}) ${releaseCallC(f.type, member)};` : `(void)0;`}`);
|
|
1564
|
+
d.push(` ${member} = nv;`);
|
|
1565
|
+
}
|
|
1566
|
+
else {
|
|
1567
|
+
// typeEquals(f.type, iv) — the frontend fences everything else.
|
|
1568
|
+
if (isRefCounted(f.type)) {
|
|
1569
|
+
d.push(` if (${member}) ${releaseCallC(f.type, member)};`);
|
|
1570
|
+
}
|
|
1571
|
+
d.push(` ${member} = v;`);
|
|
1572
|
+
}
|
|
1573
|
+
d.push(` return;`);
|
|
1574
|
+
d.push(` }`);
|
|
1575
|
+
}
|
|
1576
|
+
const m = `r->${OVERFLOW_MEMBER}`;
|
|
1577
|
+
if (iv.kind === "f64" || iv.kind === "bool") {
|
|
1578
|
+
d.push(` scr_map_set_str_${iv.kind === "f64" ? "f64" : "bool"}(${m}, k, v);`);
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
d.push(` scr_map_set_str_ref(${m}, k, v); /* v moves in */`);
|
|
1582
|
+
}
|
|
1583
|
+
d.push(`}`, ``);
|
|
1584
|
+
E.walkerDefs.push(...d);
|
|
1585
|
+
return name;
|
|
1586
|
+
}
|
|
1587
|
+
//# sourceMappingURL=emit-walkers.js.map
|