@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.
Files changed (206) hide show
  1. package/LICENSE +202 -0
  2. package/ambient/package.json +3 -0
  3. package/ambient/scriptc-node-fallback.d.ts +3001 -0
  4. package/ambient/scriptc-overrides.d.ts +95 -0
  5. package/ambient/scriptc.d.ts +74 -0
  6. package/dist/backend/cc.d.ts +169 -0
  7. package/dist/backend/cc.js +897 -0
  8. package/dist/backend/cc.js.map +1 -0
  9. package/dist/backend/emission/emit-async.d.ts +145 -0
  10. package/dist/backend/emission/emit-async.js +996 -0
  11. package/dist/backend/emission/emit-async.js.map +1 -0
  12. package/dist/backend/emission/emit-exprs.d.ts +3 -0
  13. package/dist/backend/emission/emit-exprs.js +5949 -0
  14. package/dist/backend/emission/emit-exprs.js.map +1 -0
  15. package/dist/backend/emission/emit-island.d.ts +45 -0
  16. package/dist/backend/emission/emit-island.js +271 -0
  17. package/dist/backend/emission/emit-island.js.map +1 -0
  18. package/dist/backend/emission/emit-shapes.d.ts +142 -0
  19. package/dist/backend/emission/emit-shapes.js +575 -0
  20. package/dist/backend/emission/emit-shapes.js.map +1 -0
  21. package/dist/backend/emission/emit-stmts.d.ts +78 -0
  22. package/dist/backend/emission/emit-stmts.js +960 -0
  23. package/dist/backend/emission/emit-stmts.js.map +1 -0
  24. package/dist/backend/emission/emit-types.d.ts +65 -0
  25. package/dist/backend/emission/emit-types.js +652 -0
  26. package/dist/backend/emission/emit-types.js.map +1 -0
  27. package/dist/backend/emission/emit-walkers.d.ts +154 -0
  28. package/dist/backend/emission/emit-walkers.js +1587 -0
  29. package/dist/backend/emission/emit-walkers.js.map +1 -0
  30. package/dist/backend/emission/emitter.d.ts +452 -0
  31. package/dist/backend/emission/emitter.js +1249 -0
  32. package/dist/backend/emission/emitter.js.map +1 -0
  33. package/dist/backend/emission/may-throw.d.ts +15 -0
  34. package/dist/backend/emission/may-throw.js +250 -0
  35. package/dist/backend/emission/may-throw.js.map +1 -0
  36. package/dist/backend/llvm/blocks.d.ts +21 -0
  37. package/dist/backend/llvm/blocks.js +68 -0
  38. package/dist/backend/llvm/blocks.js.map +1 -0
  39. package/dist/backend/llvm/classes.d.ts +84 -0
  40. package/dist/backend/llvm/classes.js +438 -0
  41. package/dist/backend/llvm/classes.js.map +1 -0
  42. package/dist/backend/llvm/dyn.d.ts +123 -0
  43. package/dist/backend/llvm/dyn.js +2624 -0
  44. package/dist/backend/llvm/dyn.js.map +1 -0
  45. package/dist/backend/llvm/emitter.d.ts +3 -0
  46. package/dist/backend/llvm/emitter.js +10102 -0
  47. package/dist/backend/llvm/emitter.js.map +1 -0
  48. package/dist/backend/llvm/shapes.d.ts +78 -0
  49. package/dist/backend/llvm/shapes.js +754 -0
  50. package/dist/backend/llvm/shapes.js.map +1 -0
  51. package/dist/backend/llvm/unsupported.d.ts +6 -0
  52. package/dist/backend/llvm/unsupported.js +12 -0
  53. package/dist/backend/llvm/unsupported.js.map +1 -0
  54. package/dist/backend/llvm/walkers.d.ts +58 -0
  55. package/dist/backend/llvm/walkers.js +793 -0
  56. package/dist/backend/llvm/walkers.js.map +1 -0
  57. package/dist/backend/mangle.d.ts +124 -0
  58. package/dist/backend/mangle.js +232 -0
  59. package/dist/backend/mangle.js.map +1 -0
  60. package/dist/coverage/report.d.ts +65 -0
  61. package/dist/coverage/report.js +238 -0
  62. package/dist/coverage/report.js.map +1 -0
  63. package/dist/diagnostics/diagnostic.d.ts +140 -0
  64. package/dist/diagnostics/diagnostic.js +458 -0
  65. package/dist/diagnostics/diagnostic.js.map +1 -0
  66. package/dist/diagnostics/render.d.ts +11 -0
  67. package/dist/diagnostics/render.js +58 -0
  68. package/dist/diagnostics/render.js.map +1 -0
  69. package/dist/frontend/cjs-lexer.d.ts +20 -0
  70. package/dist/frontend/cjs-lexer.js +813 -0
  71. package/dist/frontend/cjs-lexer.js.map +1 -0
  72. package/dist/frontend/lowering/http2-constants.d.ts +1 -0
  73. package/dist/frontend/lowering/http2-constants.js +251 -0
  74. package/dist/frontend/lowering/http2-constants.js.map +1 -0
  75. package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
  76. package/dist/frontend/lowering/lib-boundary.js +143 -0
  77. package/dist/frontend/lowering/lib-boundary.js.map +1 -0
  78. package/dist/frontend/lowering/lower-assert.d.ts +18 -0
  79. package/dist/frontend/lowering/lower-assert.js +1269 -0
  80. package/dist/frontend/lowering/lower-assert.js.map +1 -0
  81. package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
  82. package/dist/frontend/lowering/lower-builtins.js +5249 -0
  83. package/dist/frontend/lowering/lower-builtins.js.map +1 -0
  84. package/dist/frontend/lowering/lower-calls.d.ts +581 -0
  85. package/dist/frontend/lowering/lower-calls.js +6286 -0
  86. package/dist/frontend/lowering/lower-calls.js.map +1 -0
  87. package/dist/frontend/lowering/lower-classes.d.ts +727 -0
  88. package/dist/frontend/lowering/lower-classes.js +4701 -0
  89. package/dist/frontend/lowering/lower-classes.js.map +1 -0
  90. package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
  91. package/dist/frontend/lowering/lower-comptime.js +244 -0
  92. package/dist/frontend/lowering/lower-comptime.js.map +1 -0
  93. package/dist/frontend/lowering/lower-containers.d.ts +486 -0
  94. package/dist/frontend/lowering/lower-containers.js +6345 -0
  95. package/dist/frontend/lowering/lower-containers.js.map +1 -0
  96. package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
  97. package/dist/frontend/lowering/lower-dgram.js +319 -0
  98. package/dist/frontend/lowering/lower-dgram.js.map +1 -0
  99. package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
  100. package/dist/frontend/lowering/lower-emitter.js +688 -0
  101. package/dist/frontend/lowering/lower-emitter.js.map +1 -0
  102. package/dist/frontend/lowering/lower-enums.d.ts +22 -0
  103. package/dist/frontend/lowering/lower-enums.js +235 -0
  104. package/dist/frontend/lowering/lower-enums.js.map +1 -0
  105. package/dist/frontend/lowering/lower-expando.d.ts +35 -0
  106. package/dist/frontend/lowering/lower-expando.js +276 -0
  107. package/dist/frontend/lowering/lower-expando.js.map +1 -0
  108. package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
  109. package/dist/frontend/lowering/lower-exprs.js +8111 -0
  110. package/dist/frontend/lowering/lower-exprs.js.map +1 -0
  111. package/dist/frontend/lowering/lower-generators.d.ts +50 -0
  112. package/dist/frontend/lowering/lower-generators.js +421 -0
  113. package/dist/frontend/lowering/lower-generators.js.map +1 -0
  114. package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
  115. package/dist/frontend/lowering/lower-inspect.js +1074 -0
  116. package/dist/frontend/lowering/lower-inspect.js.map +1 -0
  117. package/dist/frontend/lowering/lower-island.d.ts +108 -0
  118. package/dist/frontend/lowering/lower-island.js +650 -0
  119. package/dist/frontend/lowering/lower-island.js.map +1 -0
  120. package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
  121. package/dist/frontend/lowering/lower-mixins.js +534 -0
  122. package/dist/frontend/lowering/lower-mixins.js.map +1 -0
  123. package/dist/frontend/lowering/lower-modules.d.ts +135 -0
  124. package/dist/frontend/lowering/lower-modules.js +1613 -0
  125. package/dist/frontend/lowering/lower-modules.js.map +1 -0
  126. package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
  127. package/dist/frontend/lowering/lower-namespaces.js +706 -0
  128. package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
  129. package/dist/frontend/lowering/lower-server.d.ts +90 -0
  130. package/dist/frontend/lowering/lower-server.js +3471 -0
  131. package/dist/frontend/lowering/lower-server.js.map +1 -0
  132. package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
  133. package/dist/frontend/lowering/lower-stmts.js +5341 -0
  134. package/dist/frontend/lowering/lower-stmts.js.map +1 -0
  135. package/dist/frontend/lowering/lower-stream.d.ts +88 -0
  136. package/dist/frontend/lowering/lower-stream.js +1533 -0
  137. package/dist/frontend/lowering/lower-stream.js.map +1 -0
  138. package/dist/frontend/lowering/lower-test.d.ts +27 -0
  139. package/dist/frontend/lowering/lower-test.js +353 -0
  140. package/dist/frontend/lowering/lower-test.js.map +1 -0
  141. package/dist/frontend/lowering/lowerer.d.ts +1748 -0
  142. package/dist/frontend/lowering/lowerer.js +6257 -0
  143. package/dist/frontend/lowering/lowerer.js.map +1 -0
  144. package/dist/frontend/lowering/surfaces.d.ts +272 -0
  145. package/dist/frontend/lowering/surfaces.js +1096 -0
  146. package/dist/frontend/lowering/surfaces.js.map +1 -0
  147. package/dist/frontend/npm-static.d.ts +55 -0
  148. package/dist/frontend/npm-static.js +300 -0
  149. package/dist/frontend/npm-static.js.map +1 -0
  150. package/dist/frontend/npm.d.ts +330 -0
  151. package/dist/frontend/npm.js +1259 -0
  152. package/dist/frontend/npm.js.map +1 -0
  153. package/dist/frontend/program.d.ts +186 -0
  154. package/dist/frontend/program.js +2255 -0
  155. package/dist/frontend/program.js.map +1 -0
  156. package/dist/frontend/provenance-registry.d.ts +48 -0
  157. package/dist/frontend/provenance-registry.js +87 -0
  158. package/dist/frontend/provenance-registry.js.map +1 -0
  159. package/dist/frontend/provenance.d.ts +6 -0
  160. package/dist/frontend/provenance.js +459 -0
  161. package/dist/frontend/provenance.js.map +1 -0
  162. package/dist/frontend/resolve.d.ts +56 -0
  163. package/dist/frontend/resolve.js +682 -0
  164. package/dist/frontend/resolve.js.map +1 -0
  165. package/dist/frontend/shared.d.ts +67 -0
  166. package/dist/frontend/shared.js +241 -0
  167. package/dist/frontend/shared.js.map +1 -0
  168. package/dist/frontend/ts7/adapter.d.ts +7 -0
  169. package/dist/frontend/ts7/adapter.js +54 -0
  170. package/dist/frontend/ts7/adapter.js.map +1 -0
  171. package/dist/frontend/ts7/ast.d.ts +50 -0
  172. package/dist/frontend/ts7/ast.js +211 -0
  173. package/dist/frontend/ts7/ast.js.map +1 -0
  174. package/dist/frontend/ts7/census-check.d.ts +1 -0
  175. package/dist/frontend/ts7/census-check.js +12 -0
  176. package/dist/frontend/ts7/census-check.js.map +1 -0
  177. package/dist/frontend/ts7/checker.d.ts +140 -0
  178. package/dist/frontend/ts7/checker.js +544 -0
  179. package/dist/frontend/ts7/checker.js.map +1 -0
  180. package/dist/frontend/ts7/enums.d.ts +18 -0
  181. package/dist/frontend/ts7/enums.js +43 -0
  182. package/dist/frontend/ts7/enums.js.map +1 -0
  183. package/dist/frontend/ts7/program.d.ts +99 -0
  184. package/dist/frontend/ts7/program.js +278 -0
  185. package/dist/frontend/ts7/program.js.map +1 -0
  186. package/dist/frontend/ts7/world-check.d.ts +3 -0
  187. package/dist/frontend/ts7/world-check.js +34 -0
  188. package/dist/frontend/ts7/world-check.js.map +1 -0
  189. package/dist/frontend/types.d.ts +205 -0
  190. package/dist/frontend/types.js +2485 -0
  191. package/dist/frontend/types.js.map +1 -0
  192. package/dist/index.d.ts +85 -0
  193. package/dist/index.js +429 -0
  194. package/dist/index.js.map +1 -0
  195. package/dist/ir/nodes.d.ts +4334 -0
  196. package/dist/ir/nodes.js +1906 -0
  197. package/dist/ir/nodes.js.map +1 -0
  198. package/dist/ir/serialize.d.ts +4 -0
  199. package/dist/ir/serialize.js +34 -0
  200. package/dist/ir/serialize.js.map +1 -0
  201. package/dist/ir/validate.d.ts +36 -0
  202. package/dist/ir/validate.js +4885 -0
  203. package/dist/ir/validate.js.map +1 -0
  204. package/package.json +30 -6
  205. package/README.md +0 -3
  206. package/index.js +0 -2
