behavior-contracts 0.2.3 → 0.2.5

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 (34) hide show
  1. package/dist/generator/emit-straightline-go.d.ts +27 -46
  2. package/dist/generator/emit-straightline-go.d.ts.map +1 -1
  3. package/dist/generator/emit-straightline-go.js +688 -190
  4. package/dist/generator/emit-straightline-go.js.map +1 -1
  5. package/dist/generator/emit-straightline-php.d.ts +1 -1
  6. package/dist/generator/emit-straightline-php.d.ts.map +1 -1
  7. package/dist/generator/emit-straightline-php.js +451 -20
  8. package/dist/generator/emit-straightline-php.js.map +1 -1
  9. package/dist/generator/emit-straightline-python.d.ts.map +1 -1
  10. package/dist/generator/emit-straightline-python.js +426 -22
  11. package/dist/generator/emit-straightline-python.js.map +1 -1
  12. package/dist/generator/emit-straightline-rust.d.ts +5 -0
  13. package/dist/generator/emit-straightline-rust.d.ts.map +1 -1
  14. package/dist/generator/emit-straightline-rust.js +597 -49
  15. package/dist/generator/emit-straightline-rust.js.map +1 -1
  16. package/dist/generator/emit-straightline-typed-go.d.ts.map +1 -1
  17. package/dist/generator/emit-straightline-typed-go.js +50 -18
  18. package/dist/generator/emit-straightline-typed-go.js.map +1 -1
  19. package/dist/generator/emit-straightline-typed-rust.d.ts.map +1 -1
  20. package/dist/generator/emit-straightline-typed-rust.js +66 -23
  21. package/dist/generator/emit-straightline-typed-rust.js.map +1 -1
  22. package/dist/generator/emit-straightline-typescript.d.ts +26 -15
  23. package/dist/generator/emit-straightline-typescript.d.ts.map +1 -1
  24. package/dist/generator/emit-straightline-typescript.js +703 -147
  25. package/dist/generator/emit-straightline-typescript.js.map +1 -1
  26. package/dist/generator/literal.d.ts +3 -1
  27. package/dist/generator/literal.d.ts.map +1 -1
  28. package/dist/generator/literal.js +13 -4
  29. package/dist/generator/literal.js.map +1 -1
  30. package/dist/generator/straightline.d.ts +81 -0
  31. package/dist/generator/straightline.d.ts.map +1 -1
  32. package/dist/generator/straightline.js +176 -0
  33. package/dist/generator/straightline.js.map +1 -1
  34. package/package.json +1 -1
@@ -1,21 +1,42 @@
1
- import { emitDocLiteral, emitDocLiteralCompact } from "./literal.js";
2
- import { emitStraightlineModule, } from "./straightline.js";
1
+ import { emitDocLiteralCompact } from "./literal.js";
2
+ import { emitStraightlineModule, buildComponentPlan, sequentialOrder, analyzeSequentialOps, classifyExpr, isScopeFree, componentNeedsScope, } from "./straightline.js";
3
3
  /** runtime package alias(生成コード内)。emit-go.ts と同じ `dslcontracts`。 */
4
4
  const PKG = "dslcontracts";
5
+ /** scope 式(RunPlan 経路のみ)。逐次経路は進行形 scope ローカル `scope` を使う。 */
6
+ const SCOPE = "baseScope()";
7
+ /** map 本体の要素 scope 式(RunPlan 経路の map 展開用 — 逐次経路は per-map ローカル)。 */
8
+ const MAP_SCOPE = "mscope()";
5
9
  /**
6
- * scope 式(生成コード内)。scope は「input + それまでの ok 結果」のその時点のスナップ。
7
- * baseScope() 関数呼びで呼び時点の results を読む(決定的)。
10
+ * 生成中 module json.Number 使用 / helper 要否の構造的トラッカー(emitModule が emit 前に reset)。
11
+ * 生成テキストの substring 判定(文字列リテラル内容に誤反応)はしない。
8
12
  */
9
- const SCOPE = "baseScope()";
13
+ const usage = {
14
+ json: false,
15
+ runPlan: false,
16
+ slBind: false,
17
+ slField: false,
18
+ slCat: false,
19
+ slEmpty: false,
20
+ slPart: false, // RunPlan 経路の concat closure part(実並行のみ)
21
+ unproduced: false,
22
+ };
10
23
  /**
11
- * map 本体(over/guard/ports)の要素 scope 式(生成コード内)。RunBehavior map と同じく
12
- * baseScope `as`→要素値 を足したスナップ。生成コードは map arm 内で per-element
13
- * `mscope()`(*Obj を返すクロージャ)をローカル定義し、port/guard 式はそれを呼ぶ。
24
+ * typed emitter(bc#47 go-typed)が base straight-line モジュールを同梱し、その上に **RunPlan-style の
25
+ * struct-native runner** を積む場合、runner `sync` RunPlan-path native helper(slRef/slConcat/slPart/
26
+ * slVal)を参照する。bc#75 base は逐次 component から RunPlan-path helper を落とすため、typed 層は
27
+ * この flag で base に「RunPlan-path helper と sync を必ず含めよ」と要求する(typed 経路の後方互換)。
14
28
  */
15
- const MAP_SCOPE = "mscope()";
29
+ let forceRunPlanHelpers = false;
30
+ export function setGoForceRunPlanHelpers(on) {
31
+ forceRunPlanHelpers = on;
32
+ }
33
+ function resetUsage() {
34
+ for (const k of Object.keys(usage))
35
+ usage[k] = false;
36
+ }
16
37
  /** IR リテラルを単一行 Go 式として emit(primitive 被演算子用)。 */
17
38
  function lit(node) {
18
- return emitDocLiteralCompact(node, "go");
39
+ return emitDocLiteralCompact(node, "go", "$", usage);
19
40
  }
20
41
  /** args 配列(binary/unary primitive の被演算子)を個別 IR リテラルへ落として `, ` 連結。 */
21
42
  function argList(arg) {
@@ -23,9 +44,8 @@ function argList(arg) {
23
44
  }
24
45
  /**
25
46
  * OP_PRIM — overflow/短絡/型判定を持つ演算子 → A0 primitive 直呼びの綴じ方。
26
- * 命名は go/primitives.go の `Prim*`。被演算子は expr.go の Expr 契約どおり**未評価 IR リテラル**
27
- * (`and`/`or`/`cond`/`coalesce` の短絡を primitive evaluate が保存する)。
28
- * NB: ここに `ref`/`refOpt`/`concat` は無い(native 化するため — AC#2)。
47
+ * 命名は go/primitives.go の `Prim*`。被演算子は expr.go の Expr 契約どおり**未評価 IR リテラル**。
48
+ * NB: ここに `ref`/`refOpt`/`concat` は無い(native 化する AC#2)。
29
49
  */
30
50
  const OP_PRIM = {
31
51
  int: (a, s) => `${PKG}.PrimIntLit(${lit(a)}, ${s})`,
@@ -52,25 +72,10 @@ const OP_PRIM = {
52
72
  len: (a, s) => `${PKG}.PrimLen(${argList(a)}, ${s})`,
53
73
  };
54
74
  /**
55
- * emitExpr — Expression IR ノードを「scope を受けて (Value, error) を返す」Go 式に落とす。
56
- *
57
- * 単純式は **native Go helper** へ(脱解釈 — AC#2)、overflow/短絡系は A0 primitive SSoT へ、
58
- * むき出しスカラは EvalExpr(同一 SSoT)へ落とす。生成される Go 式はどれも `(Value, error)` を
59
- * 返すので、直線関数側は `must(<expr>)` で 1 行に畳んで scope へ束ねる。
60
- * - `{ref:[...]}` → `${PKG}.slRef(<path>, scope)` (native scope/field walk)
61
- * - `{refOpt:[...]}` → `${PKG}? no — slRefOpt` (native, null 伝播)
62
- * - `{concat:[...]}` → `slConcat([]slPart{...}, scope)` (native strings.Builder)
63
- * - overflow/短絡 → `${PKG}.PrimAdd(...)` 等 (SSoT 直呼び)
64
- * - bare scalar → `${PKG}.EvalExpr(<lit>, scope)` (SSoT)
65
- * slRef/slRefOpt/slConcat は **生成モジュール内の native helper**(`EvaluateExpression`/`Prim*`
66
- * を呼ばない)で、pure ref/concat の生成コードには evaluate/primitive 呼びが一切現れない
67
- * (AC#2 の構造的 grep 証明)。
75
+ * emitExpr — Expression IR ノードを「scope を受けて (Value, error) を返す」Go 式に落とす
76
+ * (**dynamic 式専用**の legacy 経路 — RunPlan 経路と typed emitter が流用する seam)。
68
77
  */
69
78
  function emitExpr(node, scopeVar) {
70
- // ── bare スカラ literal: 型判定の無い string/bool/null は native 化(脱解釈)──────
71
- // これらは expr.go でも素通し(INVALID_LITERAL/range 判定を持たない)ので、native Go 値を
72
- // そのまま (Value, error) へ包む。number は checked-range/型区別(int vs float)の意味論を
73
- // 持つため native 化せず EvalExpr(SSoT) に委譲する(下の fall-through)。
74
79
  if (node === null)
75
80
  return `slVal(nil)`;
76
81
  if (typeof node === "boolean")
@@ -82,22 +87,20 @@ function emitExpr(node, scopeVar) {
82
87
  if (keys.length === 1) {
83
88
  const op = keys[0];
84
89
  const arg = node[op];
85
- // ── 単純式: native 化(脱解釈) ─────────────────────────────────────────
86
90
  if (op === "ref" || op === "refOpt") {
87
- // path は string リテラル配列。native helper Go の []string を直接渡す。
91
+ // RunPlan-path: native slRef/slRefOpt(slPart 枝で定義)を使う。逐次 slFieldVE ではない。
92
+ usage.slPart = true;
88
93
  return `slRef${op === "refOpt" ? "Opt" : ""}(${emitStringPath(arg)}, ${scopeVar})`;
89
94
  }
90
95
  if (op === "concat") {
91
- // parts は Expression IR 配列。各 part を再帰 native emit した slPart のスライスへ。
96
+ usage.slPart = true;
92
97
  return `slConcat([]slPart{${emitConcatParts(arg, scopeVar)}}, ${scopeVar})`;
93
98
  }
94
- // ── overflow/短絡/型判定: A0 primitive SSoT(再実装しない) ──────────────
95
99
  const prim = OP_PRIM[op];
96
100
  if (prim)
97
101
  return prim(arg, scopeVar);
98
102
  }
99
103
  }
100
- // むき出しスカラ(string/number/bool/null)等 — evaluate SSoT に委譲(同一意味論)。
101
104
  return `${PKG}.EvalExpr(${lit(node)}, ${scopeVar})`;
102
105
  }
103
106
  /** ref/refOpt の path(string リテラル配列)を Go の `[]string{...}` へ。非 string は不正 IR。 */
@@ -111,19 +114,453 @@ function emitStringPath(arg) {
111
114
  });
112
115
  return `[]string{${segs.join(", ")}}`;
113
116
  }
