behavior-contracts 0.5.0 → 0.7.0

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 (112) hide show
  1. package/dist/behavior.d.ts +7 -0
  2. package/dist/behavior.d.ts.map +1 -1
  3. package/dist/behavior.js.map +1 -1
  4. package/dist/builder.d.ts.map +1 -1
  5. package/dist/builder.js +10 -3
  6. package/dist/builder.js.map +1 -1
  7. package/dist/generator/async-plan.d.ts +78 -0
  8. package/dist/generator/async-plan.d.ts.map +1 -0
  9. package/dist/generator/async-plan.js +116 -0
  10. package/dist/generator/async-plan.js.map +1 -0
  11. package/dist/generator/core.d.ts +30 -9
  12. package/dist/generator/core.d.ts.map +1 -1
  13. package/dist/generator/core.js.map +1 -1
  14. package/dist/generator/{emit-straightline-typed-go.d.ts → emit-shared-go-typed.d.ts} +11 -17
  15. package/dist/generator/emit-shared-go-typed.d.ts.map +1 -0
  16. package/dist/generator/emit-shared-go-typed.js +572 -0
  17. package/dist/generator/emit-shared-go-typed.js.map +1 -0
  18. package/dist/generator/emit-shared-go.d.ts +29 -0
  19. package/dist/generator/emit-shared-go.d.ts.map +1 -0
  20. package/dist/generator/{emit-straightline-go.js → emit-shared-go.js} +2 -34
  21. package/dist/generator/emit-shared-go.js.map +1 -0
  22. package/dist/generator/{emit-straightline-typed-rust.d.ts → emit-shared-rust-typed.d.ts} +11 -17
  23. package/dist/generator/emit-shared-rust-typed.d.ts.map +1 -0
  24. package/dist/generator/emit-shared-rust-typed.js +576 -0
  25. package/dist/generator/emit-shared-rust-typed.js.map +1 -0
  26. package/dist/generator/{emit-straightline-rust.d.ts → emit-shared-rust.d.ts} +1 -36
  27. package/dist/generator/emit-shared-rust.d.ts.map +1 -0
  28. package/dist/generator/{emit-straightline-rust.js → emit-shared-rust.js} +2 -16
  29. package/dist/generator/emit-shared-rust.js.map +1 -0
  30. package/dist/generator/emit-shared-typescript.d.ts +12 -0
  31. package/dist/generator/emit-shared-typescript.d.ts.map +1 -0
  32. package/dist/generator/{emit-straightline-typescript.js → emit-shared-typescript.js} +5 -20
  33. package/dist/generator/emit-shared-typescript.js.map +1 -0
  34. package/dist/generator/emit-straightline-typed-typescript.d.ts +1 -1
  35. package/dist/generator/emit-straightline-typed-typescript.d.ts.map +1 -1
  36. package/dist/generator/emit-straightline-typed-typescript.js +1 -1
  37. package/dist/generator/emit-straightline-typed-typescript.js.map +1 -1
  38. package/dist/generator/emit-typed-native-go.d.ts.map +1 -1
  39. package/dist/generator/emit-typed-native-go.js +546 -82
  40. package/dist/generator/emit-typed-native-go.js.map +1 -1
  41. package/dist/generator/emit-typed-native-rust.d.ts +2 -1
  42. package/dist/generator/emit-typed-native-rust.d.ts.map +1 -1
  43. package/dist/generator/emit-typed-native-rust.js +597 -110
  44. package/dist/generator/emit-typed-native-rust.js.map +1 -1
  45. package/dist/generator/index.d.ts +2 -12
  46. package/dist/generator/index.d.ts.map +1 -1
  47. package/dist/generator/index.js +10 -62
  48. package/dist/generator/index.js.map +1 -1
  49. package/dist/generator/native-expr.d.ts +120 -0
  50. package/dist/generator/native-expr.d.ts.map +1 -0
  51. package/dist/generator/native-expr.js +456 -0
  52. package/dist/generator/native-expr.js.map +1 -0
  53. package/dist/generator/typed.d.ts.map +1 -1
  54. package/dist/generator/typed.js +8 -0
  55. package/dist/generator/typed.js.map +1 -1
  56. package/dist/guard.js +14 -0
  57. package/dist/guard.js.map +1 -1
  58. package/dist/index.d.ts +2 -2
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/plan.d.ts +6 -2
  63. package/dist/plan.d.ts.map +1 -1
  64. package/dist/plan.js +4 -0
  65. package/dist/plan.js.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/generator/emit-go.d.ts +0 -19
  68. package/dist/generator/emit-go.d.ts.map +0 -1
  69. package/dist/generator/emit-go.js +0 -79
  70. package/dist/generator/emit-go.js.map +0 -1
  71. package/dist/generator/emit-raw-abi-go.d.ts +0 -45
  72. package/dist/generator/emit-raw-abi-go.d.ts.map +0 -1
  73. package/dist/generator/emit-raw-abi-go.js +0 -346
  74. package/dist/generator/emit-raw-abi-go.js.map +0 -1
  75. package/dist/generator/emit-raw-abi-rust.d.ts +0 -45
  76. package/dist/generator/emit-raw-abi-rust.d.ts.map +0 -1
  77. package/dist/generator/emit-raw-abi-rust.js +0 -380
  78. package/dist/generator/emit-raw-abi-rust.js.map +0 -1
  79. package/dist/generator/emit-rust.d.ts +0 -25
  80. package/dist/generator/emit-rust.d.ts.map +0 -1
  81. package/dist/generator/emit-rust.js +0 -125
  82. package/dist/generator/emit-rust.js.map +0 -1
  83. package/dist/generator/emit-straightline-go.d.ts +0 -60
  84. package/dist/generator/emit-straightline-go.d.ts.map +0 -1
  85. package/dist/generator/emit-straightline-go.js.map +0 -1
  86. package/dist/generator/emit-straightline-native-go.d.ts +0 -54
  87. package/dist/generator/emit-straightline-native-go.d.ts.map +0 -1
  88. package/dist/generator/emit-straightline-native-go.js +0 -835
  89. package/dist/generator/emit-straightline-native-go.js.map +0 -1
  90. package/dist/generator/emit-straightline-native-rust.d.ts +0 -27
  91. package/dist/generator/emit-straightline-native-rust.d.ts.map +0 -1
  92. package/dist/generator/emit-straightline-native-rust.js +0 -624
  93. package/dist/generator/emit-straightline-native-rust.js.map +0 -1
  94. package/dist/generator/emit-straightline-php.d.ts +0 -46
  95. package/dist/generator/emit-straightline-php.d.ts.map +0 -1
  96. package/dist/generator/emit-straightline-php.js +0 -941
  97. package/dist/generator/emit-straightline-php.js.map +0 -1
  98. package/dist/generator/emit-straightline-python.d.ts +0 -36
  99. package/dist/generator/emit-straightline-python.d.ts.map +0 -1
  100. package/dist/generator/emit-straightline-python.js +0 -855
  101. package/dist/generator/emit-straightline-python.js.map +0 -1
  102. package/dist/generator/emit-straightline-rust.d.ts.map +0 -1
  103. package/dist/generator/emit-straightline-rust.js.map +0 -1
  104. package/dist/generator/emit-straightline-typed-go.d.ts.map +0 -1
  105. package/dist/generator/emit-straightline-typed-go.js +0 -1114
  106. package/dist/generator/emit-straightline-typed-go.js.map +0 -1
  107. package/dist/generator/emit-straightline-typed-rust.d.ts.map +0 -1
  108. package/dist/generator/emit-straightline-typed-rust.js +0 -1155
  109. package/dist/generator/emit-straightline-typed-rust.js.map +0 -1
  110. package/dist/generator/emit-straightline-typescript.d.ts +0 -47
  111. package/dist/generator/emit-straightline-typescript.d.ts.map +0 -1
  112. package/dist/generator/emit-straightline-typescript.js.map +0 -1