@@ -0,0 +1,996 @@
1
+ import { mangleArgPack, mangleAsyncSpawn, mangleChildDataThunk, mangleChildExitThunk, mangleCloseBindThunk, mangleCloseOverrideWrap, mangleConnectSockThunk, mangleDgramMsgThunk, mangleDnsLookupThunk, mangleField, mangleFunction, mangleGenDrop, mangleGenResThunk, mangleGenSpawn, mangleLocal, mangleRaceThunk, mangleRawParam, mangleNetLookupAnswerThunk, mangleEmitterInvokeThunk, mangleStreamCbThunk, mangleStreamDoneFn, mangleRecordNew, mangleRecordRelease, mangleRecordStruct, mangleResolveThunk, mangleSniAnswerThunk, mangleTrampoline } from "../mangle.js";
2
+ import { cDecl, cType, releaseCallC, retainCallC, vAdapters } from "./emit-types.js";
3
+ import { isRefCounted, isUnitType, typeEquals, typeKey } from "../../ir/nodes.js";
4
+ /** Per-async-function machinery: an argument pack, a fiber trampoline
5
+ * (unpacks, runs the ordinary compiled body, settles the promise), and a
6
+ * spawn wrapper call sites use. Lifted async lambdas additionally thread
7
+ * their closure env through the pack (+1, released after the body). */
8
+ export function emitAsyncScaffolding(E, out) {
9
+ for (const fn of E.mod.functions) {
10
+ if (!fn.async)
11
+ continue;
12
+ const pack = mangleArgPack(fn.name);
13
+ const lifted = fn.captures !== undefined;
14
+ const fields = [];
15
+ if (lifted)
16
+ fields.push("ScrClosure *sc_env;");
17
+ const boxedIds = new Set(fn.locals.filter((l) => l.boxed).map((l) => l.id));
18
+ const pname = (p) => boxedIds.has(p.localId) ? mangleRawParam(p.localId) : mangleLocal(p.localId);
19
+ for (const p of fn.params)
20
+ fields.push(`${cDecl(p.type, pname(p))};`);
21
+ out.push(``, `typedef struct { ${fields.join(" ") || "char sc_unused;"} } ${pack};`);
22
+ const callArgs = [
23
+ ...(lifted ? ["sc_a.sc_env"] : []),
24
+ ...fn.params.map((p) => `sc_a.${pname(p)}`),
25
+ ].join(", ");
26
+ const ret = fn.returnType;
27
+ const bodyCall = `${mangleFunction(fn.name)}(${callArgs})`;
28
+ const lines = [
29
+ `static void ${mangleTrampoline(fn.name)}(ScrFiber *sc_self, void *sc_ap0) {`,
30
+ ` ${pack} sc_a = *(${pack} *)sc_ap0;`,
31
+ ` free(sc_ap0);`,
32
+ ];
33
+ if (ret.kind === "void") {
34
+ lines.push(` ${bodyCall};`);
35
+ }
36
+ else {
37
+ lines.push(` ${cDecl(ret, "sc_r")} = ${bodyCall};`);
38
+ }
39
+ if (lifted)
40
+ lines.push(` scr_closure_release(sc_a.sc_env);`);
41
+ lines.push(` if (!scr_exc_pending()) {`);
42
+ switch (ret.kind) {
43
+ case "void":
44
+ lines.push(` scr_promise_fulfill_void(scr_fiber_promise(sc_self));`);
45
+ break;
46
+ case "f64":
47
+ lines.push(` scr_promise_fulfill_f64(scr_fiber_promise(sc_self), sc_r);`);
48
+ break;
49
+ case "bool":
50
+ lines.push(` scr_promise_fulfill_bool(scr_fiber_promise(sc_self), sc_r);`);
51
+ break;
52
+ case "string":
53
+ lines.push(` scr_promise_fulfill_str(scr_fiber_promise(sc_self), sc_r);`);
54
+ break;
55
+ default: {
56
+ const v = vAdapters(ret);
57
+ lines.push(` scr_promise_fulfill_ref(scr_fiber_promise(sc_self), sc_r, ${v.retain}, ${v.release}, ${E.traceArgC(ret)});`);
58
+ }
59
+ }
60
+ lines.push(` }`);
61
+ if (ret.kind !== "void" && isRefCounted(ret)) {
62
+ // An escaping throw means sc_r is the never-read dummy (NULL).
63
+ lines.push(` else { ${releaseCallC(ret, "sc_r")}; }`);
64
+ }
65
+ lines.push(`}`);
66
+ out.push(...lines);
67
+ const spawnParams = [
68
+ ...(lifted ? ["ScrClosure *sc_env"] : []),
69
+ ...fn.params.map((p) => cDecl(p.type, pname(p))),
70
+ ];
71
+ out.push(`static ScrPromise *${mangleAsyncSpawn(fn.name)}(${spawnParams.join(", ") || "void"}) {`, ` ${pack} *sc_ap = malloc(sizeof *sc_ap);`, ` if (!sc_ap) { fputs("scriptc: out of memory\\n", stderr); abort(); }`, ...(lifted ? [` sc_ap->sc_env = scr_closure_retain(sc_env);`] : []), ...fn.params.map((p) => ` sc_ap->${pname(p)} = ${pname(p)};`), ` return scr_async_spawn(&${mangleTrampoline(fn.name)}, sc_ap);`, `}`);
72
+ }
73
+ emitGenScaffolding(E, out);
74
+ }
75
+ /** Per-generator-function machinery — the async scaffolding's lazy
76
+ * sibling: the same argument pack, a fiber trampoline whose epilogue
77
+ * stores the COMPLETION value (or consumes the GENRET sentinel,
78
+ * promoting the parked .return value), a spawn wrapper that only
79
+ * ALLOCATES the suspended fiber (nothing runs until the first .next()),
80
+ * and the never-started teardown that drops the packed (+1) arguments. */
81
+ function emitGenScaffolding(E, out) {
82
+ for (const fn of E.mod.functions) {
83
+ if (!fn.generator)
84
+ continue;
85
+ const pack = mangleArgPack(fn.name);
86
+ const lifted = fn.captures !== undefined;
87
+ const fields = [];
88
+ if (lifted)
89
+ fields.push("ScrClosure *sc_env;");
90
+ const boxedIds = new Set(fn.locals.filter((l) => l.boxed).map((l) => l.id));
91
+ const pname = (p) => boxedIds.has(p.localId) ? mangleRawParam(p.localId) : mangleLocal(p.localId);
92
+ for (const p of fn.params)
93
+ fields.push(`${cDecl(p.type, pname(p))};`);
94
+ out.push(``, `typedef struct { ${fields.join(" ") || "char sc_unused;"} } ${pack};`);
95
+ const callArgs = [
96
+ ...(lifted ? ["sc_a.sc_env"] : []),
97
+ ...fn.params.map((p) => `sc_a.${pname(p)}`),
98
+ ].join(", ");
99
+ const ret = fn.returnType;
100
+ const bodyCall = `${mangleFunction(fn.name)}(${callArgs})`;
101
+ const lines = [
102
+ `static void ${mangleTrampoline(fn.name)}(ScrFiber *sc_self, void *sc_ap0) {`,
103
+ ` ${pack} sc_a = *(${pack} *)sc_ap0;`,
104
+ ` free(sc_ap0);`,
105
+ ];
106
+ if (ret.kind === "void") {
107
+ lines.push(` ${bodyCall};`);
108
+ }
109
+ else {
110
+ lines.push(` ${cDecl(ret, "sc_r")} = ${bodyCall};`);
111
+ }
112
+ if (lifted)
113
+ lines.push(` scr_closure_release(sc_a.sc_env);`);
114
+ lines.push(` ScrGen *sc_g = scr_gen_of_fiber(sc_self);`);
115
+ // Normal completion stores the (typed) return value; void completes
116
+ // with the NONE slot — JS's undefined done-value. A GENRET unwind
117
+ // consumes the sentinel and promotes the parked .return value; a
118
+ // real exception stays pending (the consumer-side resume moves it).
119
+ const store = (() => {
120
+ switch (ret.kind) {
121
+ case "void":
122
+ return null;
123
+ case "f64":
124
+ return `scr_gen_out_f64(sc_g, sc_r);`;
125
+ case "bool":
126
+ return `scr_gen_out_bool(sc_g, sc_r);`;
127
+ default: {
128
+ const v = vAdapters(ret);
129
+ return `scr_gen_out_ref(sc_g, sc_r, ${v.release});`;
130
+ }
131
+ }
132
+ })();
133
+ lines.push(` if (!scr_exc_pending()) {`, ...(store ? [` ${store}`] : [` /* void body: the done value is undefined (NONE) */`]), ` } else {`, ` if (scr_exc_genret_pending()) { scr_exc_clear(); scr_gen_ret_to_out(sc_g); }`, ...(ret.kind !== "void" && isRefCounted(ret)
134
+ ? [` ${releaseCallC(ret, "sc_r")}; /* unwound: the never-read dummy */`]
135
+ : []), ` }`, `}`);
136
+ out.push(...lines);
137
+ const spawnParams = [
138
+ ...(lifted ? ["ScrClosure *sc_env"] : []),
139
+ ...fn.params.map((p) => cDecl(p.type, pname(p))),
140
+ ];
141
+ out.push(`static void ${mangleGenDrop(fn.name)}(void *sc_ap0) {`, ` ${pack} sc_a = *(${pack} *)sc_ap0;`, ` free(sc_ap0);`, ...(lifted ? [` scr_closure_release(sc_a.sc_env);`] : []), ...fn.params
142
+ .filter((p) => isRefCounted(p.type))
143
+ .map((p) => ` ${releaseCallC(p.type, `sc_a.${pname(p)}`)};`), ...(!lifted && !fn.params.some((p) => isRefCounted(p.type)) ? [` (void)sc_a;`] : []), `}`, `static ScrGen *${mangleGenSpawn(fn.name)}(${spawnParams.join(", ") || "void"}) {`, ` ${pack} *sc_ap = malloc(sizeof *sc_ap);`, ` if (!sc_ap) { fputs("scriptc: out of memory\\n", stderr); abort(); }`, ...(lifted ? [` sc_ap->sc_env = scr_closure_retain(sc_env);`] : []), ...fn.params.map((p) => ` sc_ap->${pname(p)} = ${pname(p)};`), ` return scr_gen_new(&${mangleTrampoline(fn.name)}, sc_ap, &${mangleGenDrop(fn.name)});`, `}`);
144
+ }
145
+ }
146
+ /** Interned per-union child exit adapter: the runtime invokes
147
+ * adapter(cb, has_code, code, signal_name); the adapter builds the
148
+ * `number | null` union value (tags are program data — the frontend
149
+ * guaranteed the param IS that union) and calls the listener, which
150
+ * owns the union param per the universal convention (unit-arm
151
+ * instances are immortal; their release is a no-op). The one-param
152
+ * shape ignores the signal. */
153
+ export function childExitThunkFor(E, param) {
154
+ if (param.kind !== "union")
155
+ throw new Error("emitter bug: exit listener param not a union");
156
+ const key = param.unionId;
157
+ let sym = E.childExitThunks.get(key);
158
+ if (!sym) {
159
+ sym = mangleChildExitThunk(E.childExitThunks.size);
160
+ E.childExitThunks.set(key, sym);
161
+ const def = E.unionsById.get(param.unionId);
162
+ const f64Tag = def ? def.arms.findIndex((a) => a.kind === "f64") : -1;
163
+ const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1;
164
+ if (f64Tag < 0 || nullTag < 0) {
165
+ throw new Error("emitter bug: exit listener union lacks its arms");
166
+ }
167
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, bool sc_has, double sc_code, const char *sc_sig);`);
168
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, bool sc_has, double sc_code, const char *sc_sig) {`, ` (void)sc_sig;`, ` ScrUnion *sc_u = sc_has ? scr_union_new_f64(${f64Tag}, sc_code)`, ` : ${E.unitInstanceRef(param.unionId, nullTag)};`, ` ((void (*)(ScrClosure *, ScrUnion *))sc_cb->fn)(sc_cb, sc_u);`, `}`);
169
+ }
170
+ return sym;
171
+ }
172
+ /** The TWO-parameter exit adapter — Node's `(code, signal)` listener:
173
+ * the code union as above plus the signal as its own `string | null`
174
+ * union (a fresh string from the runtime's static signal name when a
175
+ * signal killed the child, the null arm otherwise). */
176
+ export function childExitThunkFor2(E, codeParam, sigParam) {
177
+ if (codeParam.kind !== "union" || sigParam.kind !== "union") {
178
+ throw new Error("emitter bug: exit listener params not unions");
179
+ }
180
+ const key = `${codeParam.unionId}+${sigParam.unionId}`;
181
+ let sym = E.childExitThunks.get(key);
182
+ if (!sym) {
183
+ sym = mangleChildExitThunk(E.childExitThunks.size);
184
+ E.childExitThunks.set(key, sym);
185
+ const codeDef = E.unionsById.get(codeParam.unionId);
186
+ const f64Tag = codeDef ? codeDef.arms.findIndex((a) => a.kind === "f64") : -1;
187
+ const codeNullTag = codeDef ? codeDef.arms.findIndex((a) => a.kind === "nullT") : -1;
188
+ const sigDef = E.unionsById.get(sigParam.unionId);
189
+ const strTag = sigDef ? sigDef.arms.findIndex((a) => a.kind === "string") : -1;
190
+ const sigNullTag = sigDef ? sigDef.arms.findIndex((a) => a.kind === "nullT") : -1;
191
+ if (f64Tag < 0 || codeNullTag < 0 || strTag < 0 || sigNullTag < 0) {
192
+ throw new Error("emitter bug: exit listener unions lack their arms");
193
+ }
194
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, bool sc_has, double sc_code, const char *sc_sig);`);
195
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, bool sc_has, double sc_code, const char *sc_sig) {`, ` ScrUnion *sc_u = sc_has ? scr_union_new_f64(${f64Tag}, sc_code)`, ` : ${E.unitInstanceRef(codeParam.unionId, codeNullTag)};`, ` ScrUnion *sc_s = sc_sig`, ` ? scr_union_new_ref(${strTag}, scr_str_new(sc_sig, strlen(sc_sig)), &scr_str_retain_v, &scr_str_release_v, NULL)`, ` : ${E.unitInstanceRef(sigParam.unionId, sigNullTag)};`, ` ((void (*)(ScrClosure *, ScrUnion *, ScrUnion *))sc_cb->fn)(sc_cb, sc_u, sc_s);`, `}`);
196
+ }
197
+ return sym;
198
+ }
199
+ /** Interned per-union child-stream data adapter: the runtime invokes
200
+ * adapter(cb, chunk) with the chunk BORROWED; the adapter wraps a
201
+ * retained chunk at the union's Buffer arm (the frontend guaranteed
202
+ * the arm exists — the ngrok `Buffer | string` listener; strings never
203
+ * fire from a pipe) and calls the listener, which owns the union param
204
+ * per the universal convention. */
205
+ export function childDataThunkFor(E, param) {
206
+ if (param.kind !== "union")
207
+ throw new Error("emitter bug: stream data listener param not a union");
208
+ const key = param.unionId;
209
+ let sym = E.childDataThunks.get(key);
210
+ if (!sym) {
211
+ sym = mangleChildDataThunk(E.childDataThunks.size);
212
+ E.childDataThunks.set(key, sym);
213
+ const def = E.unionsById.get(param.unionId);
214
+ const bytesTag = def ? def.arms.findIndex((a) => a.kind === "bytes" && a.elem === "u8") : -1;
215
+ if (bytesTag < 0) {
216
+ throw new Error("emitter bug: stream data listener union lacks its Buffer arm");
217
+ }
218
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, ScrBytes *sc_chunk);`);
219
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, ScrBytes *sc_chunk) {`, ` ScrUnion *sc_u = scr_union_new_ref(${bytesTag}, scr_bytes_retain(sc_chunk), &scr_bytes_retain_v, &scr_bytes_release_v, NULL);`, ` ((void (*)(ScrClosure *, ScrUnion *))sc_cb->fn)(sc_cb, sc_u);`, `}`);
220
+ }
221
+ return sym;
222
+ }
223
+ /** Interned per-union bound-close adapter (`server.close.bind(server)`
224
+ * as a value): invoked through the closure ABI with the callback-union
225
+ * argument, it closes the server DIRECTLY (never through the override —
226
+ * the proxy-through idiom cannot recurse), registering a present
227
+ * callback as a once-'close' listener — a ONE-param callback (Node's
228
+ * `(err?: Error) => void`) behind an emitted zero-arg trampoline that
229
+ * fires it with the undefined arm (a clean close carries no error) —
230
+ * and answers the +1 server (Node's chaining return). */
231
+ export function closeBindThunkFor(E, cbUnion, retServer) {
232
+ if (cbUnion.kind !== "union")
233
+ throw new Error("emitter bug: bound-close callback param not a union");
234
+ const key = `${cbUnion.unionId}:${retServer ? "srv" : "void"}`;
235
+ let sym = E.closeBindThunks.get(key);
236
+ if (sym)
237
+ return sym;
238
+ sym = mangleCloseBindThunk(E.closeBindThunks.size);
239
+ E.closeBindThunks.set(key, sym);
240
+ const def = E.unionsById.get(cbUnion.unionId);
241
+ const funcTag = def ? def.arms.findIndex((a) => a.kind === "func") : -1;
242
+ const funcArm = funcTag >= 0 ? def.arms[funcTag] : null;
243
+ if (!funcArm)
244
+ throw new Error("emitter bug: bound-close callback union lacks its func arm");
245
+ const oneParam = funcArm.params.length === 1;
246
+ let trampoline = null;
247
+ if (oneParam) {
248
+ const errParam = funcArm.params[0];
249
+ if (errParam.kind !== "union") {
250
+ throw new Error("emitter bug: bound-close callback's err param is not a union");
251
+ }
252
+ const errDef = E.unionsById.get(errParam.unionId);
253
+ const undefTag = errDef ? errDef.arms.findIndex((a) => a.kind === "undefinedT") : -1;
254
+ if (undefTag < 0)
255
+ throw new Error("emitter bug: bound-close err union lacks its undefined arm");
256
+ trampoline = `${sym}_cb`;
257
+ E.walkerProtos.push(`static void ${trampoline}(ScrClosure *sc_self);`);
258
+ E.walkerDefs.push(`static void ${trampoline}(ScrClosure *sc_self) { /* close cb: fire with no error */`, ` ScrClosure *sc_inner = (ScrClosure *)scr_box_get_ref(sc_self->caps[0]); /* +1 */`, ` ((void (*)(ScrClosure *, ScrUnion *))sc_inner->fn)(sc_inner, ${E.unitInstanceRef(errParam.unionId, undefTag)});`, ` scr_closure_release(sc_inner);`, `}`);
259
+ }
260
+ const retC = retServer ? "ScrNetServer *" : "void";
261
+ E.walkerProtos.push(`static ${retC}${retServer ? "" : " "}${sym}(ScrClosure *sc_self, ScrUnion *sc_cb);`);
262
+ E.walkerDefs.push(`static ${retC}${retServer ? "" : " "}${sym}(ScrClosure *sc_self, ScrUnion *sc_cb) { /* bound server.close */`, ` ScrNetServer *sc_srv = (ScrNetServer *)scr_box_get_ref(sc_self->caps[0]); /* +1 */`, ` ScrClosure *sc_reg = NULL;`, ` if (sc_cb->tag == ${funcTag}) {`, ...(oneParam
263
+ ? [
264
+ ` sc_reg = scr_closure_new((void *)&${trampoline}, 1);`,
265
+ ` sc_reg->caps[0] = scr_box_new(SCR_BOX_FUNC);`,
266
+ ` scr_box_set_ref(sc_reg->caps[0], scr_closure_retain((ScrClosure *)scr_union_peek(sc_cb)));`,
267
+ ]
268
+ : [` sc_reg = scr_closure_retain((ScrClosure *)scr_union_peek(sc_cb));`]), ` }`, ` scr_union_release(sc_cb); /* the callee owns its +1 param */`, ` scr_net_server_close_direct(sc_srv, sc_reg); /* sc_reg moves */`, ...(retServer
269
+ ? [` return sc_srv; /* +1 from the env read */`]
270
+ : [` scr_net_server_release(sc_srv);`]), `}`);
271
+ return sym;
272
+ }
273
+ /** Interned close-override zero-arg wrapper (`server.close = fn`): the
274
+ * stored override must be invocable by the runtime WITHOUT program
275
+ * types, so this wrapper carries the user function and fires it with
276
+ * the undefined-arm callback argument (tags are program data),
277
+ * releasing the chaining-return server when the signature answers one. */
278
+ export function closeOverrideWrapFor(E, cbUnion, retServer) {
279
+ if (cbUnion.kind !== "union")
280
+ throw new Error("emitter bug: close-override callback param not a union");
281
+ const key = `${cbUnion.unionId}:${retServer ? "srv" : "void"}`;
282
+ let sym = E.closeOverrideWraps.get(key);
283
+ if (sym)
284
+ return sym;
285
+ sym = mangleCloseOverrideWrap(E.closeOverrideWraps.size);
286
+ E.closeOverrideWraps.set(key, sym);
287
+ const def = E.unionsById.get(cbUnion.unionId);
288
+ const undefTag = def ? def.arms.findIndex((a) => a.kind === "undefinedT") : -1;
289
+ if (undefTag < 0)
290
+ throw new Error("emitter bug: close-override callback union lacks its undefined arm");
291
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_self);`);
292
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_self) { /* close override wrapper */`, ` ScrClosure *sc_inner = (ScrClosure *)scr_box_get_ref(sc_self->caps[0]); /* +1 */`, ...(retServer
293
+ ? [
294
+ ` ScrNetServer *sc_r = ((ScrNetServer *(*)(ScrClosure *, ScrUnion *))sc_inner->fn)(sc_inner, ${E.unitInstanceRef(cbUnion.unionId, undefTag)});`,
295
+ ` scr_net_server_release(sc_r); /* the chaining return is unobserved here */`,
296
+ ]
297
+ : [
298
+ ` ((void (*)(ScrClosure *, ScrUnion *))sc_inner->fn)(sc_inner, ${E.unitInstanceRef(cbUnion.unionId, undefTag)});`,
299
+ ]), ` scr_closure_release(sc_inner);`, `}`);
300
+ return sym;
301
+ }
302
+ /** Interned per-shape dgram message adapter for the TWO-parameter
303
+ * (msg: Buffer, rinfo) listener: the rinfo RECORD is program data, so
304
+ * the adapter builds it from the runtime's parts (address/family
305
+ * borrowed and retained in; port/size by value) and calls the listener,
306
+ * which owns both params per the universal convention (msg retains like
307
+ * the net data thunk). Zero/one-param listeners take the runtime's own
308
+ * scr_dgram_msg_thunk0/1. */
309
+ export function dgramMsgThunkFor(E, param) {
310
+ if (param.kind !== "record")
311
+ throw new Error("emitter bug: message listener rinfo not a record");
312
+ const key = param.shapeId;
313
+ let sym = E.dgramMsgThunks.get(key);
314
+ if (!sym) {
315
+ sym = mangleDgramMsgThunk(E.dgramMsgThunks.size);
316
+ E.dgramMsgThunks.set(key, sym);
317
+ const struct = mangleRecordStruct(param.shapeId);
318
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, ScrBytes *sc_msg, ScrStr *sc_addr, ScrStr *sc_family, double sc_port, double sc_size);`);
319
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, ScrBytes *sc_msg, ScrStr *sc_addr, ScrStr *sc_family, double sc_port, double sc_size) {`, ` ${struct} *sc_ri = ${mangleRecordNew(param.shapeId)}();`, ` sc_ri->${mangleField("address")} = scr_str_retain(sc_addr);`, ` sc_ri->${mangleField("family")} = scr_str_retain(sc_family);`, ` sc_ri->${mangleField("port")} = sc_port;`, ` sc_ri->${mangleField("size")} = sc_size;`, ` ((void (*)(ScrClosure *, ScrBytes *, ${struct} *))sc_cb->fn)(sc_cb, scr_bytes_retain(sc_msg), sc_ri);`, `}`);
320
+ }
321
+ return sym;
322
+ }
323
+ /** Interned dns.lookup callback adapter, per (union id, param count):
324
+ * the runtime invokes adapter(cb, errmsg-or-NULL, address, family) with
325
+ * everything borrowed; the adapter builds the `Error | null` first
326
+ * argument (tags are program data — the frontend pinned the union) and
327
+ * calls the listener with as many arguments as it declared, each owned
328
+ * per the universal convention. */
329
+ export function dnsLookupThunkFor(E, cbT) {
330
+ if (cbT.kind !== "func")
331
+ throw new Error("emitter bug: dns.lookup callback not a func");
332
+ const nparams = cbT.params.length;
333
+ if (nparams === 0)
334
+ return "scr_dns_thunk0";
335
+ const errT = cbT.params[0];
336
+ if (errT.kind !== "union")
337
+ throw new Error("emitter bug: dns.lookup err param not a union");
338
+ const key = `${errT.unionId}/${nparams}`;
339
+ let sym = E.dnsLookupThunks.get(key);
340
+ if (!sym) {
341
+ sym = mangleDnsLookupThunk(E.dnsLookupThunks.size);
342
+ E.dnsLookupThunks.set(key, sym);
343
+ const def = E.unionsById.get(errT.unionId);
344
+ const errTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1;
345
+ const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1;
346
+ if (errTag < 0 || nullTag < 0) {
347
+ throw new Error("emitter bug: dns.lookup err union lacks its arms");
348
+ }
349
+ const callSig = ["ScrClosure *", "ScrUnion *", ...(nparams >= 2 ? ["ScrStr *"] : []), ...(nparams >= 3 ? ["double"] : [])].join(", ");
350
+ const callArgs = ["sc_cb", "sc_u", ...(nparams >= 2 ? ["scr_str_retain(sc_addr)"] : []), ...(nparams >= 3 ? ["sc_family"] : [])].join(", ");
351
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, ScrStr *sc_err, ScrStr *sc_addr, double sc_family);`);
352
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, ScrStr *sc_err, ScrStr *sc_addr, double sc_family) {`, ` (void)sc_addr; (void)sc_family;`, ` ScrUnion *sc_u = sc_err`, ` ? scr_union_new_ref(${errTag}, scr_error_new(0, sc_err), &scr_error_retain_v, &scr_error_release_v, NULL)`, ` : ${E.unitInstanceRef(errT.unionId, nullTag)};`, ` ((void (*)(${callSig}))sc_cb->fn)(${callArgs});`, `}`);
353
+ }
354
+ return sym;
355
+ }
356
+ /** Interned CONNECT-listener adapter for a UNION socket slot — the h2
357
+ * compat listener (`(req, resOrSocket: Http2ServerResponse | net.Socket)
358
+ * => void`): the runtime fires (cb, req, sock, head) like an upgrade;
359
+ * the adapter wraps the socket at the union's netSocket arm (its tag is
360
+ * program data) and drops `head` when the listener declares two params.
361
+ * All three runtime args arrive +1; the union takes the socket's. */
362
+ export function connectSockThunkFor(E, cbT) {
363
+ if (cbT.kind !== "func" || cbT.params[1]?.kind !== "union") {
364
+ throw new Error("emitter bug: connect listener union shape (frontend must fence)");
365
+ }
366
+ const key = typeKey(cbT);
367
+ let sym = E.connectSockThunks.get(key);
368
+ if (sym)
369
+ return sym;
370
+ sym = mangleConnectSockThunk(E.connectSockThunks.size);
371
+ E.connectSockThunks.set(key, sym);
372
+ const sockT = cbT.params[1];
373
+ const def = E.unionsById.get(sockT.unionId);
374
+ const tag = def ? def.arms.findIndex((a) => a.kind === "netSocket") : -1;
375
+ if (tag < 0)
376
+ throw new Error("emitter bug: connect listener union lacks its socket arm");
377
+ const three = cbT.params.length === 3;
378
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, ScrHttpReq *sc_req, ScrNetSocket *sc_sock, ScrBytes *sc_head);`);
379
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, ScrHttpReq *sc_req, ScrNetSocket *sc_sock, ScrBytes *sc_head) {`, ...(three ? [] : [` scr_bytes_release(sc_head);`]), ` ScrUnion *sc_u = scr_union_new_ref(${tag}, sc_sock, &scr_net_sock_retain_v, &scr_net_sock_release_v, NULL);`, three
380
+ ? ` ((void (*)(ScrClosure *, ScrHttpReq *, ScrUnion *, ScrBytes *))sc_cb->fn)(sc_cb, sc_req, sc_u, sc_head);`
381
+ : ` ((void (*)(ScrClosure *, ScrHttpReq *, ScrUnion *))sc_cb->fn)(sc_cb, sc_req, sc_u);`, `}`);
382
+ return sym;
383
+ }
384
+ /** Interned net.connect-lookup answer thunk: the fn of the runtime-minted
385
+ * callback the caller's `lookup` option receives (the SNI-answer
386
+ * pattern). COMPILED code calls it per its static type — `(err:
387
+ * ErrnoException | null, addresses: { address: string, ... }[]) =>
388
+ * void` — so the thunk decodes the program-interned err union (null
389
+ * tag) and the addresses record's field layout down to the runtime's
390
+ * (has_err, message, ip-list) answer. Params arrive +1 and release
391
+ * here; the extracted message/ips hand over +1. */
392
+ export function netLookupAnswerThunkFor(E, cbT) {
393
+ if (cbT.kind !== "func" || cbT.params.length !== 2) {
394
+ throw new Error("emitter bug: lookup answer cb shape (frontend must fence)");
395
+ }
396
+ const key = typeKey(cbT);
397
+ let sym = E.netLookupAnswerThunks.get(key);
398
+ if (sym)
399
+ return sym;
400
+ sym = mangleNetLookupAnswerThunk(E.netLookupAnswerThunks.size);
401
+ E.netLookupAnswerThunks.set(key, sym);
402
+ const errT = cbT.params[0];
403
+ if (errT.kind !== "union")
404
+ throw new Error("emitter bug: lookup answer err param not a union");
405
+ const errDef = E.unionsById.get(errT.unionId);
406
+ const nullTag = errDef ? errDef.arms.findIndex((a) => a.kind === "nullT") : -1;
407
+ if (nullTag < 0)
408
+ throw new Error("emitter bug: lookup answer err union lacks its null arm");
409
+ const addrsT = cbT.params[1];
410
+ if (addrsT.kind !== "array" || addrsT.elem.kind !== "record") {
411
+ throw new Error("emitter bug: lookup answer addresses param not a record array");
412
+ }
413
+ const recStruct = mangleRecordStruct(addrsT.elem.shapeId);
414
+ const recRelease = mangleRecordRelease(addrsT.elem.shapeId);
415
+ const addrField = mangleField("address");
416
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_self, ScrUnion *sc_err, ScrArr *sc_addrs);`);
417
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_self, ScrUnion *sc_err, ScrArr *sc_addrs) {`, ` bool sc_has_err = sc_err->tag != ${nullTag};`, ` ScrStr *sc_msg = NULL;`, ` if (sc_has_err) sc_msg = scr_str_retain(((ScrError *)scr_union_peek(sc_err))->message);`, ` ScrArr *sc_ips = NULL;`, ` if (!sc_has_err) {`, ` double sc_n = scr_arr_len(sc_addrs);`, ` sc_ips = scr_arr_new(SCR_ELEM_STR, (size_t)sc_n);`, ` for (double sc_i = 0; sc_i < sc_n; sc_i++) {`, ` ${recStruct} *sc_r = (${recStruct} *)scr_arr_get_ref(sc_addrs, sc_i);`, ` scr_arr_push_ref(sc_ips, scr_str_retain(sc_r->${addrField}));`, ` ${recRelease}(sc_r);`, ` }`, ` }`, ` scr_net_lookup_answer(sc_self, sc_has_err, sc_msg, sc_ips);`, ` scr_str_release(sc_msg);`, ` scr_arr_release(sc_ips);`, ` scr_union_release(sc_err);`, ` scr_arr_release(sc_addrs);`, `}`);
418
+ return sym;
419
+ }
420
+ /** Interned SNI answer-closure thunk: the fn of the runtime-minted `cb`
421
+ * a TLS server's SNICallback receives. COMPILED code calls cb per its
422
+ * static type — `(err: Error | null, ctx?: SecureContext) => void`, or
423
+ * a shorter declared shape — so the thunk's signature is that ABI, and
424
+ * it decodes the program-interned unions (tags are program data) down
425
+ * to the runtime's (has_err, ctx-or-NULL) answer. The thunk owns its
426
+ * union params per the universal convention (the ctx payload retains
427
+ * +1 before its union releases; ownership moves to the runtime). */
428
+ export function sniAnswerThunkFor(E, cbT) {
429
+ if (cbT.kind !== "func")
430
+ throw new Error("emitter bug: SNI answer cb not a func");
431
+ const key = typeKey(cbT);
432
+ let sym = E.sniAnswerThunks.get(key);
433
+ if (sym)
434
+ return sym;
435
+ sym = mangleSniAnswerThunk(E.sniAnswerThunks.size);
436
+ E.sniAnswerThunks.set(key, sym);
437
+ const nparams = cbT.params.length;
438
+ const params = ["ScrClosure *sc_self"];
439
+ const body = [];
440
+ let hasErr = "false";
441
+ if (nparams >= 1) {
442
+ const errT = cbT.params[0];
443
+ if (errT.kind !== "union")
444
+ throw new Error("emitter bug: SNI answer err param not a union");
445
+ const def = E.unionsById.get(errT.unionId);
446
+ const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1;
447
+ if (nullTag < 0)
448
+ throw new Error("emitter bug: SNI answer err union lacks its null arm");
449
+ params.push("ScrUnion *sc_err");
450
+ hasErr = `sc_err->tag != ${nullTag}`;
451
+ }
452
+ let ctx = "NULL";
453
+ if (nparams >= 2) {
454
+ const ctxT = cbT.params[1];
455
+ if (ctxT.kind !== "union")
456
+ throw new Error("emitter bug: SNI answer ctx param not a union");
457
+ const def = E.unionsById.get(ctxT.unionId);
458
+ const ctxTag = def ? def.arms.findIndex((a) => a.kind === "secureCtx") : -1;
459
+ if (ctxTag < 0)
460
+ throw new Error("emitter bug: SNI answer ctx union lacks its SecureContext arm");
461
+ params.push("ScrUnion *sc_ctx");
462
+ body.push(` ScrSecureCtx *sc_c = sc_ctx->tag == ${ctxTag} ? scr_secure_ctx_retain((ScrSecureCtx *)scr_union_peek(sc_ctx)) : NULL;`, ` scr_union_release(sc_ctx);`);
463
+ ctx = "sc_c";
464
+ }
465
+ body.push(` bool sc_has_err = ${hasErr};`);
466
+ if (nparams >= 1)
467
+ body.push(` scr_union_release(sc_err);`);
468
+ body.push(` scr_tls_sni_answer(sc_self, sc_has_err, ${ctx});`);
469
+ E.walkerProtos.push(`static void ${sym}(${params.join(", ")});`);
470
+ E.walkerDefs.push(`static void ${sym}(${params.join(", ")}) {`, ...body, `}`);
471
+ return sym;
472
+ }
473
+ /** Interned Promise.race fulfillment adapter: converts a settled entry's
474
+ * payload (inner type `from`) into the result promise's inner type `to`
475
+ * and fulfills the destination. Three shapes, mirroring the frontend's
476
+ * compatibility fence: identical types share the runtime's raw copy;
477
+ * a plain entry under a union result wraps into its arm; a sub-union
478
+ * entry re-tags arm-wise (canonical arm order makes the mapping a
479
+ * typeKey lookup). Rejections never reach adapters — the runtime copies
480
+ * them raw. */
481
+ export function raceAdapterFor(E, from, to) {
482
+ if (typeEquals(from, to))
483
+ return "scr_promise_adapt_copy";
484
+ const key = `${typeKey(from)}=>${typeKey(to)}`;
485
+ let sym = E.raceThunks.get(key);
486
+ if (sym)
487
+ return sym;
488
+ sym = mangleRaceThunk(E.raceThunks.size);
489
+ E.raceThunks.set(key, sym);
490
+ if (to.kind !== "union")
491
+ throw new Error("emitter bug: race adapter to a non-union");
492
+ const toDef = E.unionsById.get(to.unionId);
493
+ if (!toDef)
494
+ throw new Error("emitter bug: race adapter to an unknown union");
495
+ const tagOf = (t) => {
496
+ const tag = toDef.arms.findIndex((a) => typeEquals(a, t));
497
+ if (tag < 0)
498
+ throw new Error("emitter bug: race adapter arm missing (frontend must fence)");
499
+ return tag;
500
+ };
501
+ const rv = vAdapters(to);
502
+ const fulfill = (value) => `scr_promise_fulfill_ref(sc_dst, ${value}, ${rv.retain}, ${rv.release}, ${E.traceArgC(to)});`;
503
+ E.walkerProtos.push(`static void ${sym}(ScrPromise *sc_dst, ScrPromise *sc_src);`);
504
+ if (from.kind !== "union") {
505
+ // One arm wrap, straight off the payload accessors.
506
+ const tag = tagOf(from);
507
+ let value;
508
+ switch (from.kind) {
509
+ case "f64":
510
+ value = `scr_union_new_f64(${tag}, scr_promise_payload_f64(sc_src))`;
511
+ break;
512
+ case "bool":
513
+ value = `scr_union_new_bool(${tag}, scr_promise_payload_bool(sc_src))`;
514
+ break;
515
+ case "string":
516
+ value = `scr_union_new_ref(${tag}, scr_promise_payload_str(sc_src), scr_str_retain_v, scr_str_release_v, NULL)`;
517
+ break;
518
+ default: {
519
+ const fv = vAdapters(from);
520
+ value = `scr_union_new_ref(${tag}, scr_promise_payload_ref(sc_src), ${fv.retain}, ${fv.release}, ${E.traceArgC(from)})`;
521
+ }
522
+ }
523
+ E.walkerDefs.push(`static void ${sym}(ScrPromise *sc_dst, ScrPromise *sc_src) {`, ` ${fulfill(value)}`, `}`);
524
+ return sym;
525
+ }
526
+ // Sub-union re-tag: switch over the entry's arms, rebuild under the
527
+ // result's tags (payloads retained through each arm's own adapters).
528
+ const fromDef = E.unionsById.get(from.unionId);
529
+ if (!fromDef)
530
+ throw new Error("emitter bug: race adapter from an unknown union");
531
+ const cases = fromDef.arms.map((arm, i) => {
532
+ const tag = tagOf(arm);
533
+ let build;
534
+ if (isUnitType(arm)) {
535
+ build = E.unitInstanceRef(to.unionId, tag);
536
+ }
537
+ else if (arm.kind === "f64") {
538
+ build = `scr_union_new_f64(${tag}, scr_union_get_f64(sc_u))`;
539
+ }
540
+ else if (arm.kind === "bool") {
541
+ build = `scr_union_new_bool(${tag}, scr_union_get_bool(sc_u))`;
542
+ }
543
+ else {
544
+ const av = vAdapters(arm);
545
+ build = `scr_union_new_ref(${tag}, ${av.retain}(scr_union_peek(sc_u)), ${av.retain}, ${av.release}, ${E.traceArgC(arm)})`;
546
+ }
547
+ return ` case ${i}: sc_v = ${build}; break;`;
548
+ });
549
+ E.walkerDefs.push(`static void ${sym}(ScrPromise *sc_dst, ScrPromise *sc_src) {`, ` ScrUnion *sc_u = (ScrUnion *)scr_promise_payload_ref(sc_src);`, ` ScrUnion *sc_v = NULL;`, ` switch (sc_u->tag) {`, ...cases, ` default: break;`, ` }`, ` scr_union_release(sc_u);`, ` ${fulfill("sc_v")}`, `}`);
550
+ return sym;
551
+ }
552
+ /** Interned generator-resume result builder: reads the post-resume state
553
+ * of a generator into a fresh IteratorResult record `{ done, value }`.
554
+ * One helper per generator type (every next/return/throw on it shares
555
+ * the record shape). The value slot: while suspended, the yielded value
556
+ * moves out of the OUT slot into its arm of V (the yield type retags
557
+ * arm-wise into a superset V, the raceAdapterFor technique); once done,
558
+ * a present completion value wraps the same way and an empty OUT is
559
+ * JS's undefined (the interned undefined arm — or the dyn undefined
560
+ * when V is dyn, the any/unknown channel). */
561
+ export function genResultThunkFor(E, genT, recT) {
562
+ const key = typeKey(genT);
563
+ let sym = E.genResThunks.get(key);
564
+ if (sym)
565
+ return sym;
566
+ sym = mangleGenResThunk(E.genResThunks.size);
567
+ E.genResThunks.set(key, sym);
568
+ const struct = mangleRecordStruct(recT.shapeId);
569
+ const shape = E.mod.records?.find((r) => r.id === recT.shapeId);
570
+ const valueT = shape?.fields.find((f) => f.name === "value")?.type;
571
+ if (!valueT)
572
+ throw new Error("emitter bug: genResume record lacks its value field");
573
+ const lines = [
574
+ `static ${struct} *${sym}(ScrGen *sc_g) {`,
575
+ ` ${struct} *sc_r = ${mangleRecordNew(recT.shapeId)}();`,
576
+ ` bool sc_d = scr_gen_done(sc_g);`,
577
+ ` sc_r->${mangleField("done")} = sc_d;`,
578
+ ];
579
+ if (valueT.kind === "dyn") {
580
+ // The any/unknown channel: OUT holds a dyn (or nothing — undefined).
581
+ lines.push(` sc_r->${mangleField("value")} = scr_gen_out_has(sc_g)`, ` ? (ScrDyn *)scr_gen_take_out_ref(sc_g)`, ` : scr_dyn_retain(scr_dyn_undefined());`);
582
+ }
583
+ else {
584
+ if (valueT.kind !== "union") {
585
+ throw new Error("emitter bug: genResume value slot is neither dyn nor a union");
586
+ }
587
+ const def = E.unionsById.get(valueT.unionId);
588
+ if (!def)
589
+ throw new Error("emitter bug: genResume value union unknown");
590
+ const tagOf = (t) => {
591
+ const tag = def.arms.findIndex((a) => typeEquals(a, t));
592
+ if (tag < 0)
593
+ throw new Error("emitter bug: genResume value union lacks an arm");
594
+ return tag;
595
+ };
596
+ const undefTag = def.arms.findIndex((a) => a.kind === "undefinedT");
597
+ if (undefTag < 0)
598
+ throw new Error("emitter bug: genResume value union lacks undefined");
599
+ // Lines that leave the wrapped value in `sc_v`, taking OUT's payload.
600
+ const wrapFrom = (srcT) => {
601
+ if (srcT.kind === "f64") {
602
+ return [` sc_v = scr_union_new_f64(${tagOf(srcT)}, scr_gen_take_out_f64(sc_g));`];
603
+ }
604
+ if (srcT.kind === "bool") {
605
+ return [` sc_v = scr_union_new_bool(${tagOf(srcT)}, scr_gen_take_out_bool(sc_g));`];
606
+ }
607
+ if (srcT.kind !== "union") {
608
+ const v = vAdapters(srcT);
609
+ return [
610
+ ` sc_v = scr_union_new_ref(${tagOf(srcT)}, scr_gen_take_out_ref(sc_g), ${v.retain}, ${v.release}, ${E.traceArgC(srcT)});`,
611
+ ];
612
+ }
613
+ // A union channel: OUT holds the union box itself. Identical V
614
+ // passes through; a superset V retags arm-wise (payloads retained
615
+ // through each arm's own adapters, then the source box drops).
616
+ if (typeEquals(srcT, valueT)) {
617
+ return [` sc_v = (ScrUnion *)scr_gen_take_out_ref(sc_g);`];
618
+ }
619
+ const srcDef = E.unionsById.get(srcT.unionId);
620
+ if (!srcDef)
621
+ throw new Error("emitter bug: genResume channel union unknown");
622
+ const cases = srcDef.arms.map((arm, i) => {
623
+ const tag = tagOf(arm);
624
+ let build;
625
+ if (isUnitType(arm)) {
626
+ build = E.unitInstanceRef(valueT.unionId, tag);
627
+ }
628
+ else if (arm.kind === "f64") {
629
+ build = `scr_union_new_f64(${tag}, scr_union_get_f64(sc_u))`;
630
+ }
631
+ else if (arm.kind === "bool") {
632
+ build = `scr_union_new_bool(${tag}, scr_union_get_bool(sc_u))`;
633
+ }
634
+ else {
635
+ const av = vAdapters(arm);
636
+ build = `scr_union_new_ref(${tag}, ${av.retain}(scr_union_peek(sc_u)), ${av.retain}, ${av.release}, ${E.traceArgC(arm)})`;
637
+ }
638
+ return ` case ${i}: sc_v = ${build}; break;`;
639
+ });
640
+ return [
641
+ ` { ScrUnion *sc_u = (ScrUnion *)scr_gen_take_out_ref(sc_g);`,
642
+ ` switch (sc_u->tag) {`,
643
+ ...cases,
644
+ ` default: sc_v = ${E.unitInstanceRef(valueT.unionId, undefTag)}; break;`,
645
+ ` }`,
646
+ ` scr_union_release(sc_u); }`,
647
+ ];
648
+ };
649
+ const undefLine = ` sc_v = ${E.unitInstanceRef(valueT.unionId, undefTag)};`;
650
+ lines.push(` ScrUnion *sc_v;`);
651
+ lines.push(` if (!sc_d) {`);
652
+ // yieldT VOID marks a generator that can never yield (TS's `never`):
653
+ // the suspended branch is unreachable — the undefined arm keeps the
654
+ // C total.
655
+ lines.push(...(genT.yieldT.kind === "void" ? [undefLine] : wrapFrom(genT.yieldT)));
656
+ lines.push(` } else if (scr_gen_out_has(sc_g)) {`);
657
+ lines.push(...(genT.retT.kind === "void" ? [undefLine] : wrapFrom(genT.retT)));
658
+ lines.push(` } else {`, undefLine, ` }`);
659
+ lines.push(` sc_r->${mangleField("value")} = sc_v;`);
660
+ }
661
+ lines.push(` return sc_r;`, `}`);
662
+ E.walkerProtos.push(`static ${struct} *${sym}(ScrGen *sc_g);`);
663
+ E.walkerDefs.push(...lines, ``);
664
+ return sym;
665
+ }
666
+ /** Interned per inner-type resolve thunk for ref-kind new Promise. */
667
+ export function resolveThunkFor(E, inner) {
668
+ const key = typeKey(inner);
669
+ let sym = E.resolveThunks.get(key);
670
+ if (!sym) {
671
+ sym = mangleResolveThunk(E.resolveThunks.size);
672
+ E.resolveThunks.set(key, sym);
673
+ const v = vAdapters(inner);
674
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_self, ${cDecl(inner, "sc_v")});`);
675
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_self, ${cDecl(inner, "sc_v")}) {`, ` scr_resolve_ref_impl(sc_self, sc_v, ${v.retain}, ${v.release}, ${E.traceArgC(inner)});`, `}`);
676
+ }
677
+ return sym;
678
+ }
679
+ /** The EventEmitter listener invoke adapter for one listener signature:
680
+ * `void (ScrClosure *, va_list)` — va_args exactly the listener's own
681
+ * parameter prefix of the emit site's typed tail (the frontend unified
682
+ * the tuple, so reads agree with writes), retains each refcounted value
683
+ * (the callee owns +1 per the universal convention; f64/bool copy), and
684
+ * calls cb->fn behind its exact C signature. Interned per func-type key. */
685
+ export function emitterInvokeThunkFor(E, cbT) {
686
+ if (cbT.kind !== "func") {
687
+ throw new Error("emitter bug: emitter.on listener not a func (frontend must fence)");
688
+ }
689
+ const key = typeKey(cbT);
690
+ let sym = E.emitterInvokeThunks.get(key);
691
+ if (sym)
692
+ return sym;
693
+ sym = mangleEmitterInvokeThunk(E.emitterInvokeThunks.size);
694
+ E.emitterInvokeThunks.set(key, sym);
695
+ const reads = [];
696
+ const passed = [];
697
+ cbT.params.forEach((p, i) => {
698
+ const name = `sc_a${i}`;
699
+ if (p.kind === "f64") {
700
+ reads.push(` double ${name} = va_arg(sc_ap, double);`);
701
+ passed.push(name);
702
+ }
703
+ else if (p.kind === "bool") {
704
+ // bool promotes to int through a variadic call.
705
+ reads.push(` bool ${name} = (bool)va_arg(sc_ap, int);`);
706
+ passed.push(name);
707
+ }
708
+ else {
709
+ reads.push(` ${cDecl(p, name)} = va_arg(sc_ap, ${cType(p).trim()});`);
710
+ passed.push(retainCallC(p, name));
711
+ }
712
+ });
713
+ const sigParams = ["ScrClosure *", ...cbT.params.map((p) => cType(p).trim())].join(", ");
714
+ // The call goes through the listener's TRUE signature; a non-void
715
+ // result is discarded (refcounted ones released) — Node ignores
716
+ // listener return values.
717
+ const retC = cbT.ret.kind === "void" ? "void" : cType(cbT.ret).trim();
718
+ const invoke = `((${retC} (*)(${sigParams}))sc_cb->fn)(${["sc_cb", ...passed].join(", ")})`;
719
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, va_list sc_ap);`);
720
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, va_list sc_ap) {`, ...(cbT.params.length === 0 ? [` (void)sc_ap;`] : reads), ...(cbT.ret.kind === "void"
721
+ ? [` ${invoke};`]
722
+ : isRefCounted(cbT.ret)
723
+ ? [` ${retC} sc_r = ${invoke};`, ` if (sc_r) ${releaseCallC(cbT.ret, "sc_r")};`]
724
+ : [` (void)${invoke};`]), `}`, ``);
725
+ return sym;
726
+ }
727
+ /** The stream completion-callback closure fn for one done func type: the
728
+ * `callback` a user's write/final/destroy/transform/flush receives. The
729
+ * closure's one capture box holds the stream (+1); calling it unwraps the
730
+ * (optional) error/data union arguments and reports completion to the
731
+ * runtime. Kind names the runtime entry: "w" scr_stream_write_done, "f"
732
+ * scr_stream_final_done, "d" scr_stream_destroy_done, "t"
733
+ * scr_stream_transform_done, "l" scr_stream_flush_done. Args arrive
734
+ * callee-owned (+1) per the universal convention and are released here. */
735
+ export function streamDoneFnFor(E, kind, doneT) {
736
+ if (doneT.kind !== "func") {
737
+ throw new Error("emitter bug: stream done callback not a func (frontend must fence)");
738
+ }
739
+ const key = `${kind}:${typeKey(doneT)}`;
740
+ let sym = E.streamDoneFns.get(key);
741
+ if (sym)
742
+ return sym;
743
+ sym = mangleStreamDoneFn(E.streamDoneFns.size);
744
+ E.streamDoneFns.set(key, sym);
745
+ const params = ["ScrClosure *sc_self"];
746
+ const body = [
747
+ ` ScrStream *sc_s = (ScrStream *)scr_box_get_ref(sc_self->caps[0]); /* +1 */`,
748
+ ` ScrError *sc_err = NULL;`,
749
+ ];
750
+ const errT = doneT.params[0];
751
+ if (errT !== undefined) {
752
+ if (errT.kind !== "union")
753
+ throw new Error("emitter bug: stream done err param not a union");
754
+ const def = E.unionsById.get(errT.unionId);
755
+ const errTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1;
756
+ if (errTag < 0)
757
+ throw new Error("emitter bug: stream done err union lacks its Error arm");
758
+ params.push("ScrUnion *sc_e");
759
+ body.push(` if (sc_e && sc_e->tag == ${errTag}) sc_err = scr_error_retain((ScrError *)scr_union_peek(sc_e));`);
760
+ }
761
+ const dataLines = [` ScrBytes *sc_data = NULL;`, ` ScrStr *sc_dataStr = NULL;`];
762
+ const dataT = doneT.params[1];
763
+ if (kind === "t" || kind === "l") {
764
+ body.push(...dataLines);
765
+ if (dataT !== undefined) {
766
+ if (dataT.kind !== "union")
767
+ throw new Error("emitter bug: stream done data param not a union");
768
+ const def = E.unionsById.get(dataT.unionId);
769
+ const bytesTag = def ? def.arms.findIndex((a) => a.kind === "bytes") : -1;
770
+ const strTag = def ? def.arms.findIndex((a) => a.kind === "string") : -1;
771
+ params.push("ScrUnion *sc_d");
772
+ body.push(...(bytesTag >= 0
773
+ ? [` if (sc_d && sc_d->tag == ${bytesTag}) sc_data = scr_bytes_retain((ScrBytes *)scr_union_peek(sc_d));`]
774
+ : []), ...(strTag >= 0
775
+ ? [` if (sc_d && sc_d->tag == ${strTag}) sc_dataStr = scr_str_retain((ScrStr *)scr_union_peek(sc_d));`]
776
+ : []));
777
+ }
778
+ }
779
+ const entry = kind === "w" ? "scr_stream_write_done" :
780
+ kind === "f" ? "scr_stream_final_done" :
781
+ kind === "d" ? "scr_stream_destroy_done" :
782
+ kind === "t" ? "scr_stream_transform_done" : "scr_stream_flush_done";
783
+ const tail = kind === "t" || kind === "l"
784
+ ? `${entry}(sc_s, sc_err, sc_data, sc_dataStr);`
785
+ : `${entry}(sc_s, sc_err);`;
786
+ E.walkerProtos.push(`static void ${sym}(${params.join(", ")});`);
787
+ E.walkerDefs.push(`static void ${sym}(${params.join(", ")}) {`, ...body, ` ${tail} /* moves sc_err/sc_data; borrows sc_s */`, ` scr_stream_release(sc_s);`, ...(errT !== undefined ? [` if (sc_e) scr_union_release(sc_e);`] : []), ...((kind === "t" || kind === "l") && dataT !== undefined ? [` if (sc_d) scr_union_release(sc_d);`] : []), `}`, ``);
788
+ return sym;
789
+ }
790
+ /** The stream-'data' listener invoke adapter for one listener func type:
791
+ * the runtime's 'data' emission carries BOTH payload slots (ScrBytes *b,
792
+ * ScrStr *s — exactly one non-NULL; encoded streams deliver strings),
793
+ * and this thunk unwraps the listener's declared side. A listener
794
+ * declaring the WRONG side for the stream's runtime mode gets a clear
795
+ * TypeError instead of a type-confused payload (Node, untyped, would
796
+ * just hand the value over — SEMANTICS.md). Zero-parameter listeners
797
+ * ignore both slots; DYN-parameter listeners (the JS lane's adapter,
798
+ * emitter.onDataDyn) box by tag, which is always right. */
799
+ export function streamDataThunkFor(E, cbT) {
800
+ if (cbT.kind !== "func") {
801
+ throw new Error("emitter bug: stream data listener not a func (frontend must fence)");
802
+ }
803
+ const key = `data:${typeKey(cbT)}`;
804
+ let sym = E.emitterInvokeThunks.get(key);
805
+ if (sym)
806
+ return sym;
807
+ sym = mangleEmitterInvokeThunk(E.emitterInvokeThunks.size);
808
+ E.emitterInvokeThunks.set(key, sym);
809
+ const p = cbT.params[0];
810
+ if (cbT.params.length > 1 || (p && p.kind !== "bytes" && p.kind !== "string" && p.kind !== "dyn")) {
811
+ throw new Error("emitter bug: stream data listener param shape (frontend must fence)");
812
+ }
813
+ const body = [
814
+ ` ScrBytes *sc_b = va_arg(sc_ap, ScrBytes *);`,
815
+ ` ScrStr *sc_s = va_arg(sc_ap, ScrStr *);`,
816
+ ];
817
+ const passed = ["sc_cb"];
818
+ if (p === undefined) {
819
+ body.push(` (void)sc_b;`, ` (void)sc_s;`);
820
+ }
821
+ else if (p.kind === "bytes") {
822
+ body.push(` if (!sc_b) {`, ` static const char sc_m[] = "a 'data' listener declaring a Buffer chunk received a string (the stream has an encoding set)";`, ` scr_throw_error_msg(SCR_ERR_TYPE, sc_m, sizeof sc_m - 1);`, ` return;`, ` }`);
823
+ passed.push("scr_bytes_retain(sc_b)");
824
+ }
825
+ else if (p.kind === "string") {
826
+ body.push(` if (!sc_s) {`, ` static const char sc_m[] = "a 'data' listener declaring a string chunk received a Buffer (call setEncoding, or declare the chunk as a Buffer)";`, ` scr_throw_error_msg(SCR_ERR_TYPE, sc_m, sizeof sc_m - 1);`, ` return;`, ` }`);
827
+ passed.push("scr_str_retain(sc_s)");
828
+ }
829
+ else {
830
+ // dyn: box by runtime tag — the JS lane's adapter parameter.
831
+ body.push(` ScrDyn *sc_d = sc_b ? scr_dyn_new_buffer_copy(sc_b) : scr_dyn_new_str(sc_s);`);
832
+ passed.push("sc_d");
833
+ }
834
+ const sigParams = ["ScrClosure *", ...cbT.params.map((q) => cType(q).trim())].join(", ");
835
+ const retC = cbT.ret.kind === "void" ? "void" : cType(cbT.ret).trim();
836
+ const invoke = `((${retC} (*)(${sigParams}))sc_cb->fn)(${passed.join(", ")})`;
837
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, va_list sc_ap);`);
838
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, va_list sc_ap) {`, ...body, ...(cbT.ret.kind === "void"
839
+ ? [` ${invoke};`]
840
+ : isRefCounted(cbT.ret)
841
+ ? [` ${retC} sc_r = ${invoke};`, ` if (sc_r) ${releaseCallC(cbT.ret, "sc_r")};`]
842
+ : [` (void)${invoke};`]), `}`, ``);
843
+ return sym;
844
+ }
845
+ /** The stream option-callback invoke adapter for one (kind, callback
846
+ * type): the runtime calls the user's read/write/final/destroy/transform/
847
+ * flush closure through it. The stream rides first (the leading `this`
848
+ * param every option callback's lifted fn takes); the user may have
849
+ * declared any PREFIX of the Node signature, so the adapter passes
850
+ * exactly the declared prefix (retaining each ref per the callee-owns
851
+ * convention) and materializes the completion-callback closure only when
852
+ * declared — an undeclared completion callback means the operation can
853
+ * never complete, exactly Node. Kinds: "r" read(size), "w" write(chunk,
854
+ * enc, cb), "f" final(cb), "d" destroy(err, cb), "t" transform(chunk,
855
+ * enc, cb), "l" flush(cb). */
856
+ export function streamCbThunkFor(E, kind, cbT) {
857
+ if (cbT.kind !== "func") {
858
+ throw new Error("emitter bug: stream option callback not a func (frontend must fence)");
859
+ }
860
+ const key = `${kind}:${typeKey(cbT)}`;
861
+ let sym = E.streamCbThunks.get(key);
862
+ if (sym)
863
+ return sym;
864
+ sym = mangleStreamCbThunk(E.streamCbThunks.size);
865
+ E.streamCbThunks.set(key, sym);
866
+ // The runtime-facing C signatures, per kind.
867
+ const runtimeParams = kind === "r" ? ["ScrStream *sc_s", "double sc_size"] :
868
+ kind === "w" || kind === "t" ? ["ScrStream *sc_s", "ScrBytes *sc_chunk"] :
869
+ kind === "d" || kind === "e" ? ["ScrStream *sc_s", "ScrError *sc_err"] :
870
+ ["ScrStream *sc_s"];
871
+ // The user callback's declared prefix: params[0] is the stream (`this`)
872
+ // for the inline forms; a THISLESS callback (the checked-dynamic VALUE
873
+ // adapter — its first param is dyn, never an object) takes the Node
874
+ // positions from param 0 and never receives the stream (the dyn call
875
+ // ABI carries no receiver; SEMANTICS.md).
876
+ const declared = cbT.params;
877
+ const hasThis = declared[0] !== undefined && declared[0].kind === "object";
878
+ if (declared.length === 0) {
879
+ throw new Error("emitter bug: stream option callback with no params (frontend must fence)");
880
+ }
881
+ const passed = hasThis
882
+ ? ["sc_cb", `(${cType(declared[0]).trim()})scr_stream_retain(sc_s)`]
883
+ : ["sc_cb"];
884
+ const body = [];
885
+ const off = hasThis ? 1 : 0;
886
+ const full = (kind === "r" ? 1 : kind === "w" || kind === "t" ? 3 : kind === "d" ? 2 : 1) + off;
887
+ // "e" (the finished/pipeline callback): ONE Node position — the error.
888
+ if (declared.length > full) {
889
+ throw new Error(`emitter bug: stream '${kind}' callback declares ${declared.length} params (frontend must fence)`);
890
+ }
891
+ for (let i = off; i < declared.length; i++) {
892
+ const p = declared[i];
893
+ const pos = i - off; // 0-based Node signature position
894
+ if (kind === "r") {
895
+ // read(size) — a dyn-declared size (the JS lane) boxes the number.
896
+ passed.push(p.kind === "dyn" ? "scr_dyn_new_num(sc_size)" : "sc_size");
897
+ continue;
898
+ }
899
+ const isChunkPos = (kind === "w" || kind === "t") && pos === 0;
900
+ const isEncPos = (kind === "w" || kind === "t") && pos === 1;
901
+ const isErrPos = (kind === "d" || kind === "e") && pos === 0;
902
+ const isDonePos = kind === "e" ? false
903
+ : (kind === "w" || kind === "t") ? pos === 2 : (kind === "f" || kind === "l") ? pos === 0 : pos === 1;
904
+ // The JS lane's implicitly-any parameters: each position boxes into
905
+ // dyn by kind — the chunk as DOM bytes, the encoding as a string,
906
+ // the error as the boundary's {name, message} shape (null when
907
+ // none), and the completion callback as a CALLABLE dyn whose glue
908
+ // dispatches to the runtime's *_done entry (the closure's one cap
909
+ // boxes the retained stream).
910
+ if (p.kind === "dyn") {
911
+ if (isChunkPos) {
912
+ passed.push(`scr_dyn_new_buffer_copy(sc_chunk)`);
913
+ }
914
+ else if (isEncPos) {
915
+ body.push(` ScrStr *sc_encs = scr_str_new("buffer", 6);`, ` ScrDyn *sc_encd = scr_dyn_new_str(sc_encs);`, ` scr_str_release(sc_encs);`);
916
+ passed.push(`sc_encd`);
917
+ }
918
+ else if (isErrPos) {
919
+ // finished/pipeline succeed with UNDEFINED (Node calls the eos
920
+ // callback with no arguments); destroy passes null.
921
+ passed.push(kind === "e"
922
+ ? `sc_err ? scr_dyn_from_error(sc_err) : scr_dyn_retain(scr_dyn_undefined())`
923
+ : `sc_err ? scr_dyn_from_error(sc_err) : scr_dyn_new_null()`);
924
+ }
925
+ else if (isDonePos) {
926
+ const glue = `scr_stream_done_dyn_${kind}`;
927
+ body.push(` ScrClosure *sc_dclo = scr_closure_new((void *)&${glue}, 1);`, ` sc_dclo->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);`, ` scr_box_set_ref(sc_dclo->caps[0], scr_stream_retain(sc_s));`, ` ScrDyn *sc_done = scr_dyn_new_func(sc_dclo, &${glue}, ${kind === "t" || kind === "l" ? 2 : 1}, ${JSON.stringify(kind === "t" || kind === "l" ? "(error,data)" : "(error)")}, "callback");`);
928
+ passed.push(`sc_done`);
929
+ }
930
+ else {
931
+ throw new Error(`emitter bug: stream '${kind}' dyn callback param ${i} has no adapter`);
932
+ }
933
+ continue;
934
+ }
935
+ if (isChunkPos) {
936
+ passed.push(`scr_bytes_retain(sc_chunk)`);
937
+ }
938
+ else if (isEncPos) {
939
+ // Node's encoding for decoded (Buffer) chunks is 'buffer'.
940
+ body.push(` ScrStr *sc_enc = scr_str_new("buffer", 6);`);
941
+ passed.push(`sc_enc`);
942
+ }
943
+ else if (isErrPos) {
944
+ // destroy's error argument: `Error | null` — wrap type-directedly.
945
+ // The finished/pipeline callback ("e") may declare `Error | null |
946
+ // undefined` (the @types signature); success prefers the undefined
947
+ // arm there (Node calls the eos callback with NO arguments).
948
+ if (p.kind !== "union")
949
+ throw new Error("emitter bug: stream destroy err param not a union");
950
+ const def = E.unionsById.get(p.unionId);
951
+ const errTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1;
952
+ const undefTag = kind === "e" && def ? def.arms.findIndex((a) => a.kind === "undefinedT") : -1;
953
+ const nullTag = def
954
+ ? (undefTag >= 0 ? undefTag : def.arms.findIndex((a) => a.kind === "nullT"))
955
+ : -1;
956
+ if (errTag < 0 || nullTag < 0)
957
+ throw new Error("emitter bug: stream destroy err union lacks its arms");
958
+ body.push(` ScrUnion *sc_eu = sc_err ? scr_union_new_ref(${errTag}, scr_error_retain(sc_err), &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()) : scr_union_retain(${E.unitInstanceRef(p.unionId, nullTag)});`);
959
+ passed.push(`sc_eu`);
960
+ }
961
+ else if (isDonePos) {
962
+ const doneKind = kind; // "e" has no done position
963
+ const doneFn = streamDoneFnFor(E, doneKind, p);
964
+ body.push(` ScrClosure *sc_done = scr_closure_new((void *)&${doneFn}, 1);`, ` sc_done->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);`, ` scr_box_set_ref(sc_done->caps[0], scr_stream_retain(sc_s));`);
965
+ passed.push(`sc_done`);
966
+ }
967
+ else {
968
+ throw new Error(`emitter bug: stream '${kind}' callback param ${i} has no adapter`);
969
+ }
970
+ }
971
+ const sigParams = ["ScrClosure *", ...declared.map((p) => cType(p).trim())].join(", ");
972
+ const unused = [];
973
+ if (declared.length < off + 1) {
974
+ if (kind === "r")
975
+ unused.push(` (void)sc_size;`);
976
+ if (kind === "w" || kind === "t")
977
+ unused.push(` (void)sc_chunk;`);
978
+ if (kind === "d" || kind === "e")
979
+ unused.push(` (void)sc_err;`);
980
+ }
981
+ if (!hasThis)
982
+ unused.push(` (void)sc_s;`); /* the done/err adapters may still read it */
983
+ // The call goes through the callback's TRUE signature; a non-void
984
+ // result is discarded (refcounted ones released) — Node ignores option
985
+ // callback results (`read: () => this.push(null)`).
986
+ const retC = cbT.ret.kind === "void" ? "void" : cType(cbT.ret).trim();
987
+ const invoke = `((${retC} (*)(${sigParams}))sc_cb->fn)(${passed.join(", ")})`;
988
+ E.walkerProtos.push(`static void ${sym}(ScrClosure *sc_cb, ${runtimeParams.join(", ")});`);
989
+ E.walkerDefs.push(`static void ${sym}(ScrClosure *sc_cb, ${runtimeParams.join(", ")}) {`, ...unused, ...body, ...(cbT.ret.kind === "void"
990
+ ? [` ${invoke};`]
991
+ : isRefCounted(cbT.ret)
992
+ ? [` ${retC} sc_r = ${invoke};`, ` if (sc_r) ${releaseCallC(cbT.ret, "sc_r")};`]
993
+ : [` (void)${invoke};`]), `}`, ``);
994
+ return sym;
995
+ }
996
+ //# sourceMappingURL=emit-async.js.map