114
- /**
115
- * concat の各 part を native `slPart` へ。slPart は「self-eval なクロージャ」= func(scope) (Value, error)。
116
- * part 自身が式なので `func(s *${PKG}.Obj) (${PKG}.Value, error) { return <emitExpr(part)>; }` で
117
- * 再帰 native emit する(part が別の concat/ref なら native、overflow なら primitive のまま)。
118
- */
117
+ /** concat の各 part を native `slPart` closure へ(RunPlan 経路のみ)。 */
119
118
  function emitConcatParts(arg, scopeVar) {
120
119
  if (!Array.isArray(arg))
121
120
  throw new Error("straight-line go: concat args must be an array literal");
122
- // part の scope 変数は helper 引数 `s`(呼び時点の scope を受ける)。scopeVar は無視される。
123
121
  void scopeVar;
124
122
  return arg.map((p) => `func(s *${PKG}.Obj) (${PKG}.Value, error) { return ${emitExpr(p, "s")} }`).join(", ");
125
123
  }
126
- // ── per-node exec 分岐 / component 関数 / module の綴じ方 ──────────────────────
124
+ /** dynamic 式に渡す scope 式(scope-free なら空 scope 定数)。 */
125
+ function seqScope(node, ctx) {
126
+ if (isScopeFree(node)) {
127
+ usage.slEmpty = true;
128
+ return "slEmpty()";
129
+ }
130
+ if (ctx.mapAs)
131
+ return ctx.mapAs.mscopeVar;
132
+ if (!ctx.scopeVar)
133
+ throw new Error("straight-line go: scope-reading expr in a scopeless component (analysis bug)");
134
+ return ctx.scopeVar;
135
+ }
136
+ /**
137
+ * 静的 ref の head を配線解決した Go 式(`slVE` を返す)。ローカル直読み / input fallback /
138
+ * UNKNOWN_BINDING。Go は多値 spread が「唯一引数」のときしか許さないため、逐次経路の式は
139
+ * すべて単一の `slVE` 値({Val, Err})へ落とし、slField/slCat が合成できるようにする。
140
+ */
141
+ function seqRefHead(head, ctx) {
142
+ if (ctx.mapAs && head === ctx.mapAs.as) {
143
+ usage.slBind = true;
144
+ return `slOkVE(${ctx.mapAs.elVar})`;
145
+ }
146
+ const idx = ctx.idToIndex.get(head);
147
+ if (idx !== undefined) {
148
+ const executedBefore = ctx.atOutput || ctx.pos[idx] < ctx.currentPos;
149
+ if (executedBefore) {
150
+ const meta = ctx.metas[idx];
151
+ const local = ctx.local.get(head);
152
+ usage.slBind = true;
153
+ if (ctx.atOutput || !(meta.canSkip || meta.alwaysSkip))
154
+ return `slOkVE(${local})`;
155
+ return `slHeadOrVE(${ctx.okFlag.get(head)}, ${local}, input, ${JSON.stringify(head)})`;
156
+ }
157
+ }
158
+ usage.slBind = true;
159
+ return `slBindVE(input, ${JSON.stringify(head)})`;
160
+ }
161
+ /** 分類済み StaticExpr を Go 式へ(`slVE` を返す — 逐次経路のネイティブ inline)。 */
162
+ function seqStatic(e, ctx) {
163
+ switch (e.kind) {
164
+ case "str":
165
+ usage.slBind = true;
166
+ return `slValVE(${JSON.stringify(e.value)})`;
167
+ case "bool":
168
+ usage.slBind = true;
169
+ return `slValVE(${e.value ? "true" : "false"})`;
170
+ case "null":
171
+ usage.slBind = true;
172
+ return `slValVE(nil)`;
173
+ case "ref": {
174
+ const head = seqRefHead(e.path[0], ctx);
175
+ if (e.path.length === 1)
176
+ return head;
177
+ usage.slField = true;
178
+ const segs = e.path.slice(1).map((p) => JSON.stringify(p)).join(", ");
179
+ return `slFieldVE(${JSON.stringify(e.opt ? "refOpt" : "ref")}, ${head}, []string{${segs}})`;
180
+ }
181
+ case "concat": {
182
+ usage.slCat = true;
183
+ return `slCatVE(${e.parts.map((p) => seqStatic(p, ctx)).join(", ")})`;
184
+ }
185
+ case "dynamic":
186
+ // primitive 経路は (Value, error) を返す — slVE2 で単一 slVE へ包む(唯一引数 spread)。
187
+ usage.slBind = true;
188
+ return `slVE2(${emitExpr(e.node, seqScope(e.node, ctx))})`;
189
+ }
190
+ }
191
+ /** Expression IR → Go 式(逐次経路、`slVE` を返す)。 */
192
+ function seqExpr(node, ctx) {
193
+ return seqStatic(classifyExpr(node), ctx);
194
+ }
195
+ /** component 名を Go 関数名に使える形へ(決定的。名前一意は DUPLICATE_COMPONENT で保証)。 */
196
+ function sanitize(name) {
197
+ return name.replace(/[^A-Za-z0-9_]/g, "_");
198
+ }
199
+ /** node id 群 → 衝突しない決定的ローカル名。 */
200
+ function allocLocals(prefix, ids) {
201
+ const out = new Map();
202
+ const taken = new Set();
203
+ ids.forEach((id, i) => {
204
+ let name = `${prefix}${sanitize(id)}`;
205
+ if (taken.has(name))
206
+ name = `${name}_${i}`;
207
+ taken.add(name);
208
+ out.set(id, name);
209
+ });
210
+ return out;
211
+ }
212
+ /** 未生成表現の Go 式(single → nil / connection → 空 connection Obj)。 */
213
+ function unproducedGo(relationKind) {
214
+ usage.unproduced = true;
215
+ return `${PKG}.UnproducedValue(${JSON.stringify(relationKind === "connection" ? "connection" : "single")})`;
216
+ }
217
+ /** policy 解釈(fail/retry → OP_FAILED、continue → skip)。plan.go interpretOutcome と code/msg 一致。 */
218
+ function seqPolicyThrow(outcomeVar, opId, meta, ind) {
219
+ if (meta.policy === "retry") {
220
+ return [
221
+ `${ind}if ${outcomeVar}.IsError {`,
222
+ `${ind}\treturn nil, ${PKG}.NewPlanFailure("OP_FAILED", fmt.Sprintf("operation '%s' failed under 'retry' policy (exhausted): %s", ${JSON.stringify(opId)}, ${outcomeVar}.Err))`,
223
+ `${ind}}`,
224
+ ];
225
+ }
226
+ // fail(既定・未知は手前で UNKNOWN_POLICY throw 済みで到達しない)。
227
+ return [
228
+ `${ind}if ${outcomeVar}.IsError {`,
229
+ `${ind}\treturn nil, ${PKG}.NewPlanFailure("OP_FAILED", fmt.Sprintf("operation '%s' failed under 'fail' policy: %s", ${JSON.stringify(opId)}, ${outcomeVar}.Err))`,
230
+ `${ind}}`,
231
+ ];
232
+ }
233
+ /**
234
+ * emitSeqOp — 逐次直線化された 1 op の文列。policy 検証 → skip 判定(parent 連鎖 / bindField)→
235
+ * 実行(componentRef/map/cond)→ policy 解釈 → 結果ローカル格納(+進行形 scope 書き足し)。
236
+ */
237
+ function emitSeqOp(op, plan, ctx, lines) {
238
+ const i = op.index;
239
+ const meta = ctx.metas[i];
240
+ const o = plan.opsLiteral[i];
241
+ const id = o.id;
242
+ const local = ctx.local.get(id);
243
+ lines.push(`\t// ── op '${id}' (${op.kind === "cond" ? "cond" : op.kind === "map" ? `map ${op.component}` : op.component}${o.policy ? `, policy ${JSON.stringify(o.policy)}` : ""}) ──`);
244
+ // policy 検証は skip 判定より先(plan.go preflightOp と同順)。
245
+ if (typeof meta.policy === "object") {
246
+ lines.push(`\treturn nil, ${PKG}.NewPlanFailure("UNKNOWN_POLICY", ${JSON.stringify(`unknown policy kind: ${meta.policy.invalid}`)})`);
247
+ return;
248
+ }
249
+ const canSkip = meta.canSkip || meta.alwaysSkip;
250
+ if (canSkip) {
251
+ lines.push(`\tvar ${local} ${PKG}.Value = nil`, `\t${ctx.okFlag.get(id)} := false`);
252
+ }
253
+ if (meta.alwaysSkip) {
254
+ lines.push(`\t// parent '${o.parent}' runs later — preflight sees an unsettled parent: unconditional skip.`);
255
+ return;
256
+ }
257
+ const guards = [];
258
+ if (o.parent !== null) {
259
+ const pMeta = ctx.metas[o.parent];
260
+ if (pMeta.canSkip || pMeta.alwaysSkip)
261
+ guards.push(ctx.okFlag.get(plan.opsLiteral[o.parent].id));
262
+ }
263
+ if (o.bindField !== undefined && o.parent !== null) {
264
+ const pLocal = ctx.local.get(plan.opsLiteral[o.parent].id);
265
+ const bv = `bv_${sanitize(id)}`;
266
+ const inner = [];
267
+ const ind = guards.length > 0 ? "\t\t\t" : "\t\t";
268
+ // parent result[bindField](parent が Obj のときのみ)。null/欠落なら skip。
269
+ inner.push(`${ind}${bv}Obj, ${bv}IsObj := ${pLocal}.(*${PKG}.Obj)`);
270
+ inner.push(`${ind}if ${bv}IsObj {`);
271
+ inner.push(`${ind}\t${bv}, ${bv}Present := ${bv}Obj.Get(${JSON.stringify(o.bindField)})`);
272
+ inner.push(`${ind}\tif ${bv}Present && ${bv} != nil {`);
273
+ emitSeqExec(op, plan, ctx, inner, `${ind}\t\t`);
274
+ inner.push(`${ind}\t}`);
275
+ inner.push(`${ind}}`);
276
+ if (guards.length > 0) {
277
+ lines.push(`\tif ${guards[0]} {`);
278
+ lines.push(...inner);
279
+ lines.push(`\t}`);
280
+ }
281
+ else {
282
+ lines.push(...inner);
283
+ }
284
+ return;
285
+ }
286
+ if (guards.length > 0) {
287
+ lines.push(`\tif ${guards[0]} {`);
288
+ emitSeqExec(op, plan, ctx, lines, "\t\t");
289
+ lines.push(`\t}`);
290
+ return;
291
+ }
292
+ emitSeqExec(op, plan, ctx, lines, "\t");
293
+ }
294
+ /** op 本体(handler 呼び + policy 解釈 + 結果格納)を indent 付きで emit。 */
295
+ function emitSeqExec(op, plan, ctx, lines, ind) {
296
+ const i = op.index;
297
+ const meta = ctx.metas[i];
298
+ const o = plan.opsLiteral[i];
299
+ const id = o.id;
300
+ const local = ctx.local.get(id);
301
+ const canSkip = meta.canSkip || meta.alwaysSkip;
302
+ // 成功時の格納(ローカル + 進行形 scope)。
303
+ const store = (valueExpr, target, sind) => {
304
+ if (canSkip) {
305
+ target.push(`${sind}${local} = ${valueExpr}`, `${sind}${ctx.okFlag.get(id)} = true`);
306
+ }
307
+ else {
308
+ target.push(`${sind}${local} := ${valueExpr}`, `${sind}_ = ${local}`);
309
+ }
310
+ if (ctx.scopeVar)
311
+ target.push(`${sind}${ctx.scopeVar}.Set(${JSON.stringify(id)}, ${local})`);
312
+ };
313
+ if (op.kind === "cond") {
314
+ const c = plan.component.body[i].cond;
315
+ const node = { cond: [c.if, c.then, c.else] };
316
+ const v = `v_${sanitize(id)}`;
317
+ lines.push(`${ind}${v}, ${v}Err := ${emitExpr(node, seqScope(node, ctx))}`);
318
+ lines.push(`${ind}if ${v}Err != nil {`, `${ind}\treturn nil, ${v}Err`, `${ind}}`);
319
+ store(v, lines, ind);
320
+ return;
321
+ }
322
+ if (op.kind === "map") {
323
+ emitSeqMap(op, plan, ctx, lines, ind, store);
324
+ return;
325
+ }
326
+ // componentRef: ports 構築 → handler 呼び → policy 解釈。
327
+ const savedPos = ctx.currentPos;
328
+ const portsVar = `ports_${sanitize(id)}`;
329
+ lines.push(`${ind}${portsVar} := ${PKG}.NewObj()`);
330
+ for (const p of op.ports) {
331
+ const node = plan.component.body[i].ports[p.name];
332
+ const pv = `pv_${sanitize(id)}_${p.name.replace(/[^A-Za-z0-9_]/g, "_")}`;
333
+ lines.push(`${ind}${pv} := ${seqExpr(node, ctx)}`);
334
+ lines.push(`${ind}if ${pv}.Err != nil {`, `${ind}\treturn nil, ${pv}.Err`, `${ind}}`);
335
+ lines.push(`${ind}${portsVar}.Set(${JSON.stringify(p.name)}, ${pv}.Val)`);
336
+ }
337
+ ctx.currentPos = savedPos;
338
+ const oc = `o_${sanitize(id)}`;
339
+ lines.push(`${ind}${oc}, ${oc}Resolved := ${PKG}.ExecHandler(handlers, ${JSON.stringify(id)}, ${JSON.stringify(op.component)}, ${portsVar}, nil)`);
340
+ lines.push(`${ind}if !${oc}Resolved {`, `${ind}\treturn nil, ${PKG}.NewBehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")`, `${ind}}`);
341
+ if (meta.policy === "continue") {
342
+ lines.push(`${ind}if !${oc}.IsError {`);
343
+ store(`${oc}.Ok`, lines, `${ind}\t`);
344
+ lines.push(`${ind}}`);
345
+ }
346
+ else {
347
+ lines.push(...seqPolicyThrow(oc, id, meta, ind));
348
+ store(`${oc}.Ok`, lines, ind);
349
+ }
350
+ }
351
+ /** emitSeqMap — map ノードの逐次直線展開(RunPlan 経路と観測等価。per-expr scope snapshot 無し)。 */
352
+ function emitSeqMap(op, plan, ctx, lines, ind, store) {
353
+ const i = op.index;
354
+ const meta = ctx.metas[i];
355
+ const m = plan.component.body[i].map;
356
+ const id = op.id;
357
+ const s = sanitize(id);
358
+ const comp = JSON.stringify(op.component);
359
+ const asKey = JSON.stringify(m.as);
360
+ const guardIR = m.when === undefined ? undefined : { cond: [m.when, true, false] };
361
+ const mapNeedsScope = (guardIR !== undefined && !isScopeFree(guardIR)) ||
362
+ Object.keys(m.ports).some((k) => {
363
+ const cls = classifyExpr(m.ports[k]);
364
+ const needs = (e) => e.kind === "dynamic" ? !isScopeFree(e.node) : e.kind === "concat" ? e.parts.some(needs) : false;
365
+ return needs(cls);
366
+ });
367
+ const baseScopeExpr = ctx.scopeVar;
368
+ const elVar = `el_${s}`;
369
+ const mscopeVar = `mscope_${s}`;
370
+ const overVar = `over_${s}`;
371
+ const keptVar = `kept_${s}`;
372
+ const collected = `collected_${s}`;
373
+ const contFlag = meta.policy === "continue";
374
+ const skipVar = `skip_${s}`;
375
+ const mapCtx = { ...ctx, mapAs: { as: m.as, elVar, mscopeVar } };
376
+ lines.push(`${ind}${overVar}VE := ${seqExpr(m.over, ctx)}`);
377
+ lines.push(`${ind}if ${overVar}VE.Err != nil {`, `${ind}\treturn nil, ${overVar}VE.Err`, `${ind}}`);
378
+ lines.push(`${ind}${overVar}, ${overVar}IsArr := ${overVar}VE.Val.([]${PKG}.Value)`);
379
+ lines.push(`${ind}if !${overVar}IsArr {`, `${ind}\treturn nil, ${PKG}.NewBehaviorFailure("MAP_OVER_NOT_ARRAY", "map '${id}': 'over' did not evaluate to an array")`, `${ind}}`);
380
+ lines.push(`${ind}${keptVar} := make([]int, 0, len(${overVar}))`);
381
+ if (contFlag)
382
+ lines.push(`${ind}${skipVar} := false`);
383
+ const mscopeLines = (ind2) => {
384
+ if (!mapNeedsScope)
385
+ return [];
386
+ return [
387
+ `${ind2}${mscopeVar} := ${PKG}.NewObj()`,
388
+ ...(baseScopeExpr
389
+ ? [`${ind2}for _, ${mscopeVar}k := range ${baseScopeExpr}.Keys {`, `${ind2}\t${mscopeVar}.Set(${mscopeVar}k, ${baseScopeExpr}.Vals[${mscopeVar}k])`, `${ind2}}`]
390
+ : [`${ind2}for _, ${mscopeVar}k := range input.Keys {`, `${ind2}\t${mscopeVar}.Set(${mscopeVar}k, input.Vals[${mscopeVar}k])`, `${ind2}}`]),
391
+ `${ind2}${mscopeVar}.Set(${asKey}, ${elVar})`,
392
+ ];
393
+ };
394
+ const guardLines = (ind2) => {
395
+ if (guardIR === undefined)
396
+ return [];
397
+ const g = `g_${s}`;
398
+ return [
399
+ `${ind2}${g}, ${g}Err := ${emitExpr(guardIR, isScopeFree(guardIR) ? "slEmpty()" : mscopeVar)}`,
400
+ `${ind2}if ${g}Err != nil {`,
401
+ `${ind2}\treturn nil, ${g}Err`,
402
+ `${ind2}}`,
403
+ `${ind2}if ${g} != true {`,
404
+ `${ind2}\tcontinue`,
405
+ `${ind2}}`,
406
+ ];
407
+ };
408
+ const portLines = (ind2, portsVar) => {
409
+ const out = [`${ind2}${portsVar} := ${PKG}.NewObj()`];
410
+ for (const p of op.ports) {
411
+ const pv = `pv_${s}_${p.name.replace(/[^A-Za-z0-9_]/g, "_")}`;
412
+ out.push(`${ind2}${pv} := ${seqExpr(m.ports[p.name], mapCtx)}`);
413
+ out.push(`${ind2}if ${pv}.Err != nil {`, `${ind2}\treturn nil, ${pv}.Err`, `${ind2}}`);
414
+ out.push(`${ind2}${portsVar}.Set(${JSON.stringify(p.name)}, ${pv}.Val)`);
415
+ }
416
+ return out;
417
+ };
418
+ if (op.batched) {
419
+ const items = `items_${s}`;
420
+ lines.push(`${ind}${items} := make([]${PKG}.Value, 0, len(${overVar}))`);
421
+ lines.push(`${ind}for ${elVar}Idx, ${elVar} := range ${overVar} {`);
422
+ lines.push(...mscopeLines(`${ind}\t`), ...guardLines(`${ind}\t`), ...portLines(`${ind}\t`, `ports_${s}`));
423
+ lines.push(`${ind}\t${items} = append(${items}, ports_${s})`, `${ind}\t${keptVar} = append(${keptVar}, ${elVar}Idx)`, `${ind}}`);
424
+ lines.push(`${ind}var ${collected} []${PKG}.Value`);
425
+ lines.push(`${ind}if len(${items}) == 0 {`, `${ind}\t${collected} = []${PKG}.Value{}`, `${ind}} else {`);
426
+ const bp = `bp_${s}`;
427
+ lines.push(`${ind}\t${bp} := ${PKG}.NewObj()`, `${ind}\t${bp}.Set("items", ${items})`);
428
+ const oc = `mo_${s}`;
429
+ lines.push(`${ind}\t${oc}, ${oc}Resolved := ${PKG}.ExecHandler(handlers, ${JSON.stringify(id)}, ${comp}, ${bp}, nil)`);
430
+ lines.push(`${ind}\tif !${oc}Resolved {`, `${ind}\t\treturn nil, ${PKG}.NewBehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")`, `${ind}\t}`);
431
+ if (contFlag) {
432
+ lines.push(`${ind}\tif ${oc}.IsError {`, `${ind}\t\t${skipVar} = true`, `${ind}\t} else {`);
433
+ lines.push(...batchOk(oc, collected, items, id, s, `${ind}\t\t`));
434
+ lines.push(`${ind}\t}`);
435
+ }
436
+ else {
437
+ lines.push(...seqPolicyThrow(oc, id, meta, `${ind}\t`));
438
+ lines.push(...batchOk(oc, collected, items, id, s, `${ind}\t`));
439
+ }
440
+ lines.push(`${ind}}`);
441
+ }
442
+ else {
443
+ lines.push(`${ind}${collected} := make([]${PKG}.Value, 0, len(${overVar}))`);
444
+ lines.push(`${ind}for ${elVar}Idx, ${elVar} := range ${overVar} {`);
445
+ lines.push(...mscopeLines(`${ind}\t`), ...guardLines(`${ind}\t`), ...portLines(`${ind}\t`, `ports_${s}`));
446
+ const oc = `mo_${s}`;
447
+ lines.push(`${ind}\t${oc}, ${oc}Resolved := ${PKG}.ExecHandler(handlers, ${JSON.stringify(id)}, ${comp}, ports_${s}, ${elVar})`);
448
+ lines.push(`${ind}\tif !${oc}Resolved {`, `${ind}\t\treturn nil, ${PKG}.NewBehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")`, `${ind}\t}`);
449
+ if (contFlag) {
450
+ lines.push(`${ind}\tif ${oc}.IsError {`, `${ind}\t\t${skipVar} = true`, `${ind}\t\tbreak`, `${ind}\t}`);
451
+ }
452
+ else {
453
+ lines.push(...seqPolicyThrow(oc, id, meta, `${ind}\t`));
454
+ }
455
+ lines.push(`${ind}\t${collected} = append(${collected}, ${oc}.Ok)`, `${ind}\t${keptVar} = append(${keptVar}, ${elVar}Idx)`, `${ind}}`);
456
+ }
457
+ // finish(into zip-attach or collected)— continue はエラー時 skip して未生成。
458
+ const finish = [];
459
+ const fInd = contFlag ? `${ind}\t` : ind;
460
+ if (m.into === undefined) {
461
+ store(collected, finish, fInd);
462
+ }
463
+ else {
464
+ const aug = `aug_${s}`;
465
+ finish.push(`${fInd}${aug} := make([]${PKG}.Value, 0, len(${overVar}))`);
466
+ finish.push(`${fInd}mk_${s} := 0`);
467
+ finish.push(`${fInd}for mj_${s}, oel_${s} := range ${overVar} {`);
468
+ finish.push(`${fInd}\tif mk_${s} < len(${keptVar}) && ${keptVar}[mk_${s}] == mj_${s} {`);
469
+ finish.push(`${fInd}\t\toelObj_${s}, isObj_${s} := oel_${s}.(*${PKG}.Obj)`);
470
+ finish.push(`${fInd}\t\tif !isObj_${s} {`, `${fInd}\t\t\treturn nil, ${PKG}.NewBehaviorFailure("MAP_INTO_ELEMENT_NOT_OBJECT", fmt.Sprintf("map '${id}': 'into' requires object elements (element %d is not an object)", mj_${s}))`, `${fInd}\t\t}`);
471
+ finish.push(`${fInd}\t\tcp_${s} := ${PKG}.NewObj()`);
472
+ finish.push(`${fInd}\t\tfor _, fk_${s} := range oelObj_${s}.Keys {`, `${fInd}\t\t\tcp_${s}.Set(fk_${s}, oelObj_${s}.Vals[fk_${s}])`, `${fInd}\t\t}`);
473
+ finish.push(`${fInd}\t\tcp_${s}.Set(${JSON.stringify(m.into)}, ${collected}[mk_${s}])`);
474
+ finish.push(`${fInd}\t\t${aug} = append(${aug}, cp_${s})`, `${fInd}\t\tmk_${s}++`);
475
+ finish.push(`${fInd}\t} else {`, `${fInd}\t\t${aug} = append(${aug}, oel_${s})`, `${fInd}\t}`);
476
+ finish.push(`${fInd}}`);
477
+ store(aug, finish, fInd);
478
+ }
479
+ if (contFlag) {
480
+ lines.push(`${ind}if !${skipVar} {`);
481
+ lines.push(...finish);
482
+ lines.push(`${ind}}`);
483
+ }
484
+ else {
485
+ lines.push(...finish);
486
+ }
487
+ }
488
+ /** batched handler の ok を collected へ(長さ整合検査つき)。 */
489
+ function batchOk(oc, collected, items, id, s, ind) {
490
+ return [
491
+ `${ind}r_batch_${s}, isArr_${s} := ${oc}.Ok.([]${PKG}.Value)`,
492
+ `${ind}if !isArr_${s} || len(r_batch_${s}) != len(${items}) {`,
493
+ `${ind}\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})))`,
494
+ `${ind}}`,
495
+ `${ind}${collected} = r_batch_${s}`,
496
+ ];
497
+ }
498
+ /**
499
+ * emitSequentialGoFn — 逐次 plan の component を素の文列として綴じる(bc#75 の核)。
500
+ * RunPlan / OpSpec テーブル / baseScope snapshot / exec closure / per-op ID 探索は無い。
501
+ */
502
+ function emitSequentialGoFn(plan, order) {
503
+ const metas = analyzeSequentialOps(plan.opsLiteral, order);
504
+ const pos = new Array(plan.opsLiteral.length);
505
+ order.forEach((opIdx, k) => (pos[opIdx] = k));
506
+ const idToIndex = new Map();
507
+ plan.opsLiteral.forEach((o, i) => idToIndex.set(o.id, i));
508
+ const needScope = componentNeedsScope(plan);
509
+ const ids = plan.opsLiteral.map((o) => o.id);
510
+ const local = allocLocals("r_", ids);
511
+ const okFlag = allocLocals("ok_", ids);
512
+ const ctx = {
513
+ pos,
514
+ idToIndex,
515
+ metas,
516
+ local,
517
+ okFlag,
518
+ scopeVar: needScope ? "scope" : null,
519
+ currentPos: 0,
520
+ atOutput: false,
521
+ };
522
+ const lines = [];
523
+ lines.push(`func run_${sanitize(plan.name)}(handlers ${PKG}.ComponentExec, input *${PKG}.Obj) (${PKG}.Value, error) {`);
524
+ lines.push(`\tif input == nil {`, `\t\tinput = ${PKG}.NewObj()`, `\t}`);
525
+ if (needScope) {
526
+ // 進行形 scope: input のコピー(bind 側で input は private コピー済み)へノード結果を書き足す。
527
+ lines.push(`\tscope := ${PKG}.NewObj()`);
528
+ lines.push(`\tfor _, k := range input.Keys {`, `\t\tscope.Set(k, input.Vals[k])`, `\t}`);
529
+ }
530
+ let unreachable = false;
531
+ for (const k of order.keys()) {
532
+ const opIdx = order[k];
533
+ ctx.currentPos = k;
534
+ if (unreachable)
535
+ break;
536
+ emitSeqOp(plan.ops[opIdx], plan, ctx, lines);
537
+ if (typeof metas[opIdx].policy === "object")
538
+ unreachable = true;
539
+ }
540
+ if (!unreachable) {
541
+ ctx.atOutput = true;
542
+ for (const i of order) {
543
+ const meta = metas[i];
544
+ if (!(meta.canSkip || meta.alwaysSkip))
545
+ continue;
546
+ const id = plan.opsLiteral[i].id;
547
+ lines.push(`\tif !${okFlag.get(id)} {`);
548
+ lines.push(`\t\t${local.get(id)} = ${unproducedGo(plan.opsLiteral[i].relationKind)}`);
549
+ if (needScope)
550
+ lines.push(`\t\tscope.Set(${JSON.stringify(id)}, ${local.get(id)})`);
551
+ lines.push(`\t}`);
552
+ }
553
+ const outNode = plan.component.output;
554
+ lines.push(`\toutVE := ${seqExpr(outNode, ctx)}`);
555
+ lines.push(`\tif outVE.Err != nil {`, `\t\treturn nil, outVE.Err`, `\t}`);
556
+ lines.push(`\treturn outVE.Val, nil`);
557
+ }
558
+ lines.push(`}`);
559
+ return lines.join("\n");
560
+ }
561
+ // ══════════════════════════════════════════════════════════════════════════════════
562
+ // RunPlan 経路(実並行 plan のみ — bounded 並行の意味論 SSoT を保持)
563
+ // ══════════════════════════════════════════════════════════════════════════════════
127
564
  /** ops リテラル(RunPlan に渡す []OpSpec の Go リテラル)を綴じる。 */
