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,941 +0,0 @@
|
|
|
1
|
-
import { emitDocLiteralCompact } from "./literal.js";
|
|
2
|
-
import { emitStraightlineModule, sequentialOrder, analyzeSequentialOps, classifyExpr, isScopeFree, componentNeedsScope, } from "./straightline.js";
|
|
3
|
-
/** primitive クラスを保持する生成コード内変数(可変クラス名で静的呼び — namespace 非依存)。 */
|
|
4
|
-
const P = "$__P";
|
|
5
|
-
/**
|
|
6
|
-
* plan runtime クラス(`Plan::runPlan`)を保持する変数。runPlan は式演算子 primitive ではなく
|
|
7
|
-
* runtime top-level の実行 SSoT(TS oracle が top-level `runPlan` を import するのと同じ位置づけ)。
|
|
8
|
-
*/
|
|
9
|
-
const PLAN = "$__plan";
|
|
10
|
-
/** ref/refOpt/concat の native 化 helper(生成モジュール内クロージャ変数 — RunPlan 経路)。 */
|
|
11
|
-
const REF = "$__sl_ref";
|
|
12
|
-
const REF_OPT = "$__sl_ref_opt";
|
|
13
|
-
const CONCAT = "$__sl_concat";
|
|
14
|
-
/** 逐次経路の static-wiring helper(bc#75): local 直読み head bind / field walk / n-ary concat。 */
|
|
15
|
-
const SL_BIND = "$__sl_bind";
|
|
16
|
-
const SL_FIELD = "$__sl_field";
|
|
17
|
-
const SL_CAT = "$__sl_cat";
|
|
18
|
-
/**
|
|
19
|
-
* scope 式(生成コード内)。scope は「input + それまでの ok 結果」のその時点のスナップで
|
|
20
|
-
* あり、runBehavior の baseScope() と同じく呼び時点の $results を読む(決定的)。
|
|
21
|
-
*/
|
|
22
|
-
const SCOPE = "$scope()";
|
|
23
|
-
/**
|
|
24
|
-
* map 本体(over/guard/ports)の要素 scope 式(生成コード内)。runBehavior の map と同じく
|
|
25
|
-
* `array_merge($scope(), [$as => $el])` を per-element で組む(生成コードは map arm 内で
|
|
26
|
-
* `$mscope` クロージャをローカル定義し、port/guard 式はそれを呼ぶ)。
|
|
27
|
-
*/
|
|
28
|
-
const MAP_SCOPE = "$mscope()";
|
|
29
|
-
/**
|
|
30
|
-
* bc: codegen は IR を出力してはならない(no-ir invariant)。
|
|
31
|
-
* 非脱解釈の式(obj/arr/scalar/演算子被演算子)を IR リテラルとして埋め込み A0 primitive に
|
|
32
|
-
* 実行時解釈させる=インタプリタの松葉杖(=癌)は撤去。ここに来る式はネイティブ脱解釈が必要(次タスク)。
|
|
33
|
-
*/
|
|
34
|
-
function lit(node) {
|
|
35
|
-
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)`);
|
|
36
|
-
}
|
|
37
|
-
/** args 配列を要素ごとに IR リテラルへ落として `, ` 連結(二項/単項 primitive 用)。 */
|
|
38
|
-
function argList(arg) {
|
|
39
|
-
return arg.map((a) => lit(a)).join(", ");
|
|
40
|
-
}
|
|
41
|
-
const OP_EMITTERS = {
|
|
42
|
-
// リテラルラッパ(int は文字列, float は number)— 範囲/有限性検査は primitive SSoT。
|
|
43
|
-
int: (arg, s) => `${P}::intLit(${lit(arg)}, ${s})`,
|
|
44
|
-
float: (arg, s) => `${P}::floatLit(${lit(arg)}, ${s})`,
|
|
45
|
-
// 構築(fields/elements は未評価 IR)
|
|
46
|
-
obj: (arg, s) => `${P}::obj(${lit(arg)}, ${s})`,
|
|
47
|
-
arr: (arg, s) => `${P}::arr(${lit(arg)}, ${s})`,
|
|
48
|
-
// 数値(二項は [a,b]・単項は [a])
|
|
49
|
-
add: (arg, s) => `${P}::add(${argList(arg)}, ${s})`,
|
|
50
|
-
sub: (arg, s) => `${P}::sub(${argList(arg)}, ${s})`,
|
|
51
|
-
mul: (arg, s) => `${P}::mul(${argList(arg)}, ${s})`,
|
|
52
|
-
neg: (arg, s) => `${P}::neg(${argList(arg)}, ${s})`,
|
|
53
|
-
div: (arg, s) => `${P}::div(${argList(arg)}, ${s})`,
|
|
54
|
-
mod: (arg, s) => `${P}::mod(${argList(arg)}, ${s})`,
|
|
55
|
-
// 比較(二項)
|
|
56
|
-
eq: (arg, s) => `${P}::eq(${argList(arg)}, ${s})`,
|
|
57
|
-
ne: (arg, s) => `${P}::ne(${argList(arg)}, ${s})`,
|
|
58
|
-
lt: (arg, s) => `${P}::lt(${argList(arg)}, ${s})`,
|
|
59
|
-
le: (arg, s) => `${P}::le(${argList(arg)}, ${s})`,
|
|
60
|
-
gt: (arg, s) => `${P}::gt(${argList(arg)}, ${s})`,
|
|
61
|
-
ge: (arg, s) => `${P}::ge(${argList(arg)}, ${s})`,
|
|
62
|
-
// bool/null(短絡: 被演算子は未評価 IR で渡す — primitive 内 evaluate が短絡)
|
|
63
|
-
and: (arg, s) => `${P}::and(${argList(arg)}, ${s})`,
|
|
64
|
-
or: (arg, s) => `${P}::or(${argList(arg)}, ${s})`,
|
|
65
|
-
not: (arg, s) => `${P}::not(${argList(arg)}, ${s})`,
|
|
66
|
-
coalesce: (arg, s) => `${P}::coalesce(${argList(arg)}, ${s})`,
|
|
67
|
-
cond: (arg, s) => {
|
|
68
|
-
const a = arg;
|
|
69
|
-
return `${P}::cond(${lit(a[0])}, ${lit(a[1])}, ${lit(a[2])}, ${s})`;
|
|
70
|
-
},
|
|
71
|
-
len: (arg, s) => `${P}::len(${argList(arg)}, ${s})`,
|
|
72
|
-
};
|
|
73
|
-
/**
|
|
74
|
-
* emitExpr — Expression IR ノードを PHP の評価式へ落とす(A2 = 脱解釈)。context-free。
|
|
75
|
-
*
|
|
76
|
-
* NATIVE-IZE(式ツリーの evaluate 解釈を単純式で消す — AC2):
|
|
77
|
-
* - `ref`/`refOpt` → native helper 呼び(`$__sl_ref(path, scope)`)。path は静的 array リテラル。
|
|
78
|
-
* `ExprEval::evaluate` を経由せず PHP array/stdClass アクセスで直接解決する。
|
|
79
|
-
* - `concat` → native helper 呼び(`$__sl_concat([<各 part を再帰 emit>], scope)`)。
|
|
80
|
-
* 各 part は再帰的に emitExpr するので、part 内の ref も native になる(純 concat(ref…) は
|
|
81
|
-
* evaluate/primitive を一切踏まない)。
|
|
82
|
-
* - string/bool/null のむき出しリテラル → PHP リテラル直書き(evaluate は scalar を素通しする
|
|
83
|
-
* だけなので値等価。ネイティブ化で primitive 呼びも消える)。
|
|
84
|
-
*
|
|
85
|
-
* ROUTE-TO-PRIMITIVE(fail-closed 意味論の SSoT を再実装しない):
|
|
86
|
-
* - overflow/短絡/比較/構築/リテラルラッパ演算子 → {@link OP_EMITTERS}(A0 primitive 直呼び)。
|
|
87
|
-
* - bare number(安全整数域・有限性の検査が要る)や未知の形 → `$__P::evalExpr`(同一 SSoT)。
|
|
88
|
-
*
|
|
89
|
-
* Portability Guard を通過済み(生成時検査)なので未知 operator はここに来ない。
|
|
90
|
-
*/
|
|
91
|
-
function emitExpr(node, scopeVar) {
|
|
92
|
-
// むき出しスカラ(string/bool/null)は PHP リテラル直書きで native 化(値等価・primitive 消去)。
|
|
93
|
-
if (node === null)
|
|
94
|
-
return "null";
|
|
95
|
-
if (typeof node === "boolean")
|
|
96
|
-
return node ? "true" : "false";
|
|
97
|
-
if (typeof node === "string")
|
|
98
|
-
return lit(node);
|
|
99
|
-
if (typeof node === "object" && !Array.isArray(node)) {
|
|
100
|
-
const keys = Object.keys(node);
|
|
101
|
-
if (keys.length === 1) {
|
|
102
|
-
const op = keys[0];
|
|
103
|
-
const arg = node[op];
|
|
104
|
-
// ── native 化する単純式(AC2)─────────────────────────────────────────
|
|
105
|
-
if (op === "ref")
|
|
106
|
-
return `${REF}(${lit(arg)}, ${scopeVar})`;
|
|
107
|
-
if (op === "refOpt")
|
|
108
|
-
return `${REF_OPT}(${lit(arg)}, ${scopeVar})`;
|
|
109
|
-
if (op === "concat") {
|
|
110
|
-
const parts = arg.map((p) => emitExpr(p, scopeVar));
|
|
111
|
-
return `${CONCAT}([${parts.join(", ")}], ${scopeVar})`;
|
|
112
|
-
}
|
|
113
|
-
// ── overflow/短絡/比較/構築 → A0 primitive SSoT(fail-closed 意味論)──────
|
|
114
|
-
const em = OP_EMITTERS[op];
|
|
115
|
-
if (em)
|
|
116
|
-
return em(arg, scopeVar);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// bare number(範囲/有限性検査)・その他 → evaluate SSoT(同一意味論)へ委譲。
|
|
120
|
-
return `${P}::evalExpr(${lit(node)}, ${scopeVar})`;
|
|
121
|
-
}
|
|
122
|
-
// ── per-node exec 分岐 / component クロージャ / module の綴じ方 ──────────────────
|
|
123
|
-
/** ops リテラル(runPlan に渡す OpSpec[] の PHP array リテラル)を綴じる。 */
|
|
124
|
-
function emitOpsLiteral(plan) {
|
|
125
|
-
const rows = plan.opsLiteral.map((o) => {
|
|
126
|
-
const parts = [`'id' => ${lit(o.id)}`, `'parent' => ${o.parent === null ? "null" : String(o.parent)}`];
|
|
127
|
-
if (o.bindField !== undefined)
|
|
128
|
-
parts.push(`'bindField' => ${lit(o.bindField)}`);
|
|
129
|
-
if (o.relationKind !== undefined)
|
|
130
|
-
parts.push(`'relationKind' => ${lit(o.relationKind)}`);
|
|
131
|
-
if (o.policy !== undefined)
|
|
132
|
-
parts.push(`'policy' => ${lit(o.policy)}`);
|
|
133
|
-
return ` [${parts.join(", ")}],`;
|
|
134
|
-
});
|
|
135
|
-
return `[\n${rows.join("\n")}\n ]`;
|
|
136
|
-
}
|
|
137
|
-
/** map arm の要素 ports リテラル(要素 scope `$mscope()` で評価)。 */
|
|
138
|
-
function emitMapPorts(op) {
|
|
139
|
-
if (op.ports.length === 0)
|
|
140
|
-
return "[]";
|
|
141
|
-
return `[\n${op.ports
|
|
142
|
-
.map((p) => ` ${lit(p.name)} => ${p.expr},`)
|
|
143
|
-
.join("\n")}\n ]`;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* emitMapArm — map ノードの直線展開(要素反復・guard skip・per-element/batched handler・into
|
|
147
|
-
* zip-attach)。Behavior.php の map 分岐を生成 PHP へ直線展開する。op-log 等価のため handler は
|
|
148
|
-
* runBehavior と同じ ctx(非 batched は bound 付き、batched は無し)・同じ ports 形で呼ぶ。guard は
|
|
149
|
-
* Primitives::cond(strict-bool SSoT)、into は inline zip-attach(MAP_INTO_ELEMENT_NOT_OBJECT
|
|
150
|
-
* fail-closed で runBehavior と Failure code 一致)。要素反復は宣言どおり逐次。
|
|
151
|
-
*/
|
|
152
|
-
function emitMapArm(op) {
|
|
153
|
-
const comp = lit(op.component);
|
|
154
|
-
const nodeId = lit(op.id);
|
|
155
|
-
const asKey = lit(op.as);
|
|
156
|
-
const guardCheck = op.guard === undefined ? "" : `\n if (${op.guard} !== true) { continue; }`;
|
|
157
|
-
const intoTail = op.into === undefined
|
|
158
|
-
? " return ['ok' => $collected];"
|
|
159
|
-
: ` $augmented = [];
|
|
160
|
-
$mk = 0;
|
|
161
|
-
foreach ($over as $mj => $oel) {
|
|
162
|
-
if ($mk < count($keptIdx) && $keptIdx[$mk] === $mj) {
|
|
163
|
-
if (!($oel instanceof \\stdClass)) {
|
|
164
|
-
$__bhFail::raise('MAP_INTO_ELEMENT_NOT_OBJECT', "map '${op.id}': 'into' requires object elements (element {$mj} is not an object)");
|
|
165
|
-
}
|
|
166
|
-
$aug = clone $oel;
|
|
167
|
-
$aug->{${lit(op.into)}} = $collected[$mk];
|
|
168
|
-
$augmented[] = $aug;
|
|
169
|
-
$mk++;
|
|
170
|
-
} else {
|
|
171
|
-
$augmented[] = $oel;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return ['ok' => $augmented];`;
|
|
175
|
-
const body = op.batched
|
|
176
|
-
? ` $items = [];
|
|
177
|
-
foreach ($over as $mi => $el) {
|
|
178
|
-
$mscope = static function () use ($scope, $el): array { return array_merge($scope(), [${asKey} => $el]); };
|
|
179
|
-
$mscope; // bound by port/guard exprs${guardCheck}
|
|
180
|
-
$items[] = ${emitMapPorts(op)};
|
|
181
|
-
$keptIdx[] = $mi;
|
|
182
|
-
}
|
|
183
|
-
if (count($items) === 0) {
|
|
184
|
-
$collected = [];
|
|
185
|
-
} else {
|
|
186
|
-
if (!isset($handlers[${comp}])) {
|
|
187
|
-
$__bhFail::raise('UNKNOWN_COMPONENT', "component '${op.component}' has no handler (fail-closed)");
|
|
188
|
-
}
|
|
189
|
-
$h = $handlers[${comp}];
|
|
190
|
-
$outcome = $h(['items' => $items], ['nodeId' => ${nodeId}, 'component' => ${comp}]);
|
|
191
|
-
if (array_key_exists('error', $outcome)) { return $outcome; }
|
|
192
|
-
$r = $outcome['ok'];
|
|
193
|
-
if (!is_array($r) || !array_is_list($r) || count($r) !== count($items)) {
|
|
194
|
-
$__bhFail::raise('MAP_BATCH_RESULT_MISMATCH', "map '${op.id}': batched handler must return a list aligned to items (want " . count($items) . ')');
|
|
195
|
-
}
|
|
196
|
-
$collected = $r;
|
|
197
|
-
}
|
|
198
|
-
${intoTail}`
|
|
199
|
-
: ` $collected = [];
|
|
200
|
-
if (!isset($handlers[${comp}])) {
|
|
201
|
-
$__bhFail::raise('UNKNOWN_COMPONENT', "component '${op.component}' has no handler (fail-closed)");
|
|
202
|
-
}
|
|
203
|
-
$h = $handlers[${comp}];
|
|
204
|
-
foreach ($over as $mi => $el) {
|
|
205
|
-
$mscope = static function () use ($scope, $el): array { return array_merge($scope(), [${asKey} => $el]); };
|
|
206
|
-
$mscope; // bound by port/guard exprs${guardCheck}
|
|
207
|
-
$ports = ${emitMapPorts(op)};
|
|
208
|
-
$outcome = $h($ports, ['nodeId' => ${nodeId}, 'component' => ${comp}, 'bound' => $el]);
|
|
209
|
-
if (array_key_exists('error', $outcome)) { return $outcome; }
|
|
210
|
-
$collected[] = $outcome['ok'];
|
|
211
|
-
$keptIdx[] = $mi;
|
|
212
|
-
}
|
|
213
|
-
${intoTail}`;
|
|
214
|
-
return ` if ($i === ${op.index}) {
|
|
215
|
-
$over = ${op.over};
|
|
216
|
-
if (!is_array($over)) {
|
|
217
|
-
$__bhFail::raise('MAP_OVER_NOT_ARRAY', "map '${op.id}': 'over' did not evaluate to an array");
|
|
218
|
-
}
|
|
219
|
-
$keptIdx = [];
|
|
220
|
-
${body}
|
|
221
|
-
}`;
|
|
222
|
-
}
|
|
223
|
-
/** 1ノードの exec 分岐本体(index で分岐 — nodeKind ディスパッチではない)。 */
|
|
224
|
-
function emitExecArm(op) {
|
|
225
|
-
if (op.kind === "cond") {
|
|
226
|
-
// cond は純 Expression。scope を渡して1式評価(handler は呼ばない)。
|
|
227
|
-
return ` if ($i === ${op.index}) return ['ok' => ${op.expr}];`;
|
|
228
|
-
}
|
|
229
|
-
if (op.kind === "map")
|
|
230
|
-
return emitMapArm(op);
|
|
231
|
-
// componentRef: 各 port を名前付き native/primitive 評価 → handler 注入呼び。
|
|
232
|
-
const portLines = op.ports.length === 0
|
|
233
|
-
? " $ports = [];"
|
|
234
|
-
: ` $ports = [\n${op.ports
|
|
235
|
-
.map((p) => ` ${lit(p.name)} => ${p.expr},`)
|
|
236
|
-
.join("\n")}\n ];`;
|
|
237
|
-
return ` if ($i === ${op.index}) {
|
|
238
|
-
${portLines}
|
|
239
|
-
if (!isset($handlers[${lit(op.component)}])) {
|
|
240
|
-
$__bhFail::raise('UNKNOWN_COMPONENT', "component '${op.component}' has no handler (fail-closed)");
|
|
241
|
-
}
|
|
242
|
-
$h = $handlers[${lit(op.component)}];
|
|
243
|
-
return $h($ports, ['nodeId' => ${lit(op.id)}, 'component' => ${lit(op.component)}]);
|
|
244
|
-
}`;
|
|
245
|
-
}
|
|
246
|
-
/** component 名をクロージャキーに使える形へ(決定的・名前一意は DUPLICATE_COMPONENT で保証)。 */
|
|
247
|
-
function sanitize(name) {
|
|
248
|
-
return name.replace(/[^A-Za-z0-9_]/g, "_");
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* emitPlanSpec — plan を `Plan::runPlan(?array $plan, …)` が受ける **PHP array**(stdClass ではない)
|
|
252
|
-
* として綴じる。Behavior.php の `$planSpec`(`['groups'=>…, 'concurrency'=>…]`)と鏡映。plan 不在は
|
|
253
|
-
* `null`(1 op = 1 stage の逐次 fallback)。groups は int の list of list(IR リテラルをそのまま)。
|
|
254
|
-
*/
|
|
255
|
-
function emitPlanSpec(plan) {
|
|
256
|
-
if (plan === null || plan === undefined)
|
|
257
|
-
return "null";
|
|
258
|
-
const p = plan;
|
|
259
|
-
const groups = p.groups === undefined ? "[]" : lit(p.groups);
|
|
260
|
-
const concurrency = typeof p.concurrency === "number" ? String(p.concurrency) : "1";
|
|
261
|
-
return `['groups' => ${groups}, 'concurrency' => ${concurrency}]`;
|
|
262
|
-
}
|
|
263
|
-
/** dynamic 式に渡す scope 式(scope-free なら空 array)。 */
|
|
264
|
-
function phpSeqScope(node, ctx) {
|
|
265
|
-
if (isScopeFree(node))
|
|
266
|
-
return "[]";
|
|
267
|
-
if (ctx.mapAs)
|
|
268
|
-
return ctx.mapAs.mscopeVar;
|
|
269
|
-
if (!ctx.scopeVar)
|
|
270
|
-
throw new Error("straight-line php: scope-reading expr in a scopeless component (analysis bug)");
|
|
271
|
-
return ctx.scopeVar;
|
|
272
|
-
}
|
|
273
|
-
/** 静的 ref の head を配線解決した PHP 式(ローカル直読み / input fallback / UNKNOWN_BINDING)。 */
|
|
274
|
-
function phpSeqRefHead(head, ctx) {
|
|
275
|
-
if (ctx.mapAs && head === ctx.mapAs.as)
|
|
276
|
-
return ctx.mapAs.elVar;
|
|
277
|
-
const idx = ctx.idToIndex.get(head);
|
|
278
|
-
if (idx !== undefined) {
|
|
279
|
-
const executedBefore = ctx.atOutput || ctx.pos[idx] < ctx.currentPos;
|
|
280
|
-
if (executedBefore) {
|
|
281
|
-
const meta = ctx.metas[idx];
|
|
282
|
-
const local = ctx.local.get(head);
|
|
283
|
-
if (ctx.atOutput || !(meta.canSkip || meta.alwaysSkip))
|
|
284
|
-
return local;
|
|
285
|
-
return `(${ctx.okFlag.get(head)} ? ${local} : ${SL_BIND}($input, ${lit(head)}))`;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return `${SL_BIND}($input, ${lit(head)})`;
|
|
289
|
-
}
|
|
290
|
-
/** 分類済み StaticExpr を PHP 式へ(逐次経路のネイティブ inline)。 */
|
|
291
|
-
function phpSeqStatic(e, ctx) {
|
|
292
|
-
switch (e.kind) {
|
|
293
|
-
case "str":
|
|
294
|
-
return lit(e.value);
|
|
295
|
-
case "bool":
|
|
296
|
-
return e.value ? "true" : "false";
|
|
297
|
-
case "null":
|
|
298
|
-
return "null";
|
|
299
|
-
case "ref": {
|
|
300
|
-
const head = phpSeqRefHead(e.path[0], ctx);
|
|
301
|
-
if (e.path.length === 1)
|
|
302
|
-
return head;
|
|
303
|
-
const rest = e.path.slice(1).map((p) => lit(p)).join(", ");
|
|
304
|
-
return `${SL_FIELD}(${lit(e.opt ? "refOpt" : "ref")}, ${head}, [${rest}])`;
|
|
305
|
-
}
|
|
306
|
-
case "concat":
|
|
307
|
-
return `${SL_CAT}([${e.parts.map((p) => phpSeqStatic(p, ctx)).join(", ")}])`;
|
|
308
|
-
case "dynamic":
|
|
309
|
-
return emitExpr(e.node, phpSeqScope(e.node, ctx));
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
/** Expression IR → PHP 式(逐次経路)。 */
|
|
313
|
-
function phpSeqExpr(node, ctx) {
|
|
314
|
-
return phpSeqStatic(classifyExpr(node), ctx);
|
|
315
|
-
}
|
|
316
|
-
/** node id 群 → 衝突しない決定的ローカル名(PHP 変数、`$` 付き)。 */
|
|
317
|
-
function allocLocals(prefix, ids) {
|
|
318
|
-
const out = new Map();
|
|
319
|
-
const taken = new Set();
|
|
320
|
-
ids.forEach((id, i) => {
|
|
321
|
-
let name = `$${prefix}${sanitize(id)}`;
|
|
322
|
-
if (taken.has(name))
|
|
323
|
-
name = `${name}_${i}`;
|
|
324
|
-
taken.add(name);
|
|
325
|
-
out.set(id, name);
|
|
326
|
-
});
|
|
327
|
-
return out;
|
|
328
|
-
}
|
|
329
|
-
/** 未生成表現の PHP 式。 */
|
|
330
|
-
function unproducedPhp(relationKind) {
|
|
331
|
-
return `${P}::unproducedValue(${relationKind === "connection" ? "'connection'" : "null"})`;
|
|
332
|
-
}
|
|
333
|
-
/** policy 解釈(fail/retry → OP_FAILED、continue → skip)。Plan.php と code/msg 一致。 */
|
|
334
|
-
function phpSeqPolicyRaise(errExpr, opId, meta, ind) {
|
|
335
|
-
const msg = meta.policy === "retry"
|
|
336
|
-
? `"operation '${opId}' failed under 'retry' policy (exhausted): " . ${errExpr}`
|
|
337
|
-
: `"operation '${opId}' failed under 'fail' policy: " . ${errExpr}`;
|
|
338
|
-
return [`${ind}$__planFail::raise('OP_FAILED', ${msg});`];
|
|
339
|
-
}
|
|
340
|
-
/** op 本体(ports 構築 + handler 呼び + policy 解釈 + 結果格納)を indent 付きで emit。 */
|
|
341
|
-
function emitPhpSeqExec(op, plan, ctx, lines, ind) {
|
|
342
|
-
const i = op.index;
|
|
343
|
-
const meta = ctx.metas[i];
|
|
344
|
-
const o = plan.opsLiteral[i];
|
|
345
|
-
const id = o.id;
|
|
346
|
-
const local = ctx.local.get(id);
|
|
347
|
-
const store = (valueExpr, target, sind) => {
|
|
348
|
-
target.push(`${sind}${local} = ${valueExpr};`);
|
|
349
|
-
if (meta.canSkip || meta.alwaysSkip)
|
|
350
|
-
target.push(`${sind}${ctx.okFlag.get(id)} = true;`);
|
|
351
|
-
if (ctx.scopeVar)
|
|
352
|
-
target.push(`${sind}${ctx.scopeVar}[${lit(id)}] = ${local};`);
|
|
353
|
-
};
|
|
354
|
-
if (op.kind === "cond") {
|
|
355
|
-
const c = plan.component.body[i].cond;
|
|
356
|
-
const node = { cond: [c.if, c.then, c.else] };
|
|
357
|
-
store(emitExpr(node, phpSeqScope(node, ctx)), lines, ind);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (op.kind === "map") {
|
|
361
|
-
emitPhpSeqMap(op, plan, ctx, lines, ind, store);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
// componentRef: ports 構築 → handler 呼び → policy 解釈。
|
|
365
|
-
const savedPos = ctx.currentPos;
|
|
366
|
-
const portsVar = `$ports_${sanitize(id)}`;
|
|
367
|
-
if (op.ports.length === 0) {
|
|
368
|
-
lines.push(`${ind}${portsVar} = [];`);
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
lines.push(`${ind}${portsVar} = [`);
|
|
372
|
-
for (const p of op.ports) {
|
|
373
|
-
const node = plan.component.body[i].ports[p.name];
|
|
374
|
-
lines.push(`${ind} ${lit(p.name)} => ${phpSeqExpr(node, ctx)},`);
|
|
375
|
-
}
|
|
376
|
-
lines.push(`${ind}];`);
|
|
377
|
-
}
|
|
378
|
-
ctx.currentPos = savedPos;
|
|
379
|
-
const oc = `$o_${sanitize(id)}`;
|
|
380
|
-
lines.push(`${ind}if (!isset($handlers[${lit(op.component)}])) {`);
|
|
381
|
-
lines.push(`${ind} $__bhFail::raise('UNKNOWN_COMPONENT', "component '${op.component}' has no handler (fail-closed)");`);
|
|
382
|
-
lines.push(`${ind}}`);
|
|
383
|
-
lines.push(`${ind}$h_${sanitize(id)} = $handlers[${lit(op.component)}];`);
|
|
384
|
-
lines.push(`${ind}${oc} = $h_${sanitize(id)}(${portsVar}, ['nodeId' => ${lit(id)}, 'component' => ${lit(op.component)}]);`);
|
|
385
|
-
if (meta.policy === "continue") {
|
|
386
|
-
lines.push(`${ind}if (!array_key_exists('error', ${oc})) {`);
|
|
387
|
-
store(`${oc}['ok']`, lines, `${ind} `);
|
|
388
|
-
lines.push(`${ind}}`);
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
lines.push(`${ind}if (array_key_exists('error', ${oc})) {`);
|
|
392
|
-
lines.push(...phpSeqPolicyRaise(`${oc}['error']`, id, meta, `${ind} `));
|
|
393
|
-
lines.push(`${ind}}`);
|
|
394
|
-
store(`${oc}['ok']`, lines, ind);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
/** emitPhpSeqMap — map ノードの逐次直線展開(php)。 */
|
|
398
|
-
function emitPhpSeqMap(op, plan, ctx, lines, ind, store) {
|
|
399
|
-
const i = op.index;
|
|
400
|
-
const meta = ctx.metas[i];
|
|
401
|
-
const m = plan.component.body[i].map;
|
|
402
|
-
const id = op.id;
|
|
403
|
-
const s = sanitize(id);
|
|
404
|
-
const comp = lit(op.component);
|
|
405
|
-
const nodeId = lit(op.id);
|
|
406
|
-
const asKey = lit(m.as);
|
|
407
|
-
const guardIR = m.when === undefined ? undefined : { cond: [m.when, true, false] };
|
|
408
|
-
const mapNeedsScope = (guardIR !== undefined && !isScopeFree(guardIR)) ||
|
|
409
|
-
Object.keys(m.ports).some((k) => {
|
|
410
|
-
const cls = classifyExpr(m.ports[k]);
|
|
411
|
-
const needs = (e) => e.kind === "dynamic" ? !isScopeFree(e.node) : e.kind === "concat" ? e.parts.some(needs) : false;
|
|
412
|
-
return needs(cls);
|
|
413
|
-
});
|
|
414
|
-
const elVar = `$el_${s}`;
|
|
415
|
-
const mscopeVar = `$mscope_${s}`;
|
|
416
|
-
const overVar = `$over_${s}`;
|
|
417
|
-
const keptVar = `$kept_${s}`;
|
|
418
|
-
const collected = `$collected_${s}`;
|
|
419
|
-
const contFlag = meta.policy === "continue";
|
|
420
|
-
const skipVar = `$skip_${s}`;
|
|
421
|
-
const baseScopeExpr = ctx.scopeVar;
|
|
422
|
-
const mapCtx = { ...ctx, mapAs: { as: m.as, elVar, mscopeVar } };
|
|
423
|
-
lines.push(`${ind}${overVar} = ${phpSeqExpr(m.over, ctx)};`);
|
|
424
|
-
lines.push(`${ind}if (!is_array(${overVar})) {`);
|
|
425
|
-
lines.push(`${ind} $__bhFail::raise('MAP_OVER_NOT_ARRAY', "map '${id}': 'over' did not evaluate to an array");`);
|
|
426
|
-
lines.push(`${ind}}`);
|
|
427
|
-
lines.push(`${ind}${keptVar} = [];`);
|
|
428
|
-
lines.push(`${ind}${collected} = [];`);
|
|
429
|
-
if (contFlag)
|
|
430
|
-
lines.push(`${ind}${skipVar} = false;`);
|
|
431
|
-
const mscopeLine = (ind2) => {
|
|
432
|
-
if (!mapNeedsScope)
|
|
433
|
-
return [];
|
|
434
|
-
const base = baseScopeExpr ?? "$input";
|
|
435
|
-
return [`${ind2}${mscopeVar} = array_merge(${base}, [${asKey} => ${elVar}]);`];
|
|
436
|
-
};
|
|
437
|
-
const guardLine = (ind2) => {
|
|
438
|
-
if (guardIR === undefined)
|
|
439
|
-
return [];
|
|
440
|
-
return [`${ind2}if (${emitExpr(guardIR, isScopeFree(guardIR) ? "[]" : mscopeVar)} !== true) { continue; }`];
|
|
441
|
-
};
|
|
442
|
-
const portsExpr = (ind2) => {
|
|
443
|
-
if (op.ports.length === 0)
|
|
444
|
-
return "[]";
|
|
445
|
-
return `[\n${op.ports
|
|
446
|
-
.map((p) => `${ind2} ${lit(p.name)} => ${phpSeqExpr(m.ports[p.name], mapCtx)},`)
|
|
447
|
-
.join("\n")}\n${ind2}]`;
|
|
448
|
-
};
|
|
449
|
-
lines.push(`${ind}if (!isset($handlers[${comp}])) {`);
|
|
450
|
-
lines.push(`${ind} $__bhFail::raise('UNKNOWN_COMPONENT', "component '${op.component}' has no handler (fail-closed)");`);
|
|
451
|
-
lines.push(`${ind}}`);
|
|
452
|
-
lines.push(`${ind}$h_${s} = $handlers[${comp}];`);
|
|
453
|
-
if (op.batched) {
|
|
454
|
-
const items = `$items_${s}`;
|
|
455
|
-
lines.push(`${ind}${items} = [];`);
|
|
456
|
-
lines.push(`${ind}foreach (${overVar} as $mi_${s} => ${elVar}) {`);
|
|
457
|
-
lines.push(...mscopeLine(`${ind} `), ...guardLine(`${ind} `));
|
|
458
|
-
lines.push(`${ind} ${items}[] = ${portsExpr(`${ind} `)};`);
|
|
459
|
-
lines.push(`${ind} ${keptVar}[] = $mi_${s};`);
|
|
460
|
-
lines.push(`${ind}}`);
|
|
461
|
-
lines.push(`${ind}if (count(${items}) > 0) {`);
|
|
462
|
-
const oc = `$mo_${s}`;
|
|
463
|
-
lines.push(`${ind} ${oc} = $h_${s}(['items' => ${items}], ['nodeId' => ${nodeId}, 'component' => ${comp}]);`);
|
|
464
|
-
if (contFlag) {
|
|
465
|
-
lines.push(`${ind} if (array_key_exists('error', ${oc})) { ${skipVar} = true; } else {`);
|
|
466
|
-
lines.push(...batchOkPhp(oc, collected, items, id, s, `${ind} `));
|
|
467
|
-
lines.push(`${ind} }`);
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
lines.push(`${ind} if (array_key_exists('error', ${oc})) {`);
|
|
471
|
-
lines.push(...phpSeqPolicyRaise(`${oc}['error']`, id, meta, `${ind} `));
|
|
472
|
-
lines.push(`${ind} }`);
|
|
473
|
-
lines.push(...batchOkPhp(oc, collected, items, id, s, `${ind} `));
|
|
474
|
-
}
|
|
475
|
-
lines.push(`${ind}}`);
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
lines.push(`${ind}foreach (${overVar} as $mi_${s} => ${elVar}) {`);
|
|
479
|
-
lines.push(...mscopeLine(`${ind} `), ...guardLine(`${ind} `));
|
|
480
|
-
const oc = `$mo_${s}`;
|
|
481
|
-
lines.push(`${ind} $ports_${s} = ${portsExpr(`${ind} `)};`);
|
|
482
|
-
lines.push(`${ind} ${oc} = $h_${s}($ports_${s}, ['nodeId' => ${nodeId}, 'component' => ${comp}, 'bound' => ${elVar}]);`);
|
|
483
|
-
if (contFlag) {
|
|
484
|
-
lines.push(`${ind} if (array_key_exists('error', ${oc})) { ${skipVar} = true; break; }`);
|
|
485
|
-
}
|
|
486
|
-
else {
|
|
487
|
-
lines.push(`${ind} if (array_key_exists('error', ${oc})) {`);
|
|
488
|
-
lines.push(...phpSeqPolicyRaise(`${oc}['error']`, id, meta, `${ind} `));
|
|
489
|
-
lines.push(`${ind} }`);
|
|
490
|
-
}
|
|
491
|
-
lines.push(`${ind} ${collected}[] = ${oc}['ok'];`, `${ind} ${keptVar}[] = $mi_${s};`);
|
|
492
|
-
lines.push(`${ind}}`);
|
|
493
|
-
}
|
|
494
|
-
const finish = [];
|
|
495
|
-
const fInd = contFlag ? `${ind} ` : ind;
|
|
496
|
-
if (m.into === undefined) {
|
|
497
|
-
store(collected, finish, fInd);
|
|
498
|
-
}
|
|
499
|
-
else {
|
|
500
|
-
const aug = `$aug_${s}`;
|
|
501
|
-
finish.push(`${fInd}${aug} = [];`);
|
|
502
|
-
finish.push(`${fInd}$mk_${s} = 0;`);
|
|
503
|
-
finish.push(`${fInd}foreach (${overVar} as $mj_${s} => $oel_${s}) {`);
|
|
504
|
-
finish.push(`${fInd} if ($mk_${s} < count(${keptVar}) && ${keptVar}[$mk_${s}] === $mj_${s}) {`);
|
|
505
|
-
finish.push(`${fInd} if (!($oel_${s} instanceof \\stdClass)) {`);
|
|
506
|
-
finish.push(`${fInd} $__bhFail::raise('MAP_INTO_ELEMENT_NOT_OBJECT', "map '${id}': 'into' requires object elements (element {$mj_${s}} is not an object)");`);
|
|
507
|
-
finish.push(`${fInd} }`);
|
|
508
|
-
finish.push(`${fInd} $augOne_${s} = clone $oel_${s};`);
|
|
509
|
-
finish.push(`${fInd} $augOne_${s}->{${lit(m.into)}} = ${collected}[$mk_${s}];`);
|
|
510
|
-
finish.push(`${fInd} ${aug}[] = $augOne_${s};`);
|
|
511
|
-
finish.push(`${fInd} $mk_${s}++;`);
|
|
512
|
-
finish.push(`${fInd} } else {`);
|
|
513
|
-
finish.push(`${fInd} ${aug}[] = $oel_${s};`);
|
|
514
|
-
finish.push(`${fInd} }`);
|
|
515
|
-
finish.push(`${fInd}}`);
|
|
516
|
-
store(aug, finish, fInd);
|
|
517
|
-
}
|
|
518
|
-
if (contFlag) {
|
|
519
|
-
lines.push(`${ind}if (!${skipVar}) {`);
|
|
520
|
-
lines.push(...finish);
|
|
521
|
-
lines.push(`${ind}}`);
|
|
522
|
-
}
|
|
523
|
-
else {
|
|
524
|
-
lines.push(...finish);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
/** batched handler の ok を collected へ(長さ整合検査つき)。 */
|
|
528
|
-
function batchOkPhp(oc, collected, items, id, s, ind) {
|
|
529
|
-
return [
|
|
530
|
-
`${ind}$r_${s} = ${oc}['ok'];`,
|
|
531
|
-
`${ind}if (!is_array($r_${s}) || !array_is_list($r_${s}) || count($r_${s}) !== count(${items})) {`,
|
|
532
|
-
`${ind} $__bhFail::raise('MAP_BATCH_RESULT_MISMATCH', "map '${id}': batched handler must return a list aligned to items (want " . count(${items}) . ')');`,
|
|
533
|
-
`${ind}}`,
|
|
534
|
-
`${ind}${collected} = $r_${s};`,
|
|
535
|
-
];
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* emitPhpSeqOp — 逐次直線化された 1 op の文列。policy 検証 → skip 判定(parent 連鎖 / bindField)→
|
|
539
|
-
* 実行 → policy 解釈 → 結果ローカル格納(+進行形 scope 書き足し)。
|
|
540
|
-
*/
|
|
541
|
-
function emitPhpSeqOp(op, plan, ctx, lines) {
|
|
542
|
-
const i = op.index;
|
|
543
|
-
const meta = ctx.metas[i];
|
|
544
|
-
const o = plan.opsLiteral[i];
|
|
545
|
-
const id = o.id;
|
|
546
|
-
const local = ctx.local.get(id);
|
|
547
|
-
lines.push(` // ── op '${id}' (${op.kind === "cond" ? "cond" : op.kind === "map" ? `map ${op.component}` : op.component}${o.policy ? `, policy ${JSON.stringify(o.policy)}` : ""}) ──`);
|
|
548
|
-
if (typeof meta.policy === "object") {
|
|
549
|
-
lines.push(` $__planFail::raise('UNKNOWN_POLICY', ${lit(`unknown policy kind: ${meta.policy.invalid}`)});`);
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
const canSkip = meta.canSkip || meta.alwaysSkip;
|
|
553
|
-
if (canSkip)
|
|
554
|
-
lines.push(` ${local} = null;`, ` ${ctx.okFlag.get(id)} = false;`);
|
|
555
|
-
if (meta.alwaysSkip) {
|
|
556
|
-
lines.push(` // parent '${o.parent}' runs later — unconditional skip.`);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
const guards = [];
|
|
560
|
-
if (o.parent !== null) {
|
|
561
|
-
const pMeta = ctx.metas[o.parent];
|
|
562
|
-
if (pMeta.canSkip || pMeta.alwaysSkip)
|
|
563
|
-
guards.push(ctx.okFlag.get(plan.opsLiteral[o.parent].id));
|
|
564
|
-
}
|
|
565
|
-
if (o.bindField !== undefined && o.parent !== null) {
|
|
566
|
-
const pLocal = ctx.local.get(plan.opsLiteral[o.parent].id);
|
|
567
|
-
const bv = `$bv_${sanitize(id)}`;
|
|
568
|
-
const inner = [];
|
|
569
|
-
const ind = guards.length > 0 ? " " : " ";
|
|
570
|
-
inner.push(`${ind}if (${pLocal} instanceof \\stdClass) {`);
|
|
571
|
-
inner.push(`${ind} $bvProps_${sanitize(id)} = get_object_vars(${pLocal});`);
|
|
572
|
-
inner.push(`${ind} ${bv} = $bvProps_${sanitize(id)}[${lit(o.bindField)}] ?? null;`);
|
|
573
|
-
inner.push(`${ind} if (${bv} !== null) {`);
|
|
574
|
-
emitPhpSeqExec(op, plan, ctx, inner, `${ind} `);
|
|
575
|
-
inner.push(`${ind} }`);
|
|
576
|
-
inner.push(`${ind}}`);
|
|
577
|
-
if (guards.length > 0) {
|
|
578
|
-
lines.push(` if (${guards[0]}) {`);
|
|
579
|
-
lines.push(...inner);
|
|
580
|
-
lines.push(` }`);
|
|
581
|
-
}
|
|
582
|
-
else {
|
|
583
|
-
lines.push(...inner);
|
|
584
|
-
}
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
if (guards.length > 0) {
|
|
588
|
-
lines.push(` if (${guards[0]}) {`);
|
|
589
|
-
emitPhpSeqExec(op, plan, ctx, lines, " ");
|
|
590
|
-
lines.push(` }`);
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
emitPhpSeqExec(op, plan, ctx, lines, " ");
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* emitSequentialPhpClosure — 逐次 plan の component を素の文列クロージャとして綴じる(bc#75 の核)。
|
|
597
|
-
* runPlan / OpSpec 配列 / baseScope snapshot / exec closure / per-op ID 探索は無い。
|
|
598
|
-
*/
|
|
599
|
-
function emitSequentialPhpClosure(plan, order) {
|
|
600
|
-
const metas = analyzeSequentialOps(plan.opsLiteral, order);
|
|
601
|
-
const pos = new Array(plan.opsLiteral.length);
|
|
602
|
-
order.forEach((opIdx, k) => (pos[opIdx] = k));
|
|
603
|
-
const idToIndex = new Map();
|
|
604
|
-
plan.opsLiteral.forEach((o, i) => idToIndex.set(o.id, i));
|
|
605
|
-
const needScope = componentNeedsScope(plan);
|
|
606
|
-
const ids = plan.opsLiteral.map((o) => o.id);
|
|
607
|
-
const local = allocLocals("r_", ids);
|
|
608
|
-
const okFlag = allocLocals("ok_", ids);
|
|
609
|
-
const ctx = {
|
|
610
|
-
pos,
|
|
611
|
-
idToIndex,
|
|
612
|
-
metas,
|
|
613
|
-
local,
|
|
614
|
-
okFlag,
|
|
615
|
-
scopeVar: needScope ? "$scope" : null,
|
|
616
|
-
currentPos: 0,
|
|
617
|
-
atOutput: false,
|
|
618
|
-
};
|
|
619
|
-
const body = [];
|
|
620
|
-
if (needScope)
|
|
621
|
-
body.push(` $scope = $input;`);
|
|
622
|
-
let unreachable = false;
|
|
623
|
-
for (const k of order.keys()) {
|
|
624
|
-
const opIdx = order[k];
|
|
625
|
-
ctx.currentPos = k;
|
|
626
|
-
if (unreachable)
|
|
627
|
-
break;
|
|
628
|
-
emitPhpSeqOp(plan.ops[opIdx], plan, ctx, body);
|
|
629
|
-
if (typeof metas[opIdx].policy === "object")
|
|
630
|
-
unreachable = true;
|
|
631
|
-
}
|
|
632
|
-
if (!unreachable) {
|
|
633
|
-
ctx.atOutput = true;
|
|
634
|
-
for (const i of order) {
|
|
635
|
-
const meta = metas[i];
|
|
636
|
-
if (!(meta.canSkip || meta.alwaysSkip))
|
|
637
|
-
continue;
|
|
638
|
-
const id = plan.opsLiteral[i].id;
|
|
639
|
-
body.push(` if (!${okFlag.get(id)}) {`);
|
|
640
|
-
body.push(` ${local.get(id)} = ${unproducedPhp(plan.opsLiteral[i].relationKind)};`);
|
|
641
|
-
if (needScope)
|
|
642
|
-
body.push(` ${ctx.scopeVar}[${lit(id)}] = ${local.get(id)};`);
|
|
643
|
-
body.push(` }`);
|
|
644
|
-
}
|
|
645
|
-
body.push(` return ${phpSeqExpr(plan.component.output, ctx)};`);
|
|
646
|
-
}
|
|
647
|
-
// 逐次経路の use キャプチャ: primitive クラス・native helper・failure クラス。
|
|
648
|
-
return ` ${lit(sanitize(plan.name))} => static function (array $handlers, array $input) use (${P}, ${SL_BIND}, ${SL_FIELD}, ${SL_CAT}, ${REF}, ${REF_OPT}, ${CONCAT}, $__bhFail, $__planFail): mixed {
|
|
649
|
-
${body.join("\n")}
|
|
650
|
-
}`;
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* emitPlanDrivenPhpClosure — 実並行 plan(複数 op の stage)の component を runPlan primitive で綴じる。
|
|
654
|
-
* runBehavior の実行フロー(results 蓄積 + baseScope + runPlan + writeUnproduced + output 評価)を
|
|
655
|
-
* 生成コードに直線展開する。baseScope snapshot は実並行の決定性のためこの経路だけが組む(bc#75)。
|
|
656
|
-
*/
|
|
657
|
-
function emitComponentClosure(plan) {
|
|
658
|
-
const arms = plan.ops.map(emitExecArm).join("\n");
|
|
659
|
-
const planLiteral = emitPlanSpec(plan.component.plan);
|
|
660
|
-
const output = emitExpr(plan.component.output, SCOPE);
|
|
661
|
-
const relationKinds = plan.opsLiteral.map((o) => (o.relationKind === "connection" ? "'connection'" : "null"));
|
|
662
|
-
return ` ${lit(sanitize(plan.name))} => static function (array $handlers, array $input) use (${P}, ${PLAN}, ${REF}, ${REF_OPT}, ${CONCAT}, $__bhFail): mixed {
|
|
663
|
-
$ops = ${emitOpsLiteral(plan)};
|
|
664
|
-
$results = [];
|
|
665
|
-
$scope = static function () use ($input, &$results): array {
|
|
666
|
-
return array_merge($input, $results);
|
|
667
|
-
};
|
|
668
|
-
$relationKinds = [${relationKinds.join(", ")}];
|
|
669
|
-
$exec = static function (array $op, mixed $bound) use ($ops, $handlers, $scope, ${P}, ${REF}, ${REF_OPT}, ${CONCAT}, $__bhFail): array {
|
|
670
|
-
$i = null;
|
|
671
|
-
foreach ($ops as $k => $o) {
|
|
672
|
-
if ($o['id'] === $op['id']) { $i = $k; break; }
|
|
673
|
-
}
|
|
674
|
-
${arms}
|
|
675
|
-
$__bhFail::raise('UNKNOWN_NODE_KIND', "op '{$op['id']}' has no generated exec arm (fail-closed)");
|
|
676
|
-
};
|
|
677
|
-
$wrappedExec = static function (array $op, mixed $bound) use ($exec, &$results): array {
|
|
678
|
-
$outcome = $exec($op, $bound);
|
|
679
|
-
if (array_key_exists('ok', $outcome)) {
|
|
680
|
-
$results[$op['id']] = $outcome['ok'];
|
|
681
|
-
}
|
|
682
|
-
return $outcome;
|
|
683
|
-
};
|
|
684
|
-
$run = ${PLAN}::runPlan(${planLiteral}, $ops, $wrappedExec);
|
|
685
|
-
foreach ($run['states'] as $idx => $st) {
|
|
686
|
-
if ($st !== null && ($st['status'] ?? null) === 'skipped') {
|
|
687
|
-
$results[$ops[$idx]['id']] = ${P}::unproducedValue($relationKinds[$idx]);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
return ${output};
|
|
691
|
-
}`;
|
|
692
|
-
}
|
|
693
|
-
function emitComponent(plan, _ctx) {
|
|
694
|
-
const order = sequentialOrder(plan.component);
|
|
695
|
-
if (order !== null)
|
|
696
|
-
return emitSequentialPhpClosure(plan, order);
|
|
697
|
-
return emitComponentClosure(plan);
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* native helper($__sl_ref/$__sl_ref_opt/$__sl_concat)の生成テキスト。
|
|
701
|
-
* `ExprEval::evalRef` / `ExprEval::evaluate(concat)` の意味論を native PHP で完全再現する
|
|
702
|
-
* (evaluate を経由しない = 脱解釈の実体)。Failure code は ExprEval と一致。
|
|
703
|
-
*/
|
|
704
|
-
function emitNativeHelpers() {
|
|
705
|
-
return `// ── native-ized simple-expression helpers (bc#41 A2 de-interpretation) ──────────────
|
|
706
|
-
// These resolve ref/refOpt/concat by DIRECT php array/stdClass access + '.' concat,
|
|
707
|
-
// WITHOUT the ExprEval interpreter tree-walk (the point of the straight-line path). Their
|
|
708
|
-
// semantics + Failure codes match the ExprEval ref/concat evaluation exactly.
|
|
709
|
-
${REF} = static function (array $path, array $scope) use ($__exprFail): mixed {
|
|
710
|
-
// == ExprEval::evalRef('ref', ...) の native 版(中間 null は NULL_REF)。
|
|
711
|
-
if (count($path) === 0) {
|
|
712
|
-
$__exprFail::raise('INVALID_NODE', 'ref expects a non-empty string path');
|
|
713
|
-
}
|
|
714
|
-
foreach ($path as $p) {
|
|
715
|
-
if (!is_string($p)) {
|
|
716
|
-
$__exprFail::raise('INVALID_NODE', 'ref expects a non-empty string path');
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
$head = $path[0];
|
|
720
|
-
if (!array_key_exists($head, $scope)) {
|
|
721
|
-
$__exprFail::raise('UNKNOWN_BINDING', "unknown binding: {$head}");
|
|
722
|
-
}
|
|
723
|
-
$cur = $scope[$head];
|
|
724
|
-
for ($k = 1; $k < count($path); $k++) {
|
|
725
|
-
$seg = $path[$k];
|
|
726
|
-
if ($cur === null) {
|
|
727
|
-
$__exprFail::raise('NULL_REF', "null intermediate at .{$seg}(?. を使う)");
|
|
728
|
-
}
|
|
729
|
-
if (!($cur instanceof \\stdClass)) {
|
|
730
|
-
$__exprFail::raise('TYPE_MISMATCH', "cannot access .{$seg} on " . gettype($cur));
|
|
731
|
-
}
|
|
732
|
-
$props = get_object_vars($cur);
|
|
733
|
-
if (!array_key_exists($seg, $props)) {
|
|
734
|
-
$__exprFail::raise('MISSING_PROP', "missing property .{$seg}");
|
|
735
|
-
}
|
|
736
|
-
$cur = $props[$seg];
|
|
737
|
-
}
|
|
738
|
-
return $cur;
|
|
739
|
-
};
|
|
740
|
-
${REF_OPT} = static function (array $path, array $scope) use ($__exprFail): mixed {
|
|
741
|
-
// == ExprEval::evalRef('refOpt', ...) の native 版(中間 null は null 伝播)。
|
|
742
|
-
if (count($path) === 0) {
|
|
743
|
-
$__exprFail::raise('INVALID_NODE', 'refOpt expects a non-empty string path');
|
|
744
|
-
}
|
|
745
|
-
foreach ($path as $p) {
|
|
746
|
-
if (!is_string($p)) {
|
|
747
|
-
$__exprFail::raise('INVALID_NODE', 'refOpt expects a non-empty string path');
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
$head = $path[0];
|
|
751
|
-
if (!array_key_exists($head, $scope)) {
|
|
752
|
-
$__exprFail::raise('UNKNOWN_BINDING', "unknown binding: {$head}");
|
|
753
|
-
}
|
|
754
|
-
$cur = $scope[$head];
|
|
755
|
-
for ($k = 1; $k < count($path); $k++) {
|
|
756
|
-
$seg = $path[$k];
|
|
757
|
-
if ($cur === null) {
|
|
758
|
-
return null; // ?. の null 伝播
|
|
759
|
-
}
|
|
760
|
-
if (!($cur instanceof \\stdClass)) {
|
|
761
|
-
$__exprFail::raise('TYPE_MISMATCH', "cannot access .{$seg} on " . gettype($cur));
|
|
762
|
-
}
|
|
763
|
-
$props = get_object_vars($cur);
|
|
764
|
-
if (!array_key_exists($seg, $props)) {
|
|
765
|
-
$__exprFail::raise('MISSING_PROP', "missing property .{$seg}");
|
|
766
|
-
}
|
|
767
|
-
$cur = $props[$seg];
|
|
768
|
-
}
|
|
769
|
-
return $cur;
|
|
770
|
-
};
|
|
771
|
-
${CONCAT} = static function (array $parts, array $scope) use ($__exprFail): string {
|
|
772
|
-
// == ExprEval concat の native 版(n-ary, min 2, string のみ, php '.' で結合)。
|
|
773
|
-
// parts は emitExpr で各 part を native 評価済みの値配列(part 内 ref も native)。
|
|
774
|
-
if (count($parts) < 2) {
|
|
775
|
-
$__exprFail::raise('INVALID_NODE', 'concat expects >= 2 args, got ' . count($parts));
|
|
776
|
-
}
|
|
777
|
-
$out = '';
|
|
778
|
-
foreach ($parts as $p) {
|
|
779
|
-
if (!is_string($p)) {
|
|
780
|
-
$__exprFail::raise('TYPE_MISMATCH', 'concat: string のみ(got ' . gettype($p) . ')');
|
|
781
|
-
}
|
|
782
|
-
$out .= $p;
|
|
783
|
-
}
|
|
784
|
-
return $out;
|
|
785
|
-
};
|
|
786
|
-
|
|
787
|
-
// ── sequential-path static-wiring helpers (bc#75) — no scope snapshots, no closures ──
|
|
788
|
-
// $__sl_bind reads a scope-bound head directly; $__sl_field walks a static path over an
|
|
789
|
-
// ALREADY-RESOLVED head local (the head is a local read, not a scope walk); $__sl_cat
|
|
790
|
-
// concatenates already-evaluated string parts. Same ExprEval Failure codes.
|
|
791
|
-
${SL_BIND} = static function (array $scope, string $head) use ($__exprFail): mixed {
|
|
792
|
-
if (!array_key_exists($head, $scope)) {
|
|
793
|
-
$__exprFail::raise('UNKNOWN_BINDING', "unknown binding: {$head}");
|
|
794
|
-
}
|
|
795
|
-
return $scope[$head];
|
|
796
|
-
};
|
|
797
|
-
${SL_FIELD} = static function (string $op, mixed $cur, array $path) use ($__exprFail): mixed {
|
|
798
|
-
foreach ($path as $seg) {
|
|
799
|
-
if ($cur === null) {
|
|
800
|
-
if ($op === 'refOpt') { return null; }
|
|
801
|
-
$__exprFail::raise('NULL_REF', "null intermediate at .{$seg}(?. を使う)");
|
|
802
|
-
}
|
|
803
|
-
if (!($cur instanceof \\stdClass)) {
|
|
804
|
-
$__exprFail::raise('TYPE_MISMATCH', "cannot access .{$seg} on " . gettype($cur));
|
|
805
|
-
}
|
|
806
|
-
$props = get_object_vars($cur);
|
|
807
|
-
if (!array_key_exists($seg, $props)) {
|
|
808
|
-
$__exprFail::raise('MISSING_PROP', "missing property .{$seg}");
|
|
809
|
-
}
|
|
810
|
-
$cur = $props[$seg];
|
|
811
|
-
}
|
|
812
|
-
return $cur;
|
|
813
|
-
};
|
|
814
|
-
${SL_CAT} = static function (array $parts) use ($__exprFail): string {
|
|
815
|
-
// already-evaluated string parts (bc#75). Same all-eval → string check as ExprEval concat.
|
|
816
|
-
$out = '';
|
|
817
|
-
foreach ($parts as $p) {
|
|
818
|
-
if (!is_string($p)) {
|
|
819
|
-
$__exprFail::raise('TYPE_MISMATCH', 'concat: string のみ(got ' . gettype($p) . ')');
|
|
820
|
-
}
|
|
821
|
-
$out .= $p;
|
|
822
|
-
}
|
|
823
|
-
return $out;
|
|
824
|
-
};`;
|
|
825
|
-
}
|
|
826
|
-
function emitModule(plans, ctx) {
|
|
827
|
-
const { fingerprint, specVersions, componentNames, runtimeImport } = ctx;
|
|
828
|
-
const names = componentNames.map((n) => lit(n)).join(", ");
|
|
829
|
-
const closures = plans.map((p) => emitComponent(p, ctx)).join(",\n");
|
|
830
|
-
const dispatchArms = plans
|
|
831
|
-
.map((p) => ` if ($name === ${lit(p.name)}) return ($componentFns[${lit(sanitize(p.name))}])($handlers, $input);`)
|
|
832
|
-
.join("\n");
|
|
833
|
-
return `<?php
|
|
834
|
-
|
|
835
|
-
// GENERATED by behavior-contracts common generator (bc#41, straight-line) — DO NOT EDIT.
|
|
836
|
-
// Input: portable component-graph IR only (scp-ir-architecture.md §5/§7.4).
|
|
837
|
-
// This is the STRAIGHT-LINE codegen endpoint (typed-codegen.md §4.1 / bc#41 A2-php): each
|
|
838
|
-
// component is emitted as a native PHP closure that drives the plan via the runPlan
|
|
839
|
-
// primitive. Simple expressions are NATIVE-IZED (ref/refOpt via direct array/stdClass
|
|
840
|
-
// access, concat via php '.'), removing the ExprEval::evaluate tree-walk for them; only
|
|
841
|
-
// overflow/short-circuit ops route through the A0 Primitives SSoT (#36). It does NOT go
|
|
842
|
-
// through Behavior::runBehavior's tree-walk (no nodeKind dispatch / no evalPorts loop /
|
|
843
|
-
// no generic exec). Scope: componentRef + cond + simple Expression ports
|
|
844
|
-
// (map/batched/concurrency → A3, hydration → A4). Handlers are ALWAYS injected at the
|
|
845
|
-
// boundary; they are never generated.
|
|
846
|
-
// The portable IR is NOT embedded: this module carries only IR_FINGERPRINT +
|
|
847
|
-
// COMPONENT_NAMES, so interpreting the IR here is physically impossible. The fail-closed
|
|
848
|
-
// skew gate lives on the consumer side: compare IR_FINGERPRINT against the fingerprint of
|
|
849
|
-
// the live IR the consumer loaded.
|
|
850
|
-
// irFingerprint: ${fingerprint}
|
|
851
|
-
|
|
852
|
-
declare(strict_types=1);
|
|
853
|
-
|
|
854
|
-
// Spec versions baked at generation time (fail-closed constant comparison at require).
|
|
855
|
-
$expectedSpecVersions = ['behavior' => ${specVersions.behavior}, 'expression' => ${specVersions.expression}, 'plan' => ${specVersions.plan}];
|
|
856
|
-
|
|
857
|
-
// FNV-1a 64 fingerprint of the source portable IR (canonical-json discipline, #208),
|
|
858
|
-
// computed at GENERATION time. The IR itself is not embedded — consumers compare this
|
|
859
|
-
// constant against the fingerprint of the live IR they loaded (fail-closed skew gate).
|
|
860
|
-
$irFingerprint = ${lit(fingerprint)};
|
|
861
|
-
|
|
862
|
-
// Component names exposed by bind(), in IR declaration order.
|
|
863
|
-
$componentNames = [${names}];
|
|
864
|
-
|
|
865
|
-
// Require-time fail-closed checks (#208 prepared-artifact discipline). IR-independent
|
|
866
|
-
// checks only (spec-version envelope pin) — the module has no embedded IR to self-check.
|
|
867
|
-
$runtimeSpecVersions = [
|
|
868
|
-
'behavior' => \\${runtimeImport}\\SpecVersions::BEHAVIOR,
|
|
869
|
-
'expression' => \\${runtimeImport}\\SpecVersions::EXPRESSION,
|
|
870
|
-
'plan' => \\${runtimeImport}\\SpecVersions::PLAN,
|
|
871
|
-
];
|
|
872
|
-
foreach ($expectedSpecVersions as $specKey => $specWant) {
|
|
873
|
-
$specGot = $runtimeSpecVersions[$specKey] ?? null;
|
|
874
|
-
if ($specGot !== $specWant) {
|
|
875
|
-
throw new \\RuntimeException("behavior-contracts generated module: spec-version skew for '{$specKey}' (generated={$specWant}, runtime={$specGot}) — regenerate against this runtime (fail-closed)");
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// Bound symbols used by the straight-line closures (primitive class + failure classes as
|
|
880
|
-
// variable class-names, so the closures stay namespace-agnostic).
|
|
881
|
-
${P} = '\\${runtimeImport}\\Primitives';
|
|
882
|
-
${PLAN} = '\\${runtimeImport}\\Plan';
|
|
883
|
-
$__bhFail = '\\${runtimeImport}\\BehaviorFailure';
|
|
884
|
-
$__exprFail = '\\${runtimeImport}\\ExprFailure';
|
|
885
|
-
$__planFail = '\\${runtimeImport}\\PlanFailure';
|
|
886
|
-
|
|
887
|
-
${emitNativeHelpers()}
|
|
888
|
-
|
|
889
|
-
// ── straight-line component closures (no runBehavior tree-walk) ─────────────────────
|
|
890
|
-
$componentFns = [
|
|
891
|
-
${closures},
|
|
892
|
-
];
|
|
893
|
-
|
|
894
|
-
// bind — inject handlers (boundary injection; catalog name → implementation) and get one
|
|
895
|
-
// executor closure per component. Each callable runs the straight-line closure for that
|
|
896
|
-
// component (plan via runPlan primitive + native/primitive port eval + injected handler)
|
|
897
|
-
// — NOT the runBehavior tree-walk. Observationally equivalent to the runBehavior path:
|
|
898
|
-
// same values, same emitted op sequence, same Failure code.
|
|
899
|
-
return (object) [
|
|
900
|
-
'EXPECTED_SPEC_VERSIONS' => $expectedSpecVersions,
|
|
901
|
-
'IR_FINGERPRINT' => $irFingerprint,
|
|
902
|
-
'COMPONENT_NAMES' => $componentNames,
|
|
903
|
-
'bind' => static function (array $handlers) use ($componentNames, $componentFns, $__bhFail): array {
|
|
904
|
-
$bound = [];
|
|
905
|
-
foreach ($componentNames as $name) {
|
|
906
|
-
$bound[$name] = static function (array $input = []) use ($name, $handlers, $componentFns, $__bhFail): mixed {
|
|
907
|
-
${dispatchArms}
|
|
908
|
-
$__bhFail::raise('UNKNOWN_ENTRY', "component '{$name}' not found in this module (fail-closed)");
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
return $bound;
|
|
912
|
-
},
|
|
913
|
-
];
|
|
914
|
-
`;
|
|
915
|
-
}
|
|
916
|
-
const dialect = {
|
|
917
|
-
emitExpr,
|
|
918
|
-
mapScopeVar: MAP_SCOPE,
|
|
919
|
-
emitComponent,
|
|
920
|
-
emitModule,
|
|
921
|
-
};
|
|
922
|
-
function emit(_ctx) {
|
|
923
|
-
// bc no-ir invariant: php の straight-line codegen は **未サポート**(IR 埋め込み + インタプリタ
|
|
924
|
-
// 委譲の初期プロトタイプ。php ランタイムは `ir`/interpret exec surface を使う)。fake codegen を
|
|
925
|
-
// 出さず loud に拒否。本物の脱解釈 codegen は rust/go/ts のみ。機構は将来用に残す(void 参照)。
|
|
926
|
-
void [dialect, emitStraightlineModule, SCOPE];
|
|
927
|
-
throw new Error("straight-line codegen is not supported for php (bc no-ir: not de-interpreted) — " +
|
|
928
|
-
"use the ir/interpret exec surface, or generate rust/go/ts codegen");
|
|
929
|
-
}
|
|
930
|
-
/**
|
|
931
|
-
* phpStraightlineEmitter — 言語識別子 `"php-straightline"` の emitter plugin。
|
|
932
|
-
* 既存の `php`(リテラル焼き込み・runBehavior 委譲)と併存する additive な第2モード。
|
|
933
|
-
*/
|
|
934
|
-
export const phpStraightlineEmitter = {
|
|
935
|
-
language: "php-straightline",
|
|
936
|
-
fileExtension: ".php",
|
|
937
|
-
defaultRuntimeImport: "BehaviorContracts",
|
|
938
|
-
filenameHint: "behaviors.straightline.generated.php",
|
|
939
|
-
emit,
|
|
940
|
-
};
|
|
941
|
-
//# sourceMappingURL=emit-straightline-php.js.map
|