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.
- package/dist/generator/emit-straightline-go.d.ts +27 -46
- package/dist/generator/emit-straightline-go.d.ts.map +1 -1
- package/dist/generator/emit-straightline-go.js +688 -190
- package/dist/generator/emit-straightline-go.js.map +1 -1
- package/dist/generator/emit-straightline-php.d.ts +1 -1
- package/dist/generator/emit-straightline-php.d.ts.map +1 -1
- package/dist/generator/emit-straightline-php.js +451 -20
- package/dist/generator/emit-straightline-php.js.map +1 -1
- package/dist/generator/emit-straightline-python.d.ts.map +1 -1
- package/dist/generator/emit-straightline-python.js +426 -22
- package/dist/generator/emit-straightline-python.js.map +1 -1
- package/dist/generator/emit-straightline-rust.d.ts +5 -0
- package/dist/generator/emit-straightline-rust.d.ts.map +1 -1
- package/dist/generator/emit-straightline-rust.js +597 -49
- package/dist/generator/emit-straightline-rust.js.map +1 -1
- package/dist/generator/emit-straightline-typed-go.d.ts.map +1 -1
- package/dist/generator/emit-straightline-typed-go.js +50 -18
- package/dist/generator/emit-straightline-typed-go.js.map +1 -1
- package/dist/generator/emit-straightline-typed-rust.d.ts.map +1 -1
- package/dist/generator/emit-straightline-typed-rust.js +66 -23
- package/dist/generator/emit-straightline-typed-rust.js.map +1 -1
- package/dist/generator/emit-straightline-typescript.d.ts +26 -15
- package/dist/generator/emit-straightline-typescript.d.ts.map +1 -1
- package/dist/generator/emit-straightline-typescript.js +703 -147
- package/dist/generator/emit-straightline-typescript.js.map +1 -1
- package/dist/generator/literal.d.ts +3 -1
- package/dist/generator/literal.d.ts.map +1 -1
- package/dist/generator/literal.js +13 -4
- package/dist/generator/literal.js.map +1 -1
- package/dist/generator/straightline.d.ts +81 -0
- package/dist/generator/straightline.d.ts.map +1 -1
- package/dist/generator/straightline.js +176 -0
- package/dist/generator/straightline.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { emitDocLiteral, emitDocLiteralCompact } from "./literal.js";
|
|
2
|
-
import { emitStraightlineModule, } from "./straightline.js";
|
|
2
|
+
import { emitStraightlineModule, buildComponentPlan, sequentialOrder, analyzeSequentialOps, classifyExpr, isScopeFree, componentNeedsScope, } from "./straightline.js";
|
|
3
3
|
/** primitive namespace alias(生成コード内)。 */
|
|
4
4
|
const CGP = "cgp";
|
|
5
5
|
/**
|
|
6
|
-
* scope
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
6
|
+
* scope 式(生成コード内・RunPlan 経路のみ)。実並行 plan の component は結果が並行に確定する
|
|
7
|
+
* ため、runBehavior の `baseScope()` と同じく exec ごとにスナップを組む(決定的)。
|
|
8
|
+
* 逐次 component は進行形 scope(`scope` ローカル)を使い、この関数形は emit しない。
|
|
9
9
|
*/
|
|
10
10
|
const SCOPE = "scope()";
|
|
11
|
-
/**
|
|
12
|
-
* map 本体(over/guard/ports)の要素 scope 式(生成コード内)。runBehavior の map と同じく
|
|
13
|
-
* `{ ...baseScope(), [as]: el }` のスナップを per-element で組む。生成コードはこの名前
|
|
14
|
-
* (`mscope()`)で要素 scope を読む関数を map arm 内にローカル定義する。
|
|
15
|
-
*/
|
|
11
|
+
/** map 本体(over/guard/ports)の要素 scope 式(RunPlan 経路の map 展開用 — 逐次経路は `mscope` ローカル)。 */
|
|
16
12
|
const MAP_SCOPE = "mscope()";
|
|
17
13
|
/** IR リテラルを TS 式として emit(primitive 被演算子用の単一行リテラル)。 */
|
|
18
14
|
function lit(node) {
|
|
@@ -58,14 +54,33 @@ const OP_EMITTERS = {
|
|
|
58
54
|
cond: (arg, s) => `${CGP}.cond(${argList(arg)}, ${s})`,
|
|
59
55
|
len: (arg, s) => `${CGP}.len(${argList(arg)}, ${s})`,
|
|
60
56
|
};
|
|
57
|
+
/** module 単位の使用トラッカー(import / 生成 helper の要否を構造的に決める — テキスト scan 禁止)。 */
|
|
58
|
+
const usage = {
|
|
59
|
+
cgp: false,
|
|
60
|
+
runPlan: false,
|
|
61
|
+
runPlanAsync: false,
|
|
62
|
+
unproducedValue: false,
|
|
63
|
+
planFailure: false,
|
|
64
|
+
exprFailure: false,
|
|
65
|
+
behaviorFailure: false,
|
|
66
|
+
slBind: false,
|
|
67
|
+
slField: false,
|
|
68
|
+
slCat: false,
|
|
69
|
+
planTypes: false, // Exec/AsyncExec/OpSpec/RelationKind(RunPlan 経路のみ)
|
|
70
|
+
handlerType: false, // Handler/AsyncHandler(handler を呼ぶ component の run_* param 型)
|
|
71
|
+
};
|
|
72
|
+
function resetUsage() {
|
|
73
|
+
for (const k of Object.keys(usage))
|
|
74
|
+
usage[k] = false;
|
|
75
|
+
}
|
|
61
76
|
/**
|
|
62
|
-
* emitExpr — Expression IR ノードを TS の primitive
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* Portability Guard を通過済み(生成時検査)なので未知 operator はここに来ない。
|
|
77
|
+
* emitExpr — Expression IR ノードを TS の primitive 呼び式に落とす(**dynamic 式専用**の
|
|
78
|
+
* legacy 経路 — typed emitter(bc#46)が同じ綴じ方を流用する seam でもある)。
|
|
79
|
+
* 単一キーの演算子ノード → 対応 primitive(被演算子は IR リテラル=A0 の Expr 契約)、
|
|
80
|
+
* むき出しスカラ/未知形 → `evalExpr`(同一 SSoT)。
|
|
67
81
|
*/
|
|
68
82
|
function emitExpr(node, scopeVar) {
|
|
83
|
+
usage.cgp = true;
|
|
69
84
|
if (node !== null && typeof node === "object" && !Array.isArray(node)) {
|
|
70
85
|
const keys = Object.keys(node);
|
|
71
86
|
if (keys.length === 1) {
|
|
@@ -78,7 +93,461 @@ function emitExpr(node, scopeVar) {
|
|
|
78
93
|
// スカラリテラル(string/number/bool/null)や obj 以外 — evaluate SSoT に委譲(同一意味論)。
|
|
79
94
|
return `${CGP}.evalExpr(${lit(node)}, ${scopeVar})`;
|
|
80
95
|
}
|
|
81
|
-
|
|
96
|
+
/** dynamic 式に渡す scope 式(scope-free なら空 scope 定数 — リテラル評価は scope を読まない)。 */
|
|
97
|
+
function seqScopeExpr(node, ctx) {
|
|
98
|
+
if (isScopeFree(node))
|
|
99
|
+
return "{}";
|
|
100
|
+
if (ctx.mapAs)
|
|
101
|
+
return ctx.mapAs.mscopeVar;
|
|
102
|
+
if (!ctx.scopeVar)
|
|
103
|
+
throw new Error("straight-line ts: scope-reading expression in a component without a scope (analysis bug)");
|
|
104
|
+
return ctx.scopeVar;
|
|
105
|
+
}
|
|
106
|
+
/** 静的 ref の head を配線解決した TS 式へ(ローカル直読み / input fallback / UNKNOWN_BINDING)。 */
|
|
107
|
+
function emitRefHead(head, ctx) {
|
|
108
|
+
// TS の scope 合成 {...input, ...results, [as]: el} は as → results → input の順で shadow する。
|
|
109
|
+
if (ctx.mapAs && head === ctx.mapAs.as)
|
|
110
|
+
return ctx.mapAs.elVar;
|
|
111
|
+
const idx = ctx.idToIndex.get(head);
|
|
112
|
+
if (idx !== undefined) {
|
|
113
|
+
const executedBefore = ctx.atOutput || ctx.pos[idx] < ctx.currentPos;
|
|
114
|
+
if (executedBefore) {
|
|
115
|
+
const meta = ctx.metas[idx];
|
|
116
|
+
const local = ctx.local.get(head);
|
|
117
|
+
if (ctx.atOutput || !(meta.canSkip || meta.alwaysSkip))
|
|
118
|
+
return local;
|
|
119
|
+
// 実行済みだが skip され得る: 未生成なら scope 合成は input の同名 key へ fall through する。
|
|
120
|
+
usage.slBind = true;
|
|
121
|
+
return `(${ctx.okFlag.get(head)} ? ${local} : slBind(input, ${JSON.stringify(head)}))`;
|
|
122
|
+
}
|
|
123
|
+
// まだ実行されていないノード id — results に無いので input の同名 key(無ければ UNKNOWN_BINDING)。
|
|
124
|
+
}
|
|
125
|
+
usage.slBind = true;
|
|
126
|
+
return `slBind(input, ${JSON.stringify(head)})`;
|
|
127
|
+
}
|
|
128
|
+
/** 分類済み StaticExpr を TS 式へ(逐次経路のネイティブ inline)。 */
|
|
129
|
+
function emitStaticExpr(e, ctx) {
|
|
130
|
+
switch (e.kind) {
|
|
131
|
+
case "str":
|
|
132
|
+
return JSON.stringify(e.value);
|
|
133
|
+
case "bool":
|
|
134
|
+
return e.value ? "true" : "false";
|
|
135
|
+
case "null":
|
|
136
|
+
return "null";
|
|
137
|
+
case "ref": {
|
|
138
|
+
const head = emitRefHead(e.path[0], ctx);
|
|
139
|
+
if (e.path.length === 1)
|
|
140
|
+
return head;
|
|
141
|
+
usage.slField = true;
|
|
142
|
+
return `slField(${JSON.stringify(e.opt ? "refOpt" : "ref")}, ${head}, [${e.path
|
|
143
|
+
.slice(1)
|
|
144
|
+
.map((p) => JSON.stringify(p))
|
|
145
|
+
.join(", ")}])`;
|
|
146
|
+
}
|
|
147
|
+
case "concat": {
|
|
148
|
+
// 可変長 slCat: 引数位置で左→右に評価(evaluate と同順)→ helper 内で string 検査
|
|
149
|
+
// (evaluate の concat と同じ「全評価 → 型検査」順・同じ TYPE_MISMATCH)。closure 無し。
|
|
150
|
+
usage.slCat = true;
|
|
151
|
+
return `slCat(${e.parts.map((p) => emitStaticExpr(p, ctx)).join(", ")})`;
|
|
152
|
+
}
|
|
153
|
+
case "dynamic":
|
|
154
|
+
return emitExpr(e.node, seqScopeExpr(e.node, ctx));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Expression IR → TS 式(逐次経路: 静的形状は inline・residual は primitive SSoT)。 */
|
|
158
|
+
function emitSeqExpr(node, ctx) {
|
|
159
|
+
return emitStaticExpr(classifyExpr(node), ctx);
|
|
160
|
+
}
|
|
161
|
+
/** component 名を関数名に使える形へ(決定的・名前は一意 — DUPLICATE_COMPONENT で保証)。 */
|
|
162
|
+
function sanitize(name) {
|
|
163
|
+
return name.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
164
|
+
}
|
|
165
|
+
/** node id 群 → 衝突しない決定的ローカル名(prefix + sanitize、衝突時は op index を後置)。 */
|
|
166
|
+
function allocLocals(prefix, ids) {
|
|
167
|
+
const out = new Map();
|
|
168
|
+
const taken = new Set();
|
|
169
|
+
ids.forEach((id, i) => {
|
|
170
|
+
let name = `${prefix}${sanitize(id)}`;
|
|
171
|
+
if (taken.has(name))
|
|
172
|
+
name = `${name}_${i}`;
|
|
173
|
+
taken.add(name);
|
|
174
|
+
out.set(id, name);
|
|
175
|
+
});
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/** component が使う catalog 名(宣言順・重複なし)。bind 時解決の対象。 */
|
|
179
|
+
function componentCatalogNames(plan) {
|
|
180
|
+
const names = [];
|
|
181
|
+
for (const op of plan.ops) {
|
|
182
|
+
if (op.kind === "cond")
|
|
183
|
+
continue;
|
|
184
|
+
if (!names.includes(op.component))
|
|
185
|
+
names.push(op.component);
|
|
186
|
+
}
|
|
187
|
+
return names;
|
|
188
|
+
}
|
|
189
|
+
/** catalog 名 → 解決済み handler のローカル変数名(決定的・衝突は index 後置)。 */
|
|
190
|
+
function handlerVars(names) {
|
|
191
|
+
const out = new Map();
|
|
192
|
+
const taken = new Set();
|
|
193
|
+
names.forEach((n, i) => {
|
|
194
|
+
let v = `h$${sanitize(n)}`;
|
|
195
|
+
if (taken.has(v))
|
|
196
|
+
v = `${v}_${i}`;
|
|
197
|
+
taken.add(v);
|
|
198
|
+
out.set(n, v);
|
|
199
|
+
});
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
/** 未生成表現の TS リテラル(single → null / connection → 空 connection)。 */
|
|
203
|
+
function unproducedLit(relationKind) {
|
|
204
|
+
return relationKind === "connection" ? "{ items: [], cursor: null }" : "null";
|
|
205
|
+
}
|
|
206
|
+
/** handler の error outcome を policy で解釈する文(fail/retry → OP_FAILED throw、continue → skip)。 */
|
|
207
|
+
function policyLines(outcomeVar, opId, meta, indent, onContinueSkip) {
|
|
208
|
+
usage.planFailure = true;
|
|
209
|
+
if (meta.policy === "fail" || typeof meta.policy === "object") {
|
|
210
|
+
// 未知 policy はこの手前で UNKNOWN_POLICY を投げているため到達しない(fail は既定)。
|
|
211
|
+
return [
|
|
212
|
+
`${indent}if ("error" in ${outcomeVar}) throw new PlanFailure("OP_FAILED", \`operation '${opId}' failed under 'fail' policy: \${${outcomeVar}.error}\`);`,
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
if (meta.policy === "retry") {
|
|
216
|
+
return [
|
|
217
|
+
`${indent}if ("error" in ${outcomeVar}) throw new PlanFailure("OP_FAILED", \`operation '${opId}' failed under 'retry' policy (exhausted): \${${outcomeVar}.error}\`);`,
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
// continue: 失敗 op は Port 未生成 → skip(下流へ連鎖)。
|
|
221
|
+
return [`${indent}if ("error" in ${outcomeVar}) {`, ...onContinueSkip.map((l) => `${indent} ${l}`), `${indent}} else {`];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* emitSeqOp — 逐次直線化された 1 op の文列。skip 判定(parent 連鎖 / bindField)→ 実行
|
|
225
|
+
* (componentRef/map/cond)→ policy 解釈 → 結果ローカル格納(+進行形 scope 書き足し)。
|
|
226
|
+
*/
|
|
227
|
+
function emitSeqOp(op, plan, ctx, hvars, aw, lines) {
|
|
228
|
+
const i = op.index;
|
|
229
|
+
const meta = ctx.metas[i];
|
|
230
|
+
const o = plan.opsLiteral[i];
|
|
231
|
+
const id = o.id;
|
|
232
|
+
const local = ctx.local.get(id);
|
|
233
|
+
lines.push(` // ── op '${id}' (${op.kind === "cond" ? "cond" : op.kind === "map" ? `map ${op.component}` : op.component}${o.policy ? `, policy ${JSON.stringify(o.policy)}` : ""}) ──`);
|
|
234
|
+
// policy 検証は skip 判定より先(plan.ts preflightOp と同順 — UNKNOWN_POLICY は skip でも投げる)。
|
|
235
|
+
if (typeof meta.policy === "object") {
|
|
236
|
+
usage.planFailure = true;
|
|
237
|
+
lines.push(` throw new PlanFailure("UNKNOWN_POLICY", ${JSON.stringify(`unknown policy kind: ${meta.policy.invalid}`)});`);
|
|
238
|
+
return; // 以降は到達しない(生成もしない)
|
|
239
|
+
}
|
|
240
|
+
const canSkip = meta.canSkip || meta.alwaysSkip;
|
|
241
|
+
if (canSkip)
|
|
242
|
+
lines.push(` let ${local}: Value = null;`, ` let ${ctx.okFlag.get(id)} = false;`);
|
|
243
|
+
if (meta.alwaysSkip) {
|
|
244
|
+
lines.push(` // parent '${plan.opsLiteral[i].parent}' runs later — preflight sees an unsettled parent: unconditional skip.`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// skip ガード(parent produced / bindField null-binding)。
|
|
248
|
+
const guards = [];
|
|
249
|
+
if (o.parent !== null) {
|
|
250
|
+
const pid = plan.opsLiteral[o.parent].id;
|
|
251
|
+
const pMeta = ctx.metas[o.parent];
|
|
252
|
+
const pLocal = ctx.local.get(pid);
|
|
253
|
+
if (pMeta.canSkip || pMeta.alwaysSkip)
|
|
254
|
+
guards.push(`${ctx.okFlag.get(pid)}`);
|
|
255
|
+
if (o.bindField !== undefined) {
|
|
256
|
+
const bv = `bv_${sanitize(id)}`;
|
|
257
|
+
// 親 result[bindField](親が plain object のときのみ)。null/欠落なら skip(plan.ts preflightOp)。
|
|
258
|
+
const read = `${pLocal} !== null && typeof ${pLocal} === "object" && !Array.isArray(${pLocal}) ? (${pLocal} as Record<string, Value>)[${JSON.stringify(o.bindField)}] : undefined`;
|
|
259
|
+
if (guards.length > 0) {
|
|
260
|
+
lines.push(` if (${guards[0]}) {`);
|
|
261
|
+
emitSeqOpExec(op, plan, ctx, hvars, aw, lines, " ", bv, read);
|
|
262
|
+
lines.push(` }`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
emitSeqOpExec(op, plan, ctx, hvars, aw, lines, " ", bv, read);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (guards.length > 0) {
|
|
270
|
+
lines.push(` if (${guards[0]}) {`);
|
|
271
|
+
emitSeqOpExec(op, plan, ctx, hvars, aw, lines, " ");
|
|
272
|
+
lines.push(` }`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
emitSeqOpExec(op, plan, ctx, hvars, aw, lines, " ");
|
|
276
|
+
}
|
|
277
|
+
/** op 本体(bindField 判定含む)を indent 付きで emit。 */
|
|
278
|
+
function emitSeqOpExec(op, plan, ctx, hvars, aw, lines, indent, bindVar, bindRead) {
|
|
279
|
+
const i = op.index;
|
|
280
|
+
const meta = ctx.metas[i];
|
|
281
|
+
const o = plan.opsLiteral[i];
|
|
282
|
+
const id = o.id;
|
|
283
|
+
const local = ctx.local.get(id);
|
|
284
|
+
const canSkip = meta.canSkip || meta.alwaysSkip;
|
|
285
|
+
const inner = [];
|
|
286
|
+
const bodyIndent = bindVar ? indent + " " : indent;
|
|
287
|
+
// 成功時の格納(ローカル + 進行形 scope)。
|
|
288
|
+
const store = (valueExpr, ind, target) => {
|
|
289
|
+
if (canSkip) {
|
|
290
|
+
target.push(`${ind}${local} = ${valueExpr};`, `${ind}${ctx.okFlag.get(id)} = true;`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
target.push(`${ind}const ${local}: Value = ${valueExpr};`);
|
|
294
|
+
}
|
|
295
|
+
if (ctx.scopeVar)
|
|
296
|
+
target.push(`${ind}${ctx.scopeVar}[${JSON.stringify(id)}] = ${local};`);
|
|
297
|
+
};
|
|
298
|
+
if (op.kind === "cond") {
|
|
299
|
+
// cond は純 Expression(handler 非呼び出し・policy 解釈対象の error outcome も無い)。
|
|
300
|
+
const c = plan.component.body[i].cond;
|
|
301
|
+
const expr = emitExpr({ cond: [c.if, c.then, c.else] }, seqScopeExpr({ cond: [c.if, c.then, c.else] }, ctx));
|
|
302
|
+
store(expr, bodyIndent, inner);
|
|
303
|
+
}
|
|
304
|
+
else if (op.kind === "map") {
|
|
305
|
+
emitSeqMap(op, plan, ctx, hvars, aw, inner, bodyIndent, store);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
// componentRef: ports 構築(1 回)→ 解決済み handler 直呼び → policy 解釈。
|
|
309
|
+
const hv = hvars.get(op.component);
|
|
310
|
+
usage.behaviorFailure = true;
|
|
311
|
+
const savedPos = ctx.currentPos;
|
|
312
|
+
const portsExpr = op.ports.length === 0
|
|
313
|
+
? "{}"
|
|
314
|
+
: `{\n${op.ports
|
|
315
|
+
.map((p) => {
|
|
316
|
+
const node = plan.component.body[i].ports[p.name];
|
|
317
|
+
return `${bodyIndent} ${JSON.stringify(p.name)}: ${emitSeqExpr(node, ctx)},`;
|
|
318
|
+
})
|
|
319
|
+
.join("\n")}\n${bodyIndent}}`;
|
|
320
|
+
ctx.currentPos = savedPos;
|
|
321
|
+
inner.push(`${bodyIndent}if (!${hv}) throw new BehaviorFailure("UNKNOWN_COMPONENT", ${JSON.stringify(`component '${op.component}' has no handler (fail-closed)`)});`);
|
|
322
|
+
inner.push(`${bodyIndent}const ports_${sanitize(id)}: Record<string, Value> = ${portsExpr};`);
|
|
323
|
+
inner.push(`${bodyIndent}const o_${sanitize(id)} = ${aw}${hv}(ports_${sanitize(id)}, { nodeId: ${JSON.stringify(id)}, component: ${JSON.stringify(op.component)} });`);
|
|
324
|
+
if (meta.policy === "continue") {
|
|
325
|
+
const cont = policyLines(`o_${sanitize(id)}`, id, meta, bodyIndent, ["// continue policy: no Port produced — downstream skips"]);
|
|
326
|
+
inner.push(...cont);
|
|
327
|
+
store(`o_${sanitize(id)}.ok`, bodyIndent + " ", inner);
|
|
328
|
+
inner.push(`${bodyIndent}}`);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
inner.push(...policyLines(`o_${sanitize(id)}`, id, meta, bodyIndent, []));
|
|
332
|
+
store(`o_${sanitize(id)}.ok`, bodyIndent, inner);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (bindVar && bindRead) {
|
|
336
|
+
lines.push(`${indent}const ${bindVar} = ${bindRead};`);
|
|
337
|
+
lines.push(`${indent}if (${bindVar} !== undefined && ${bindVar} !== null) {`);
|
|
338
|
+
lines.push(...inner);
|
|
339
|
+
lines.push(`${indent}}`);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
lines.push(...inner);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* emitSeqMap — map ノードの逐次直線展開。runBehavior の map 分岐と観測等価(要素反復・guard skip・
|
|
347
|
+
* per-element/batched handler・into zip-attach・Failure code/順序)だが、per-expression scope
|
|
348
|
+
* snapshot は無い: 要素 scope(mscope)は「進行形 scope のコピー + as 束縛」を**要素ごとに 1 回**
|
|
349
|
+
* だけ、しかも scope を読む residual 式が map 内にあるときだけ組む。
|
|
350
|
+
*/
|
|
351
|
+
function emitSeqMap(op, plan, ctx, hvars, aw, out, ind, store) {
|
|
352
|
+
const i = op.index;
|
|
353
|
+
const meta = ctx.metas[i];
|
|
354
|
+
const m = plan.component.body[i].map;
|
|
355
|
+
const id = op.id;
|
|
356
|
+
const s = sanitize(id);
|
|
357
|
+
const hv = hvars.get(op.component);
|
|
358
|
+
const asKey = JSON.stringify(m.as);
|
|
359
|
+
usage.behaviorFailure = true;
|
|
360
|
+
// 全ローカルを node id で一意化する(同一逐次関数内に複数 map があると素の mi/el/mscope/ports/mk/mj/oel が
|
|
361
|
+
// 衝突するため — bc#75)。loop 本体は block-scope だが into-walk の mk は関数 body 直下に出るので特に必須。
|
|
362
|
+
const elVar = `el_${s}`;
|
|
363
|
+
const mscopeVar = `mscope_${s}`;
|
|
364
|
+
const portsVar = `ports_${s}`;
|
|
365
|
+
const miVar = `mi_${s}`;
|
|
366
|
+
const mjVar = `mj_${s}`;
|
|
367
|
+
const mkVar = `mk_${s}`;
|
|
368
|
+
const oelVar = `oel_${s}`;
|
|
369
|
+
// map 内で scope を読む residual 式があるときだけ mscope を組む(要素ごとに 1 回)。
|
|
370
|
+
const guardIR = m.when === undefined ? undefined : { cond: [m.when, true, false] };
|
|
371
|
+
const mapNeedsScope = (guardIR !== undefined && !isScopeFree(guardIR)) ||
|
|
372
|
+
Object.keys(m.ports).some((k) => {
|
|
373
|
+
const cls = classifyExpr(m.ports[k]);
|
|
374
|
+
const needs = (e) => e.kind === "dynamic" ? !isScopeFree(e.node) : e.kind === "concat" ? e.parts.some(needs) : false;
|
|
375
|
+
return needs(cls);
|
|
376
|
+
});
|
|
377
|
+
const baseScopeExpr = ctx.scopeVar ?? "input";
|
|
378
|
+
const over = `over_${s}`;
|
|
379
|
+
out.push(`${ind}const ${over} = ${emitSeqExpr(m.over, ctx)};`);
|
|
380
|
+
out.push(`${ind}if (!Array.isArray(${over})) throw new BehaviorFailure("MAP_OVER_NOT_ARRAY", ${JSON.stringify(`map '${id}': 'over' did not evaluate to an array`)});`);
|
|
381
|
+
const kept = `kept_${s}`;
|
|
382
|
+
out.push(`${ind}const ${kept}: number[] = [];`);
|
|
383
|
+
const mapCtx = { ...ctx, mapAs: { as: m.as, elVar, mscopeVar } };
|
|
384
|
+
const guardExpr = guardIR === undefined ? undefined : emitExpr(guardIR, isScopeFree(guardIR) ? "{}" : mscopeVar);
|
|
385
|
+
const portLines = (ind2) => {
|
|
386
|
+
if (op.ports.length === 0)
|
|
387
|
+
return [`${ind2}const ${portsVar}: Record<string, Value> = {};`];
|
|
388
|
+
return [
|
|
389
|
+
`${ind2}const ${portsVar}: Record<string, Value> = {`,
|
|
390
|
+
...op.ports.map((p) => `${ind2} ${JSON.stringify(p.name)}: ${emitSeqExpr(m.ports[p.name], mapCtx)},`),
|
|
391
|
+
`${ind2}};`,
|
|
392
|
+
];
|
|
393
|
+
};
|
|
394
|
+
const mscopeLine = mapNeedsScope
|
|
395
|
+
? [`${ind} const ${mscopeVar}: Scope = { ...${baseScopeExpr}, [${asKey}]: ${elVar} };`]
|
|
396
|
+
: [];
|
|
397
|
+
const guardLines = guardExpr === undefined ? [] : [`${ind} if (${guardExpr} !== true) continue;`];
|
|
398
|
+
const collected = `collected_${s}`;
|
|
399
|
+
const outcomeVar = `mo_${s}`;
|
|
400
|
+
const itemsVar = `items_${s}`;
|
|
401
|
+
const skipVar = `skip_${s}`;
|
|
402
|
+
// handler error outcome → policy 解釈(map ノードの policy)。continue はループごと打ち切って skip。
|
|
403
|
+
const errInterp = (ind2) => {
|
|
404
|
+
if (meta.policy === "continue") {
|
|
405
|
+
// continue: この op は未生成のまま — 生成側は skip フラグを立てず外へ抜ける。
|
|
406
|
+
return [`${ind2}if ("error" in ${outcomeVar}) { ${collected}.length = 0; ${skipVar} = true; break; }`];
|
|
407
|
+
}
|
|
408
|
+
return policyLines(outcomeVar, id, meta, ind2, []);
|
|
409
|
+
};
|
|
410
|
+
const contFlag = meta.policy === "continue";
|
|
411
|
+
if (contFlag)
|
|
412
|
+
out.push(`${ind}let ${skipVar} = false;`);
|
|
413
|
+
if (op.batched) {
|
|
414
|
+
out.push(`${ind}const ${itemsVar}: Value[] = [];`);
|
|
415
|
+
out.push(`${ind}for (let ${miVar} = 0; ${miVar} < (${over} as Value[]).length; ${miVar}++) {`);
|
|
416
|
+
out.push(`${ind} const ${elVar} = (${over} as Value[])[${miVar}];`);
|
|
417
|
+
out.push(...mscopeLine, ...guardLines, ...portLines(`${ind} `));
|
|
418
|
+
out.push(`${ind} ${itemsVar}.push(${portsVar});`, `${ind} ${kept}.push(${miVar});`, `${ind}}`);
|
|
419
|
+
out.push(`${ind}let ${collected}: Value[] = [];`);
|
|
420
|
+
out.push(`${ind}if (${itemsVar}.length > 0) {`);
|
|
421
|
+
out.push(`${ind} if (!${hv}) throw new BehaviorFailure("UNKNOWN_COMPONENT", ${JSON.stringify(`component '${op.component}' has no handler (fail-closed)`)});`);
|
|
422
|
+
out.push(`${ind} const ${outcomeVar} = ${aw}${hv}({ items: ${itemsVar} }, { nodeId: ${JSON.stringify(id)}, component: ${JSON.stringify(op.component)} });`);
|
|
423
|
+
const okLines = [
|
|
424
|
+
`${ind} if (!("error" in ${outcomeVar})) {`,
|
|
425
|
+
`${ind} if (!Array.isArray(${outcomeVar}.ok) || ${outcomeVar}.ok.length !== ${itemsVar}.length)`,
|
|
426
|
+
`${ind} throw new BehaviorFailure("MAP_BATCH_RESULT_MISMATCH", \`map '${id}': batched handler must return a list aligned to items (want \${${itemsVar}.length}, got \${Array.isArray(${outcomeVar}.ok) ? ${outcomeVar}.ok.length : typeof ${outcomeVar}.ok})\`);`,
|
|
427
|
+
`${ind} ${collected} = ${outcomeVar}.ok as Value[];`,
|
|
428
|
+
`${ind} }`,
|
|
429
|
+
];
|
|
430
|
+
if (contFlag) {
|
|
431
|
+
// continue 版: エラー時は break 済み — ここは ok のみ。
|
|
432
|
+
out.push(...errInterp(`${ind} `));
|
|
433
|
+
out.push(...okLines);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// fail/retry 版: policyLines が throw 済み — ok を素で読む。
|
|
437
|
+
out.push(...policyLines(outcomeVar, id, meta, `${ind} `, []));
|
|
438
|
+
out.push(`${ind} if (!Array.isArray(${outcomeVar}.ok) || ${outcomeVar}.ok.length !== ${itemsVar}.length)`, `${ind} throw new BehaviorFailure("MAP_BATCH_RESULT_MISMATCH", \`map '${id}': batched handler must return a list aligned to items (want \${${itemsVar}.length}, got \${Array.isArray(${outcomeVar}.ok) ? ${outcomeVar}.ok.length : typeof ${outcomeVar}.ok})\`);`, `${ind} ${collected} = ${outcomeVar}.ok as Value[];`);
|
|
439
|
+
}
|
|
440
|
+
out.push(`${ind}}`);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
out.push(`${ind}const ${collected}: Value[] = [];`);
|
|
444
|
+
out.push(`${ind}if (!${hv}) throw new BehaviorFailure("UNKNOWN_COMPONENT", ${JSON.stringify(`component '${op.component}' has no handler (fail-closed)`)});`);
|
|
445
|
+
out.push(`${ind}for (let ${miVar} = 0; ${miVar} < (${over} as Value[]).length; ${miVar}++) {`);
|
|
446
|
+
out.push(`${ind} const ${elVar} = (${over} as Value[])[${miVar}];`);
|
|
447
|
+
out.push(...mscopeLine, ...guardLines, ...portLines(`${ind} `));
|
|
448
|
+
out.push(`${ind} const ${outcomeVar} = ${aw}${hv}(${portsVar}, { nodeId: ${JSON.stringify(id)}, component: ${JSON.stringify(op.component)}, bound: ${elVar} });`);
|
|
449
|
+
if (contFlag) {
|
|
450
|
+
out.push(`${ind} if ("error" in ${outcomeVar}) { ${skipVar} = true; break; }`);
|
|
451
|
+
out.push(`${ind} ${collected}.push(${outcomeVar}.ok);`, `${ind} ${kept}.push(${miVar});`);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
out.push(...policyLines(outcomeVar, id, meta, `${ind} `, []));
|
|
455
|
+
out.push(`${ind} ${collected}.push(${outcomeVar}.ok);`, `${ind} ${kept}.push(${miVar});`);
|
|
456
|
+
}
|
|
457
|
+
out.push(`${ind}}`);
|
|
458
|
+
}
|
|
459
|
+
const finish = [];
|
|
460
|
+
if (m.into === undefined) {
|
|
461
|
+
store(collected, contFlag ? `${ind} ` : ind, finish);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
const aug = `aug_${s}`;
|
|
465
|
+
const ind2 = contFlag ? `${ind} ` : ind;
|
|
466
|
+
finish.push(`${ind2}// map.into zip-attach (behavior.ts applyInto): over と同じ長さ・順序で guard 通過要素へ`, `${ind2}// ${JSON.stringify(m.into)} を書き戻す(skip 要素は無変更で pass-through、親不変=shallow copy)。`, `${ind2}const ${aug}: Value[] = [];`, `${ind2}let ${mkVar} = 0;`, `${ind2}for (let ${mjVar} = 0; ${mjVar} < (${over} as Value[]).length; ${mjVar}++) {`, `${ind2} const ${oelVar} = (${over} as Value[])[${mjVar}];`, `${ind2} if (${mkVar} < ${kept}.length && ${kept}[${mkVar}] === ${mjVar}) {`, `${ind2} if (${oelVar} === null || typeof ${oelVar} !== "object" || Array.isArray(${oelVar}))`, `${ind2} throw new BehaviorFailure("MAP_INTO_ELEMENT_NOT_OBJECT", \`map '${id}': 'into' requires object elements (element \${${mjVar}} is not an object)\`);`, `${ind2} ${aug}.push({ ...(${oelVar} as Record<string, Value>), [${JSON.stringify(m.into)}]: ${collected}[${mkVar}] });`, `${ind2} ${mkVar}++;`, `${ind2} } else {`, `${ind2} ${aug}.push(${oelVar});`, `${ind2} }`, `${ind2}}`);
|
|
467
|
+
store(aug, ind2, finish);
|
|
468
|
+
}
|
|
469
|
+
if (contFlag) {
|
|
470
|
+
out.push(`${ind}if (!${skipVar}) {`);
|
|
471
|
+
out.push(...finish);
|
|
472
|
+
out.push(`${ind}}`);
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
out.push(...finish);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* emitSequentialFn — 逐次 plan の component を素の文列として綴じる(bc#75 の核)。
|
|
480
|
+
* RunPlan / ops テーブル / exec closure / per-op ID 探索 / per-expression scope snapshot は無い。
|
|
481
|
+
*/
|
|
482
|
+
function emitSequentialFn(plan, order, aw) {
|
|
483
|
+
const metas = analyzeSequentialOps(plan.opsLiteral, order);
|
|
484
|
+
const pos = new Array(plan.opsLiteral.length);
|
|
485
|
+
order.forEach((opIdx, k) => (pos[opIdx] = k));
|
|
486
|
+
const idToIndex = new Map();
|
|
487
|
+
plan.opsLiteral.forEach((o, i) => idToIndex.set(o.id, i));
|
|
488
|
+
const needScope = componentNeedsScope(plan);
|
|
489
|
+
const ids = plan.opsLiteral.map((o) => o.id);
|
|
490
|
+
const local = allocLocals("r_", ids);
|
|
491
|
+
const okFlag = allocLocals("ok_", ids);
|
|
492
|
+
const ctx = {
|
|
493
|
+
pos,
|
|
494
|
+
idToIndex,
|
|
495
|
+
metas,
|
|
496
|
+
local,
|
|
497
|
+
okFlag,
|
|
498
|
+
scopeVar: needScope ? "scope" : null,
|
|
499
|
+
currentPos: 0,
|
|
500
|
+
atOutput: false,
|
|
501
|
+
};
|
|
502
|
+
const catalogs = componentCatalogNames(plan);
|
|
503
|
+
if (catalogs.length > 0)
|
|
504
|
+
usage.handlerType = true;
|
|
505
|
+
const hvars = handlerVars(catalogs);
|
|
506
|
+
const fn = sanitize(plan.name);
|
|
507
|
+
const suffix = aw === "" ? "" : "_async";
|
|
508
|
+
const hType = aw === "" ? "Handler" : "AsyncHandler";
|
|
509
|
+
const ret = aw === "" ? "Value" : "Promise<Value>";
|
|
510
|
+
const params = catalogs.map((c) => `${hvars.get(c)}: ${hType} | undefined`).concat(`input: Scope`).join(", ");
|
|
511
|
+
const lines = [];
|
|
512
|
+
lines.push(`${aw === "" ? "function" : "async function"} run_${fn}${suffix}(${params}): ${ret} {`);
|
|
513
|
+
if (needScope) {
|
|
514
|
+
// 進行形 scope: input のプライベートコピー(bind 側で作成済み)へノード結果を書き足す。
|
|
515
|
+
// per-expression snapshot は無い — scope を読む residual 式だけがこのオブジェクトを受ける。
|
|
516
|
+
lines.push(` const scope: Scope = input;`);
|
|
517
|
+
}
|
|
518
|
+
let unreachable = false;
|
|
519
|
+
for (const k of order.keys()) {
|
|
520
|
+
const opIdx = order[k];
|
|
521
|
+
ctx.currentPos = k;
|
|
522
|
+
if (unreachable)
|
|
523
|
+
break;
|
|
524
|
+
emitSeqOp(plan.ops[opIdx], plan, ctx, hvars, aw, lines);
|
|
525
|
+
if (typeof metas[opIdx].policy === "object")
|
|
526
|
+
unreachable = true;
|
|
527
|
+
}
|
|
528
|
+
if (!unreachable) {
|
|
529
|
+
// 未生成書き戻し(output の ref/coalesce が読めるように — writeUnproduced と同一)。
|
|
530
|
+
ctx.atOutput = true;
|
|
531
|
+
for (const i of order) {
|
|
532
|
+
const meta = metas[i];
|
|
533
|
+
if (!(meta.canSkip || meta.alwaysSkip))
|
|
534
|
+
continue;
|
|
535
|
+
const id = plan.opsLiteral[i].id;
|
|
536
|
+
const up = unproducedLit(plan.opsLiteral[i].relationKind);
|
|
537
|
+
lines.push(` if (!${okFlag.get(id)}) {`);
|
|
538
|
+
lines.push(` ${local.get(id)} = ${up};`);
|
|
539
|
+
if (needScope)
|
|
540
|
+
lines.push(` scope[${JSON.stringify(id)}] = ${local.get(id)};`);
|
|
541
|
+
lines.push(` }`);
|
|
542
|
+
}
|
|
543
|
+
lines.push(` return ${emitSeqExpr(plan.component.output, ctx)};`);
|
|
544
|
+
}
|
|
545
|
+
lines.push(`}`);
|
|
546
|
+
return lines.join("\n");
|
|
547
|
+
}
|
|
548
|
+
// ══════════════════════════════════════════════════════════════════════════════════
|
|
549
|
+
// RunPlan 経路(実並行 plan のみ — bounded 並行の意味論 SSoT を保持)
|
|
550
|
+
// ══════════════════════════════════════════════════════════════════════════════════
|
|
82
551
|
/** ops リテラル(runPlan に渡す OpSpec[] の TS リテラル)を綴じる。 */
|
|
83
552
|
function emitOpsLiteral(plan) {
|
|
84
553
|
const rows = plan.opsLiteral.map((o) => {
|
|
@@ -102,23 +571,17 @@ function emitMapPorts(op) {
|
|
|
102
571
|
.join("\n")}\n };`;
|
|
103
572
|
}
|
|
104
573
|
/**
|
|
105
|
-
* emitMapArm — map
|
|
106
|
-
*
|
|
107
|
-
* 等価のため、handler は runBehavior と同じ ctx(非 batched は `{nodeId,component,bound}`、batched は
|
|
108
|
-
* `{nodeId,component}`)・同じ ports 形で呼ぶ。guard は cond primitive(strict-bool SSoT)。into は
|
|
109
|
-
* A0 `hydrateInto` primitive(zip-attach SSoT)。要素反復は宣言どおり逐次(並列単位は body ノード)。
|
|
574
|
+
* emitMapArm — map ノードの直線展開(RunPlan 経路)。runBehavior の map 分岐(behavior.ts §map)と
|
|
575
|
+
* 観測等価。op-log 等価のため、handler は runBehavior と同じ ctx・同じ ports 形で呼ぶ。
|
|
110
576
|
*/
|
|
111
|
-
function emitMapArm(op, aw) {
|
|
112
|
-
const comp = JSON.stringify(op.component);
|
|
577
|
+
function emitMapArm(op, aw, hv) {
|
|
113
578
|
const nodeId = JSON.stringify(op.id);
|
|
579
|
+
const comp = JSON.stringify(op.component);
|
|
114
580
|
const asKey = JSON.stringify(op.as);
|
|
115
581
|
const guardCheck = op.guard === undefined ? "" : `\n if (${op.guard} !== true) continue;`;
|
|
116
582
|
const intoTail = op.into === undefined
|
|
117
583
|
? " return { ok: collected };"
|
|
118
|
-
: `
|
|
119
|
-
// ${JSON.stringify(op.into)} を書き戻す(skip 要素は無変更で pass-through、親不変=shallow copy)。
|
|
120
|
-
// Failure code は runBehavior と一致(非 object 要素は MAP_INTO_ELEMENT_NOT_OBJECT)。
|
|
121
|
-
{
|
|
584
|
+
: ` {
|
|
122
585
|
const augmented: Value[] = [];
|
|
123
586
|
let mk = 0;
|
|
124
587
|
for (let mj = 0; mj < (over as Value[]).length; mj++) {
|
|
@@ -148,9 +611,8 @@ function emitMapArm(op, aw) {
|
|
|
148
611
|
if (items.length === 0) {
|
|
149
612
|
collected = [];
|
|
150
613
|
} else {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const outcome = ${aw}h({ items }, { nodeId: ${nodeId}, component: ${comp} });
|
|
614
|
+
if (!${hv}) throw new BehaviorFailure("UNKNOWN_COMPONENT", \`component '${op.component}' has no handler (fail-closed)\`);
|
|
615
|
+
const outcome = ${aw}${hv}({ items }, { nodeId: ${nodeId}, component: ${comp} });
|
|
154
616
|
if ("error" in outcome) return outcome;
|
|
155
617
|
if (!Array.isArray(outcome.ok) || outcome.ok.length !== items.length)
|
|
156
618
|
throw new BehaviorFailure("MAP_BATCH_RESULT_MISMATCH", \`map '${op.id}': batched handler must return a list aligned to items (want \${items.length}, got \${Array.isArray(outcome.ok) ? outcome.ok.length : typeof outcome.ok})\`);
|
|
@@ -158,20 +620,19 @@ function emitMapArm(op, aw) {
|
|
|
158
620
|
}
|
|
159
621
|
${intoTail}`
|
|
160
622
|
: ` const collected: Value[] = [];
|
|
161
|
-
|
|
162
|
-
if (!h) throw new BehaviorFailure("UNKNOWN_COMPONENT", \`component '${op.component}' has no handler (fail-closed)\`);
|
|
623
|
+
if (!${hv}) throw new BehaviorFailure("UNKNOWN_COMPONENT", \`component '${op.component}' has no handler (fail-closed)\`);
|
|
163
624
|
for (let mi = 0; mi < (over as Value[]).length; mi++) {
|
|
164
625
|
const el = (over as Value[])[mi];
|
|
165
626
|
const mscope = (): Scope => ({ ...scope(), [${asKey}]: el });
|
|
166
627
|
void mscope;${guardCheck}
|
|
167
628
|
${emitMapPorts(op)}
|
|
168
|
-
const outcome = ${aw}
|
|
629
|
+
const outcome = ${aw}${hv}(ports, { nodeId: ${nodeId}, component: ${comp}, bound: el });
|
|
169
630
|
if ("error" in outcome) return outcome;
|
|
170
631
|
collected.push(outcome.ok);
|
|
171
632
|
keptIdx.push(mi);
|
|
172
633
|
}
|
|
173
634
|
${intoTail}`;
|
|
174
|
-
return `
|
|
635
|
+
return ` {
|
|
175
636
|
const over = ${op.over};
|
|
176
637
|
if (!Array.isArray(over)) throw new BehaviorFailure("MAP_OVER_NOT_ARRAY", \`map '${op.id}': 'over' did not evaluate to an array\`);
|
|
177
638
|
const keptIdx: number[] = [];
|
|
@@ -179,139 +640,244 @@ ${body}
|
|
|
179
640
|
}`;
|
|
180
641
|
}
|
|
181
642
|
/**
|
|
182
|
-
* 1ノードの exec
|
|
183
|
-
* await キーワード(同期経路は ""、非同期経路 bindAsync は "await ")。cond は handler を呼ばない
|
|
184
|
-
* ので aw は無関係。
|
|
643
|
+
* 1ノードの exec 本体(RunPlan 経路 — 事前解決した per-op 関数として綴じる。ID 探索は無い)。
|
|
185
644
|
*/
|
|
186
|
-
function
|
|
645
|
+
function emitExecFn(op, aw, hvars) {
|
|
187
646
|
if (op.kind === "cond") {
|
|
188
|
-
|
|
189
|
-
return ` if (i === ${op.index}) return { ok: ${op.expr} };`;
|
|
647
|
+
return ` return { ok: ${op.expr} };`;
|
|
190
648
|
}
|
|
191
649
|
if (op.kind === "map")
|
|
192
|
-
return emitMapArm(op, aw);
|
|
193
|
-
|
|
650
|
+
return emitMapArm(op, aw, hvars.get(op.component));
|
|
651
|
+
const hv = hvars.get(op.component);
|
|
194
652
|
const portLines = op.ports.length === 0
|
|
195
653
|
? " const ports: Record<string, Value> = {};"
|
|
196
654
|
: ` const ports: Record<string, Value> = {\n${op.ports
|
|
197
655
|
.map((p) => ` ${JSON.stringify(p.name)}: ${p.expr},`)
|
|
198
656
|
.join("\n")}\n };`;
|
|
199
|
-
return `
|
|
657
|
+
return ` {
|
|
200
658
|
${portLines}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return ${aw}h(ports, { nodeId: ${JSON.stringify(op.id)}, component: ${JSON.stringify(op.component)} });
|
|
659
|
+
if (!${hv}) throw new BehaviorFailure("UNKNOWN_COMPONENT", \`component '${op.component}' has no handler (fail-closed)\`);
|
|
660
|
+
return ${aw}${hv}(ports, { nodeId: ${JSON.stringify(op.id)}, component: ${JSON.stringify(op.component)} });
|
|
204
661
|
}`;
|
|
205
662
|
}
|
|
206
663
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
664
|
+
* emitPlanDrivenFn — 実並行 plan(複数 op の stage)の component を RunPlan primitive で綴じる。
|
|
665
|
+
* bounded 並行・Skip 伝播・Policy Kind の意味論 SSoT は runPlan/runPlanAsync が保持する。
|
|
666
|
+
* per-op exec は事前解決した関数表(id → fn)で引く(線形 ID 探索は無い)。scope は並行確定に
|
|
667
|
+
* 備えて exec ごとのスナップ(runBehavior の baseScope と同一・決定的)。
|
|
210
668
|
*/
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
669
|
+
function emitPlanDrivenFn(plan, aw) {
|
|
670
|
+
usage.cgp = usage.cgp; // no-op(明示)
|
|
671
|
+
usage.behaviorFailure = true;
|
|
672
|
+
usage.unproducedValue = true;
|
|
673
|
+
usage.planTypes = true;
|
|
674
|
+
if (aw === "")
|
|
675
|
+
usage.runPlan = true;
|
|
676
|
+
else
|
|
677
|
+
usage.runPlanAsync = true;
|
|
678
|
+
const catalogs = componentCatalogNames(plan);
|
|
679
|
+
if (catalogs.length > 0)
|
|
680
|
+
usage.handlerType = true;
|
|
681
|
+
const hvars = handlerVars(catalogs);
|
|
682
|
+
const fn = sanitize(plan.name);
|
|
683
|
+
const suffix = aw === "" ? "" : "_async";
|
|
684
|
+
const hType = aw === "" ? "Handler" : "AsyncHandler";
|
|
685
|
+
const ret = aw === "" ? "Value" : "Promise<Value>";
|
|
686
|
+
const execT = aw === "" ? "Exec" : "AsyncExec";
|
|
687
|
+
const params = catalogs.map((c) => `${hvars.get(c)}: ${hType} | undefined`).concat(`input: Scope`).join(", ");
|
|
688
|
+
const planLiteral = plan.component.plan ? emitDocLiteral(plan.component.plan, "ts", 1) : "null";
|
|
217
689
|
const output = emitExpr(plan.component.output, SCOPE);
|
|
218
690
|
const relationKinds = plan.opsLiteral.map((o) => (o.relationKind === "connection" ? "connection" : "single"));
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const exec: Exec = (op) => {
|
|
226
|
-
const i = ops.findIndex((o) => o.id === op.id);
|
|
227
|
-
${syncArms}
|
|
228
|
-
throw new BehaviorFailure("UNKNOWN_NODE_KIND", \`op '\${op.id}' has no generated exec arm (fail-closed)\`);
|
|
229
|
-
};
|
|
230
|
-
const wrappedExec: Exec = (op, bound) => {
|
|
231
|
-
const outcome = exec(op, bound);
|
|
232
|
-
if ("ok" in outcome) results[op.id] = outcome.ok;
|
|
233
|
-
return outcome;
|
|
234
|
-
};
|
|
235
|
-
const run = runPlan(${planLiteral}, ops, wrappedExec);
|
|
236
|
-
run.states.forEach((st, i) => {
|
|
237
|
-
if (st.status === "skipped") results[ops[i].id] = unproducedValue(relationKinds[i]);
|
|
238
|
-
});
|
|
239
|
-
return ${output};
|
|
240
|
-
}`;
|
|
241
|
-
// async 版: plan の stage 内独立 op を plan.concurrency で bounded 並列に dispatch する
|
|
242
|
-
// (runPlanAsync = A0 の bounded 並行 plan primitive を per-node thunk で直呼び)。並行を
|
|
243
|
-
// 潰さず runBehaviorAsync と同一の並列挙動(同一 emitted-op 多重集合 / 同一結果)を持つ。
|
|
244
|
-
// 逐次化(concurrency=1 へ改変)すると in-flight 並列が消え pin が壊れる(AC#2/#3 非空虚)。
|
|
245
|
-
const asyncFn = `async function run_${sanitize(plan.name)}_async(handlers: AsyncHandlers, input: Scope): Promise<Value> {
|
|
691
|
+
const fns = plan.ops
|
|
692
|
+
.map((o) => ` ${JSON.stringify(o.id)}: ${aw === "" ? "" : "async "}()${aw === "" ? ": ExecOutcome" : ": Promise<ExecOutcome>"} => {
|
|
693
|
+
${emitExecFn(o, aw, hvars)}
|
|
694
|
+
},`)
|
|
695
|
+
.join("\n");
|
|
696
|
+
return `${aw === "" ? "function" : "async function"} run_${fn}${suffix}(${params}): ${ret} {
|
|
246
697
|
const ops: OpSpec[] = ${emitOpsLiteral(plan)};
|
|
247
698
|
const results: Record<string, Value> = {};
|
|
248
699
|
const scope = (): Scope => ({ ...input, ...results });
|
|
249
|
-
${
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
${
|
|
253
|
-
throw new BehaviorFailure("UNKNOWN_NODE_KIND", \`op '\${op.id}' has no generated exec arm (fail-closed)\`);
|
|
700
|
+
const relationKinds: (RelationKind | undefined)[] = ${JSON.stringify(relationKinds)};
|
|
701
|
+
// per-op exec は事前解決した関数表で引く(線形 ID 探索は無い — bc#75)。
|
|
702
|
+
const execs: Record<string, ${aw === "" ? "() => ExecOutcome" : "() => Promise<ExecOutcome>"}> = {
|
|
703
|
+
${fns}
|
|
254
704
|
};
|
|
255
|
-
const
|
|
256
|
-
const
|
|
705
|
+
const exec: ${execT} = ${aw === "" ? "" : "async "}(op) => {
|
|
706
|
+
const f = execs[op.id];
|
|
707
|
+
if (!f) throw new BehaviorFailure("UNKNOWN_NODE_KIND", \`op '\${op.id}' has no generated exec arm (fail-closed)\`);
|
|
708
|
+
const outcome = ${aw}f();
|
|
257
709
|
if ("ok" in outcome) results[op.id] = outcome.ok;
|
|
258
710
|
return outcome;
|
|
259
711
|
};
|
|
260
|
-
const run =
|
|
712
|
+
const run = ${aw}${aw === "" ? "runPlan" : "runPlanAsync"}(${planLiteral}, ops, exec);
|
|
261
713
|
run.states.forEach((st, i) => {
|
|
262
714
|
if (st.status === "skipped") results[ops[i].id] = unproducedValue(relationKinds[i]);
|
|
263
715
|
});
|
|
264
716
|
return ${output};
|
|
265
717
|
}`;
|
|
266
|
-
return `${sync}\n\n${asyncFn}`;
|
|
267
718
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
719
|
+
// ══════════════════════════════════════════════════════════════════════════════════
|
|
720
|
+
// module 綴じ
|
|
721
|
+
// ══════════════════════════════════════════════════════════════════════════════════
|
|
722
|
+
function emitComponentFns(plan) {
|
|
723
|
+
const order = sequentialOrder(plan.component);
|
|
724
|
+
if (order !== null) {
|
|
725
|
+
const sync = emitSequentialFn(plan, order, "");
|
|
726
|
+
const asyncFn = emitSequentialFn(plan, order, "await ");
|
|
727
|
+
return `${sync}\n\n${asyncFn}`;
|
|
728
|
+
}
|
|
729
|
+
const sync = emitPlanDrivenFn(plan, "");
|
|
730
|
+
const asyncFn = emitPlanDrivenFn(plan, "await ");
|
|
731
|
+
return `${sync}\n\n${asyncFn}`;
|
|
271
732
|
}
|
|
272
733
|
function emitComponent(plan, _ctx) {
|
|
273
|
-
return
|
|
734
|
+
return emitComponentFns(plan);
|
|
735
|
+
}
|
|
736
|
+
/** bind 時 handler 解決の per-component wrapper(sync/async)。 */
|
|
737
|
+
function emitBindWrappers(plan) {
|
|
738
|
+
const catalogs = componentCatalogNames(plan);
|
|
739
|
+
if (catalogs.length > 0)
|
|
740
|
+
usage.handlerType = true;
|
|
741
|
+
const hvars = handlerVars(catalogs);
|
|
742
|
+
const fn = sanitize(plan.name);
|
|
743
|
+
const resolveSync = catalogs.map((c) => ` const ${hvars.get(c)} = handlers[${JSON.stringify(c)}];`).join("\n");
|
|
744
|
+
const argsSync = catalogs.map((c) => hvars.get(c)).concat("{ ...input }").join(", ");
|
|
745
|
+
return `function bind_${fn}(handlers: Handlers): (input?: Scope) => Value {
|
|
746
|
+
// handler 解決は bind 時に 1 回(catalog 名 → 実装)。呼び出しは解決済み参照の直呼び。
|
|
747
|
+
${resolveSync}
|
|
748
|
+
return (input: Scope = {}) => run_${fn}(${argsSync});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function bindAsync_${fn}(handlers: AsyncHandlers): (input?: Scope) => Promise<Value> {
|
|
752
|
+
${catalogs.map((c) => ` const ${hvars.get(c)} = handlers[${JSON.stringify(c)}];`).join("\n")}
|
|
753
|
+
return (input: Scope = {}) => run_${fn}_async(${argsSync});
|
|
754
|
+
}`;
|
|
755
|
+
}
|
|
756
|
+
/** 生成 module 内 native helper(静的配線 ref/concat の tail walk・型検査 — closure 無し)。 */
|
|
757
|
+
function emitNativeHelpers() {
|
|
758
|
+
const parts = [];
|
|
759
|
+
if (usage.slBind || usage.slField || usage.slCat) {
|
|
760
|
+
usage.exprFailure = true;
|
|
761
|
+
parts.push(`// ── native static-wiring helpers (bc#75) — no scope snapshots, no closures ─────────
|
|
762
|
+
// These reproduce expr-eval's ref/refOpt/concat semantics (same Failure codes) for the
|
|
763
|
+
// statically-wired inline path; the semantics SSoT stays the ExprFailure taxonomy.
|
|
764
|
+
function slTypeName(v: Value): string {
|
|
765
|
+
if (v === null) return "null";
|
|
766
|
+
if (typeof v === "bigint") return "int";
|
|
767
|
+
if (typeof v === "number") return "float";
|
|
768
|
+
if (Array.isArray(v)) return "arr";
|
|
769
|
+
return typeof v;
|
|
770
|
+
}
|
|
771
|
+
void slTypeName;`);
|
|
772
|
+
}
|
|
773
|
+
if (usage.slBind)
|
|
774
|
+
parts.push(`// slBind — scope 束縛 head の直読み(UNKNOWN_BINDING は evaluate と同一)。
|
|
775
|
+
function slBind(scope: Scope, head: string): Value {
|
|
776
|
+
if (!Object.prototype.hasOwnProperty.call(scope, head))
|
|
777
|
+
throw new ExprFailure("UNKNOWN_BINDING", \`unknown binding: \${head}\`);
|
|
778
|
+
return scope[head];
|
|
779
|
+
}`);
|
|
780
|
+
if (usage.slField)
|
|
781
|
+
parts.push(`// slField — 静的 path の field walk(NULL_REF / MISSING_PROP / TYPE_MISMATCH / refOpt null 伝播)。
|
|
782
|
+
function slField(op: "ref" | "refOpt", cur: Value, path: readonly string[]): Value {
|
|
783
|
+
for (const seg of path) {
|
|
784
|
+
if (cur === null) {
|
|
785
|
+
if (op === "refOpt") return null;
|
|
786
|
+
throw new ExprFailure("NULL_REF", \`null intermediate at .\${seg}(?. を使う)\`);
|
|
787
|
+
}
|
|
788
|
+
if (typeof cur !== "object" || Array.isArray(cur))
|
|
789
|
+
throw new ExprFailure("TYPE_MISMATCH", \`cannot access .\${seg} on \${slTypeName(cur)}\`);
|
|
790
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg))
|
|
791
|
+
throw new ExprFailure("MISSING_PROP", \`missing property .\${seg}\`);
|
|
792
|
+
cur = (cur as Record<string, Value>)[seg];
|
|
793
|
+
}
|
|
794
|
+
return cur;
|
|
795
|
+
}`);
|
|
796
|
+
if (usage.slCat)
|
|
797
|
+
parts.push(`// slCat — n-ary concat(引数位置で評価済みの値を受ける — closure も part 関数値も無い。
|
|
798
|
+
// evaluate の concat と同じ「全評価 → string 検査」順・同じ TYPE_MISMATCH)。
|
|
799
|
+
function slCat(...parts: Value[]): string {
|
|
800
|
+
for (const p of parts)
|
|
801
|
+
if (typeof p !== "string")
|
|
802
|
+
throw new ExprFailure("TYPE_MISMATCH", \`concat: string のみ(got \${slTypeName(p)}。数値の暗黙 toString は存在しない)\`);
|
|
803
|
+
return (parts as string[]).join("");
|
|
804
|
+
}`);
|
|
805
|
+
return parts.join("\n\n");
|
|
274
806
|
}
|
|
275
807
|
function emitModule(plans, ctx) {
|
|
276
808
|
const { fingerprint, specVersions, componentNames, runtimeImport } = ctx;
|
|
277
809
|
const spec = runtimeImport;
|
|
278
810
|
const names = componentNames.map((n) => JSON.stringify(n)).join(", ");
|
|
279
|
-
|
|
811
|
+
// buildComponentPlan(straightline.ts)は逐次経路が使わない op.expr を先に emit するため usage を
|
|
812
|
+
// 過大に立てる(例: cgp)。ここ(実 emit 直前)で reset し、実際に emit される emitComponent 内の
|
|
813
|
+
// 呼び出しだけが import/helper 要否を立てるようにする(bc#75: 全静的 component は cgp を使わない)。
|
|
814
|
+
resetUsage();
|
|
280
815
|
const fns = plans.map((p) => emitComponent(p, ctx)).join("\n\n");
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
.map((p) => ` if (name === ${JSON.stringify(p.name)}) return run_${sanitize(p.name)}_async(handlers, { ...input });`)
|
|
816
|
+
const wrappers = plans.map((p) => emitBindWrappers(p)).join("\n\n");
|
|
817
|
+
const bindArms = plans.map((p) => ` ${JSON.stringify(p.name)}: bind_${sanitize(p.name)}(handlers),`).join("\n");
|
|
818
|
+
const bindAsyncArms = plans
|
|
819
|
+
.map((p) => ` ${JSON.stringify(p.name)}: bindAsync_${sanitize(p.name)}(handlers),`)
|
|
286
820
|
.join("\n");
|
|
287
|
-
|
|
821
|
+
const helpers = emitNativeHelpers();
|
|
822
|
+
const valueImports = ["SPEC_VERSIONS"];
|
|
823
|
+
if (usage.behaviorFailure)
|
|
824
|
+
valueImports.push("BehaviorFailure");
|
|
825
|
+
if (usage.planFailure)
|
|
826
|
+
valueImports.push("PlanFailure");
|
|
827
|
+
if (usage.exprFailure)
|
|
828
|
+
valueImports.push("ExprFailure");
|
|
829
|
+
if (usage.cgp)
|
|
830
|
+
valueImports.push(`codegenPrimitives as ${CGP}`);
|
|
831
|
+
if (usage.runPlan)
|
|
832
|
+
valueImports.push("runPlan");
|
|
833
|
+
if (usage.runPlanAsync)
|
|
834
|
+
valueImports.push("runPlanAsync");
|
|
835
|
+
if (usage.unproducedValue)
|
|
836
|
+
valueImports.push("unproducedValue");
|
|
837
|
+
// Handlers/AsyncHandlers は bind/bindAsync のシグネチャで常に使う。Handler/AsyncHandler(単数)は
|
|
838
|
+
// handler を呼ぶ component(componentRef/map)を持つ run_* だけが param 型に使う(bc#75: cond-only
|
|
839
|
+
// 逐次 component は handler param を取らないので unused import になる)。
|
|
840
|
+
const typeImports = ["Handlers", "AsyncHandlers", "Scope", "Value"];
|
|
841
|
+
if (usage.handlerType)
|
|
842
|
+
typeImports.push("Handler", "AsyncHandler");
|
|
843
|
+
if (usage.planTypes)
|
|
844
|
+
typeImports.push("AsyncExec", "Exec", "ExecOutcome", "OpSpec", "RelationKind");
|
|
845
|
+
typeImports.sort();
|
|
846
|
+
return `// GENERATED by behavior-contracts common generator (bc#37/#42/#75, straight-line) — DO NOT EDIT.
|
|
288
847
|
// Input: portable component-graph IR only (scp-ir-architecture.md §5/§7.4).
|
|
289
848
|
// This is the STRAIGHT-LINE codegen endpoint (typed-codegen.md §4.1): each component is
|
|
290
|
-
// emitted as
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
849
|
+
// emitted as STATIC native code (bc#75). Strictly-sequential plans compile to plain
|
|
850
|
+
// statements in dependency order — no RunPlan, no ops table, no per-op ID search, no
|
|
851
|
+
// per-expression scope snapshots. Statically-wired refs read locals directly; static
|
|
852
|
+
// concat is a flat slCat(...) over already-evaluated values. Only plans with REAL bounded
|
|
853
|
+
// concurrency (multi-op stages) drive the runPlan/runPlanAsync primitive (semantics SSoT),
|
|
854
|
+
// and there the per-op exec is a pre-resolved function table. Fail-closed operator
|
|
855
|
+
// semantics (overflow arithmetic, strict-bool short-circuit and/or/cond/coalesce, obj/arr
|
|
856
|
+
// construction, checked literals) still route through the A0 expression-operator
|
|
857
|
+
// primitives (#36) — the semantics SSoT stays single-sourced. Handlers are resolved ONCE
|
|
858
|
+
// at bind() time and invoked through the resolved reference (UNKNOWN_COMPONENT stays
|
|
859
|
+
// fail-closed at op execution). Handlers are ALWAYS injected at the boundary; they are
|
|
860
|
+
// never generated. The portable IR is NOT embedded: this module carries only
|
|
861
|
+
// IR_FINGERPRINT + COMPONENT_NAMES (consumer-side fail-closed skew gate).
|
|
298
862
|
// irFingerprint: ${fingerprint}
|
|
299
|
-
import {
|
|
300
|
-
import type {
|
|
863
|
+
import { ${valueImports.join(", ")} } from ${JSON.stringify(spec)};
|
|
864
|
+
import type { ${typeImports.join(", ")} } from ${JSON.stringify(spec)};
|
|
301
865
|
|
|
302
866
|
/** Spec versions baked at generation time (fail-closed constant comparison at load). */
|
|
303
867
|
export const EXPECTED_SPEC_VERSIONS = { behavior: ${specVersions.behavior}, expression: ${specVersions.expression}, plan: ${specVersions.plan} } as const;
|
|
304
868
|
|
|
305
|
-
/**
|
|
869
|
+
/**
|
|
870
|
+
* FNV-1a 64 fingerprint of the source portable IR (canonicalJson discipline, #208),
|
|
871
|
+
* computed at GENERATION time. The IR itself is not embedded — consumers compare this
|
|
872
|
+
* constant against the fingerprint of the live IR they loaded (fail-closed skew gate).
|
|
873
|
+
*/
|
|
306
874
|
export const IR_FINGERPRINT = ${JSON.stringify(fingerprint)};
|
|
307
875
|
|
|
308
876
|
/** Component names exposed by bind(), in IR declaration order. */
|
|
309
877
|
export const COMPONENT_NAMES: readonly string[] = [${names}];
|
|
310
878
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// ── load-time fail-closed checks (#208 prepared-artifact discipline, checked ONCE) ─────
|
|
879
|
+
// ── load-time fail-closed checks (#208 prepared-artifact discipline, checked ONCE).
|
|
880
|
+
// IR-independent checks only (spec-version envelope pin) — no embedded IR to self-check. ──
|
|
315
881
|
for (const [k, want] of Object.entries(EXPECTED_SPEC_VERSIONS)) {
|
|
316
882
|
const got = (SPEC_VERSIONS as Record<string, number>)[k];
|
|
317
883
|
if (got !== want)
|
|
@@ -319,49 +885,34 @@ for (const [k, want] of Object.entries(EXPECTED_SPEC_VERSIONS)) {
|
|
|
319
885
|
\`behavior-contracts generated module: spec-version skew for '\${k}' (generated=\${want}, runtime=\${got}) — regenerate against this runtime (fail-closed)\`,
|
|
320
886
|
);
|
|
321
887
|
}
|
|
322
|
-
|
|
323
|
-
throw new Error(
|
|
324
|
-
\`behavior-contracts generated module: IR fingerprint mismatch (baked=\${IR_FINGERPRINT}, embedded literal=\${fingerprintComponentGraph(IR)}) — the generated code was modified or corrupted (fail-closed)\`,
|
|
325
|
-
);
|
|
326
|
-
|
|
888
|
+
${helpers ? `\n${helpers}\n` : ""}
|
|
327
889
|
// ── straight-line component functions (no runBehavior tree-walk) ───────────────────
|
|
328
890
|
${fns}
|
|
329
891
|
|
|
892
|
+
${wrappers}
|
|
893
|
+
|
|
330
894
|
/**
|
|
331
895
|
* bind — inject handlers (boundary injection; catalog name → implementation) and get one
|
|
332
|
-
* callable per component.
|
|
333
|
-
*
|
|
896
|
+
* callable per component. Handler resolution happens HERE, once; each callable runs the
|
|
897
|
+
* static straight-line function for that component through the resolved references — NOT
|
|
334
898
|
* the runBehavior tree-walk. Observationally equivalent to the runBehavior path:
|
|
335
899
|
* same values, same emitted op sequence, same Failure code/message.
|
|
336
900
|
*/
|
|
337
901
|
export function bind(handlers: Handlers): Record<string, (input?: Scope) => Value> {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function dispatch(name: string, handlers: Handlers, input: Scope): Value {
|
|
344
|
-
${dispatchArms}
|
|
345
|
-
throw new BehaviorFailure("UNKNOWN_ENTRY", \`component '\${name}' not found in this module (fail-closed)\`);
|
|
902
|
+
return {
|
|
903
|
+
${bindArms}
|
|
904
|
+
};
|
|
346
905
|
}
|
|
347
906
|
|
|
348
907
|
/**
|
|
349
|
-
* bindAsync — as bind, but
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
* the async ir path (same emitted-op multiset + same result). Map bodies iterate elements in
|
|
353
|
-
* declaration order (the parallel unit is the body node, per §7). Observationally equivalent to
|
|
354
|
-
* the async ir path.
|
|
908
|
+
* bindAsync — as bind, but async handlers are awaited. Components with a real-concurrency
|
|
909
|
+
* plan drive the runPlanAsync bounded-parallel primitive (same in-flight behavior as the
|
|
910
|
+
* async ir path); strictly-sequential components run as awaited straight statements.
|
|
355
911
|
*/
|
|
356
912
|
export function bindAsync(handlers: AsyncHandlers): Record<string, (input?: Scope) => Promise<Value>> {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function dispatchAsync(name: string, handlers: AsyncHandlers, input: Scope): Promise<Value> {
|
|
363
|
-
${dispatchAsyncArms}
|
|
364
|
-
throw new BehaviorFailure("UNKNOWN_ENTRY", \`component '\${name}' not found in this module (fail-closed)\`);
|
|
913
|
+
return {
|
|
914
|
+
${bindAsyncArms}
|
|
915
|
+
};
|
|
365
916
|
}
|
|
366
917
|
`;
|
|
367
918
|
}
|
|
@@ -370,15 +921,18 @@ const dialect = {
|
|
|
370
921
|
mapScopeVar: MAP_SCOPE,
|
|
371
922
|
emitComponent,
|
|
372
923
|
emitModule,
|
|
924
|
+
beginModule() {
|
|
925
|
+
resetUsage();
|
|
926
|
+
},
|
|
373
927
|
};
|
|
374
928
|
/**
|
|
375
929
|
* 公開再エクスポート(bc#46 B2 typed emitter が同じ TS dialect / scope 名を流用するため)。
|
|
376
930
|
* typed 経路は runtime 本体をこの straight-line dialect で綴じ、byte 同一の runtime を保つ。
|
|
377
931
|
*/
|
|
378
932
|
export const straightlineTsDialect = dialect;
|
|
379
|
-
/** 生成コード内の scope 式(`scope()
|
|
933
|
+
/** 生成コード内の scope 式(`scope()` — RunPlan 経路)。typed emitter が同名で port/output を評価する。 */
|
|
380
934
|
export const TS_SCOPE = SCOPE;
|
|
381
|
-
/** 生成コード内の map 要素 scope 式(`mscope()
|
|
935
|
+
/** 生成コード内の map 要素 scope 式(`mscope()` — RunPlan 経路)。 */
|
|
382
936
|
export const TS_MAP_SCOPE = MAP_SCOPE;
|
|
383
937
|
function emit(ctx) {
|
|
384
938
|
return emitStraightlineModule(ctx, dialect, SCOPE);
|
|
@@ -394,4 +948,6 @@ export const typescriptStraightlineEmitter = {
|
|
|
394
948
|
filenameHint: "behaviors.straightline.generated.ts",
|
|
395
949
|
emit,
|
|
396
950
|
};
|
|
951
|
+
void buildComponentPlan;
|
|
952
|
+
void 0;
|
|
397
953
|
//# sourceMappingURL=emit-straightline-typescript.js.map
|