128
565
  function emitOpsLiteral(plan) {
129
566
  const rows = plan.opsLiteral.map((o) => {
@@ -143,7 +580,6 @@ function emitExecArm(op) {
143
580
  if (op.kind === "map")
144
581
  return emitMapArm(op);
145
582
  if (op.kind === "cond") {
146
- // cond は純 Expression。scope を渡して 1 式評価(handler は呼ばない)。式は (Value,error)。
147
583
  return `\t\tif i == ${op.index} {
148
584
  \t\t\tv, err := ${op.expr}
149
585
  \t\t\tif err != nil {
@@ -152,7 +588,6 @@ function emitExecArm(op) {
152
588
  \t\t\treturn ${PKG}.OkOutcome(v)
153
589
  \t\t}`;
154
590
  }
155
- // componentRef: 各 port を名前付き native/primitive 呼びで評価 → handler 注入呼び。
156
591
  const portEvals = op.ports
157
592
  .map((p) => `\t\t\t${goPortVar(p.name)}, err := ${p.expr}
158
593
  \t\t\tif err != nil {
@@ -170,11 +605,11 @@ function emitExecArm(op) {
170
605
  \t\t\treturn outcome
171
606
  \t\t}`;
172
607
  }
173
- /** port ローカル変数名(Go 識別子として安全・一意)。宣言順に p0,p1,… を index で衝突回避。 */
608
+ /** port ローカル変数名(Go 識別子として安全)。 */
174
609
  function goPortVar(name) {
175
610
  return `pv_${name.replace(/[^A-Za-z0-9_]/g, "_")}`;
176
611
  }
177
- /** map arm の要素 ports 構築(要素 scope `mscope()` で各 port を native/primitive 評価)。 */
612
+ /** map arm の要素 ports 構築(RunPlan 経路)。 */
178
613
  function emitMapPortEvals(op) {
179
614
  if (op.ports.length === 0)
180
615
  return `\t\t\t\tports := ${PKG}.NewObj()`;
@@ -187,13 +622,7 @@ function emitMapPortEvals(op) {
187
622
  .join("\n");
188
623
  return `\t\t\t\tports := ${PKG}.NewObj()\n${lines}`;
189
624
  }
190
- /**
191
- * emitMapArm — map ノードの直線展開(要素反復・guard skip・per-element/batched handler・into
192
- * zip-attach)。RunBehavior の map 分岐(behavior.go)を生成 Go へ直線展開する。op-log 等価のため
193
- * ExecHandler は RunBehavior と同じ ctx(非 batched は bound=要素値、batched は nil)・同じ ports で
194
- * 呼ぶ。guard は PrimCond(strict-bool SSoT)、into は inline zip-attach(MAP_INTO_ELEMENT_NOT_OBJECT
195
- * fail-closed で Failure code 一致)。要素反復は宣言どおり逐次。
196
- */
625
+ /** emitMapArm — map ノードの直線展開(RunPlan 経路)。 */
197
626
  function emitMapArm(op) {
198
627
  const comp = JSON.stringify(op.component);
199
628
  const nodeId = JSON.stringify(op.id);
@@ -293,16 +722,27 @@ ${body}
293
722
  \t\t}`;
294
723
  }
295
724
  /**
296
- * emitComponentFn1 component の直線 Go 関数を綴じる。RunBehavior の実行フロー(results 蓄積 +
297
- * baseScope + RunPlan + skip→unproduced + output 評価)を **生成コードに直線展開**する
298
- * (汎用 exec/nodeKind/evalPorts は無い。各 exec 分岐は index で確定・ハードコード)。
725
+ * emitPlanDrivenGoFn実並行 plan(複数 op stage)の component RunPlan primitive で綴じる。
726
+ * bounded 並行・Skip 伝播・Policy Kind の意味論 SSoT は RunPlan が保持する。per-op exec index
727
+ * 確定した分岐(`for k := range ops` 線形 ID 探索は idx 解決に一度だけ・switch はハードコード)。
299
728
  */
300
- function emitComponentFn(plan) {
729
+ function emitPlanDrivenGoFn(plan) {
730
+ usage.runPlan = true;
731
+ usage.unproduced = true;
301
732
  const arms = plan.ops.map(emitExecArm).join("\n");
302
733
  const planLiteral = plan.component.plan
303
734
  ? emitBehaviorPlanLiteral(plan.component.plan)
304
735
  : `(*${PKG}.ExecutionPlanSpec)(nil)`;
305
736
  const output = emitExpr(plan.component.output, SCOPE);
737
+ // arms は buildComponentPlan 由来の op.expr(emitModule の reset より前に生成)。json/slField/slPart/
738
+ // slConcat の使用は実 emit テキストで確定させる(arms は我々の生成した go 式で、user 由来 string は
739
+ // JObjOf/リテラルの内側にあるため token 出現=実使用で安全)。
740
+ if (/json\.Number\(|json\.RawMessage/.test(arms))
741
+ usage.json = true;
742
+ // slRef/slRefOpt/slConcat/slPart は RunPlan-path helper(usage.slPart 枝で定義)。逐次 slFieldVE
743
+ // (usage.slField 枝・slVE 型必須)ではないので slPart を立てる。
744
+ if (/slRef(Opt)?\(|slConcat\(\[\]slPart\{/.test(arms))
745
+ usage.slPart = true;
306
746
  const relationKinds = plan.opsLiteral.map((o) => (o.relationKind === "connection" ? "connection" : "single"));
307
747
  const rkLiteral = `[]string{${relationKinds.map((r) => JSON.stringify(r)).join(", ")}}`;
308
748
  return `func run_${sanitize(plan.name)}(handlers ${PKG}.ComponentExec, input *${PKG}.Obj) (${PKG}.Value, error) {
@@ -312,7 +752,8 @@ function emitComponentFn(plan) {
312
752
  \t}
313
753
  \tresults := ${PKG}.NewObj()
314
754
  \tvar resultsMu sync.Mutex
315
- \t// baseScope = input params + accumulated ok results (snapshot at call time).
755
+ \t// baseScope = input params + accumulated ok results (snapshot at call time) — REAL
756
+ \t// concurrency needs a deterministic scope snapshot per exec (bc#75: only this path).
316
757
  \tbaseScope := func() *${PKG}.Obj {
317
758
  \t\ts := ${PKG}.NewObj()
318
759
  \t\tfor _, k := range input.Keys {
@@ -326,28 +767,25 @@ function emitComponentFn(plan) {
326
767
  \t\treturn s
327
768
  \t}
328
769
  \trelationKinds := ${rkLiteral}
329
- \t// exec-internal failures (BehaviorFailure/ExprFailure) cannot unwind through the
330
- \t// RunPlan ExecOutcome seam, so each op records its own error and returns a synthetic
331
- \t// aborted ErrOutcome; sequential precedence is restored after RunPlan (bc#23).
332
770
  \texecErrs := make([]error, len(ops))
333
771
  \tabort := func(idx int, err error) ${PKG}.ExecOutcome {
334
772
  \t\texecErrs[idx] = err
335
773
  \t\treturn ${PKG}.ErrOutcome("aborted")
336
774
  \t}
337
- \texec := func(op ${PKG}.OpSpec, _ ${PKG}.Value) ${PKG}.ExecOutcome {
338
- \t\tidx := -1
339
- \t\tfor k := range ops {
340
- \t\t\tif ops[k].ID == op.ID {
341
- \t\t\t\tidx = k
342
- \t\t\t\tbreak
343
- \t\t\t}
344
- \t\t}
775
+ \t// per-op exec は op の index(RunPlan が渡す OpSpec の位置)で確定する switch。opIndex
776
+ \t// ops リテラルと 1:1 なので線形 ID 探索は不要(RunPlan が visit 順に呼ぶ i を使う)。
777
+ \texecByIndex := func(idx int) ${PKG}.ExecOutcome {
345
778
  \t\ti := idx
346
779
  ${arms}
347
- \t\treturn abort(idx, ${PKG}.NewBehaviorFailure("UNKNOWN_NODE_KIND", "op '"+op.ID+"' has no generated exec arm (fail-closed)"))
780
+ \t\treturn abort(idx, ${PKG}.NewBehaviorFailure("UNKNOWN_NODE_KIND", "op has no generated exec arm (fail-closed)"))
781
+ \t}
782
+ \topIndex := make(map[string]int, len(ops))
783
+ \tfor k := range ops {
784
+ \t\topIndex[ops[k].ID] = k
348
785
  \t}
349
786
  \twrappedExec := func(op ${PKG}.OpSpec, bound ${PKG}.Value) ${PKG}.ExecOutcome {
350
- \t\toutcome := exec(op, bound)
787
+ \t\t_ = bound
788
+ \t\toutcome := execByIndex(opIndex[op.ID])
351
789
  \t\tif !outcome.IsError {
352
790
  \t\t\tresultsMu.Lock()
353
791
  \t\t\tresults.Set(op.ID, outcome.Ok)
@@ -363,17 +801,11 @@ ${arms}
363
801
  \tif err != nil {
364
802
  \t\treturn nil, err
365
803
  \t}
366
- \t// Skipped nodes contribute their unproduced representation to the scope.
367
804
  \tfor _, id := range run.Skipped {
368
805
  \t\tif _, present := results.Get(id); present {
369
806
  \t\t\tcontinue
370
807
  \t\t}
371
- \t\tfor k := range ops {
372
- \t\t\tif ops[k].ID == id {
373
- \t\t\t\tresults.Set(id, ${PKG}.UnproducedValue(relationKinds[k]))
374
- \t\t\t\tbreak
375
- \t\t\t}
376
- \t\t}
808
+ \t\tresults.Set(id, ${PKG}.UnproducedValue(relationKinds[opIndex[id]]))
377
809
  \t}
378
810
  \treturn ${output}
379
811
  }`;
@@ -386,130 +818,116 @@ function emitBehaviorPlanLiteral(plan) {
386
818
  const conc = p.concurrency ?? 1;
387
819
  return `&${PKG}.ExecutionPlanSpec{Groups: ${groupsLit}, Concurrency: ${conc}}`;
388
820
  }
389
- /** component 名を Go 関数名に使える形へ(決定的。名前一意は DUPLICATE_COMPONENT で保証)。 */
390
- function sanitize(name) {
391
- return name.replace(/[^A-Za-z0-9_]/g, "_");
392
- }
393
821
  function emitComponent(plan, _ctx) {
394
- return emitComponentFn(plan);
822
+ const order = sequentialOrder(plan.component);
823
+ if (order !== null)
824
+ return emitSequentialGoFn(plan, order);
825
+ return emitPlanDrivenGoFn(plan);
395
826
  }
396
827
  function emitModule(plans, ctx) {
397
828
  const { fingerprint, specVersions, componentNames, runtimeImport } = ctx;
398
829
  const names = componentNames.map((n) => JSON.stringify(n)).join(", ");
399
- const irLiteral = emitDocLiteral(ctx.ir, "go");
830
+ // buildComponentPlan は逐次経路が使わない op.expr を先に emit するため usage を過大に立てる。
831
+ // 実 emit 直前で reset し、emitComponent 内の呼び出しだけが import/helper 要否を立てるようにする。
832
+ resetUsage();
400
833
  const fns = plans.map((p) => emitComponent(p, ctx)).join("\n\n");
834
+ if (forceRunPlanHelpers) {
835
+ // typed 層(go-typed)の struct-native runner は RunPlan-path helper(slRef/slRefOpt/slConcat/slPart/
836
+ // slVal)と sync を参照する。それらは usage.slPart 枝(slPart type + slConcat + slRef/slRefOpt/
837
+ // slRefWalk)に集約されている。逐次 slVE 系(slField=slFieldVE 等)は typed runner が使わないので
838
+ // 立てない(誤って slFieldVE を emit すると slVE 型が要る)。
839
+ usage.slPart = true;
840
+ usage.runPlan = true; // sync import
841
+ }
842
+ const usesJson = usage.json;
401
843
  const dispatchArms = plans
402
844
  .map((p) => `\tif name == ${JSON.stringify(p.name)} {\n\t\treturn run_${sanitize(p.name)}(handlers, input)\n\t}`)
403
845
  .join("\n");
404
- return `// GENERATED by behavior-contracts common generator (bc#38, straight-line) — DO NOT EDIT.
405
- // Input: portable component-graph IR only (scp-ir-architecture.md §5/§7.4).
406
- // This is the STRAIGHT-LINE codegen endpoint (typed-codegen.md §4.1 / bc#38 A2): each
407
- // component is emitted as a native Go function that drives the plan via the RunPlan
408
- // primitive (#36) and injected handlers. Simple ports are NATIVE-IZED (Phase B
409
- // de-interpretation): ref/refOpt -> native scope/field walk (slRef/slRefOpt), concat ->
410
- // native string concatenation (slConcat) NO EvaluateExpression / Prim* call for those.
411
- // Only overflow/short-circuit-prone ops (add/sub/mul/div/mod/and/or/cond/coalesce/…) route
412
- // through the A0 primitive SSoT to preserve fail-closed semantics. It does NOT go through
413
- // RunBehavior's tree-walk (no nodeKind dispatch / no evalPorts loop / no generic exec).
414
- // Handlers are ALWAYS injected at the boundary; they are never generated.
415
- // irFingerprint: ${fingerprint}
416
-
417
- package behaviors
418
-
419
- import (
420
- \t"encoding/json"
421
- \t"fmt"
422
- \t"strings"
423
- \t"sync"
424
-
425
- \t${PKG} ${JSON.stringify(runtimeImport)}
426
- )
427
-
428
- // ExpectedSpecVersions are the spec versions baked at generation time
429
- // (fail-closed constant comparison at init).
430
- var ExpectedSpecVersions = map[string]int64{"behavior": ${specVersions.behavior}, "expression": ${specVersions.expression}, "plan": ${specVersions.plan}}
431
-
432
- // IRFingerprint is the FNV-1a 64 fingerprint of the source portable IR
433
- // (CanonicalJSON discipline, #208).
434
- const IRFingerprint = ${JSON.stringify(fingerprint)}
435
-
436
- // ComponentNames are the components exposed by Bind(), in IR declaration order.
437
- var ComponentNames = []string{${names}}
438
-
439
- // IR is the portable component-graph IR, embedded for the init-time fingerprint
440
- // self-check only (the straight-line functions do NOT interpret it at run time).
441
- var IR ${PKG}.JNode = ${irLiteral}
442
-
443
- // init — load-time fail-closed checks (#208 prepared-artifact discipline).
444
- func init() {
445
- \tfor k, want := range ExpectedSpecVersions {
446
- \t\tif got := ${PKG}.SpecVersions[k]; got != want {
447
- \t\t\tpanic(fmt.Sprintf("behavior-contracts generated module: spec-version skew for '%s' (generated=%d, runtime=%d) — regenerate against this runtime (fail-closed)", k, want, got))
448
- \t\t}
449
- \t}
450
- \tgot, err := ${PKG}.FingerprintComponentGraph(IR)
451
- \tif err != nil {
452
- \t\tpanic(fmt.Sprintf("behavior-contracts generated module: fingerprint recompute failed: %v (fail-closed)", err))
453
- \t}
454
- \tif got != IRFingerprint {
455
- \t\tpanic(fmt.Sprintf("behavior-contracts generated module: IR fingerprint mismatch (baked=%s, embedded literal=%s) — the generated code was modified or corrupted (fail-closed)", IRFingerprint, got))
456
- \t}
457
- }
458
-
459
- // ── native simple-expression helpers (Phase B de-interpretation, bc#38) ────────
460
- // These are NATIVE Go — they never call EvaluateExpression / Prim*. They reproduce
461
- // expr.go's ref/refOpt/concat semantics (same Failure codes) so the straight-line
462
- // path is observationally equivalent to RunBehavior for simple ports while removing
463
- // the expression-tree interpretation.
464
-
465
- // slPart is a self-evaluating concat part (recursively native / primitive).
466
- type slPart = func(scope *${PKG}.Obj) (${PKG}.Value, error)
467
-
468
- // slVal wraps a native Go literal (string/bool/null) as a (Value, error) result —
469
- // bare scalars carry no INVALID_LITERAL/range semantics, so no evaluate is needed.
470
- func slVal(v ${PKG}.Value) (${PKG}.Value, error) { return v, nil }
471
-
472
- // slRef walks a non-empty string path over scope (native; expr.go ref semantics).
473
- func slRef(path []string, scope *${PKG}.Obj) (${PKG}.Value, error) {
474
- \treturn slRefWalk("ref", path, scope)
846
+ // 生成 helper(構造的トラッキングで必要なものだけ emit)。
847
+ const helpers = [];
848
+ helpers.push(`// slVal wraps a native Go literal as a (Value, error) result (dynamic/RunPlan path).
849
+ func slVal(v ${PKG}.Value) (${PKG}.Value, error) { return v, nil }`);
850
+ if (usage.slEmpty)
851
+ helpers.push(`// slEmpty is the empty scope constant passed to scope-free primitive exprs (bc#75:
852
+ // literal eval never reads scope, so no snapshot is materialized).
853
+ func slEmpty() *${PKG}.Obj { return ${PKG}.NewObj() }`);
854
+ if (usage.slBind)
855
+ helpers.push(`// slVE packs a single (Value, error) result so statically-wired inline exprs compose as
856
+ // one expression (bc#75: Go only spreads a multi-return call as the sole argument).
857
+ type slVE struct {
858
+ \tVal ${PKG}.Value
859
+ \tErr error
475
860
  }
476
-
477
- // slRefOpt is the null-safe variant: a null intermediate propagates null.
478
- func slRefOpt(path []string, scope *${PKG}.Obj) (${PKG}.Value, error) {
479
- \treturn slRefWalk("refOpt", path, scope)
861
+ // slVE2 folds a primitive (Value, error) return into an slVE (dynamic residual exprs).
862
+ func slVE2(v ${PKG}.Value, err error) slVE { return slVE{Val: v, Err: err} }
863
+ // slValVE wraps a native literal (string/bool/null) — no evaluate needed.
864
+ func slValVE(v ${PKG}.Value) slVE { return slVE{Val: v} }
865
+ // slOkVE wraps an already-resolved local (statically-wired ref read: no scope snapshot).
866
+ func slOkVE(v ${PKG}.Value) slVE { return slVE{Val: v} }
867
+ // slBindVE reads a scope-bound head directly (UNKNOWN_BINDING matches expr.go).
868
+ func slBindVE(scope *${PKG}.Obj, head string) slVE {
869
+ \tv, ok := scope.Get(head)
870
+ \tif !ok {
871
+ \t\treturn slVE{Err: ${PKG}.NewExprFailure("UNKNOWN_BINDING", "unknown binding: "+head)}
872
+ \t}
873
+ \treturn slVE{Val: v}
480
874
  }
481
-
482
- func slRefWalk(op string, path []string, scope *${PKG}.Obj) (${PKG}.Value, error) {
483
- \tif len(path) == 0 {
484
- \t\treturn nil, ${PKG}.NewExprFailure("INVALID_NODE", op+" expects a non-empty string path")
875
+ // slHeadOrVE — a skippable node's ref head: its local if produced, else the same-named input
876
+ // binding (scope-composition fall-through), else UNKNOWN_BINDING (expr.go semantics).
877
+ func slHeadOrVE(ok bool, local ${PKG}.Value, scope *${PKG}.Obj, head string) slVE {
878
+ \tif ok {
879
+ \t\treturn slVE{Val: local}
485
880
  \t}
486
- \thead := path[0]
487
- \tcur, ok := scope.Get(head)
488
- \tif !ok {
489
- \t\treturn nil, ${PKG}.NewExprFailure("UNKNOWN_BINDING", "unknown binding: "+head)
881
+ \treturn slBindVE(scope, head)
882
+ }`);
883
+ if (usage.slField)
884
+ helpers.push(`// slFieldVE walks a static field path over an already-resolved head (bc#75: the head is a
885
+ // local read, not a scope walk). Reproduces expr.go ref/refOpt field semantics.
886
+ func slFieldVE(op string, head slVE, path []string) slVE {
887
+ \tif head.Err != nil {
888
+ \t\treturn head
490
889
  \t}
491
- \tfor _, seg := range path[1:] {
890
+ \tcur := head.Val
891
+ \tfor _, seg := range path {
492
892
  \t\tif cur == nil {
493
893
  \t\t\tif op == "refOpt" {
494
- \t\t\t\treturn nil, nil
894
+ \t\t\t\treturn slVE{Val: nil}
495
895
  \t\t\t}
496
- \t\t\treturn nil, ${PKG}.NewExprFailure("NULL_REF", "null intermediate at ."+seg+" (use ?.)")
896
+ \t\t\treturn slVE{Err: ${PKG}.NewExprFailure("NULL_REF", "null intermediate at ."+seg+" (use ?.)")}
497
897
  \t\t}
498
898
  \t\to, ok := cur.(*${PKG}.Obj)
499
899
  \t\tif !ok {
500
- \t\t\treturn nil, ${PKG}.NewExprFailure("TYPE_MISMATCH", "cannot access ."+seg+" on "+${PKG}.TypeName(cur))
900
+ \t\t\treturn slVE{Err: ${PKG}.NewExprFailure("TYPE_MISMATCH", "cannot access ."+seg+" on "+${PKG}.TypeName(cur))}
501
901
  \t\t}
502
902
  \t\tnext, present := o.Get(seg)
503
903
  \t\tif !present {
504
- \t\t\treturn nil, ${PKG}.NewExprFailure("MISSING_PROP", "missing property ."+seg)
904
+ \t\t\treturn slVE{Err: ${PKG}.NewExprFailure("MISSING_PROP", "missing property ."+seg)}
505
905
  \t\t}
506
906
  \t\tcur = next
507
907
  \t}
508
- \treturn cur, nil
509
- }
510
-
511
- // slConcat concatenates n>=2 string parts natively (expr.go concat semantics:
512
- // strings only, no implicit toString; arity < 2 is invalid IR).
908
+ \treturn slVE{Val: cur}
909
+ }`);
910
+ if (usage.slCat)
911
+ helpers.push(`// slCatVE concatenates already-evaluated string parts natively (bc#75: no closures, no
912
+ // slPart func values). Same "all-eval then string check" order + TYPE_MISMATCH as expr.go.
913
+ func slCatVE(parts ...slVE) slVE {
914
+ \tvar b strings.Builder
915
+ \tfor _, p := range parts {
916
+ \t\tif p.Err != nil {
917
+ \t\t\treturn p
918
+ \t\t}
919
+ \t\ts, ok := p.Val.(string)
920
+ \t\tif !ok {
921
+ \t\t\treturn slVE{Err: ${PKG}.NewExprFailure("TYPE_MISMATCH", "concat: strings only (got "+${PKG}.TypeName(p.Val)+"; no implicit toString)")}
922
+ \t\t}
923
+ \t\tb.WriteString(s)
924
+ \t}
925
+ \treturn slVE{Val: b.String()}
926
+ }`);
927
+ if (usage.slPart)
928
+ helpers.push(`// slPart is a self-evaluating concat part (RunPlan path only — real-concurrency plans).
929
+ type slPart = func(scope *${PKG}.Obj) (${PKG}.Value, error)
930
+ // slConcat concatenates n>=2 string parts (RunPlan path); arity<2 is invalid IR.
513
931
  func slConcat(parts []slPart, scope *${PKG}.Obj) (${PKG}.Value, error) {
514
932
  \tif len(parts) < 2 {
515
933
  \t\treturn nil, ${PKG}.NewExprFailure("INVALID_NODE", fmt.Sprintf("concat expects >= 2 args, got %d", len(parts)))
@@ -528,15 +946,98 @@ func slConcat(parts []slPart, scope *${PKG}.Obj) (${PKG}.Value, error) {
528
946
  \t}
529
947
  \treturn b.String(), nil
530
948
  }
949
+ // slRef/slRefOpt: RunPlan-path native scope/field walk (expr.go ref semantics).
950
+ func slRef(path []string, scope *${PKG}.Obj) (${PKG}.Value, error) { return slRefWalk("ref", path, scope) }
951
+ func slRefOpt(path []string, scope *${PKG}.Obj) (${PKG}.Value, error) { return slRefWalk("refOpt", path, scope) }
952
+ func slRefWalk(op string, path []string, scope *${PKG}.Obj) (${PKG}.Value, error) {
953
+ \tif len(path) == 0 {
954
+ \t\treturn nil, ${PKG}.NewExprFailure("INVALID_NODE", op+" expects a non-empty string path")
955
+ \t}
956
+ \tcur, ok := scope.Get(path[0])
957
+ \tif !ok {
958
+ \t\treturn nil, ${PKG}.NewExprFailure("UNKNOWN_BINDING", "unknown binding: "+path[0])
959
+ \t}
960
+ \tfor _, seg := range path[1:] {
961
+ \t\tif cur == nil {
962
+ \t\t\tif op == "refOpt" {
963
+ \t\t\t\treturn nil, nil
964
+ \t\t\t}
965
+ \t\t\treturn nil, ${PKG}.NewExprFailure("NULL_REF", "null intermediate at ."+seg+" (use ?.)")
966
+ \t\t}
967
+ \t\to, isObj := cur.(*${PKG}.Obj)
968
+ \t\tif !isObj {
969
+ \t\t\treturn nil, ${PKG}.NewExprFailure("TYPE_MISMATCH", "cannot access ."+seg+" on "+${PKG}.TypeName(cur))
970
+ \t\t}
971
+ \t\tnext, present := o.Get(seg)
972
+ \t\tif !present {
973
+ \t\t\treturn nil, ${PKG}.NewExprFailure("MISSING_PROP", "missing property ."+seg)
974
+ \t\t}
975
+ \t\tcur = next
976
+ \t}
977
+ \treturn cur, nil
978
+ }`);
979
+ // 依存 import の構造的判定。
980
+ const imports = [];
981
+ if (usesJson)
982
+ imports.push(`\t"encoding/json"`);
983
+ imports.push(`\t"fmt"`);
984
+ if (usage.slCat || usage.slPart)
985
+ imports.push(`\t"strings"`);
986
+ if (usage.runPlan)
987
+ imports.push(`\t"sync"`);
988
+ imports.push(``);
989
+ imports.push(`\t${PKG} ${JSON.stringify(runtimeImport)}`);
990
+ return `// GENERATED by behavior-contracts common generator (bc#38/#75, straight-line) — DO NOT EDIT.
991
+ // Input: portable component-graph IR only (scp-ir-architecture.md §5/§7.4).
992
+ // This is the STRAIGHT-LINE codegen endpoint (typed-codegen.md §4.1): each component is
993
+ // emitted as STATIC native Go (bc#75). Strictly-sequential plans compile to plain
994
+ // statements in dependency order — no RunPlan, no []OpSpec table, no per-op ID search, no
995
+ // baseScope() per-expression snapshots. Statically-wired refs read locals directly; static
996
+ // concat is a flat slCat(...) over already-evaluated values. Only plans with REAL bounded
997
+ // concurrency (multi-op stages) drive the RunPlan primitive (semantics SSoT), and there the
998
+ // per-op exec is index-resolved (no linear ID scan). Fail-closed operator semantics
999
+ // (overflow arithmetic, strict-bool short-circuit, obj/arr construction, checked literals)
1000
+ // still route through the A0 Prim* primitives (#36) — the semantics SSoT stays single-
1001
+ // sourced. Handlers are ALWAYS injected at the boundary; they are never generated.
1002
+ // The portable IR is NOT embedded: this module carries only IRFingerprint + ComponentNames.
1003
+ // irFingerprint: ${fingerprint}
1004
+
1005
+ package behaviors
1006
+
1007
+ import (
1008
+ ${imports.join("\n")}
1009
+ )
1010
+
1011
+ // ExpectedSpecVersions are the spec versions baked at generation time
1012
+ // (fail-closed constant comparison at init).
1013
+ var ExpectedSpecVersions = map[string]int64{"behavior": ${specVersions.behavior}, "expression": ${specVersions.expression}, "plan": ${specVersions.plan}}
1014
+
1015
+ // IRFingerprint is the FNV-1a 64 fingerprint of the source portable IR
1016
+ // (CanonicalJSON discipline, #208), computed at GENERATION time.
1017
+ const IRFingerprint = ${JSON.stringify(fingerprint)}
1018
+
1019
+ // ComponentNames are the components exposed by Bind(), in IR declaration order.
1020
+ var ComponentNames = []string{${names}}
1021
+
1022
+ // init — load-time fail-closed checks (#208 prepared-artifact discipline).
1023
+ func init() {
1024
+ \tfor k, want := range ExpectedSpecVersions {
1025
+ \t\tif got := ${PKG}.SpecVersions[k]; got != want {
1026
+ \t\t\tpanic(fmt.Sprintf("behavior-contracts generated module: spec-version skew for '%s' (generated=%d, runtime=%d) — regenerate against this runtime (fail-closed)", k, want, got))
1027
+ \t\t}
1028
+ \t}
1029
+ }
1030
+
1031
+ // ── native static-wiring helpers (bc#75) — no scope snapshots, no closures ─────
1032
+ // These are NATIVE Go — they never call EvaluateExpression / Prim*. They reproduce
1033
+ // expr.go's ref/refOpt/concat semantics (same Failure codes) for the statically-wired
1034
+ // inline path while removing the expression-tree interpretation.
1035
+ ${helpers.join("\n\n")}
531
1036
 
532
1037
  // ── straight-line component functions (no RunBehavior tree-walk) ───────────────
533
1038
  ${fns}
534
1039
 
535
- // Bind — inject handlers (boundary injection; catalog name implementation) and get
536
- // one executor per component. Each executor runs the straight-line function for that
537
- // component (plan via RunPlan primitive + per-node native/primitive port eval +
538
- // injected handler) — NOT the RunBehavior tree-walk. Observationally equivalent to the
539
- // RunBehavior path: same values, same emitted op sequence, same Failure code.
1040
+ // Bind — inject handlers (boundary injection) and get one executor per component.
540
1041
  func Bind(handlers ${PKG}.ComponentExec) map[string]func(input *${PKG}.Obj) (${PKG}.Value, error) {
541
1042
  \tbound := make(map[string]func(input *${PKG}.Obj) (${PKG}.Value, error), len(ComponentNames))
542
1043
  \tfor _, name := range ComponentNames {
@@ -559,12 +1060,12 @@ const dialect = {
559
1060
  mapScopeVar: MAP_SCOPE,
560
1061
  emitComponent,
561
1062
  emitModule,
1063
+ beginModule() {
1064
+ resetUsage();
1065
+ },
562
1066
  };
563
1067
  /**
564
- * go-typed(bc#47)が struct-native exec を綴じるために再利用する内部 emit ヘルパ。straight-line の
565
- * plan/handler-dispatch SSoT(RunPlan + ExecHandler + native/primitive port eval)をそのまま使い、
566
- * 「結果を Value scope でなく struct scope に持つ」差分だけを typed 側で足すための seam。runtime 語彙
567
- * (PKG)と ops/plan リテラル・式 emit を共有する(意味論の二重実装を避ける)。
1068
+ * go-typed(bc#47)が struct-native exec を綴じるために再利用する内部 emit ヘルパ。
568
1069
  */
569
1070
  export const goStraightlineInternals = {
570
1071
  PKG,
@@ -578,10 +1079,6 @@ export const goStraightlineInternals = {
578
1079
  function emit(ctx) {
579
1080
  return emitStraightlineModule(ctx, dialect, SCOPE);
580
1081
  }
581
- /**
582
- * goStraightlineEmitter — 言語識別子 `"go-straightline"` の emitter plugin。
583
- * 既存の `go`(リテラル焼き込み・endpoint 3-literal)と併存する additive な第2モード。
584
- */
585
1082
  export const goStraightlineEmitter = {
586
1083
  language: "go-straightline",
587
1084
  fileExtension: ".go",
@@ -589,4 +1086,5 @@ export const goStraightlineEmitter = {
589
1086
  filenameHint: "behaviors.straightline.generated.go",
590
1087
  emit,
591
1088
  };
1089
+ void buildComponentPlan;
592
1090
  //# sourceMappingURL=emit-straightline-go.js.map