behavior-contracts 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/behavior.d.ts +7 -0
- package/dist/behavior.d.ts.map +1 -1
- package/dist/behavior.js.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +10 -3
- package/dist/builder.js.map +1 -1
- package/dist/generator/async-plan.d.ts +78 -0
- package/dist/generator/async-plan.d.ts.map +1 -0
- package/dist/generator/async-plan.js +116 -0
- package/dist/generator/async-plan.js.map +1 -0
- package/dist/generator/core.d.ts +30 -9
- package/dist/generator/core.d.ts.map +1 -1
- package/dist/generator/core.js.map +1 -1
- package/dist/generator/{emit-straightline-typed-go.d.ts → emit-shared-go-typed.d.ts} +11 -17
- package/dist/generator/emit-shared-go-typed.d.ts.map +1 -0
- package/dist/generator/emit-shared-go-typed.js +572 -0
- package/dist/generator/emit-shared-go-typed.js.map +1 -0
- package/dist/generator/emit-shared-go.d.ts +29 -0
- package/dist/generator/emit-shared-go.d.ts.map +1 -0
- package/dist/generator/{emit-straightline-go.js → emit-shared-go.js} +2 -34
- package/dist/generator/emit-shared-go.js.map +1 -0
- package/dist/generator/{emit-straightline-typed-rust.d.ts → emit-shared-rust-typed.d.ts} +11 -17
- package/dist/generator/emit-shared-rust-typed.d.ts.map +1 -0
- package/dist/generator/emit-shared-rust-typed.js +576 -0
- package/dist/generator/emit-shared-rust-typed.js.map +1 -0
- package/dist/generator/{emit-straightline-rust.d.ts → emit-shared-rust.d.ts} +1 -36
- package/dist/generator/emit-shared-rust.d.ts.map +1 -0
- package/dist/generator/{emit-straightline-rust.js → emit-shared-rust.js} +2 -16
- package/dist/generator/emit-shared-rust.js.map +1 -0
- package/dist/generator/emit-shared-typescript.d.ts +12 -0
- package/dist/generator/emit-shared-typescript.d.ts.map +1 -0
- package/dist/generator/{emit-straightline-typescript.js → emit-shared-typescript.js} +5 -20
- package/dist/generator/emit-shared-typescript.js.map +1 -0
- package/dist/generator/emit-straightline-typed-typescript.d.ts +1 -1
- package/dist/generator/emit-straightline-typed-typescript.d.ts.map +1 -1
- package/dist/generator/emit-straightline-typed-typescript.js +1 -1
- package/dist/generator/emit-straightline-typed-typescript.js.map +1 -1
- package/dist/generator/emit-typed-native-go.d.ts.map +1 -1
- package/dist/generator/emit-typed-native-go.js +495 -75
- package/dist/generator/emit-typed-native-go.js.map +1 -1
- package/dist/generator/emit-typed-native-rust.d.ts +2 -1
- package/dist/generator/emit-typed-native-rust.d.ts.map +1 -1
- package/dist/generator/emit-typed-native-rust.js +553 -103
- package/dist/generator/emit-typed-native-rust.js.map +1 -1
- package/dist/generator/index.d.ts +2 -12
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +10 -62
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/native-expr.d.ts +120 -0
- package/dist/generator/native-expr.d.ts.map +1 -0
- package/dist/generator/native-expr.js +456 -0
- package/dist/generator/native-expr.js.map +1 -0
- package/dist/generator/typed.d.ts.map +1 -1
- package/dist/generator/typed.js +8 -0
- package/dist/generator/typed.js.map +1 -1
- package/dist/guard.js +14 -0
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plan.d.ts +6 -2
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +4 -0
- package/dist/plan.js.map +1 -1
- package/package.json +1 -1
- package/dist/generator/emit-go.d.ts +0 -19
- package/dist/generator/emit-go.d.ts.map +0 -1
- package/dist/generator/emit-go.js +0 -79
- package/dist/generator/emit-go.js.map +0 -1
- package/dist/generator/emit-raw-abi-go.d.ts +0 -45
- package/dist/generator/emit-raw-abi-go.d.ts.map +0 -1
- package/dist/generator/emit-raw-abi-go.js +0 -346
- package/dist/generator/emit-raw-abi-go.js.map +0 -1
- package/dist/generator/emit-raw-abi-rust.d.ts +0 -45
- package/dist/generator/emit-raw-abi-rust.d.ts.map +0 -1
- package/dist/generator/emit-raw-abi-rust.js +0 -380
- package/dist/generator/emit-raw-abi-rust.js.map +0 -1
- package/dist/generator/emit-rust.d.ts +0 -25
- package/dist/generator/emit-rust.d.ts.map +0 -1
- package/dist/generator/emit-rust.js +0 -125
- package/dist/generator/emit-rust.js.map +0 -1
- package/dist/generator/emit-straightline-go.d.ts +0 -60
- package/dist/generator/emit-straightline-go.d.ts.map +0 -1
- package/dist/generator/emit-straightline-go.js.map +0 -1
- package/dist/generator/emit-straightline-native-go.d.ts +0 -54
- package/dist/generator/emit-straightline-native-go.d.ts.map +0 -1
- package/dist/generator/emit-straightline-native-go.js +0 -835
- package/dist/generator/emit-straightline-native-go.js.map +0 -1
- package/dist/generator/emit-straightline-native-rust.d.ts +0 -27
- package/dist/generator/emit-straightline-native-rust.d.ts.map +0 -1
- package/dist/generator/emit-straightline-native-rust.js +0 -624
- package/dist/generator/emit-straightline-native-rust.js.map +0 -1
- package/dist/generator/emit-straightline-php.d.ts +0 -46
- package/dist/generator/emit-straightline-php.d.ts.map +0 -1
- package/dist/generator/emit-straightline-php.js +0 -941
- package/dist/generator/emit-straightline-php.js.map +0 -1
- package/dist/generator/emit-straightline-python.d.ts +0 -36
- package/dist/generator/emit-straightline-python.d.ts.map +0 -1
- package/dist/generator/emit-straightline-python.js +0 -855
- package/dist/generator/emit-straightline-python.js.map +0 -1
- package/dist/generator/emit-straightline-rust.d.ts.map +0 -1
- package/dist/generator/emit-straightline-rust.js.map +0 -1
- package/dist/generator/emit-straightline-typed-go.d.ts.map +0 -1
- package/dist/generator/emit-straightline-typed-go.js +0 -1114
- package/dist/generator/emit-straightline-typed-go.js.map +0 -1
- package/dist/generator/emit-straightline-typed-rust.d.ts.map +0 -1
- package/dist/generator/emit-straightline-typed-rust.js +0 -1155
- package/dist/generator/emit-straightline-typed-rust.js.map +0 -1
- package/dist/generator/emit-straightline-typescript.d.ts +0 -47
- package/dist/generator/emit-straightline-typescript.d.ts.map +0 -1
- package/dist/generator/emit-straightline-typescript.js.map +0 -1
|
@@ -1,855 +0,0 @@
|
|
|
1
|
-
import { emitDocLiteral, emitDocLiteralCompact } from "./literal.js";
|
|
2
|
-
import { emitStraightlineModule, sequentialOrder, analyzeSequentialOps, classifyExpr, isScopeFree, componentNeedsScope, } from "./straightline.js";
|
|
3
|
-
/** primitive namespace alias(生成コード内 — `behavior_contracts.primitives` を `cgp` で import)。 */
|
|
4
|
-
const CGP = "cgp";
|
|
5
|
-
/**
|
|
6
|
-
* scope 式(生成コード内)。scope は「input + それまでの ok 結果」のその時点のスナップで
|
|
7
|
-
* あり、run_behavior の base_scope() と同じく exec ごとに新規に組む。native/primitive 双方へ
|
|
8
|
-
* `_scope()`(関数呼び)を渡す(呼び時点の results を読む — 決定的)。
|
|
9
|
-
*/
|
|
10
|
-
const SCOPE = "_scope()";
|
|
11
|
-
/**
|
|
12
|
-
* map 本体(over/guard/ports)の要素 scope 式(生成コード内)。run_behavior の map と同じく
|
|
13
|
-
* `{**base_scope(), as: el}` を per-element で組む(生成コードは map arm 内で `_mscope()` を
|
|
14
|
-
* ローカル定義する)。
|
|
15
|
-
*/
|
|
16
|
-
const MAP_SCOPE = "_mscope()";
|
|
17
|
-
/**
|
|
18
|
-
* bc: codegen は IR を出力してはならない(no-ir invariant)。
|
|
19
|
-
* 非脱解釈の式(obj/arr/scalar/演算子被演算子)を IR リテラルとして埋め込み A0 primitive に
|
|
20
|
-
* 実行時解釈させる=インタプリタの松葉杖(=癌)は撤去。ここに来る式はネイティブ脱解釈が必要(次タスク)。
|
|
21
|
-
*/
|
|
22
|
-
function lit(node) {
|
|
23
|
-
throw new Error(`codegen no-ir invariant: refusing to emit IR literal for ${JSON.stringify(node).slice(0, 100)} — de-interpret this expression to native code (next task)`);
|
|
24
|
-
}
|
|
25
|
-
/** args 配列(比較・算術の二項/単項)を要素ごとに IR リテラルへ落として `, ` 連結。 */
|
|
26
|
-
function argList(arg) {
|
|
27
|
-
return arg.map((a) => lit(a)).join(", ");
|
|
28
|
-
}
|
|
29
|
-
// A0 primitives.py の引数形に合わせた op → 呼び式の綴じ方(TS oracle と違い python の primitive は
|
|
30
|
-
// 被演算子を **位置引数**で取る: add(a, b, scope) / cond(c, then, els, scope) / obj(fields, scope) …)。
|
|
31
|
-
// 命名は docs/codegen-primitives.md(python 列)に一致。ref/refOpt/concat と単純リテラルはここに無い
|
|
32
|
-
// (native 化するため emitExpr が直接 native コードを出す — 本 issue の脱解釈)。
|
|
33
|
-
const OP_EMITTERS = {
|
|
34
|
-
// リテラルラッパ(int は i64 checked / float は NaN/Inf checked — native 化しない)
|
|
35
|
-
int: (arg, s) => `${CGP}.int_lit(${lit(arg)}, ${s})`,
|
|
36
|
-
float: (arg, s) => `${CGP}.float_lit(${lit(arg)}, ${s})`,
|
|
37
|
-
// 構築(fields/elements は未評価 IR)
|
|
38
|
-
obj: (arg, s) => `${CGP}.obj(${lit(arg)}, ${s})`,
|
|
39
|
-
arr: (arg, s) => `${CGP}.arr(${lit(arg)}, ${s})`,
|
|
40
|
-
// 数値(checked。二項は a,b / 単項は a)
|
|
41
|
-
add: (arg, s) => `${CGP}.add(${argList(arg)}, ${s})`,
|
|
42
|
-
sub: (arg, s) => `${CGP}.sub(${argList(arg)}, ${s})`,
|
|
43
|
-
mul: (arg, s) => `${CGP}.mul(${argList(arg)}, ${s})`,
|
|
44
|
-
neg: (arg, s) => `${CGP}.neg(${argList(arg)}, ${s})`,
|
|
45
|
-
div: (arg, s) => `${CGP}.div(${argList(arg)}, ${s})`,
|
|
46
|
-
mod: (arg, s) => `${CGP}.mod(${argList(arg)}, ${s})`,
|
|
47
|
-
// 比較(二項)
|
|
48
|
-
eq: (arg, s) => `${CGP}.eq(${argList(arg)}, ${s})`,
|
|
49
|
-
ne: (arg, s) => `${CGP}.ne(${argList(arg)}, ${s})`,
|
|
50
|
-
lt: (arg, s) => `${CGP}.lt(${argList(arg)}, ${s})`,
|
|
51
|
-
le: (arg, s) => `${CGP}.le(${argList(arg)}, ${s})`,
|
|
52
|
-
gt: (arg, s) => `${CGP}.gt(${argList(arg)}, ${s})`,
|
|
53
|
-
ge: (arg, s) => `${CGP}.ge(${argList(arg)}, ${s})`,
|
|
54
|
-
// bool/null(短絡: 被演算子は未評価 IR で渡す — primitive 内 evaluate が短絡)
|
|
55
|
-
and: (arg, s) => `${CGP}.and_(${argList(arg)}, ${s})`,
|
|
56
|
-
or: (arg, s) => `${CGP}.or_(${argList(arg)}, ${s})`,
|
|
57
|
-
not: (arg, s) => `${CGP}.not_(${argList(arg)}, ${s})`,
|
|
58
|
-
coalesce: (arg, s) => `${CGP}.coalesce(${argList(arg)}, ${s})`,
|
|
59
|
-
cond: (arg, s) => `${CGP}.cond(${argList(arg)}, ${s})`,
|
|
60
|
-
len: (arg, s) => `${CGP}.len_(${argList(arg)}, ${s})`,
|
|
61
|
-
};
|
|
62
|
-
/** ref/refOpt の path(string 配列)を python list リテラルへ。 */
|
|
63
|
-
function pathLit(arg) {
|
|
64
|
-
return lit(arg);
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* emitExpr — Expression IR ノードを python 式へ落とす(脱解釈: 単純式は native、意味論式は primitive)。
|
|
68
|
-
*
|
|
69
|
-
* - `concat` → native `_sl_concat([...])`(str-only / arity>=2 を native チェック)。
|
|
70
|
-
* - `ref` / `refOpt` → native `_sl_ref(...)` / `_sl_ref_opt(...)`(path を native 走査)。
|
|
71
|
-
* - str/bool/None/int/float → native python リテラル(portability guard 済み → 安全域内で evaluate と同値)。
|
|
72
|
-
* - それ以外の演算子ノード → A0 primitive(`cgp.*`。被演算子は未評価 IR リテラル=短絡保存)。
|
|
73
|
-
* - 未知の形 → 到達しない(Portability Guard 通過済み)。防御的に primitive `eval_expr` へ。
|
|
74
|
-
*
|
|
75
|
-
* native 経路は `evaluate()` も `cgp.*` も呼ばない(AC#2/#7 の grep 証明の対象)。
|
|
76
|
-
*/
|
|
77
|
-
function emitExpr(node, scopeVar) {
|
|
78
|
-
// スカラリテラル(native)。
|
|
79
|
-
if (node === null)
|
|
80
|
-
return "None";
|
|
81
|
-
if (typeof node === "boolean")
|
|
82
|
-
return node ? "True" : "False";
|
|
83
|
-
if (typeof node === "string")
|
|
84
|
-
return lit(node);
|
|
85
|
-
if (typeof node === "number")
|
|
86
|
-
return lit(node); // literal.ts が安全域を fail-closed 済み
|
|
87
|
-
if (typeof node === "object" && !Array.isArray(node)) {
|
|
88
|
-
const keys = Object.keys(node);
|
|
89
|
-
if (keys.length === 1) {
|
|
90
|
-
const op = keys[0];
|
|
91
|
-
const arg = node[op];
|
|
92
|
-
// ── 単純式の native 化(脱解釈)──────────────────────────────────────
|
|
93
|
-
if (op === "ref")
|
|
94
|
-
return `_sl_ref(${pathLit(arg)}, ${scopeVar})`;
|
|
95
|
-
if (op === "refOpt")
|
|
96
|
-
return `_sl_ref_opt(${pathLit(arg)}, ${scopeVar})`;
|
|
97
|
-
if (op === "concat") {
|
|
98
|
-
const parts = arg.map((p) => emitExpr(p, scopeVar));
|
|
99
|
-
return `_sl_concat([${parts.join(", ")}])`;
|
|
100
|
-
}
|
|
101
|
-
// ── 意味論式(overflow/短絡/fail-closed)は A0 primitive SSoT へ ─────────
|
|
102
|
-
const em = OP_EMITTERS[op];
|
|
103
|
-
if (em)
|
|
104
|
-
return em(arg, scopeVar);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// 配列裸出し等の不正形は Portability Guard で弾かれ到達しないが、防御的に SSoT へ委譲。
|
|
108
|
-
return `${CGP}.eval_expr(${lit(node)}, ${scopeVar})`;
|
|
109
|
-
}
|
|
110
|
-
// ── per-node exec 分岐 / component 関数 / module の綴じ方 ──────────────────────
|
|
111
|
-
/** ops リテラル(run_plan に渡す OpSpec[] の python list リテラル)を綴じる。 */
|
|
112
|
-
function emitOpsLiteral(plan) {
|
|
113
|
-
const rows = plan.opsLiteral.map((o) => {
|
|
114
|
-
const parts = [`"id": ${JSON.stringify(o.id)}`, `"parent": ${o.parent === null ? "None" : String(o.parent)}`];
|
|
115
|
-
if (o.bindField !== undefined)
|
|
116
|
-
parts.push(`"bindField": ${JSON.stringify(o.bindField)}`);
|
|
117
|
-
if (o.relationKind !== undefined)
|
|
118
|
-
parts.push(`"relationKind": ${JSON.stringify(o.relationKind)}`);
|
|
119
|
-
if (o.policy !== undefined)
|
|
120
|
-
parts.push(`"policy": ${JSON.stringify(o.policy)}`);
|
|
121
|
-
return ` {${parts.join(", ")}},`;
|
|
122
|
-
});
|
|
123
|
-
return `[\n${rows.join("\n")}\n ]`;
|
|
124
|
-
}
|
|
125
|
-
/** map arm の要素 ports リテラル(要素 scope `_mscope()` で評価)。 */
|
|
126
|
-
function emitMapPorts(op) {
|
|
127
|
-
if (op.ports.length === 0)
|
|
128
|
-
return "{}";
|
|
129
|
-
return `{\n${op.ports
|
|
130
|
-
.map((p) => ` ${JSON.stringify(p.name)}: ${p.expr},`)
|
|
131
|
-
.join("\n")}\n }`;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* emitMapArm — map ノードの直線展開(要素反復・guard skip・per-element/batched handler・into
|
|
135
|
-
* zip-attach)。run_behavior の map 分岐を生成 python へ直線展開する。op-log 等価のため handler は
|
|
136
|
-
* run_behavior と同じ ctx(非 batched は bound 付き、batched は無し)・同じ ports 形で呼ぶ。guard は
|
|
137
|
-
* cond primitive(strict-bool SSoT)で、into は A0 `cgp.hydrate_into`(zip-attach SSoT)で解決する。
|
|
138
|
-
*/
|
|
139
|
-
function emitMapArm(op) {
|
|
140
|
-
const comp = JSON.stringify(op.component);
|
|
141
|
-
const nodeId = JSON.stringify(op.id);
|
|
142
|
-
const asKey = JSON.stringify(op.as);
|
|
143
|
-
const guardCheck = op.guard === undefined ? "" : `\n if ${op.guard} is not True:\n continue`;
|
|
144
|
-
const intoTail = op.into === undefined
|
|
145
|
-
? " return {\"ok\": _collected}"
|
|
146
|
-
: ` # map.into zip-attach(behavior.py applyInto): over と同じ長さ・順序で guard 通過要素へ
|
|
147
|
-
# 書き戻す(skip 要素は無変更、親不変)。Failure code は run_behavior と一致。
|
|
148
|
-
_augmented = []
|
|
149
|
-
_k = 0
|
|
150
|
-
for _mj, _oel in enumerate(_over):
|
|
151
|
-
if _k < len(_kept_idx) and _kept_idx[_k] == _mj:
|
|
152
|
-
if not isinstance(_oel, dict):
|
|
153
|
-
raise BehaviorFailure("MAP_INTO_ELEMENT_NOT_OBJECT", "map '${op.id}': 'into' requires object elements (element %d is not an object)" % _mj)
|
|
154
|
-
_augmented.append({**_oel, ${JSON.stringify(op.into)}: _collected[_k]})
|
|
155
|
-
_k += 1
|
|
156
|
-
else:
|
|
157
|
-
_augmented.append(_oel)
|
|
158
|
-
return {"ok": _augmented}`;
|
|
159
|
-
const body = op.batched
|
|
160
|
-
? ` _items = []
|
|
161
|
-
for _mi, _el in enumerate(_over):
|
|
162
|
-
def _mscope(_el=_el):
|
|
163
|
-
return {**_scope(), ${asKey}: _el}${guardCheck}
|
|
164
|
-
_items.append(${emitMapPorts(op)})
|
|
165
|
-
_kept_idx.append(_mi)
|
|
166
|
-
if not _items:
|
|
167
|
-
_collected = []
|
|
168
|
-
else:
|
|
169
|
-
h = handlers.get(${comp})
|
|
170
|
-
if h is None:
|
|
171
|
-
raise BehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")
|
|
172
|
-
outcome = h({"items": _items}, {"nodeId": ${nodeId}, "component": ${comp}})
|
|
173
|
-
if "error" in outcome:
|
|
174
|
-
return outcome
|
|
175
|
-
_r = outcome["ok"]
|
|
176
|
-
if not isinstance(_r, list) or len(_r) != len(_items):
|
|
177
|
-
raise BehaviorFailure("MAP_BATCH_RESULT_MISMATCH", "map '${op.id}': batched handler must return a list aligned to items (want %d)" % len(_items))
|
|
178
|
-
_collected = _r
|
|
179
|
-
${intoTail}`
|
|
180
|
-
: ` _collected = []
|
|
181
|
-
h = handlers.get(${comp})
|
|
182
|
-
if h is None:
|
|
183
|
-
raise BehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")
|
|
184
|
-
for _mi, _el in enumerate(_over):
|
|
185
|
-
def _mscope(_el=_el):
|
|
186
|
-
return {**_scope(), ${asKey}: _el}${guardCheck}
|
|
187
|
-
ports = ${emitMapPorts(op)}
|
|
188
|
-
outcome = h(ports, {"nodeId": ${nodeId}, "component": ${comp}, "bound": _el})
|
|
189
|
-
if "error" in outcome:
|
|
190
|
-
return outcome
|
|
191
|
-
_collected.append(outcome["ok"])
|
|
192
|
-
_kept_idx.append(_mi)
|
|
193
|
-
${intoTail}`;
|
|
194
|
-
return ` if i == ${op.index}:
|
|
195
|
-
_over = ${op.over}
|
|
196
|
-
if not isinstance(_over, list):
|
|
197
|
-
raise BehaviorFailure("MAP_OVER_NOT_ARRAY", "map '${op.id}': 'over' did not evaluate to an array")
|
|
198
|
-
_kept_idx = []
|
|
199
|
-
${body}`;
|
|
200
|
-
}
|
|
201
|
-
/** 1ノードの exec 分岐本体(index で分岐 — nodeKind ディスパッチではない)。 */
|
|
202
|
-
function emitExecArm(op) {
|
|
203
|
-
if (op.kind === "cond") {
|
|
204
|
-
// cond は純 Expression。scope を渡して1式評価(handler は呼ばない)。
|
|
205
|
-
return ` if i == ${op.index}:\n return {"ok": ${op.expr}}`;
|
|
206
|
-
}
|
|
207
|
-
if (op.kind === "map")
|
|
208
|
-
return emitMapArm(op);
|
|
209
|
-
// componentRef: 各 port を名前付き native/primitive 呼びで評価 → handler 注入呼び。
|
|
210
|
-
const portLines = op.ports.length === 0
|
|
211
|
-
? " ports = {}"
|
|
212
|
-
: ` ports = {\n${op.ports
|
|
213
|
-
.map((p) => ` ${JSON.stringify(p.name)}: ${p.expr},`)
|
|
214
|
-
.join("\n")}\n }`;
|
|
215
|
-
return ` if i == ${op.index}:
|
|
216
|
-
${portLines}
|
|
217
|
-
h = handlers.get(${JSON.stringify(op.component)})
|
|
218
|
-
if h is None:
|
|
219
|
-
raise BehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")
|
|
220
|
-
return h(ports, {"nodeId": ${JSON.stringify(op.id)}, "component": ${JSON.stringify(op.component)}})`;
|
|
221
|
-
}
|
|
222
|
-
/** dynamic 式に渡す scope 式(scope-free なら空 dict)。 */
|
|
223
|
-
function pySeqScope(node, ctx) {
|
|
224
|
-
if (isScopeFree(node))
|
|
225
|
-
return "{}";
|
|
226
|
-
if (ctx.mapAs)
|
|
227
|
-
return ctx.mapAs.mscopeVar;
|
|
228
|
-
if (!ctx.scopeVar)
|
|
229
|
-
throw new Error("straight-line py: scope-reading expr in a scopeless component (analysis bug)");
|
|
230
|
-
return ctx.scopeVar;
|
|
231
|
-
}
|
|
232
|
-
/** 静的 ref の head を配線解決した python 式(ローカル直読み / input fallback / UNKNOWN_BINDING)。 */
|
|
233
|
-
function pySeqRefHead(head, ctx) {
|
|
234
|
-
if (ctx.mapAs && head === ctx.mapAs.as)
|
|
235
|
-
return ctx.mapAs.elVar;
|
|
236
|
-
const idx = ctx.idToIndex.get(head);
|
|
237
|
-
if (idx !== undefined) {
|
|
238
|
-
const executedBefore = ctx.atOutput || ctx.pos[idx] < ctx.currentPos;
|
|
239
|
-
if (executedBefore) {
|
|
240
|
-
const meta = ctx.metas[idx];
|
|
241
|
-
const local = ctx.local.get(head);
|
|
242
|
-
if (ctx.atOutput || !(meta.canSkip || meta.alwaysSkip))
|
|
243
|
-
return local;
|
|
244
|
-
// 実行済みだが skip され得る: 未生成なら input の同名 key へ fall-through。
|
|
245
|
-
return `(${local} if ${ctx.okFlag.get(head)} else _sl_bind(input, ${JSON.stringify(head)}))`;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return `_sl_bind(input, ${JSON.stringify(head)})`;
|
|
249
|
-
}
|
|
250
|
-
/** 分類済み StaticExpr を python 式へ(逐次経路のネイティブ inline)。 */
|
|
251
|
-
function pySeqStatic(e, ctx) {
|
|
252
|
-
switch (e.kind) {
|
|
253
|
-
case "str":
|
|
254
|
-
return lit(e.value);
|
|
255
|
-
case "bool":
|
|
256
|
-
return e.value ? "True" : "False";
|
|
257
|
-
case "null":
|
|
258
|
-
return "None";
|
|
259
|
-
case "ref": {
|
|
260
|
-
const head = pySeqRefHead(e.path[0], ctx);
|
|
261
|
-
if (e.path.length === 1)
|
|
262
|
-
return head;
|
|
263
|
-
const rest = e.path.slice(1).map((p) => JSON.stringify(p)).join(", ");
|
|
264
|
-
return `_sl_field(${JSON.stringify(e.opt ? "refOpt" : "ref")}, ${head}, [${rest}])`;
|
|
265
|
-
}
|
|
266
|
-
case "concat":
|
|
267
|
-
return `_sl_concat([${e.parts.map((p) => pySeqStatic(p, ctx)).join(", ")}])`;
|
|
268
|
-
case "dynamic":
|
|
269
|
-
return emitExpr(e.node, pySeqScope(e.node, ctx));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/** Expression IR → python 式(逐次経路)。 */
|
|
273
|
-
function pySeqExpr(node, ctx) {
|
|
274
|
-
return pySeqStatic(classifyExpr(node), ctx);
|
|
275
|
-
}
|
|
276
|
-
/** node id 群 → 衝突しない決定的ローカル名。 */
|
|
277
|
-
function allocLocals(prefix, ids) {
|
|
278
|
-
const out = new Map();
|
|
279
|
-
const taken = new Set();
|
|
280
|
-
ids.forEach((id, i) => {
|
|
281
|
-
let name = `${prefix}${sanitize(id)}`;
|
|
282
|
-
if (taken.has(name))
|
|
283
|
-
name = `${name}_${i}`;
|
|
284
|
-
taken.add(name);
|
|
285
|
-
out.set(id, name);
|
|
286
|
-
});
|
|
287
|
-
return out;
|
|
288
|
-
}
|
|
289
|
-
/** 未生成表現の python 式。 */
|
|
290
|
-
function unproducedPy(relationKind) {
|
|
291
|
-
return `${CGP}.unproduced_value(${JSON.stringify(relationKind === "connection" ? "connection" : "single")})`;
|
|
292
|
-
}
|
|
293
|
-
/** policy 解釈(fail/retry → OP_FAILED、continue → skip)。plan.py と code/msg 一致。 */
|
|
294
|
-
function pySeqPolicyRaise(errExpr, opId, meta, ind) {
|
|
295
|
-
if (meta.policy === "retry") {
|
|
296
|
-
return [
|
|
297
|
-
`${ind}raise PlanFailure("OP_FAILED", "operation '${opId}' failed under 'retry' policy (exhausted): %s" % (${errExpr},))`,
|
|
298
|
-
];
|
|
299
|
-
}
|
|
300
|
-
return [`${ind}raise PlanFailure("OP_FAILED", "operation '${opId}' failed under 'fail' policy: %s" % (${errExpr},))`];
|
|
301
|
-
}
|
|
302
|
-
/** op 本体(ports 構築 + handler 呼び + policy 解釈 + 結果格納)を indent 付きで emit。 */
|
|
303
|
-
function emitPySeqExec(op, plan, ctx, lines, ind) {
|
|
304
|
-
const i = op.index;
|
|
305
|
-
const meta = ctx.metas[i];
|
|
306
|
-
const o = plan.opsLiteral[i];
|
|
307
|
-
const id = o.id;
|
|
308
|
-
const local = ctx.local.get(id);
|
|
309
|
-
const canSkip = meta.canSkip || meta.alwaysSkip;
|
|
310
|
-
const store = (valueExpr, target, sind) => {
|
|
311
|
-
target.push(`${sind}${local} = ${valueExpr}`);
|
|
312
|
-
if (canSkip)
|
|
313
|
-
target.push(`${sind}${ctx.okFlag.get(id)} = True`);
|
|
314
|
-
if (ctx.scopeVar)
|
|
315
|
-
target.push(`${sind}${ctx.scopeVar}[${JSON.stringify(id)}] = ${local}`);
|
|
316
|
-
};
|
|
317
|
-
if (op.kind === "cond") {
|
|
318
|
-
const c = plan.component.body[i].cond;
|
|
319
|
-
const node = { cond: [c.if, c.then, c.else] };
|
|
320
|
-
store(emitExpr(node, pySeqScope(node, ctx)), lines, ind);
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
if (op.kind === "map") {
|
|
324
|
-
emitPySeqMap(op, plan, ctx, lines, ind, store);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
// componentRef: ports 構築 → handler 呼び → policy 解釈。
|
|
328
|
-
const savedPos = ctx.currentPos;
|
|
329
|
-
const portsVar = `ports_${sanitize(id)}`;
|
|
330
|
-
if (op.ports.length === 0) {
|
|
331
|
-
lines.push(`${ind}${portsVar} = {}`);
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
lines.push(`${ind}${portsVar} = {`);
|
|
335
|
-
for (const p of op.ports) {
|
|
336
|
-
const node = plan.component.body[i].ports[p.name];
|
|
337
|
-
lines.push(`${ind} ${JSON.stringify(p.name)}: ${pySeqExpr(node, ctx)},`);
|
|
338
|
-
}
|
|
339
|
-
lines.push(`${ind}}`);
|
|
340
|
-
}
|
|
341
|
-
ctx.currentPos = savedPos;
|
|
342
|
-
const h = `h_${sanitize(id)}`;
|
|
343
|
-
const oc = `o_${sanitize(id)}`;
|
|
344
|
-
lines.push(`${ind}${h} = handlers.get(${JSON.stringify(op.component)})`);
|
|
345
|
-
lines.push(`${ind}if ${h} is None:`);
|
|
346
|
-
lines.push(`${ind} raise BehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")`);
|
|
347
|
-
lines.push(`${ind}${oc} = ${h}(${portsVar}, {"nodeId": ${JSON.stringify(id)}, "component": ${JSON.stringify(op.component)}})`);
|
|
348
|
-
if (meta.policy === "continue") {
|
|
349
|
-
lines.push(`${ind}if "error" not in ${oc}:`);
|
|
350
|
-
store(`${oc}["ok"]`, lines, `${ind} `);
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
lines.push(`${ind}if "error" in ${oc}:`);
|
|
354
|
-
lines.push(...pySeqPolicyRaise(`${oc}["error"]`, id, meta, `${ind} `));
|
|
355
|
-
store(`${oc}["ok"]`, lines, ind);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
/** emitPySeqMap — map ノードの逐次直線展開(python)。 */
|
|
359
|
-
function emitPySeqMap(op, plan, ctx, lines, ind, store) {
|
|
360
|
-
const i = op.index;
|
|
361
|
-
const meta = ctx.metas[i];
|
|
362
|
-
const m = plan.component.body[i].map;
|
|
363
|
-
const id = op.id;
|
|
364
|
-
const s = sanitize(id);
|
|
365
|
-
const comp = JSON.stringify(op.component);
|
|
366
|
-
const nodeId = JSON.stringify(op.id);
|
|
367
|
-
const asKey = JSON.stringify(m.as);
|
|
368
|
-
const guardIR = m.when === undefined ? undefined : { cond: [m.when, true, false] };
|
|
369
|
-
const mapNeedsScope = (guardIR !== undefined && !isScopeFree(guardIR)) ||
|
|
370
|
-
Object.keys(m.ports).some((k) => {
|
|
371
|
-
const cls = classifyExpr(m.ports[k]);
|
|
372
|
-
const needs = (e) => e.kind === "dynamic" ? !isScopeFree(e.node) : e.kind === "concat" ? e.parts.some(needs) : false;
|
|
373
|
-
return needs(cls);
|
|
374
|
-
});
|
|
375
|
-
const elVar = `_el_${s}`;
|
|
376
|
-
const mscopeVar = `_mscope_${s}`;
|
|
377
|
-
const overVar = `_over_${s}`;
|
|
378
|
-
const keptVar = `_kept_${s}`;
|
|
379
|
-
const collected = `_collected_${s}`;
|
|
380
|
-
const contFlag = meta.policy === "continue";
|
|
381
|
-
const skipVar = `_skip_${s}`;
|
|
382
|
-
const baseScopeExpr = ctx.scopeVar;
|
|
383
|
-
const mapCtx = { ...ctx, mapAs: { as: m.as, elVar, mscopeVar } };
|
|
384
|
-
lines.push(`${ind}${overVar} = ${pySeqExpr(m.over, ctx)}`);
|
|
385
|
-
lines.push(`${ind}if not isinstance(${overVar}, list):`);
|
|
386
|
-
lines.push(`${ind} raise BehaviorFailure("MAP_OVER_NOT_ARRAY", "map '${id}': 'over' did not evaluate to an array")`);
|
|
387
|
-
lines.push(`${ind}${keptVar} = []`);
|
|
388
|
-
lines.push(`${ind}${collected} = []`);
|
|
389
|
-
if (contFlag)
|
|
390
|
-
lines.push(`${ind}${skipVar} = False`);
|
|
391
|
-
const mscopeLine = (ind2) => {
|
|
392
|
-
if (!mapNeedsScope)
|
|
393
|
-
return [];
|
|
394
|
-
const base = baseScopeExpr ?? "input";
|
|
395
|
-
return [`${ind2}${mscopeVar} = {**${base}, ${asKey}: ${elVar}}`];
|
|
396
|
-
};
|
|
397
|
-
const guardLine = (ind2) => {
|
|
398
|
-
if (guardIR === undefined)
|
|
399
|
-
return [];
|
|
400
|
-
return [`${ind2}if ${emitExpr(guardIR, isScopeFree(guardIR) ? "{}" : mscopeVar)} is not True:`, `${ind2} continue`];
|
|
401
|
-
};
|
|
402
|
-
const portsExpr = (ind2) => {
|
|
403
|
-
if (op.ports.length === 0)
|
|
404
|
-
return "{}";
|
|
405
|
-
return `{\n${op.ports
|
|
406
|
-
.map((p) => `${ind2} ${JSON.stringify(p.name)}: ${pySeqExpr(m.ports[p.name], mapCtx)},`)
|
|
407
|
-
.join("\n")}\n${ind2}}`;
|
|
408
|
-
};
|
|
409
|
-
const h = `_h_${s}`;
|
|
410
|
-
lines.push(`${ind}${h} = handlers.get(${comp})`);
|
|
411
|
-
lines.push(`${ind}if ${h} is None:`);
|
|
412
|
-
lines.push(`${ind} raise BehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)")`);
|
|
413
|
-
if (op.batched) {
|
|
414
|
-
const items = `_items_${s}`;
|
|
415
|
-
lines.push(`${ind}${items} = []`);
|
|
416
|
-
lines.push(`${ind}for ${elVar}_i, ${elVar} in enumerate(${overVar}):`);
|
|
417
|
-
lines.push(...mscopeLine(`${ind} `), ...guardLine(`${ind} `));
|
|
418
|
-
lines.push(`${ind} ${items}.append(${portsExpr(`${ind} `)})`);
|
|
419
|
-
lines.push(`${ind} ${keptVar}.append(${elVar}_i)`);
|
|
420
|
-
lines.push(`${ind}if ${items}:`);
|
|
421
|
-
const oc = `_mo_${s}`;
|
|
422
|
-
lines.push(`${ind} ${oc} = ${h}({"items": ${items}}, {"nodeId": ${nodeId}, "component": ${comp}})`);
|
|
423
|
-
if (contFlag) {
|
|
424
|
-
lines.push(`${ind} if "error" in ${oc}:`, `${ind} ${skipVar} = True`, `${ind} else:`);
|
|
425
|
-
lines.push(...batchOkPy(oc, collected, items, id, `${ind} `));
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
lines.push(`${ind} if "error" in ${oc}:`);
|
|
429
|
-
lines.push(...pySeqPolicyRaise(`${oc}["error"]`, id, meta, `${ind} `));
|
|
430
|
-
lines.push(...batchOkPy(oc, collected, items, id, `${ind} `));
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
lines.push(`${ind}for ${elVar}_i, ${elVar} in enumerate(${overVar}):`);
|
|
435
|
-
lines.push(...mscopeLine(`${ind} `), ...guardLine(`${ind} `));
|
|
436
|
-
const oc = `_mo_${s}`;
|
|
437
|
-
lines.push(`${ind} ${oc} = ${h}(${portsExpr(`${ind} `)}, {"nodeId": ${nodeId}, "component": ${comp}, "bound": ${elVar}})`);
|
|
438
|
-
if (contFlag) {
|
|
439
|
-
lines.push(`${ind} if "error" in ${oc}:`, `${ind} ${skipVar} = True`, `${ind} break`);
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
lines.push(`${ind} if "error" in ${oc}:`);
|
|
443
|
-
lines.push(...pySeqPolicyRaise(`${oc}["error"]`, id, meta, `${ind} `));
|
|
444
|
-
}
|
|
445
|
-
lines.push(`${ind} ${collected}.append(${oc}["ok"])`, `${ind} ${keptVar}.append(${elVar}_i)`);
|
|
446
|
-
}
|
|
447
|
-
const finish = [];
|
|
448
|
-
const fInd = contFlag ? `${ind} ` : ind;
|
|
449
|
-
if (m.into === undefined) {
|
|
450
|
-
store(collected, finish, fInd);
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
const aug = `_aug_${s}`;
|
|
454
|
-
finish.push(`${fInd}${aug} = []`);
|
|
455
|
-
finish.push(`${fInd}_mk_${s} = 0`);
|
|
456
|
-
finish.push(`${fInd}for _mj_${s}, _oel_${s} in enumerate(${overVar}):`);
|
|
457
|
-
finish.push(`${fInd} if _mk_${s} < len(${keptVar}) and ${keptVar}[_mk_${s}] == _mj_${s}:`);
|
|
458
|
-
finish.push(`${fInd} if not isinstance(_oel_${s}, dict):`);
|
|
459
|
-
finish.push(`${fInd} raise BehaviorFailure("MAP_INTO_ELEMENT_NOT_OBJECT", "map '${id}': 'into' requires object elements (element %d is not an object)" % _mj_${s})`);
|
|
460
|
-
finish.push(`${fInd} ${aug}.append({**_oel_${s}, ${JSON.stringify(m.into)}: ${collected}[_mk_${s}]})`);
|
|
461
|
-
finish.push(`${fInd} _mk_${s} += 1`);
|
|
462
|
-
finish.push(`${fInd} else:`);
|
|
463
|
-
finish.push(`${fInd} ${aug}.append(_oel_${s})`);
|
|
464
|
-
store(aug, finish, fInd);
|
|
465
|
-
}
|
|
466
|
-
if (contFlag) {
|
|
467
|
-
lines.push(`${ind}if not ${skipVar}:`);
|
|
468
|
-
lines.push(...finish);
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
lines.push(...finish);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
/** batched handler の ok を collected へ(長さ整合検査つき)。 */
|
|
475
|
-
function batchOkPy(oc, collected, items, id, ind) {
|
|
476
|
-
return [
|
|
477
|
-
`${ind}_r = ${oc}["ok"]`,
|
|
478
|
-
`${ind}if not isinstance(_r, list) or len(_r) != len(${items}):`,
|
|
479
|
-
`${ind} raise BehaviorFailure("MAP_BATCH_RESULT_MISMATCH", "map '${id}': batched handler must return a list aligned to items (want %d)" % len(${items}))`,
|
|
480
|
-
`${ind}${collected} = _r`,
|
|
481
|
-
];
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* emitPySeqOp — 逐次直線化された 1 op の文列。policy 検証 → skip 判定(parent 連鎖 / bindField)→
|
|
485
|
-
* 実行 → policy 解釈 → 結果ローカル格納(+進行形 scope 書き足し)。
|
|
486
|
-
*/
|
|
487
|
-
function emitPySeqOp(op, plan, ctx, lines) {
|
|
488
|
-
const i = op.index;
|
|
489
|
-
const meta = ctx.metas[i];
|
|
490
|
-
const o = plan.opsLiteral[i];
|
|
491
|
-
const id = o.id;
|
|
492
|
-
const local = ctx.local.get(id);
|
|
493
|
-
lines.push(` # ── op '${id}' (${op.kind === "cond" ? "cond" : op.kind === "map" ? `map ${op.component}` : op.component}${o.policy ? `, policy ${JSON.stringify(o.policy)}` : ""}) ──`);
|
|
494
|
-
if (typeof meta.policy === "object") {
|
|
495
|
-
lines.push(` raise PlanFailure("UNKNOWN_POLICY", ${JSON.stringify(`unknown policy kind: ${meta.policy.invalid}`)})`);
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const canSkip = meta.canSkip || meta.alwaysSkip;
|
|
499
|
-
if (canSkip)
|
|
500
|
-
lines.push(` ${local} = None`, ` ${ctx.okFlag.get(id)} = False`);
|
|
501
|
-
if (meta.alwaysSkip) {
|
|
502
|
-
lines.push(` # parent '${o.parent}' runs later — unconditional skip.`);
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
const guards = [];
|
|
506
|
-
if (o.parent !== null) {
|
|
507
|
-
const pMeta = ctx.metas[o.parent];
|
|
508
|
-
if (pMeta.canSkip || pMeta.alwaysSkip)
|
|
509
|
-
guards.push(ctx.okFlag.get(plan.opsLiteral[o.parent].id));
|
|
510
|
-
}
|
|
511
|
-
if (o.bindField !== undefined && o.parent !== null) {
|
|
512
|
-
const pLocal = ctx.local.get(plan.opsLiteral[o.parent].id);
|
|
513
|
-
const bv = `_bv_${sanitize(id)}`;
|
|
514
|
-
const inner = [];
|
|
515
|
-
const ind = guards.length > 0 ? " " : " ";
|
|
516
|
-
inner.push(`${ind}if isinstance(${pLocal}, dict):`);
|
|
517
|
-
inner.push(`${ind} ${bv} = ${pLocal}.get(${JSON.stringify(o.bindField)})`);
|
|
518
|
-
inner.push(`${ind} if ${bv} is not None:`);
|
|
519
|
-
emitPySeqExec(op, plan, ctx, inner, `${ind} `);
|
|
520
|
-
if (guards.length > 0) {
|
|
521
|
-
lines.push(` if ${guards[0]}:`);
|
|
522
|
-
lines.push(...inner);
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
lines.push(...inner);
|
|
526
|
-
}
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
if (guards.length > 0) {
|
|
530
|
-
lines.push(` if ${guards[0]}:`);
|
|
531
|
-
emitPySeqExec(op, plan, ctx, lines, " ");
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
emitPySeqExec(op, plan, ctx, lines, " ");
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* emitSequentialPyFn — 逐次 plan の component を素の文列として綴じる(bc#75 の核)。
|
|
538
|
-
* run_plan / ops テーブル / _scope() snapshot / _exec closure / per-op ID 探索は無い。
|
|
539
|
-
*/
|
|
540
|
-
function emitSequentialPyFn(plan, order) {
|
|
541
|
-
const metas = analyzeSequentialOps(plan.opsLiteral, order);
|
|
542
|
-
const pos = new Array(plan.opsLiteral.length);
|
|
543
|
-
order.forEach((opIdx, k) => (pos[opIdx] = k));
|
|
544
|
-
const idToIndex = new Map();
|
|
545
|
-
plan.opsLiteral.forEach((o, i) => idToIndex.set(o.id, i));
|
|
546
|
-
const needScope = componentNeedsScope(plan);
|
|
547
|
-
const ids = plan.opsLiteral.map((o) => o.id);
|
|
548
|
-
const local = allocLocals("r_", ids);
|
|
549
|
-
const okFlag = allocLocals("ok_", ids);
|
|
550
|
-
const ctx = {
|
|
551
|
-
pos,
|
|
552
|
-
idToIndex,
|
|
553
|
-
metas,
|
|
554
|
-
local,
|
|
555
|
-
okFlag,
|
|
556
|
-
scopeVar: needScope ? "scope" : null,
|
|
557
|
-
currentPos: 0,
|
|
558
|
-
atOutput: false,
|
|
559
|
-
};
|
|
560
|
-
const lines = [];
|
|
561
|
-
lines.push(`def run_${sanitize(plan.name)}(handlers, input):`);
|
|
562
|
-
if (needScope)
|
|
563
|
-
lines.push(` scope = dict(input)`);
|
|
564
|
-
let unreachable = false;
|
|
565
|
-
for (const k of order.keys()) {
|
|
566
|
-
const opIdx = order[k];
|
|
567
|
-
ctx.currentPos = k;
|
|
568
|
-
if (unreachable)
|
|
569
|
-
break;
|
|
570
|
-
emitPySeqOp(plan.ops[opIdx], plan, ctx, lines);
|
|
571
|
-
if (typeof metas[opIdx].policy === "object")
|
|
572
|
-
unreachable = true;
|
|
573
|
-
}
|
|
574
|
-
if (!unreachable) {
|
|
575
|
-
ctx.atOutput = true;
|
|
576
|
-
for (const i of order) {
|
|
577
|
-
const meta = metas[i];
|
|
578
|
-
if (!(meta.canSkip || meta.alwaysSkip))
|
|
579
|
-
continue;
|
|
580
|
-
const id = plan.opsLiteral[i].id;
|
|
581
|
-
lines.push(` if not ${okFlag.get(id)}:`);
|
|
582
|
-
lines.push(` ${local.get(id)} = ${unproducedPy(plan.opsLiteral[i].relationKind)}`);
|
|
583
|
-
if (needScope)
|
|
584
|
-
lines.push(` scope[${JSON.stringify(id)}] = ${local.get(id)}`);
|
|
585
|
-
}
|
|
586
|
-
lines.push(` return ${pySeqExpr(plan.component.output, ctx)}`);
|
|
587
|
-
}
|
|
588
|
-
// 空関数体防止(unreachable かつ ops 空はあり得ないが、pass を保険で)。
|
|
589
|
-
if (lines.length === (needScope ? 2 : 1))
|
|
590
|
-
lines.push(` pass`);
|
|
591
|
-
return lines.join("\n");
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* emitPlanDrivenPyFn — 実並行 plan(複数 op の stage)の component を run_plan primitive で綴じる。
|
|
595
|
-
* run_behavior の実行フロー(results 蓄積 + _scope + run_plan + 未生成書き戻し + output 評価)を
|
|
596
|
-
* 生成コードに直線展開する。_scope snapshot は実並行の決定性のためこの経路だけが組む(bc#75)。
|
|
597
|
-
*/
|
|
598
|
-
function emitComponentFn(plan) {
|
|
599
|
-
const arms = plan.ops.map(emitExecArm).join("\n");
|
|
600
|
-
const planLiteral = plan.component.plan ? emitDocLiteral(plan.component.plan, "py", 1) : "None";
|
|
601
|
-
const output = emitExpr(plan.component.output, SCOPE);
|
|
602
|
-
const relationKinds = plan.opsLiteral.map((o) => (o.relationKind === "connection" ? "connection" : "single"));
|
|
603
|
-
const relLit = "[" + relationKinds.map((r) => JSON.stringify(r)).join(", ") + "]";
|
|
604
|
-
return `def run_${sanitize(plan.name)}(handlers, input):
|
|
605
|
-
ops = ${emitOpsLiteral(plan)}
|
|
606
|
-
results = {}
|
|
607
|
-
|
|
608
|
-
def _scope():
|
|
609
|
-
return {**input, **results}
|
|
610
|
-
|
|
611
|
-
relation_kinds = ${relLit}
|
|
612
|
-
|
|
613
|
-
def _exec(op, _bound):
|
|
614
|
-
i = _op_index(ops, op["id"])
|
|
615
|
-
${arms}
|
|
616
|
-
raise BehaviorFailure("UNKNOWN_NODE_KIND", "op '%s' has no generated exec arm (fail-closed)" % op["id"])
|
|
617
|
-
|
|
618
|
-
def _wrapped_exec(op, bound):
|
|
619
|
-
outcome = _exec(op, bound)
|
|
620
|
-
if "ok" in outcome:
|
|
621
|
-
results[op["id"]] = outcome["ok"]
|
|
622
|
-
return outcome
|
|
623
|
-
|
|
624
|
-
run = ${CGP}.run_plan(${planLiteral}, ops, _wrapped_exec)
|
|
625
|
-
for _i, _st in enumerate(run["states"]):
|
|
626
|
-
if _st is not None and _st.get("status") == "skipped":
|
|
627
|
-
results[ops[_i]["id"]] = ${CGP}.unproduced_value(relation_kinds[_i])
|
|
628
|
-
return ${output}`;
|
|
629
|
-
}
|
|
630
|
-
/** component 名を関数名に使える形へ(決定的。名前一意は DUPLICATE_COMPONENT で保証)。 */
|
|
631
|
-
function sanitize(name) {
|
|
632
|
-
return name.replace(/[^A-Za-z0-9_]/g, "_");
|
|
633
|
-
}
|
|
634
|
-
function emitComponent(plan, _ctx) {
|
|
635
|
-
const order = sequentialOrder(plan.component);
|
|
636
|
-
if (order !== null)
|
|
637
|
-
return emitSequentialPyFn(plan, order);
|
|
638
|
-
planDrivenNeeded = true;
|
|
639
|
-
return emitComponentFn(plan);
|
|
640
|
-
}
|
|
641
|
-
/** RunPlan 経路(run_plan / _op_index / _wrapped_exec)が要るか(構造的トラッキング)。 */
|
|
642
|
-
let planDrivenNeeded = false;
|
|
643
|
-
function emitModule(plans, ctx) {
|
|
644
|
-
const { fingerprint, specVersions, componentNames, runtimeImport } = ctx;
|
|
645
|
-
const names = componentNames.map((n) => JSON.stringify(n)).join(", ");
|
|
646
|
-
const namesTuple = componentNames.length === 1 ? `(${names},)` : `(${names})`;
|
|
647
|
-
planDrivenNeeded = false; // 実 emit で確定させる(buildComponentPlan の先行 emit に依らない)。
|
|
648
|
-
const fns = plans.map((p) => emitComponent(p, ctx)).join("\n\n\n");
|
|
649
|
-
// _op_index(線形 op-id 探索)は RunPlan 経路だけが使う。逐次のみのモジュールには emit しない
|
|
650
|
-
// (bc#75 anti-sham gate: 逐次生成コードに線形探索の残渣を残さない)。
|
|
651
|
-
const opIndexHelper = planDrivenNeeded
|
|
652
|
-
? `
|
|
653
|
-
|
|
654
|
-
def _op_index(ops, op_id):
|
|
655
|
-
for i, o in enumerate(ops):
|
|
656
|
-
if o["id"] == op_id:
|
|
657
|
-
return i
|
|
658
|
-
return -1`
|
|
659
|
-
: "";
|
|
660
|
-
const dispatchArms = plans
|
|
661
|
-
.map((p) => ` if name == ${JSON.stringify(p.name)}:\n return run_${sanitize(p.name)}(handlers, dict(input))`)
|
|
662
|
-
.join("\n");
|
|
663
|
-
return `# GENERATED by behavior-contracts common generator (bc#40, straight-line) — DO NOT EDIT.
|
|
664
|
-
# Input: portable component-graph IR only (scp-ir-architecture.md §5/§7.4).
|
|
665
|
-
# This is the STRAIGHT-LINE codegen endpoint (typed-codegen.md §4.1 / bc#40 A2): each
|
|
666
|
-
# component is emitted as a native python function that drives the plan via the run_plan
|
|
667
|
-
# primitive and evaluates every port + output either NATIVELY (concat -> python string
|
|
668
|
-
# concat; ref/refOpt -> native scope/field access) or through the A0 expression-operator
|
|
669
|
-
# primitives (#36) for overflow / short-circuit / fail-closed operators + injected handlers.
|
|
670
|
-
# It does NOT go through run_behavior's tree-walk (no nodeKind dispatch / no eval_ports loop
|
|
671
|
-
# / no generic exec). Scope: componentRef + cond + map (over/when/into/batched — bc#42 A3);
|
|
672
|
-
# into is zip-attached inline (native, MAP_INTO_ELEMENT_NOT_OBJECT fail-closed). plan.concurrency is preserved: the
|
|
673
|
-
# run_plan primitive dispatches same-stage independent ops up to plan.concurrency in flight
|
|
674
|
-
# (ThreadPoolExecutor), matching run_behavior's parallel behavior (bc#42 AC#2). Handlers are
|
|
675
|
-
# ALWAYS injected at the boundary.
|
|
676
|
-
# The portable IR is NOT embedded: this module carries only IR_FINGERPRINT +
|
|
677
|
-
# COMPONENT_NAMES, so interpreting the IR here is physically impossible. The fail-closed
|
|
678
|
-
# skew gate lives on the consumer side: compare IR_FINGERPRINT against the fingerprint of
|
|
679
|
-
# the live IR the consumer loaded.
|
|
680
|
-
# irFingerprint: ${fingerprint}
|
|
681
|
-
from ${runtimeImport} import SPEC_VERSIONS, BehaviorFailure, ExprFailure, PlanFailure, primitives as ${CGP}
|
|
682
|
-
|
|
683
|
-
# Spec versions baked at generation time (fail-closed constant comparison at load).
|
|
684
|
-
EXPECTED_SPEC_VERSIONS = {"behavior": ${specVersions.behavior}, "expression": ${specVersions.expression}, "plan": ${specVersions.plan}}
|
|
685
|
-
|
|
686
|
-
# FNV-1a 64 fingerprint of the source portable IR (canonical_json discipline, #208),
|
|
687
|
-
# computed at GENERATION time. The IR itself is not embedded — consumers compare this
|
|
688
|
-
# constant against the fingerprint of the live IR they loaded (fail-closed skew gate).
|
|
689
|
-
IR_FINGERPRINT = ${JSON.stringify(fingerprint)}
|
|
690
|
-
|
|
691
|
-
# Component names exposed by bind(), in IR declaration order.
|
|
692
|
-
COMPONENT_NAMES = ${namesTuple}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
def _check_generated_module():
|
|
696
|
-
# load-time fail-closed checks (#208 prepared-artifact discipline, checked ONCE).
|
|
697
|
-
# IR-independent checks only (spec-version envelope pin) — no embedded IR to self-check.
|
|
698
|
-
for _k, _want in EXPECTED_SPEC_VERSIONS.items():
|
|
699
|
-
_got = SPEC_VERSIONS.get(_k)
|
|
700
|
-
if _got != _want:
|
|
701
|
-
raise RuntimeError(
|
|
702
|
-
"behavior-contracts generated module: spec-version skew for '%s' "
|
|
703
|
-
"(generated=%s, runtime=%s) — regenerate against this runtime (fail-closed)"
|
|
704
|
-
% (_k, _want, _got)
|
|
705
|
-
)
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
_check_generated_module()
|
|
709
|
-
|
|
710
|
-
_MISSING = object()
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
# ── native-ized simple-expression helpers (de-interpretation — NO evaluate tree-walk) ──
|
|
714
|
-
# These statically unroll ref/refOpt/concat into native python, reproducing expr_eval.py's
|
|
715
|
-
# fail-closed semantics EXACTLY (same Failure codes via ExprFailure — the semantics SSoT is
|
|
716
|
-
# still one class). This is the Phase-B de-interpretation win: no recursive evaluate dispatch
|
|
717
|
-
# and no per-node operator lookup for pure ref/concat ports.
|
|
718
|
-
def _sl_ref(path, scope):
|
|
719
|
-
head = path[0]
|
|
720
|
-
if head not in scope:
|
|
721
|
-
raise ExprFailure("UNKNOWN_BINDING", "unknown binding: %s" % head)
|
|
722
|
-
cur = scope[head]
|
|
723
|
-
for seg in path[1:]:
|
|
724
|
-
if cur is None:
|
|
725
|
-
raise ExprFailure("NULL_REF", "null intermediate at .%s(?. を使う)" % seg)
|
|
726
|
-
if not isinstance(cur, dict):
|
|
727
|
-
raise ExprFailure("TYPE_MISMATCH", "cannot access .%s on %s" % (seg, _type_name(cur)))
|
|
728
|
-
got = cur.get(seg, _MISSING)
|
|
729
|
-
if got is _MISSING:
|
|
730
|
-
raise ExprFailure("MISSING_PROP", "missing property .%s" % seg)
|
|
731
|
-
cur = got
|
|
732
|
-
return cur
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
def _sl_ref_opt(path, scope):
|
|
736
|
-
head = path[0]
|
|
737
|
-
if head not in scope:
|
|
738
|
-
raise ExprFailure("UNKNOWN_BINDING", "unknown binding: %s" % head)
|
|
739
|
-
cur = scope[head]
|
|
740
|
-
for seg in path[1:]:
|
|
741
|
-
if cur is None:
|
|
742
|
-
return None # ?. null propagation
|
|
743
|
-
if not isinstance(cur, dict):
|
|
744
|
-
raise ExprFailure("TYPE_MISMATCH", "cannot access .%s on %s" % (seg, _type_name(cur)))
|
|
745
|
-
got = cur.get(seg, _MISSING)
|
|
746
|
-
if got is _MISSING:
|
|
747
|
-
raise ExprFailure("MISSING_PROP", "missing property .%s" % seg)
|
|
748
|
-
cur = got
|
|
749
|
-
return cur
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
def _sl_concat(parts):
|
|
753
|
-
# n-ary (min 2). string-only (TYPE_MISMATCH otherwise) — matches expr_eval.py concat.
|
|
754
|
-
if len(parts) < 2:
|
|
755
|
-
raise ExprFailure("INVALID_NODE", "concat expects >= 2 args, got %d" % len(parts))
|
|
756
|
-
for p in parts:
|
|
757
|
-
if not isinstance(p, str):
|
|
758
|
-
raise ExprFailure("TYPE_MISMATCH", "concat: string のみ(got %s)" % _type_name(p))
|
|
759
|
-
return "".join(parts)
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
def _type_name(v):
|
|
763
|
-
# expr_eval.py _type_name: JS-flavored type names for parity of messages.
|
|
764
|
-
if v is None:
|
|
765
|
-
return "null"
|
|
766
|
-
if isinstance(v, bool):
|
|
767
|
-
return "boolean"
|
|
768
|
-
if isinstance(v, str):
|
|
769
|
-
return "string"
|
|
770
|
-
if isinstance(v, (int, float)):
|
|
771
|
-
return "number"
|
|
772
|
-
if isinstance(v, list):
|
|
773
|
-
return "array"
|
|
774
|
-
return "object"
|
|
775
|
-
${opIndexHelper}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
# ── sequential-path static-wiring helpers (bc#75) — no scope snapshots ─────
|
|
779
|
-
# _sl_bind reads a scope-bound head directly; _sl_field walks a static path over an
|
|
780
|
-
# already-resolved head local (the head is a local read, not a scope walk). Same
|
|
781
|
-
# ExprFailure codes as expr_eval.py.
|
|
782
|
-
def _sl_bind(scope, head):
|
|
783
|
-
if head not in scope:
|
|
784
|
-
raise ExprFailure("UNKNOWN_BINDING", "unknown binding: %s" % head)
|
|
785
|
-
return scope[head]
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
def _sl_field(op, cur, path):
|
|
789
|
-
for seg in path:
|
|
790
|
-
if cur is None:
|
|
791
|
-
if op == "refOpt":
|
|
792
|
-
return None
|
|
793
|
-
raise ExprFailure("NULL_REF", "null intermediate at .%s(?. を使う)" % seg)
|
|
794
|
-
if not isinstance(cur, dict):
|
|
795
|
-
raise ExprFailure("TYPE_MISMATCH", "cannot access .%s on %s" % (seg, _type_name(cur)))
|
|
796
|
-
got = cur.get(seg, _MISSING)
|
|
797
|
-
if got is _MISSING:
|
|
798
|
-
raise ExprFailure("MISSING_PROP", "missing property .%s" % seg)
|
|
799
|
-
cur = got
|
|
800
|
-
return cur
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
# ── straight-line component functions (no run_behavior tree-walk) ───────────────────
|
|
804
|
-
${fns}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
def bind(handlers):
|
|
808
|
-
"""Inject handlers (boundary injection; catalog name -> implementation) and get one
|
|
809
|
-
callable per component. Each callable runs the straight-line function for that component
|
|
810
|
-
(plan via run_plan primitive + per-node native/primitive port eval + injected handler) —
|
|
811
|
-
NOT the run_behavior tree-walk. Observationally equivalent to the run_behavior path:
|
|
812
|
-
same values, same emitted op sequence, same Failure code/message."""
|
|
813
|
-
|
|
814
|
-
def _make(name):
|
|
815
|
-
def _call(input=None):
|
|
816
|
-
return _dispatch(name, handlers, dict(input or {}))
|
|
817
|
-
|
|
818
|
-
return _call
|
|
819
|
-
|
|
820
|
-
return {name: _make(name) for name in COMPONENT_NAMES}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
def _dispatch(name, handlers, input):
|
|
824
|
-
${dispatchArms}
|
|
825
|
-
raise BehaviorFailure("UNKNOWN_ENTRY", "component '%s' not found in this module (fail-closed)" % name)
|
|
826
|
-
`;
|
|
827
|
-
}
|
|
828
|
-
const dialect = {
|
|
829
|
-
emitExpr,
|
|
830
|
-
mapScopeVar: MAP_SCOPE,
|
|
831
|
-
emitComponent,
|
|
832
|
-
emitModule,
|
|
833
|
-
};
|
|
834
|
-
function emit(_ctx) {
|
|
835
|
-
// bc no-ir invariant: python の straight-line codegen は **未サポート**。これは IR を埋め込み
|
|
836
|
-
// インタプリタに委譲していた初期プロトタイプで、実 consumer 経路ではない(python ランタイムは
|
|
837
|
-
// `ir`/interpret exec surface を使う)。fake/半 native な codegen を出す代わりに loud に拒否する。
|
|
838
|
-
// 本物の脱解釈 codegen は rust/go/ts のみ。機構は将来の native 化に備えて配線ごと残す(void 参照)。
|
|
839
|
-
void [dialect, emitStraightlineModule, SCOPE];
|
|
840
|
-
throw new Error("straight-line codegen is not supported for python (bc no-ir: not de-interpreted) — " +
|
|
841
|
-
"use the ir/interpret exec surface, or generate rust/go/ts codegen");
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* pythonStraightlineEmitter — 言語識別子 `"python-straightline"` の emitter plugin。
|
|
845
|
-
* 既存の `python`(リテラル焼き込み・endpoint 3-literal → run_behavior)と併存する additive な
|
|
846
|
-
* 第2モード(単純式を native 化し `evaluate` 解釈を消す脱解釈経路 — typed-codegen.md §4.1)。
|
|
847
|
-
*/
|
|
848
|
-
export const pythonStraightlineEmitter = {
|
|
849
|
-
language: "python-straightline",
|
|
850
|
-
fileExtension: ".py",
|
|
851
|
-
defaultRuntimeImport: "behavior_contracts",
|
|
852
|
-
filenameHint: "behaviors.straightline.generated.py",
|
|
853
|
-
emit,
|
|
854
|
-
};
|
|
855
|
-
//# sourceMappingURL=emit-straightline-python.js.map
|