@@ -1,835 +0,0 @@
1
- import { goStraightlineEmitter, goStraightlineInternals } from "./emit-straightline-go.js";
2
- import { buildComponentPlan, sequentialOrder, analyzeSequentialOps, classifyExpr, } from "./straightline.js";
3
- const PKG = "dslcontracts";
4
- const { sanitize, emitExpr } = goStraightlineInternals;
5
- /** Go struct field name for a wire port name (exported, deterministic). */
6
- function goPortFieldName(wire) {
7
- const safe = wire.replace(/[^A-Za-z0-9_]/g, "_");
8
- const head = safe.charAt(0);
9
- return /[a-z]/.test(head) ? head.toUpperCase() + safe.slice(1) : "F_" + safe;
10
- }
11
- /** Native ports struct type name for a node (component + node id, module-unique). */
12
- function portsStructName(compName, nodeId) {
13
- return `ports_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
14
- }
15
- /** Local var name for an evaluated port value. */
16
- function portLocal(nodeId, portName) {
17
- return `pv_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}_${portName.replace(/[^A-Za-z0-9_]/g, "_")}`;
18
- }
19
- /**
20
- * inferPortType — derive the static Go field type of a port from its wired expression and the
21
- * entry component's inputPorts (the only declared types available; the callee is a catalog
22
- * reference, not in the IR). Conservative: only a shape we can PROVE the type of is typed;
23
- * everything else is a dynamic `Value` field (still no map). This is the documented dynamic
24
- * port fallback.
25
- */
26
- function inferPortType(node, inputPorts) {
27
- // A static arr port is a `[]dslcontracts.Value` — carried in a boxed `Value` struct field
28
- // (still no map, no ArrNative closure slice; built by direct []Value{...} assignment).
29
- if (staticArrElems(node) !== null)
30
- return "Value";
31
- const e = classifyExpr(node);
32
- return inferFromStatic(e, inputPorts);
33
- }
34
- function inferFromStatic(e, inputPorts) {
35
- switch (e.kind) {
36
- case "str":
37
- return "string";
38
- case "bool":
39
- return "bool";
40
- case "concat":
41
- // concat always yields a string (or fails TYPE_MISMATCH at runtime via the primitive).
42
- return "string";
43
- case "ref": {
44
- // A single-segment ref to a declared input param carries that param's type.
45
- if (e.path.length === 1) {
46
- const schema = inputPorts[e.path[0]];
47
- if (schema) {
48
- switch (schema.type) {
49
- case "string":
50
- return "string";
51
- case "int":
52
- return "int64";
53
- case "float":
54
- return "float64";
55
- case "bool":
56
- return "bool";
57
- }
58
- }
59
- }
60
- return "Value";
61
- }
62
- case "null":
63
- case "dynamic":
64
- default:
65
- return "Value";
66
- }
67
- }
68
- /**
69
- * A port is "infallible-literal" when it is a string/bool/null literal — it cannot produce an
70
- * expression Failure, so it needs NO error check (no slVE wrapper, no dead branch). Everything
71
- * else (ref, concat, dynamic operator) keeps a direct error return.
72
- */
73
- function isInfallibleLiteral(node) {
74
- const e = classifyExpr(node);
75
- return e.kind === "str" || e.kind === "bool" || e.kind === "null";
76
- }
77
- /** Go literal for an infallible literal expression. */
78
- function goLiteral(node) {
79
- if (node === null)
80
- return "nil";
81
- if (typeof node === "boolean")
82
- return node ? "true" : "false";
83
- if (typeof node === "string")
84
- return JSON.stringify(node);
85
- return "nil";
86
- }
87
- /**
88
- * A component is native-emittable when it is the clean read/write HOT PATH the owner cites:
89
- * - strictly sequential (no RunPlan / real concurrency),
90
- * - every body node is a plain componentRef (no map/cond),
91
- * - no bindField (skip-on-null-binding needs the generic skip-propagation machinery),
92
- * - default 'fail' policy on every node (continue/retry skip semantics stay on the generic path),
93
- * - no relationKind (unproduced/connection shaping stays on the generic path),
94
- * - every port expr and the output are STATICALLY resolvable (literal / ref / concat) — a
95
- * dynamic operator (coalesce/cond/arith/obj/arr/…) in a port OR the output keeps the whole
96
- * component on the generic path (its fail-closed semantics live in the primitive SSoT, and
97
- * skip/unproduced interplay with those operators is subtle — we do NOT half-cover it).
98
- * Anything else falls through to the generic straight-line Bind (correct, just not de-plumbed).
99
- * This is fail-closed conservative: the native path only claims components it can de-plumb with
100
- * PROVABLE run_behavior equivalence (the conformance gate compiles + compares every one).
101
- */
102
- function isNativeComponent(comp) {
103
- if (sequentialOrder(comp) === null)
104
- return false;
105
- for (const n of comp.body) {
106
- if (!("component" in n) || "map" in n || "cond" in n)
107
- return false;
108
- const cr = n;
109
- if (cr.bindField !== undefined)
110
- return false;
111
- if (cr.relationKind !== undefined)
112
- return false;
113
- if (cr.policy !== undefined && cr.policy !== "fail")
114
- return false;
115
- for (const p of Object.keys(cr.ports))
116
- if (!isStaticallyResolvable(cr.ports[p]))
117
- return false;
118
- }
119
- return isStaticallyResolvable(comp.output);
120
- }
121
- /**
122
- * isNativeMapComponent — a component whose body is a SINGLE `map` node is native-eligible when:
123
- * - the map's `over` is a ref (statically resolvable to an input param / prior node — here the
124
- * single-node case means an input param),
125
- * - every per-element port is statically resolvable (literal / ref-into-element / concat /
126
- * static arr) — refs into the element binding (`as`) resolve to the element native local,
127
- * - no `when` guard, no `into` zip-attach (those keep the generic path — element/kept
128
- * alignment with guard/into interplay stays single-sourced),
129
- * - default 'fail' policy, no relationKind,
130
- * - the output is statically resolvable (a ref to the map node result).
131
- * The batched vs per-element dispatch, MAP_OVER_NOT_ARRAY / MAP_BATCH_RESULT_MISMATCH, and the
132
- * kept/collected alignment are reproduced EXACTLY by emitNativeMapRunner — only the per-element
133
- * ports CONTAINER + dispatch go native. Everything else stays on the generic path.
134
- */
135
- function isNativeMapComponent(comp) {
136
- if (sequentialOrder(comp) === null)
137
- return false;
138
- if (comp.body.length !== 1)
139
- return false;
140
- const n = comp.body[0];
141
- if (!("map" in n))
142
- return false;
143
- const m = n.map;
144
- if (m.when !== undefined)
145
- return false;
146
- if (m.into !== undefined)
147
- return false;
148
- if (m.relationKind !== undefined)
149
- return false;
150
- if (m.policy !== undefined && m.policy !== "fail")
151
- return false;
152
- // `over` must be a plain ref (to an input param — single-node component has no prior node).
153
- if (classifyExpr(m.over).kind !== "ref")
154
- return false;
155
- for (const p of Object.keys(m.ports))
156
- if (!isStaticallyResolvable(m.ports[p]))
157
- return false;
158
- return isStaticallyResolvable(comp.output);
159
- }
160
- /** A native component (sequential componentRef OR single map-write). */
161
- function isNative(comp) {
162
- return isNativeComponent(comp) || isNativeMapComponent(comp);
163
- }
164
- /**
165
- * staticArrElems — if `node` is a single-key `{arr:[...]}` shape, return its element nodes;
166
- * else null. classifyExpr keeps `arr` as `dynamic` (its fail-closed construction lives in the
167
- * primitive SSoT), so we recognize the STATIC arr shape here (every element itself static) and
168
- * build it by direct `[]Value{...}` assignment — NOT the generic ArrNative(closure-slice) path.
169
- */
170
- function staticArrElems(node) {
171
- if (node === null || typeof node !== "object" || Array.isArray(node))
172
- return null;
173
- const keys = Object.keys(node);
174
- if (keys.length !== 1 || keys[0] !== "arr")
175
- return null;
176
- const arg = node.arr;
177
- return Array.isArray(arg) ? arg : null;
178
- }
179
- /**
180
- * A node is statically resolvable when it is a literal / ref / refOpt / concat (of the same),
181
- * OR a static `arr` whose every element is itself statically resolvable (built by direct
182
- * `[]dslcontracts.Value{...}` assignment, no ArrNative closure slice).
183
- */
184
- function isStaticallyResolvable(node) {
185
- const arr = staticArrElems(node);
186
- if (arr !== null)
187
- return arr.every(isStaticallyResolvable);
188
- const e = classifyExpr(node);
189
- switch (e.kind) {
190
- case "str":
191
- case "bool":
192
- case "null":
193
- case "ref":
194
- return true;
195
- case "concat":
196
- return e.parts.every((p) => isStaticallyResolvable(reconstruct(p)));
197
- case "dynamic":
198
- default:
199
- return false;
200
- }
201
- }
202
- /** Emit the native ports struct + PortReader for one componentRef node. */
203
- function emitPortsStruct(comp, node) {
204
- const structName = portsStructName(comp.name, node.id);
205
- const portNames = Object.keys(node.ports);
206
- if (portNames.length === 0) {
207
- return `// ${structName} — native ports for node '${node.id}' (component ${node.component}); no ports.\ntype ${structName} struct{}\n\nfunc (p ${structName}) Port(string) (${PKG}.Value, bool) { return nil, false }`;
208
- }
209
- const fieldTypes = portNames.map((p) => inferPortType(node.ports[p], comp.inputPorts));
210
- const fieldNames = portNames.map(goPortFieldName);
211
- const nameW = Math.max(...fieldNames.map((n) => n.length));
212
- const typeW = Math.max(...fieldTypes.map((t) => (t === "Value" ? `${PKG}.Value`.length : t.length)));
213
- const fields = portNames
214
- .map((p, i) => {
215
- const gt = fieldTypes[i] === "Value" ? `${PKG}.Value` : fieldTypes[i];
216
- return `\t${fieldNames[i].padEnd(nameW)} ${gt.padEnd(typeW)} // ${JSON.stringify(p)}`;
217
- })
218
- .join("\n");
219
- // PortReader: box ONE field on demand (uniform read for consumers that do not type-switch).
220
- const cases = portNames
221
- .map((p, i) => `\tcase ${JSON.stringify(p)}:\n\t\treturn ${fieldTypes[i] === "Value" ? "p." + fieldNames[i] : `${PKG}.Value(p.${fieldNames[i]})`}, true`)
222
- .join("\n");
223
- return `// ${structName} — NATIVE ports for node '${node.id}' (component ${node.component}). Typed fields per the
224
- // static port type; built by direct field assignment (no map alloc, no per-port boxing).
225
- type ${structName} struct {
226
- ${fields}
227
- }
228
-
229
- // Port implements dslcontracts.PortReader (boxes ONE field on demand; the hot-path consumer
230
- // type-switches to this concrete struct and reads the typed fields with zero boxing instead).
231
- func (p ${structName}) Port(name string) (${PKG}.Value, bool) {
232
- switch name {
233
- ${cases}
234
- }
235
- return nil, false
236
- }`;
237
- }
238
- /**
239
- * emitNativeRunner — the native-ports straight-line exec for one native component. Ports are
240
- * evaluated (fallible ones with a direct error return; literals inline), the native struct is
241
- * built by direct assignment, and the handler is called via ExecNativeHandler. The result of
242
- * each node is a RawValue (native-backed); ref reads between nodes read the accumulated native
243
- * node results (bc#74 cross-node ref — resolved from the native result, not a dynamic scope).
244
- */
245
- function emitNativeRunner(comp) {
246
- const order = sequentialOrder(comp);
247
- const metas = analyzeSequentialOps(buildComponentPlan(comp, goStraightlineInternals.dialect, "scope").opsLiteral, order);
248
- const fn = sanitize(comp.name);
249
- // Node results live in native locals (a RawValue per node). #74: a later node's port ref to
250
- // an earlier node's field reads THIS local directly — the native analogue of the straight-
251
- // line node-result scope, with NO dynamic *Obj scope object built at all (so the residual map
252
- // alloc is gone too). A ref head resolves statically to nr_<id> (prior node) or an input.Get.
253
- const nodeResult = (id) => `nr_${id.replace(/[^A-Za-z0-9_]/g, "_")}`;
254
- const idToPos = new Map();
255
- order.forEach((opIdx, k) => idToPos.set(comp.body[opIdx].id, k));
256
- // A ref head is a prior node if it names a body node executed before position `atPos`.
257
- const priorNodeAt = (head, atPos) => {
258
- const p = idToPos.get(head);
259
- return p !== undefined && p < atPos;
260
- };
261
- const lines = [];
262
- lines.push(`// run_native_${fn} — NATIVE-PORTS straight-line exec (bc 0.5.0): each componentRef builds a native`);
263
- lines.push(`// ports struct by direct field assignment (no map container, no slVE wrapper) and dispatches via`);
264
- lines.push(`// ExecNativeHandler. Node results are native locals; cross-node refs read them directly (#74) —`);
265
- lines.push(`// no dynamic scope *Obj is ever built, so a single-op point read allocates only the escaping struct.`);
266
- lines.push(`func run_native_${fn}(handlers ${PKG}.NativeComponentExec, input *${PKG}.Obj) (${PKG}.Value, error) {`);
267
- // NOTE: no `input = NewObj()` nil-normalization — nvBindVE is nil-safe, so a native module
268
- // allocates ZERO generic *Obj. (A pure-native module has NO NewObj/Set anywhere — grep gate.)
269
- for (const k of order.keys()) {
270
- const i = order[k];
271
- const node = comp.body[i];
272
- const meta = metas[i];
273
- const structName = portsStructName(comp.name, node.id);
274
- const portNames = Object.keys(node.ports);
275
- lines.push(`\t// ── op '${node.id}' (${node.component}) ──`);
276
- const inits = [];
277
- for (const pn of portNames) {
278
- const expr = node.ports[pn];
279
- const fieldName = goPortFieldName(pn);
280
- const ftype = inferPortType(expr, comp.inputPorts);
281
- if (isInfallibleLiteral(expr)) {
282
- inits.push(`${fieldName}: ${goLiteral(expr)}`);
283
- continue;
284
- }
285
- const local = portLocal(node.id, pn);
286
- const resolved = emitStaticRefValue(expr, k, priorNodeAt, nodeResult, lines, local);
287
- if (resolved) {
288
- // resolved is a Go expr producing (Value); errors already handled inline.
289
- if (ftype === "Value") {
290
- inits.push(`${fieldName}: ${resolved}`);
291
- }
292
- else {
293
- lines.push(`\t${local}T, ${local}Ok := (${resolved}).(${ftype})`);
294
- lines.push(`\tif !${local}Ok {`, `\t\treturn nil, ${PKG}.NewExprFailure("TYPE_MISMATCH", "port ${pn}: expected ${ftype}, got "+${PKG}.TypeName(${resolved}))`, `\t}`);
295
- inits.push(`${fieldName}: ${local}T`);
296
- }
297
- continue;
298
- }
299
- // Genuinely dynamic (multi-arg operator etc.): fall back to the primitive SSoT with an
300
- // ephemeral scope built ONLY for this expression (rare; keeps semantics single-sourced).
301
- lines.push(`\t${local}Scope := ${PKG}.NewObj()`);
302
- lines.push(`\tfor _, sk := range input.Keys {`, `\t\t${local}Scope.Set(sk, input.Vals[sk])`, `\t}`);
303
- for (let pj = 0; pj < k; pj++) {
304
- const priorId = comp.body[order[pj]].id;
305
- lines.push(`\t${local}Scope.Set(${JSON.stringify(priorId)}, ${nodeResult(priorId)})`);
306
- }
307
- lines.push(`\t${local}, ${local}Err := ${emitExpr(expr, `${local}Scope`)}`);
308
- lines.push(`\tif ${local}Err != nil {`, `\t\treturn nil, ${local}Err`, `\t}`);
309
- if (ftype === "Value") {
310
- inits.push(`${fieldName}: ${local}`);
311
- }
312
- else {
313
- lines.push(`\t${local}T, ${local}Ok := ${local}.(${ftype})`);
314
- lines.push(`\tif !${local}Ok {`, `\t\treturn nil, ${PKG}.NewExprFailure("TYPE_MISMATCH", "port ${pn}: expected ${ftype}, got "+${PKG}.TypeName(${local}))`, `\t}`);
315
- inits.push(`${fieldName}: ${local}T`);
316
- }
317
- }
318
- lines.push(`\tports_${sanitize(node.id)} := ${structName}{${inits.join(", ")}}`);
319
- const oc = `o_${sanitize(node.id)}`;
320
- lines.push(`\t${oc}, ${oc}Resolved := ${PKG}.ExecNativeHandler(handlers, ${JSON.stringify(node.id)}, ${JSON.stringify(node.component)}, ports_${sanitize(node.id)}, nil)`);
321
- lines.push(`\tif !${oc}Resolved {`, `\t\treturn nil, ${PKG}.NewBehaviorFailure("UNKNOWN_COMPONENT", "component '${node.component}' has no handler (fail-closed)")`, `\t}`);
322
- if (meta.policy === "continue") {
323
- lines.push(`\tvar ${nodeResult(node.id)} ${PKG}.Value = nil`);
324
- lines.push(`\tif !${oc}.IsError {`, `\t\t${nodeResult(node.id)} = ${oc}.Ok`, `\t}`);
325
- }
326
- else {
327
- lines.push(`\tif ${oc}.IsError {`);
328
- const policy = meta.policy === "retry" ? "retry' policy (exhausted)" : "fail' policy";
329
- lines.push(`\t\treturn nil, ${PKG}.NewPlanFailure("OP_FAILED", "operation '${node.id}' failed under '${policy}: "+${oc}.Err)`);
330
- lines.push(`\t}`);
331
- lines.push(`\t${nodeResult(node.id)} := ${oc}.Ok`, `\t_ = ${nodeResult(node.id)}`);
332
- }
333
- }
334
- // output: statically resolve the ref/concat to node-result locals / input; dynamic falls back.
335
- const outResolved = emitStaticRefValue(comp.output, order.length, priorNodeAt, nodeResult, lines, "out");
336
- if (outResolved) {
337
- lines.push(`\treturn ${outResolved}, nil`);
338
- }
339
- else {
340
- lines.push(`\toutScope := ${PKG}.NewObj()`);
341
- lines.push(`\tfor _, sk := range input.Keys {`, `\t\toutScope.Set(sk, input.Vals[sk])`, `\t}`);
342
- for (const opIdx of order) {
343
- const id = comp.body[opIdx].id;
344
- lines.push(`\toutScope.Set(${JSON.stringify(id)}, ${nodeResult(id)})`);
345
- }
346
- lines.push(`\toutV, outErr := ${emitExpr(comp.output, "outScope")}`);
347
- lines.push(`\tif outErr != nil {`, `\t\treturn nil, outErr`, `\t}`);
348
- lines.push(`\treturn outV, nil`);
349
- }
350
- lines.push(`}`);
351
- return lines.join("\n");
352
- }
353
- /**
354
- * emitMapStaticVE — the slVE-equivalent for a map element port: a ref whose head is the element
355
- * binding (`as`) reads the element native local via nvOkVE(<elVar>); any other ref head reads an
356
- * input param via nvBindVE(input, head). literal/concat/nested compose the same as emitStaticVE.
357
- * Returns null only for a genuinely dynamic operator (guarded out by isNativeMapComponent).
358
- */
359
- function emitMapStaticVE(node, asName, elVar) {
360
- const e = classifyExpr(node);
361
- switch (e.kind) {
362
- case "str":
363
- return `nvValVE(${JSON.stringify(e.value)})`;
364
- case "bool":
365
- return `nvValVE(${e.value ? "true" : "false"})`;
366
- case "null":
367
- return `nvValVE(nil)`;
368
- case "ref": {
369
- const head = e.path[0];
370
- const headVE = head === asName ? `nvOkVE(${elVar})` : `nvBindVE(input, ${JSON.stringify(head)})`;
371
- if (e.path.length === 1)
372
- return headVE;
373
- const segs = e.path.slice(1).map((p) => JSON.stringify(p)).join(", ");
374
- return `nvFieldVE(${e.opt ? '"refOpt"' : '"ref"'}, ${headVE}, []string{${segs}})`;
375
- }
376
- case "concat": {
377
- const parts = [];
378
- for (const p of e.parts) {
379
- const sub = emitMapStaticVE(reconstruct(p), asName, elVar);
380
- if (sub === null)
381
- return null;
382
- parts.push(sub);
383
- }
384
- return `nvCatVE(${parts.join(", ")})`;
385
- }
386
- default:
387
- return null;
388
- }
389
- }
390
- /** Resolve a map element port to a Go Value expr (emitting error-return lines). Handles static
391
- * arr by direct []Value{...} (recursing element-wise), else via emitMapStaticVE + unwrap. */
392
- function emitMapPortValue(node, asName, elVar, lines, ind, tmp) {
393
- const arr = staticArrElems(node);
394
- if (arr !== null) {
395
- const elems = arr.map((el, j) => emitMapPortValue(el, asName, elVar, lines, ind, `${tmp}_a${j}`));
396
- return `[]${PKG}.Value{${elems.join(", ")}}`;
397
- }
398
- const e = classifyExpr(node);
399
- if (e.kind === "str" || e.kind === "bool" || e.kind === "null")
400
- return goLiteral(node);
401
- const ve = emitMapStaticVE(node, asName, elVar);
402
- // ve is non-null (guarded); unwrap {Val,Err} with a direct error return.
403
- const rv = `${tmp}_v`;
404
- lines.push(`${ind}${rv} := ${ve}`);
405
- lines.push(`${ind}if ${rv}.Err != nil {`, `${ind}\treturn nil, ${rv}.Err`, `${ind}}`);
406
- return `${rv}.Val`;
407
- }
408
- /**
409
- * emitNativeMapRunner — the NATIVE-PORTS exec for a single-map-node write component. Reproduces
410
- * emitSeqMap's control flow EXACTLY (over→[]Value assertion, MAP_OVER_NOT_ARRAY, per-element
411
- * loop, batched vs per-element dispatch, kept/collected alignment, MAP_BATCH_RESULT_MISMATCH) —
412
- * but each element's ports are built as a NATIVE ports STRUCT (direct field assignment, no
413
- * NewObj/Set) and dispatched via ExecNativeHandler. The batched dispatch passes a native items
414
- * batch struct whose "items" port carries the []Value of per-element ports structs.
415
- */
416
- function emitNativeMapRunner(comp) {
417
- const fn = sanitize(comp.name);
418
- const m = comp.body[0].map;
419
- const id = comp.body[0].id;
420
- const s = sanitize(id);
421
- const structName = portsStructName(comp.name, id);
422
- const asName = m.as;
423
- const batched = m.batched === true;
424
- const elVar = `el_${s}`;
425
- const overVar = `over_${s}`;
426
- const keptVar = `kept_${s}`;
427
- const collected = `collected_${s}`;
428
- const items = `items_${s}`;
429
- // over head: a ref to an input param (single-node component → no prior node).
430
- const overE = classifyExpr(m.over);
431
- const overHead = overE.kind === "ref" ? overE.path[0] : "";
432
- const lines = [];
433
- lines.push(`// run_native_${fn} — NATIVE-PORTS single-map-write exec (bc 0.5.0): reproduces the generic`);
434
- lines.push(`// map control flow byte-for-byte, but each element's ports are a native struct (direct field`);
435
- lines.push(`// assignment, no NewObj/Set) dispatched via ExecNativeHandler (${batched ? "batched" : "per-element"}).`);
436
- lines.push(`func run_native_${fn}(handlers ${PKG}.NativeComponentExec, input *${PKG}.Obj) (${PKG}.Value, error) {`);
437
- // over evaluation + array assertion (matches emitSeqMap).
438
- lines.push(`\t${overVar}VE := nvBindVE(input, ${JSON.stringify(overHead)})`);
439
- lines.push(`\tif ${overVar}VE.Err != nil {`, `\t\treturn nil, ${overVar}VE.Err`, `\t}`);
440
- lines.push(`\t${overVar}, ${overVar}IsArr := ${overVar}VE.Val.([]${PKG}.Value)`);
441
- lines.push(`\tif !${overVar}IsArr {`, `\t\treturn nil, ${PKG}.NewBehaviorFailure("MAP_OVER_NOT_ARRAY", "map '${id}': 'over' did not evaluate to an array")`, `\t}`);
442
- lines.push(`\t${keptVar} := make([]int, 0, len(${overVar}))`);
443
- const portNames = Object.keys(m.ports);
444
- const fieldTypes = portNames.map((p) => inferPortType(m.ports[p], comp.inputPorts));
445
- const buildPorts = (ind, portsVar) => {
446
- const inits = [];
447
- for (let pi = 0; pi < portNames.length; pi++) {
448
- const pn = portNames[pi];
449
- const fieldName = goPortFieldName(pn);
450
- const ftype = fieldTypes[pi];
451
- // Infallible literal: assign the Go literal DIRECTLY to the typed field (no type assertion,
452
- // no error branch) — matches emitNativeRunner's isInfallibleLiteral fast path.
453
- if (isInfallibleLiteral(m.ports[pn])) {
454
- inits.push(`${fieldName}: ${goLiteral(m.ports[pn])}`);
455
- continue;
456
- }
457
- const tmp = `pv_${s}_${pn.replace(/[^A-Za-z0-9_]/g, "_")}`;
458
- const val = emitMapPortValue(m.ports[pn], asName, elVar, lines, ind, tmp);
459
- if (ftype === "Value") {
460
- inits.push(`${fieldName}: ${val}`);
461
- }
462
- else {
463
- lines.push(`${ind}${tmp}T, ${tmp}Ok := (${val}).(${ftype})`);
464
- lines.push(`${ind}if !${tmp}Ok {`, `${ind}\treturn nil, ${PKG}.NewExprFailure("TYPE_MISMATCH", "port ${pn}: expected ${ftype}, got "+${PKG}.TypeName(${val}))`, `${ind}}`);
465
- inits.push(`${fieldName}: ${tmp}T`);
466
- }
467
- }
468
- lines.push(`${ind}${portsVar} := ${structName}{${inits.join(", ")}}`);
469
- };
470
- if (batched) {
471
- lines.push(`\t${items} := make([]${PKG}.Value, 0, len(${overVar}))`);
472
- lines.push(`\tfor ${elVar}Idx, ${elVar} := range ${overVar} {`);
473
- buildPorts(`\t\t`, `ports_${s}`);
474
- lines.push(`\t\t${items} = append(${items}, ports_${s})`, `\t\t${keptVar} = append(${keptVar}, ${elVar}Idx)`, `\t}`);
475
- lines.push(`\tvar ${collected} []${PKG}.Value`);
476
- lines.push(`\tif len(${items}) == 0 {`, `\t\t${collected} = []${PKG}.Value{}`, `\t} else {`);
477
- // batched native ports: a struct exposing the "items" []Value (per-element native ports structs).
478
- const batchStruct = `${structName}_batch`;
479
- lines.push(`\t\tbp_${s} := ${batchStruct}{Items: ${items}}`);
480
- const oc = `mo_${s}`;
481
- lines.push(`\t\t${oc}, ${oc}Resolved := ${PKG}.ExecNativeHandler(handlers, ${JSON.stringify(id)}, ${JSON.stringify(m.component)}, bp_${s}, nil)`);
482
- lines.push(`\t\tif !${oc}Resolved {`, `\t\t\treturn nil, ${PKG}.NewBehaviorFailure("UNKNOWN_COMPONENT", "component '${m.component}' has no handler (fail-closed)")`, `\t\t}`);
483
- lines.push(`\t\tif ${oc}.IsError {`);
484
- lines.push(`\t\t\treturn nil, ${PKG}.NewPlanFailure("OP_FAILED", "operation '${id}' failed under 'fail' policy: "+${oc}.Err)`);
485
- lines.push(`\t\t}`);
486
- lines.push(`\t\tr_batch_${s}, isArr_${s} := ${oc}.Ok.([]${PKG}.Value)`);
487
- lines.push(`\t\tif !isArr_${s} || len(r_batch_${s}) != len(${items}) {`, `\t\t\treturn nil, ${PKG}.NewBehaviorFailure("MAP_BATCH_RESULT_MISMATCH", fmt.Sprintf("map '${id}': batched handler must return a list aligned to items (want %d)", len(${items})))`, `\t\t}`);
488
- lines.push(`\t\t${collected} = r_batch_${s}`);
489
- lines.push(`\t}`);
490
- }
491
- else {
492
- lines.push(`\t${collected} := make([]${PKG}.Value, 0, len(${overVar}))`);
493
- lines.push(`\tfor ${elVar}Idx, ${elVar} := range ${overVar} {`);
494
- buildPorts(`\t\t`, `ports_${s}`);
495
- const oc = `mo_${s}`;
496
- lines.push(`\t\t${oc}, ${oc}Resolved := ${PKG}.ExecNativeHandler(handlers, ${JSON.stringify(id)}, ${JSON.stringify(m.component)}, ports_${s}, ${elVar})`);
497
- lines.push(`\t\tif !${oc}Resolved {`, `\t\t\treturn nil, ${PKG}.NewBehaviorFailure("UNKNOWN_COMPONENT", "component '${m.component}' has no handler (fail-closed)")`, `\t\t}`);
498
- lines.push(`\t\tif ${oc}.IsError {`);
499
- lines.push(`\t\t\treturn nil, ${PKG}.NewPlanFailure("OP_FAILED", "operation '${id}' failed under 'fail' policy: "+${oc}.Err)`);
500
- lines.push(`\t\t}`);
501
- lines.push(`\t\t${collected} = append(${collected}, ${oc}.Ok)`, `\t\t${keptVar} = append(${keptVar}, ${elVar}Idx)`, `\t}`);
502
- }
503
- // node result = collected (no `into`). output resolves ref/arr/concat over this node result.
504
- lines.push(`\tnr_${s} := ${PKG}.Value(${collected})`);
505
- lines.push(`\t_ = ${keptVar}`);
506
- lines.push(`\t_ = nr_${s}`);
507
- // output: a ref to the map node result (or a static arr/concat wrapping it). emitStaticRefValue
508
- // resolves the map node's id to nr_<s> and handles the static-arr output wrapper directly.
509
- const priorNodeAt = (head) => head === id;
510
- const nodeResult = () => `nr_${s}`;
511
- const outResolved = emitStaticRefValue(comp.output, 1, (h, _p) => priorNodeAt(h), nodeResult, lines, "out");
512
- if (outResolved) {
513
- lines.push(`\treturn ${outResolved}, nil`);
514
- }
515
- else {
516
- lines.push(`\treturn nil, ${PKG}.NewBehaviorFailure("UNSUPPORTED_OUTPUT_NATIVE", "map component '${comp.name}': output not statically resolvable")`);
517
- }
518
- lines.push(`}`);
519
- return lines.join("\n");
520
- }
521
- /** Emit the per-node native ports struct(s) for a single-map-write component: the element ports
522
- * struct + a batched "items" struct (when batched) exposing the []Value items via PortReader. */
523
- function emitMapPortsStructs(comp) {
524
- const m = comp.body[0].map;
525
- const id = comp.body[0].id;
526
- const structName = portsStructName(comp.name, id);
527
- const elemStruct = emitPortsStruct(comp, { id, component: m.component, ports: m.ports });
528
- if (m.batched !== true)
529
- return elemStruct;
530
- const batchStruct = `${structName}_batch`;
531
- const portNames = Object.keys(m.ports);
532
- // Port("items") reconstruction: a consumer on the hot path DOWNCASTS to []<${structName}> and
533
- // reads typed element fields with zero boxing (no map alloc — the whole de-plumb). This uniform
534
- // PortReader path is a FALLBACK for a consumer that reads by name; it materializes the generic
535
- // {items:[{...}]} view ONLY on demand (never touched by the de-plumbed run_native_ runner, which
536
- // passes the native []${structName} straight through — the runner stays NewObj/Set-free).
537
- const reconstruct = `\t\t\to := ${PKG}.NewObj()\n\t\t\tpr, _ := e.(${PKG}.PortReader)\n\t\t\t_ = pr\n${portNames
538
- .map((pn) => `\t\t\tif pr != nil {\n\t\t\t\tif v, has := pr.Port(${JSON.stringify(pn)}); has {\n\t\t\t\t\to.Set(${JSON.stringify(pn)}, v)\n\t\t\t\t}\n\t\t\t}`)
539
- .join("\n")}\n\t\t\tout[i] = o`;
540
- return `${elemStruct}
541
-
542
- // ${batchStruct} — NATIVE batched ports for map '${id}': Items holds the typed per-element native
543
- // ports structs (a []Value of concrete ${structName}, built by direct assignment; no key-value
544
- // container in the RUNNER). A hot-path consumer downcasts each element to ${structName}; the
545
- // uniform Port("items") accessor below reconstructs the generic {items:[{...}]} view on demand.
546
- type ${batchStruct} struct {
547
- Items []${PKG}.Value
548
- }
549
-
550
- func (p ${batchStruct}) Port(name string) (${PKG}.Value, bool) {
551
- if name == "items" {
552
- out := make([]${PKG}.Value, len(p.Items))
553
- for i, e := range p.Items {
554
- ${reconstruct}
555
- }
556
- return out, true
557
- }
558
- return nil, false
559
- }`;
560
- }
561
- /**
562
- * emitStaticRefValue — statically resolve a ref/refOpt/concat/literal expression to a Go
563
- * expression that reads node-result locals (nr_<id>) or input params DIRECTLY, with no dynamic
564
- * scope object. Returns the Go expression string (already emitting any needed field-walk +
565
- * error-return lines), or null if the expression is a genuinely dynamic operator that must go
566
- * through the primitive SSoT (the caller falls back to an ephemeral scope for those).
567
- *
568
- * This is the bc#74 native cross-node resolution: a ref head that names a prior node reads that
569
- * node's NATIVE result value; a single-segment head is returned directly; a multi-segment path
570
- * is walked with slFieldVE-equivalent semantics emitted inline via slRef over a 1-key ephemeral
571
- * carrier ONLY when the head is a node result (kept as a direct value walk).
572
- */
573
- function emitStaticRefValue(node, atPos, priorNodeAt, nodeResult, lines, tmpBase) {
574
- const arr = staticArrElems(node);
575
- if (arr !== null) {
576
- // Static arr → direct []dslcontracts.Value{...} (NOT ArrNative closures). Each element is
577
- // resolved via this same function (a literal inlines; a ref/concat emits its error-return
578
- // lines and yields a Go Value expr). The whole arr is infallible once elements resolve.
579
- const elems = arr.map((el, j) => emitStaticRefValue(el, atPos, priorNodeAt, nodeResult, lines, `${tmpBase}_a${j}`));
580
- return `[]${PKG}.Value{${elems.join(", ")}}`;
581
- }
582
- const e = classifyExpr(node);
583
- if (e.kind === "str" || e.kind === "bool" || e.kind === "null") {
584
- return goLiteral(node);
585
- }
586
- // ref / concat reuse the native layer's own SSoT helpers (nvOkVE / nvBindVE / nvFieldVE /
587
- // nvCatVE — emitted unconditionally in this layer, mirroring the straight-line slVE semantics
588
- // so NULL_REF/MISSING_PROP/TYPE_MISMATCH/UNKNOWN_BINDING stay byte-identical to expr.go). They
589
- // are self-contained (do NOT depend on the generic body's usage-flagged helpers, which may be
590
- // absent for a component whose generic path never used them). We produce an nvVE and unwrap
591
- // {Val,Err} with a direct error return (the wrapper is folded away at the port).
592
- const ve = emitStaticVE(node, atPos, priorNodeAt, nodeResult);
593
- if (ve === null)
594
- return null;
595
- const rv = `${tmpBase}_v`;
596
- lines.push(`\t${rv} := ${ve}`);
597
- lines.push(`\tif ${rv}.Err != nil {`, `\t\treturn nil, ${rv}.Err`, `\t}`);
598
- return `${rv}.Val`;
599
- }
600
- /**
601
- * emitStaticVE — build the slVE expression for a static ref/concat/literal, resolving ref heads
602
- * to prior node-result locals (slOkVE(nr_<id>)) or input binds (slBindVE(input, head)). Returns
603
- * null for a genuinely dynamic operator (caller falls back to the primitive SSoT).
604
- */
605
- function emitStaticVE(node, atPos, priorNodeAt, nodeResult) {
606
- const e = classifyExpr(node);
607
- switch (e.kind) {
608
- case "str":
609
- return `nvValVE(${JSON.stringify(e.value)})`;
610
- case "bool":
611
- return `nvValVE(${e.value ? "true" : "false"})`;
612
- case "null":
613
- return `nvValVE(nil)`;
614
- case "ref": {
615
- const head = e.path[0];
616
- const headVE = priorNodeAt(head, atPos)
617
- ? `nvOkVE(${nodeResult(head)})`
618
- : `nvBindVE(input, ${JSON.stringify(head)})`;
619
- if (e.path.length === 1)
620
- return headVE;
621
- const segs = e.path.slice(1).map((p) => JSON.stringify(p)).join(", ");
622
- return `nvFieldVE(${e.opt ? '"refOpt"' : '"ref"'}, ${headVE}, []string{${segs}})`;
623
- }
624
- case "concat": {
625
- const parts = [];
626
- for (const p of e.parts) {
627
- const sub = emitStaticVE(reconstruct(p), atPos, priorNodeAt, nodeResult);
628
- if (sub === null)
629
- return null;
630
- parts.push(sub);
631
- }
632
- return `nvCatVE(${parts.join(", ")})`;
633
- }
634
- default:
635
- return null;
636
- }
637
- }
638
- /** Self-contained native-layer VE helpers (mirror the straight-line slVE SSoT; expr.go codes). */
639
- function emitNativeHelpers(needsCat) {
640
- const base = `// nvVE packs a single (Value, error) so a statically-wired native port expr composes as one
641
- // expression, then is folded away (the {Val,Err} never persists past the port). Mirrors slVE.
642
- type nvVE struct {
643
- Val ${PKG}.Value
644
- Err error
645
- }
646
- func nvValVE(v ${PKG}.Value) nvVE { return nvVE{Val: v} }
647
- func nvOkVE(v ${PKG}.Value) nvVE { return nvVE{Val: v} }
648
- // nvBindVE reads an input-param head directly (UNKNOWN_BINDING matches expr.go). A nil scope
649
- // (no input passed) has no bindings — fail closed with UNKNOWN_BINDING (no *Obj allocated).
650
- func nvBindVE(scope *${PKG}.Obj, head string) nvVE {
651
- if scope == nil {
652
- return nvVE{Err: ${PKG}.NewExprFailure("UNKNOWN_BINDING", "unknown binding: "+head)}
653
- }
654
- v, ok := scope.Get(head)
655
- if !ok {
656
- return nvVE{Err: ${PKG}.NewExprFailure("UNKNOWN_BINDING", "unknown binding: "+head)}
657
- }
658
- return nvVE{Val: v}
659
- }
660
- // nvFieldVE walks a static field path over a resolved head (expr.go ref/refOpt semantics).
661
- func nvFieldVE(op string, head nvVE, path []string) nvVE {
662
- if head.Err != nil {
663
- return head
664
- }
665
- cur := head.Val
666
- for _, seg := range path {
667
- if cur == nil {
668
- if op == "refOpt" {
669
- return nvVE{Val: nil}
670
- }
671
- return nvVE{Err: ${PKG}.NewExprFailure("NULL_REF", "null intermediate at ."+seg+" (use ?.)")}
672
- }
673
- o, ok := cur.(*${PKG}.Obj)
674
- if !ok {
675
- return nvVE{Err: ${PKG}.NewExprFailure("TYPE_MISMATCH", "cannot access ."+seg+" on "+${PKG}.TypeName(cur))}
676
- }
677
- next, present := o.Get(seg)
678
- if !present {
679
- return nvVE{Err: ${PKG}.NewExprFailure("MISSING_PROP", "missing property ."+seg)}
680
- }
681
- cur = next
682
- }
683
- return nvVE{Val: cur}
684
- }`;
685
- if (!needsCat)
686
- return base;
687
- return `${base}
688
- // nvCatVE concatenates already-evaluated string parts (expr.go concat: strings only order + code).
689
- func nvCatVE(parts ...nvVE) nvVE {
690
- var b strings.Builder
691
- for _, p := range parts {
692
- if p.Err != nil {
693
- return p
694
- }
695
- s, ok := p.Val.(string)
696
- if !ok {
697
- return nvVE{Err: ${PKG}.NewExprFailure("TYPE_MISMATCH", "concat: strings only (got "+${PKG}.TypeName(p.Val)+"; no implicit toString)")}
698
- }
699
- b.WriteString(s)
700
- }
701
- return nvVE{Val: b.String()}
702
- }`;
703
- }
704
- /** Whether the native layer needs "strings" (a concat appears in any native component). */
705
- function nativeNeedsStrings(comp) {
706
- const walk = (x) => {
707
- if (x === null || typeof x !== "object")
708
- return false;
709
- if (Array.isArray(x))
710
- return x.some(walk);
711
- const keys = Object.keys(x);
712
- if (keys.length === 1 && keys[0] === "concat")
713
- return true;
714
- return keys.some((k) => walk(x[k]));
715
- };
716
- return comp.body.some((n) => walk(n)) || walk(comp.output);
717
- }
718
- /** Reconstruct an Expression IR node from a classified StaticExpr (for recursive concat parts). */
719
- function reconstruct(e) {
720
- switch (e.kind) {
721
- case "str":
722
- return e.value;
723
- case "bool":
724
- return e.value;
725
- case "null":
726
- return null;
727
- case "ref":
728
- return { [e.opt ? "refOpt" : "ref"]: e.path };
729
- case "concat":
730
- return { concat: e.parts.map(reconstruct) };
731
- case "dynamic":
732
- return e.node;
733
- }
734
- }
735
- /** BindNative — dispatch surface for native components (mirrors Bind, native handler seam). */
736
- function emitBindNative(ctx) {
737
- const native = ctx.ir.components.filter(isNative);
738
- const arms = native
739
- .map((c) => `\tm[${JSON.stringify(c.name)}] = func(input *${PKG}.Obj) (${PKG}.Value, error) {\n\t\treturn run_native_${sanitize(c.name)}(handlers, input)\n\t}`)
740
- .join("\n");
741
- return `// BindNative is the NATIVE-PORTS dispatch surface (bc 0.5.0): it binds a NativeComponentExec
742
- // (native ports struct + native-backed result) to the de-plumbed runners. Only native-covered
743
- // components (strictly-sequential componentRef) are exposed; a consumer keeps the rest on the
744
- // generic Bind. This is ADDITIVE — the generic Bind path is unchanged.
745
- func BindNative(handlers ${PKG}.NativeComponentExec) map[string]func(input *${PKG}.Obj) (${PKG}.Value, error) {
746
- m := map[string]func(input *${PKG}.Obj) (${PKG}.Value, error){}
747
- ${arms}
748
- return m
749
- }
750
-
751
- // ComponentNamesNative — components exposed on the native-ports path (in declaration order).
752
- var ComponentNamesNative = []string{${native.map((c) => JSON.stringify(c.name)).join(", ")}}`;
753
- }
754
- /**
755
- * emit — a native-eligible component gets ONLY the native run_native_ / BindNative path; the
756
- * generic key-value run_ / Bind is emitted ONLY for components that are NOT native-eligible
757
- * (map/cond/concurrency), so a fully-native module contains ZERO NewObj/Set/generic ExecHandler
758
- * (owner purity requirement + grep gate). We drive the generic emitter with a FILTERED context
759
- * (non-native components only) so it emits the generic body — imports, spec-version init, base
760
- * helpers, run_ fns, Bind, dispatch, ComponentNames — for exactly those components. The native
761
- * surface (structs, run_native_ fns, BindNative, ComponentNamesNative) covers the native ones.
762
- */
763
- function emit(ctx) {
764
- const native = ctx.ir.components.filter(isNative);
765
- const nonNative = ctx.ir.components.filter((c) => !isNative(c));
766
- // Generic body for the NON-NATIVE components only (filtered context). When every component is
767
- // native, this body has zero components: header + spec-version init + slVal base helper +
768
- // empty Bind/dispatch/ComponentNames — and crucially NO NewObj/Set/ExecHandler (those live in
769
- // run_*, which only exist for the non-native components here).
770
- const filteredCtx = {
771
- ...ctx,
772
- ir: { ...ctx.ir, components: nonNative },
773
- componentNames: nonNative.map((c) => c.name),
774
- };
775
- let straightlineCode = goStraightlineEmitter.emit(filteredCtx);
776
- if (native.length === 0) {
777
- // Nothing native-emittable (all map/cond/concurrency): generic path only. BindNative binds
778
- // nothing. The generic body is the FULL module (nonNative === all components).
779
- return `${straightlineCode.replace(/\n+$/, "\n")}\n${emitBindNative(ctx)}\n`;
780
- }
781
- const structs = [];
782
- for (const c of native) {
783
- if (isNativeMapComponent(c))
784
- structs.push(emitMapPortsStructs(c));
785
- else
786
- for (const n of c.body)
787
- structs.push(emitPortsStruct(c, n));
788
- }
789
- const runners = native.map((c) => (isNativeMapComponent(c) ? emitNativeMapRunner(c) : emitNativeRunner(c))).join("\n\n");
790
- const bindNative = emitBindNative(ctx);
791
- const needsCat = native.some(nativeNeedsStrings);
792
- const helpers = emitNativeHelpers(needsCat);
793
- // A batched native map runner uses fmt.Sprintf (MAP_BATCH_RESULT_MISMATCH). Ensure "fmt" is
794
- // imported even if no NON-NATIVE component needed it (idempotent: skip if present).
795
- const needsFmt = native.some((c) => isNativeMapComponent(c) && c.body[0].map.batched === true);
796
- if (needsFmt && !/\n\t"fmt"\n/.test(straightlineCode)) {
797
- // inject after the opening import "(" — the generic header always emits an import block.
798
- straightlineCode = straightlineCode.replace(/\nimport \(\n/, '\nimport (\n\t"fmt"\n');
799
- }
800
- // nvCatVE uses strings.Builder. The filtered generic body imports "strings" iff a NON-NATIVE
801
- // component uses concat; if concat appears only in native components (or the module is fully
802
- // native), inject the "strings" import so nvCatVE resolves. (Idempotent: skip if present.)
803
- if (needsCat && !/\n\t"strings"\n/.test(straightlineCode)) {
804
- straightlineCode = straightlineCode.replace(/\n\t"fmt"\n/, '\n\t"fmt"\n\t"strings"\n');
805
- }
806
- const banner = `// ── NATIVE-PORTS layer (bc 0.5.0 — de-plumb the port container) ────────────────────
807
- // A native-eligible component is emitted ONLY here (run_native_ + BindNative) — its generic
808
- // key-value run_ / Bind is NOT emitted (a fully-native module has zero generic port container).
809
- // Each componentRef builds a native ports struct by direct field assignment and dispatches via
810
- // the native handler seam (NativeComponentExec -> ExecOutcome). Components that are NOT native-
811
- // eligible (map/cond/concurrency) keep the generic key-value run_ / Bind above (they can't be
812
- // de-plumbed yet). Fail-closed operator semantics still route through the A0 primitives.`;
813
- const sections = [
814
- banner,
815
- `// Self-contained native-layer VE helpers (expr.go ref/concat semantics; independent of the\n// generic body's usage-flagged helpers).\n${helpers}`,
816
- `// Native ports structs (one per componentRef node; typed per the static port type).\n${structs.join("\n\n")}`,
817
- `// Native-ports straight-line runners: the de-plumbed exec path (called by BindNative).\n${runners}`,
818
- bindNative,
819
- ];
820
- return `${straightlineCode.replace(/\n+$/, "\n")}\n${sections.join("\n\n")}\n`;
821
- }
822
- /**
823
- * goStraightlineNativeEmitter — language id `go-straightline-native` (bc 0.5.0). Emits the
824
- * generic straight-line body (fallback + helpers) plus the NATIVE-PORTS layer (per-node ports
825
- * struct built by direct assignment + BindNative through the native handler seam). Additive:
826
- * the generic Bind / literal / ir endpoints are unchanged.
827
- */
828
- export const goStraightlineNativeEmitter = {
829
- language: "go-straightline-native",
830
- fileExtension: ".go",
831
- defaultRuntimeImport: "github.com/foo-ogawa/behavior-contracts/go/coderuntime",
832
- filenameHint: "behaviors.straightline.native.generated.go",
833
- emit,
834
- };
835
- //# sourceMappingURL=emit-straightline-native-go.js.map