behavior-contracts 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +546 -82
- 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 +597 -110
- 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,1155 +0,0 @@
|
|
|
1
|
-
import { GeneratorFailure } from "./core.js";
|
|
2
|
-
import { buildTypePlan, deriveTypeRef, findDecl, } from "./typed.js";
|
|
3
|
-
import { rustStraightlineEmitter, rustStraightlineInternals, setRustForceRunPlanImports } from "./emit-straightline-rust.js";
|
|
4
|
-
import { buildComponentPlan } from "./straightline.js";
|
|
5
|
-
/** primitive namespace(straight-line と共有)。 */
|
|
6
|
-
const P = rustStraightlineInternals.P;
|
|
7
|
-
/** struct-native exec の port scope 式(input params のみ — node-result Value scope は組まない)。 */
|
|
8
|
-
const PORT_SCOPE = "port_scope()";
|
|
9
|
-
// ── Rust typed materializer(TypedMaterializer seam の Rust 具体化 — 拡張 hook 実装)────
|
|
10
|
-
/** 可搬スカラ → Rust の具体型名(de-box 後の struct field 型)。int=i64 / float=f64 で区別。 */
|
|
11
|
-
function rustScalar(scalar) {
|
|
12
|
-
switch (scalar) {
|
|
13
|
-
case "string":
|
|
14
|
-
return "String";
|
|
15
|
-
case "int":
|
|
16
|
-
return "i64";
|
|
17
|
-
case "float":
|
|
18
|
-
return "f64";
|
|
19
|
-
case "bool":
|
|
20
|
-
return "bool";
|
|
21
|
-
case "null":
|
|
22
|
-
// null 単独型は Rust に無いので Value(Null のみ格納)。opt でラップされて現れるのが通常。
|
|
23
|
-
return "Value";
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/** TypeRef を Rust の具体型名へ(named→struct 名、arr→Vec、opt→Option、scalar→具体型)。 */
|
|
27
|
-
function renderTypeRef(ref) {
|
|
28
|
-
switch (ref.kind) {
|
|
29
|
-
case "scalar":
|
|
30
|
-
return rustScalar(ref.scalar);
|
|
31
|
-
case "opt":
|
|
32
|
-
// nullable は Rust の Option で表す(None = null)。
|
|
33
|
-
return `Option<${renderTypeRef(ref.inner)}>`;
|
|
34
|
-
case "arr":
|
|
35
|
-
return `Vec<${renderTypeRef(ref.elem)}>`;
|
|
36
|
-
case "named":
|
|
37
|
-
return ref.name;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/** wire field 名 → Rust の struct field 名(決定的・安全化)。元名は struct コメント/marshaller で保持。 */
|
|
41
|
-
function rustFieldName(wire) {
|
|
42
|
-
// 非識別子文字は `_`。先頭が数字なら `f_` を前置。予約語衝突は `r#` で回避(raw identifier)。
|
|
43
|
-
let safe = wire.replace(/[^A-Za-z0-9_]/g, "_");
|
|
44
|
-
if (/^[0-9]/.test(safe))
|
|
45
|
-
safe = `f_${safe}`;
|
|
46
|
-
if (safe.length === 0)
|
|
47
|
-
safe = "f_";
|
|
48
|
-
if (RUST_KEYWORDS.has(safe))
|
|
49
|
-
return `r#${safe}`;
|
|
50
|
-
return safe;
|
|
51
|
-
}
|
|
52
|
-
/** field 名衝突を避けるための Rust 予約語(raw identifier 化する)。 */
|
|
53
|
-
const RUST_KEYWORDS = new Set([
|
|
54
|
-
"as", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern", "false", "fn",
|
|
55
|
-
"for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
|
|
56
|
-
"self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where",
|
|
57
|
-
"while", "async", "await", "abstract", "become", "box", "do", "final", "macro", "override",
|
|
58
|
-
"priv", "typeof", "unsized", "virtual", "yield", "try", "union",
|
|
59
|
-
]);
|
|
60
|
-
/** string を Rust 文字列リテラルへ。 */
|
|
61
|
-
function rustStrLit(s) {
|
|
62
|
-
return JSON.stringify(s);
|
|
63
|
-
}
|
|
64
|
-
/** 単一 char を Rust の char リテラルへ(`'x'`)。特殊文字はエスケープ。 */
|
|
65
|
-
function rustCharLit(ch) {
|
|
66
|
-
if (ch === "'")
|
|
67
|
-
return "'\\''";
|
|
68
|
-
if (ch === "\\")
|
|
69
|
-
return "'\\\\'";
|
|
70
|
-
if (ch === "\n")
|
|
71
|
-
return "'\\n'";
|
|
72
|
-
if (ch === "\r")
|
|
73
|
-
return "'\\r'";
|
|
74
|
-
if (ch === "\t")
|
|
75
|
-
return "'\\t'";
|
|
76
|
-
// 制御文字/非 ASCII 印字外は \u{..} エスケープ、その他はそのまま(Rust の char リテラルは Unicode スカラ値)。
|
|
77
|
-
const cp = ch.codePointAt(0);
|
|
78
|
-
if (cp < 0x20 || cp === 0x7f)
|
|
79
|
-
return `'\\u{${cp.toString(16)}}'`;
|
|
80
|
-
return `'${ch}'`;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* TypeRef が Rust の `Default` を導出可能か(null 単独型は `Value` に落ち Default 非実装 → false)。
|
|
84
|
-
* scalar(non-null)/opt/arr は常に Default 導出可、named は各 field が導出可なら導出可、arr/opt はラップ先に依存。
|
|
85
|
-
*/
|
|
86
|
-
function isDefaultable(ref, plan) {
|
|
87
|
-
switch (ref.kind) {
|
|
88
|
-
case "scalar":
|
|
89
|
-
return ref.scalar !== "null"; // null → Value(Default 非実装)
|
|
90
|
-
case "opt":
|
|
91
|
-
return true; // Option<T> は常に None default
|
|
92
|
-
case "arr":
|
|
93
|
-
return true; // Vec<T> は常に空 default
|
|
94
|
-
case "named": {
|
|
95
|
-
const decl = findDecl(plan, ref.name);
|
|
96
|
-
return decl ? decl.fields.every((f) => isDefaultable(f.type, plan)) : false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/** 型プランの named obj を Rust struct 宣言へ(decls 順=決定的)。field は宣言順。 */
|
|
101
|
-
function emitTypeDecls(plan) {
|
|
102
|
-
if (plan.decls.length === 0)
|
|
103
|
-
return "";
|
|
104
|
-
return plan.decls
|
|
105
|
-
.map((d) => {
|
|
106
|
-
const fields = d.fields
|
|
107
|
-
.map((f) => ` ${rustFieldName(f.name)}: ${renderTypeRef(f.type)}, // ${JSON.stringify(f.name)}`)
|
|
108
|
-
.join("\n");
|
|
109
|
-
// Clone: struct field 直参照で output に clone するため。Default: cell の初期値(RefCell::new)用。
|
|
110
|
-
// 全 field が Default 導出可能なら `#[derive(Default)]`(clippy::derivable_impls 対応)。null 単独型
|
|
111
|
-
// (Value・Default 非実装)を持つ struct だけは手書き Default を emitDefaults() で綴じる(そこでは
|
|
112
|
-
// 導出不能なので derivable_impls は発火しない)。生成 struct は output で全 field が読まれるとは限らない → dead_code 許容。
|
|
113
|
-
const derives = isDefaultable({ kind: "named", name: d.name }, plan) ? "Clone, Default" : "Clone";
|
|
114
|
-
return `#[derive(${derives})]\n#[allow(dead_code)]\nstruct ${d.name} {\n${fields}\n}`;
|
|
115
|
-
})
|
|
116
|
-
.join("\n\n");
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* emitMarshallers — 各 named struct の raw→typed marshaller(§4.4)。動的 `&Value`(`Value::Obj` 期待)を
|
|
120
|
-
* 受け、各 field を typed に読み出して具体 struct を組む。generic Value 走査を exec 経路から除去する de-box の実体。
|
|
121
|
-
* field 型ごとに再帰: named→marshal_T*、arr→要素ループ、opt→null 分岐、scalar→型 match。
|
|
122
|
-
*/
|
|
123
|
-
function emitMarshallers(plan) {
|
|
124
|
-
if (plan.decls.length === 0)
|
|
125
|
-
return "";
|
|
126
|
-
return plan.decls.map((d) => emitOneMarshaller(d, plan)).join("\n\n");
|
|
127
|
-
}
|
|
128
|
-
function emitOneMarshaller(d, plan) {
|
|
129
|
-
const lines = [];
|
|
130
|
-
// 型プランは全 struct に marshaller を emit するが、named struct が exec で 1 度も handler 結果として
|
|
131
|
-
// 現れない vector(例: outputType 専用の合成 struct)では未使用になり得る → dead_code 許容(生成ツールキット)。
|
|
132
|
-
lines.push(`#[allow(dead_code)]`);
|
|
133
|
-
lines.push(`fn marshal_${d.name}(raw: &Value) -> Result<${d.name}, BehaviorError> {`);
|
|
134
|
-
lines.push(` let pairs = match raw {`);
|
|
135
|
-
lines.push(` Value::Obj(p) => p,`);
|
|
136
|
-
lines.push(` other => return Err(marshal_type_error(${rustStrLit(`typed marshal ${d.name}: expected obj, got `)}, other.type_name())),`);
|
|
137
|
-
lines.push(` };`);
|
|
138
|
-
const fieldInits = [];
|
|
139
|
-
for (const f of d.fields) {
|
|
140
|
-
const fieldVar = `field_${rustFieldName(f.name).replace(/^r#/, "")}`;
|
|
141
|
-
const keyLit = rustStrLit(f.name);
|
|
142
|
-
lines.push(` let ${fieldVar} = {`);
|
|
143
|
-
lines.push(` let fv = match pairs.iter().find(|(k, _)| k == ${keyLit}) {`);
|
|
144
|
-
lines.push(` Some((_, v)) => v,`);
|
|
145
|
-
lines.push(` None => return Err(marshal_missing_prop(${rustStrLit(`typed marshal ${d.name}: missing property .`)}, ${keyLit})),`);
|
|
146
|
-
lines.push(` };`);
|
|
147
|
-
lines.push(indentBlock(emitFieldMaterialize("fv", f.type, plan), 2));
|
|
148
|
-
lines.push(` };`);
|
|
149
|
-
fieldInits.push(` ${rustFieldName(f.name)}: ${fieldVar},`);
|
|
150
|
-
}
|
|
151
|
-
lines.push(` Ok(${d.name} {`);
|
|
152
|
-
lines.push(...fieldInits);
|
|
153
|
-
lines.push(` })`);
|
|
154
|
-
lines.push(`}`);
|
|
155
|
-
return lines.join("\n");
|
|
156
|
-
}
|
|
157
|
-
/** テキストブロックを indent 段(4 space/段)だけ字下げする。 */
|
|
158
|
-
function indentBlock(block, levels) {
|
|
159
|
-
const pad = " ".repeat(levels);
|
|
160
|
-
return block
|
|
161
|
-
.split("\n")
|
|
162
|
-
.map((l) => (l.length === 0 ? l : pad + l))
|
|
163
|
-
.join("\n");
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* emitFieldMaterialize — `src` (`&Value` 式) を型 `ref` の具体 typed 値へ materialize する Rust **式**を返す
|
|
167
|
-
* (末尾式・値を返す block)。scalar は型 match、named は marshal_T* 呼び(`?`)、arr は要素ループ、
|
|
168
|
-
* opt は null 分岐(Option)。すべて具体 Rust 型で組む(de-box)。fail は marshaller helper(BehaviorError)。
|
|
169
|
-
*/
|
|
170
|
-
function emitFieldMaterialize(src, ref, plan) {
|
|
171
|
-
switch (ref.kind) {
|
|
172
|
-
case "scalar": {
|
|
173
|
-
if (ref.scalar === "null") {
|
|
174
|
-
// null 単独型は Value のまま保持(clone)。
|
|
175
|
-
return `${src}.clone()`;
|
|
176
|
-
}
|
|
177
|
-
const variant = ref.scalar === "int" ? "Int" : ref.scalar === "float" ? "Float" : ref.scalar === "bool" ? "Bool" : "Str";
|
|
178
|
-
// Str は clone(所有 String)、その他は Copy。
|
|
179
|
-
const bind = ref.scalar === "string" ? "s.clone()" : "*s";
|
|
180
|
-
return [
|
|
181
|
-
`match ${src} {`,
|
|
182
|
-
` Value::${variant}(s) => ${bind},`,
|
|
183
|
-
` other => return Err(marshal_type_error(${rustStrLit(`typed marshal: expected ${ref.scalar}, got `)}, other.type_name())),`,
|
|
184
|
-
`}`,
|
|
185
|
-
].join("\n");
|
|
186
|
-
}
|
|
187
|
-
case "named": {
|
|
188
|
-
return `marshal_${ref.name}(${src})?`;
|
|
189
|
-
}
|
|
190
|
-
case "arr": {
|
|
191
|
-
const elemTy = renderTypeRef(ref.elem);
|
|
192
|
-
const inner = emitFieldMaterialize("elem_raw", ref.elem, plan);
|
|
193
|
-
return [
|
|
194
|
-
`match ${src} {`,
|
|
195
|
-
` Value::Arr(items) => {`,
|
|
196
|
-
` let mut out: Vec<${elemTy}> = Vec::with_capacity(items.len());`,
|
|
197
|
-
` for elem_raw in items.iter() {`,
|
|
198
|
-
` let elem_out = ${indentBlock(inner, 3).trimStart()};`,
|
|
199
|
-
` out.push(elem_out);`,
|
|
200
|
-
` }`,
|
|
201
|
-
` out`,
|
|
202
|
-
` }`,
|
|
203
|
-
` other => return Err(marshal_type_error(${rustStrLit("typed marshal: expected arr, got ")}, other.type_name())),`,
|
|
204
|
-
`}`,
|
|
205
|
-
].join("\n");
|
|
206
|
-
}
|
|
207
|
-
case "opt": {
|
|
208
|
-
const inner = emitFieldMaterialize("inner_raw", ref.inner, plan);
|
|
209
|
-
return [
|
|
210
|
-
`match ${src} {`,
|
|
211
|
-
` Value::Null => None,`,
|
|
212
|
-
` inner_raw => {`,
|
|
213
|
-
` let inner_out = ${indentBlock(inner, 2).trimStart()};`,
|
|
214
|
-
` Some(inner_out)`,
|
|
215
|
-
` }`,
|
|
216
|
-
`}`,
|
|
217
|
-
].join("\n");
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* materializeExpr — raw な `&Value` 式 `rawExpr` を型 `ref` の具体 typed 値へ変換する **単一式**(exec 経路の
|
|
223
|
-
* de-box)。named→`marshal_T*(...)?`、scalar/arr/opt→インライン材化 block。呼び側が typed ローカルへ束ねる。
|
|
224
|
-
* これが seam の runtime-materialization hook の Rust 実装本体(go の同型 hook)。
|
|
225
|
-
*/
|
|
226
|
-
function materializeExpr(rawExpr, ref, plan) {
|
|
227
|
-
// 参照式(`&x`)を受ける前提に統一するため、marshaller/材化 block はいずれも `&Value` を読む。
|
|
228
|
-
if (ref.kind === "named")
|
|
229
|
-
return `marshal_${ref.name}(${rawExpr})?`;
|
|
230
|
-
// scalar/arr/opt は emitFieldMaterialize の block を使う(式として評価される)。
|
|
231
|
-
return `{ let __src = ${rawExpr}; ${emitFieldMaterialize("__src", ref, plan).replace(/\n/g, " ")} }`;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* typedFieldAccess — typed base 式 `baseExpr`(型 `baseRef`)に field path を **struct field 直参照**で辿る。
|
|
235
|
-
* map/obj_get lookup ではなく `.field` 連結(=de-box されている証拠)。辿り着いた TypeRef も返す。
|
|
236
|
-
*/
|
|
237
|
-
function typedFieldAccess(baseExpr, baseRef, fields, plan) {
|
|
238
|
-
let expr = baseExpr;
|
|
239
|
-
let ref = baseRef;
|
|
240
|
-
for (const f of fields) {
|
|
241
|
-
if (ref.kind !== "named") {
|
|
242
|
-
throw new Error(`typed rust: field access '.${f}' on non-struct TypeRef ${JSON.stringify(ref)}`);
|
|
243
|
-
}
|
|
244
|
-
const decl = findDecl(plan, ref.name);
|
|
245
|
-
const fld = decl?.fields.find((x) => x.name === f);
|
|
246
|
-
if (!fld)
|
|
247
|
-
throw new Error(`typed rust: field '${f}' not found on struct ${ref.name}`);
|
|
248
|
-
expr = `${expr}.${rustFieldName(f)}`; // struct field 直参照(map lookup でない)
|
|
249
|
-
ref = fld.type;
|
|
250
|
-
}
|
|
251
|
-
return { expr, ref };
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* serializeTyped — 具体 typed 値式を canonical 直列化用の動的 Value へ戻す式(観測等価 pin)。exec は
|
|
255
|
-
* typed のまま進み、component 境界でだけ Value 化して golden と突き合わせる。named→ser_T*、scalar→Value::variant、
|
|
256
|
-
* arr→要素 ser の map collect、opt→None/Some 分岐。式一発で返す。`typedExpr` は所有値(by-value)想定。
|
|
257
|
-
*/
|
|
258
|
-
function serializeTyped(typedExpr, ref, plan) {
|
|
259
|
-
switch (ref.kind) {
|
|
260
|
-
case "named":
|
|
261
|
-
return `ser_${ref.name}(${typedExpr})`;
|
|
262
|
-
case "scalar": {
|
|
263
|
-
switch (ref.scalar) {
|
|
264
|
-
case "string":
|
|
265
|
-
return `Value::Str(${typedExpr})`;
|
|
266
|
-
case "int":
|
|
267
|
-
return `Value::Int(${typedExpr})`;
|
|
268
|
-
case "float":
|
|
269
|
-
return `Value::Float(${typedExpr})`;
|
|
270
|
-
case "bool":
|
|
271
|
-
return `Value::Bool(${typedExpr})`;
|
|
272
|
-
case "null":
|
|
273
|
-
// null 単独型は Value をそのまま(clone 済みで保持)。
|
|
274
|
-
return `${typedExpr}`;
|
|
275
|
-
}
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
case "arr": {
|
|
279
|
-
const elemTy = renderTypeRef(ref.elem);
|
|
280
|
-
return `Value::Arr(${typedExpr}.into_iter().map(|e: ${elemTy}| ${serializeTyped("e", ref.elem, plan)}).collect())`;
|
|
281
|
-
}
|
|
282
|
-
case "opt":
|
|
283
|
-
return `match ${typedExpr} { Some(inner) => ${serializeTyped("inner", ref.inner, plan)}, None => Value::Null }`;
|
|
284
|
-
}
|
|
285
|
-
throw new Error(`typed rust: serializeTyped fell through for ${JSON.stringify(ref)}`);
|
|
286
|
-
}
|
|
287
|
-
export const rustTypedMaterializer = {
|
|
288
|
-
emitTypeDecls,
|
|
289
|
-
renderTypeRef,
|
|
290
|
-
emitMarshallers,
|
|
291
|
-
materializeExpr,
|
|
292
|
-
typedFieldAccess,
|
|
293
|
-
serializeTyped,
|
|
294
|
-
};
|
|
295
|
-
// ── serializer helper(named struct → Value)を型プランから emit ────────────────────
|
|
296
|
-
/** 各 named struct を canonical Value(Obj, field は宣言順)へ戻す `ser_T*` を emit(by-value consume)。 */
|
|
297
|
-
function emitSerializers(plan) {
|
|
298
|
-
return plan.decls
|
|
299
|
-
.map((d) => {
|
|
300
|
-
const sets = d.fields
|
|
301
|
-
.map((f) => ` (${rustStrLit(f.name)}.to_string(), ${serializeTyped(`v.${rustFieldName(f.name)}`, f.type, plan)}),`)
|
|
302
|
-
.join("\n");
|
|
303
|
-
// 型プランは全 struct に serializer を emit するが、output が named struct を丸ごと返さない vector では
|
|
304
|
-
// 未使用になり得る(scalar output 等) → dead_code 許容(生成ツールキット)。
|
|
305
|
-
return `#[allow(dead_code)]\nfn ser_${d.name}(v: ${d.name}) -> Value {\n Value::Obj(vec![\n${sets}\n ])\n}`;
|
|
306
|
-
})
|
|
307
|
-
.join("\n\n");
|
|
308
|
-
}
|
|
309
|
-
// ── struct-native runner(実 exec 経路 — 実 de-box)─────────────────────────────────
|
|
310
|
-
/** 生成コード内の typed ローカル cell 名(materialize 済み struct を RefCell で保持)。 */
|
|
311
|
-
function typedCell(nodeId) {
|
|
312
|
-
return `cell_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
|
|
313
|
-
}
|
|
314
|
-
/** `ref` の path 配列(Expression IR)を取り出す。単純 ref ノードでなければ undefined。 */
|
|
315
|
-
function refPathOf(node) {
|
|
316
|
-
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
317
|
-
return undefined;
|
|
318
|
-
const keys = Object.keys(node);
|
|
319
|
-
if (keys.length !== 1 || keys[0] !== "ref")
|
|
320
|
-
return undefined;
|
|
321
|
-
const path = node.ref;
|
|
322
|
-
if (!Array.isArray(path) || !path.every((p) => typeof p === "string"))
|
|
323
|
-
return undefined;
|
|
324
|
-
return path;
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* emitTypedValue — output(Φ 合流式)IR を、期待型 `expected` の **具体 Rust typed 値**式へ再帰 lower し、
|
|
328
|
-
* `{ expr, ref }` を返す。これが AC の「exec が具体 struct を保持」の実体(go と同型):
|
|
329
|
-
* - `{ref:[typedNode, …fields]}` → typed struct field 直参照(typedFieldAccess — map lookup でない。struct は clone)。
|
|
330
|
-
* - `{ref:[input/非 typed node]}` → 型付け不能な生 Value。expected があれば materialize(de-box)。
|
|
331
|
-
* - `{obj:{…}}` → 具体 struct literal(expected が named のとき各 field を期待型で lower)。
|
|
332
|
-
* - `{arr:[…]}` → 具体 Vec literal(要素は要素型で lower)。
|
|
333
|
-
* - scalar literal → 具体 Rust 値。
|
|
334
|
-
* - concat → de-box された typed 値で native String 連結(意味論保存)。他演算子は LOUD fail-closed。
|
|
335
|
-
*/
|
|
336
|
-
function emitTypedValue(node, expected, typedNodes, plan) {
|
|
337
|
-
const path = refPathOf(node);
|
|
338
|
-
if (path && path.length >= 1) {
|
|
339
|
-
const head = path[0];
|
|
340
|
-
if (typedNodes.has(head)) {
|
|
341
|
-
// typed node への参照: cell から struct を借りて field 直参照で辿る(de-box された struct access)。
|
|
342
|
-
const baseRef = typedNodes.get(head);
|
|
343
|
-
// struct を所有値として取り出す(cell.borrow().clone())。field access は clone 済みの struct に対して行う。
|
|
344
|
-
// bare な Copy スカラ node(int/float/bool、以降の field path 無し)は `.clone()` が clippy::clone_on_copy
|
|
345
|
-
// を踏むので deref-copy(`*cell.borrow()`)で取り出す(意味論同値、clippy-clean — bc#49 B3)。
|
|
346
|
-
const isBareCopyScalar = path.length === 1 &&
|
|
347
|
-
baseRef.kind === "scalar" &&
|
|
348
|
-
(baseRef.scalar === "int" || baseRef.scalar === "float" || baseRef.scalar === "bool");
|
|
349
|
-
const base = isBareCopyScalar
|
|
350
|
-
? `*${typedCell(head)}.borrow()`
|
|
351
|
-
: `${typedCell(head)}.borrow().clone()`;
|
|
352
|
-
const { expr, ref } = typedFieldAccess(base, baseRef, path.slice(1), plan);
|
|
353
|
-
return { expr, ref };
|
|
354
|
-
}
|
|
355
|
-
// input param 参照(非 typed node): input Value を局所的に読み、expected へ materialize(de-box)。
|
|
356
|
-
// 動的 Value tree の再構築ではなく input 境界値の 1 leaf 読み(許容される局所変換)。
|
|
357
|
-
if (expected)
|
|
358
|
-
return { expr: materializeExpr(`&${inputRead(path)}`, expected, plan), ref: expected };
|
|
359
|
-
return { expr: inputRead(path), ref: undefined };
|
|
360
|
-
}
|
|
361
|
-
if (node !== null && typeof node === "object" && !Array.isArray(node)) {
|
|
362
|
-
const keys = Object.keys(node);
|
|
363
|
-
if (keys.length === 1 && keys[0] === "obj" && expected && expected.kind === "named") {
|
|
364
|
-
// 具体 struct literal を組む(de-box した output assembly)。
|
|
365
|
-
const decl = findDecl(plan, expected.name);
|
|
366
|
-
const fields = node.obj;
|
|
367
|
-
const inits = decl.fields.map((f) => {
|
|
368
|
-
const sub = emitTypedValue(fields[f.name], f.type, typedNodes, plan);
|
|
369
|
-
return `${rustFieldName(f.name)}: ${sub.expr}`;
|
|
370
|
-
});
|
|
371
|
-
return { expr: `${expected.name} { ${inits.join(", ")} }`, ref: expected };
|
|
372
|
-
}
|
|
373
|
-
if (keys.length === 1 && keys[0] === "arr" && expected && expected.kind === "arr") {
|
|
374
|
-
const elems = node.arr;
|
|
375
|
-
const elemTy = renderTypeRef(expected.elem);
|
|
376
|
-
const items = elems.map((e) => emitTypedValue(e, expected.elem, typedNodes, plan).expr);
|
|
377
|
-
return { expr: `{ let v: Vec<${elemTy}> = vec![${items.join(", ")}]; v }`, ref: expected };
|
|
378
|
-
}
|
|
379
|
-
// concat 演算子: operand を **de-box された typed 値**(typed field access なら concrete String)で
|
|
380
|
-
// 読み、native String 連結する(rust-straightline の concat_native と同じ native 化パターン — 意味論保存)。
|
|
381
|
-
if (keys.length === 1 && keys[0] === "concat" && expected && expected.kind === "scalar" && expected.scalar === "string") {
|
|
382
|
-
const parts = node.concat;
|
|
383
|
-
// 各 part を push_str へ渡す `&str` 式に落とす。bare 文字列リテラルは `"..."`(str slice)を直接渡し
|
|
384
|
-
// (clippy::unnecessary_to_owned 回避 — `&("x".to_string())` にしない)、それ以外(field access で得た
|
|
385
|
-
// concrete String など)は `&(expr)` で借用する。いずれも native String 連結(意味論保存)。
|
|
386
|
-
const pushes = parts.map((p) => {
|
|
387
|
-
if (typeof p === "string") {
|
|
388
|
-
// 1 文字リテラルは push(char)(clippy::single_char_add_str 回避)、それ以外は push_str(str slice)。
|
|
389
|
-
const chars = [...p];
|
|
390
|
-
if (chars.length === 1)
|
|
391
|
-
return `__s.push(${rustCharLit(chars[0])});`;
|
|
392
|
-
return `__s.push_str(${rustStrLit(p)});`;
|
|
393
|
-
}
|
|
394
|
-
const sub = emitTypedValue(p, { kind: "scalar", scalar: "string" }, typedNodes, plan);
|
|
395
|
-
return `__s.push_str(&(${sub.expr}));`;
|
|
396
|
-
});
|
|
397
|
-
return {
|
|
398
|
-
expr: `{ let mut __s = String::new(); ${pushes.join(" ")} __s }`,
|
|
399
|
-
ref: expected,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
// その他単一キー = 別演算子(add/eq/…): struct-native exec では動的 Value output tree を組まない方針
|
|
403
|
-
// ゆえ LOUD fail-closed(bc typed corpus には output 位置の非 concat 演算子が無い。go と一致)。
|
|
404
|
-
if (keys.length === 1) {
|
|
405
|
-
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `rust-typed struct-native output: operator '${keys[0]}' at the output position is not supported in the struct-native de-box path yet (typed corpus covers only ref / obj / arr / concat outputs). Do not fall back to building the dynamic Value output tree.`);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
// scalar literal(typed operand として現れる bare リテラル): 具体 Rust スカラ。
|
|
409
|
-
if (expected && expected.kind === "scalar") {
|
|
410
|
-
return { expr: litScalar(node, expected.scalar), ref: expected };
|
|
411
|
-
}
|
|
412
|
-
return { expr: litScalarUnknown(node), ref: undefined };
|
|
413
|
-
}
|
|
414
|
-
/** input/非 typed node の scope 読み(動的 Value 式)。input params のみを持つ port_scope() から 1 leaf。 */
|
|
415
|
-
function inputRead(path) {
|
|
416
|
-
return `input_ref(&[${path.map(rustStrLit).join(", ")}], &${PORT_SCOPE})`;
|
|
417
|
-
}
|
|
418
|
-
/** 期待スカラの Rust リテラル(string/int/float/bool/null)。 */
|
|
419
|
-
function litScalar(node, scalar) {
|
|
420
|
-
if (scalar === "string" && typeof node === "string")
|
|
421
|
-
return `${rustStrLit(node)}.to_string()`;
|
|
422
|
-
if (scalar === "bool" && typeof node === "boolean")
|
|
423
|
-
return node ? "true" : "false";
|
|
424
|
-
if (scalar === "int") {
|
|
425
|
-
if (node !== null && typeof node === "object" && typeof node.int === "string")
|
|
426
|
-
return `${node.int}i64`;
|
|
427
|
-
if (typeof node === "number")
|
|
428
|
-
return `${node}i64`;
|
|
429
|
-
}
|
|
430
|
-
if (scalar === "float") {
|
|
431
|
-
if (node !== null && typeof node === "object" && typeof node.float === "number")
|
|
432
|
-
return `${node.float}f64`;
|
|
433
|
-
if (typeof node === "number")
|
|
434
|
-
return `${node}f64`;
|
|
435
|
-
}
|
|
436
|
-
if (scalar === "null")
|
|
437
|
-
return "Value::Null";
|
|
438
|
-
throw new Error(`typed rust: scalar literal ${JSON.stringify(node)} does not match expected ${scalar}`);
|
|
439
|
-
}
|
|
440
|
-
function litScalarUnknown(node) {
|
|
441
|
-
if (node === null)
|
|
442
|
-
return "Value::Null";
|
|
443
|
-
if (typeof node === "boolean")
|
|
444
|
-
return node ? "Value::Bool(true)" : "Value::Bool(false)";
|
|
445
|
-
if (typeof node === "string")
|
|
446
|
-
return `Value::Str(${rustStrLit(node)}.to_string())`;
|
|
447
|
-
return "Value::Null";
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* emitTypedRunner — 1 typed component の **struct-native exec**(bc#47 rework: LAYERED でなく実 de-box)。
|
|
451
|
-
*
|
|
452
|
-
* plan/handler-dispatch の SSoT は再利用する(`run_plan` primitive + `handlers.exec_ctx`。意味論は二重実装
|
|
453
|
-
* しない)。ただし exec closure は、handler 結果(生 Value mock;graphddb #60 では raw)を **その場で
|
|
454
|
-
* outType struct へ marshal**(raw→typed 境界)し、struct を typed cell(RefCell)へ格納する。生 Value を
|
|
455
|
-
* results Vec へ入れることは**しない**。port 式は input params だけを持つ port_scope() で評価する。component
|
|
456
|
-
* `output` は typed struct への **struct field 直参照** + struct/Vec literal で組み、最終 return 境界で **1 回だけ**
|
|
457
|
-
* Value へ serialize する(動的 Value output tree は組まない)。overflow/短絡 operator は A0 primitive SSoT のまま。
|
|
458
|
-
*
|
|
459
|
-
* map/cond を含む typed component は struct-native exec のスコープ外(LOUD fail-closed。graphddb #60 / follow-up)。
|
|
460
|
-
*/
|
|
461
|
-
function emitTypedRunner(comp, plan) {
|
|
462
|
-
const fn = rustStraightlineInternals.sanitize(comp.name);
|
|
463
|
-
// struct-native scope を組むため、port 式は port_scope()(input params のみ)で emit する。
|
|
464
|
-
const cplan = buildComponentPlan(comp, rustStraightlineInternals.dialect, PORT_SCOPE);
|
|
465
|
-
// typed node(componentRef / map / cond で outType を持つもの)を index 化。map の node 結果型は
|
|
466
|
-
// {arr:elemType}(collected/augmented list)、cond は選択枝の合流型。いずれも struct 空間で保持する。
|
|
467
|
-
const typedNodes = collectTypedNodes(comp, plan);
|
|
468
|
-
// 各 typed node の struct cell 宣言(RefCell で。marshal 後に struct を書き込む)。
|
|
469
|
-
const cellDecls = [...typedNodes.entries()]
|
|
470
|
-
.map(([id, ref]) => ` let ${typedCell(id)}: RefCell<${renderTypeRef(ref)}> = RefCell::new(Default::default());`)
|
|
471
|
-
.join("\n");
|
|
472
|
-
// #74: a child/relation node port may reference a PARENT typed node's field
|
|
473
|
-
// (e.g. GSI1PK = "USER#" + ref(["root","userId"])). port_scope must expose the accumulated typed
|
|
474
|
-
// node results (the struct-space analogue of the straight-line base_scope), serialized from the
|
|
475
|
-
// struct cells on demand — otherwise the ref hits UNKNOWN_BINDING and the key binds empty,
|
|
476
|
-
// diverging from run_behavior. Node results stay in the cells (no persistent dynamic Value scope);
|
|
477
|
-
// a produced-flag (Cell<bool>) gates serialization so only ancestors that already ran are exposed.
|
|
478
|
-
// run_plan visits parents before children, so a node's ref only reads an already-produced ancestor.
|
|
479
|
-
const producedCell = (id) => `__produced_${id.replace(/[^A-Za-z0-9_]/g, "_")}`;
|
|
480
|
-
const producedDecls = [...typedNodes.keys()]
|
|
481
|
-
.map((id) => ` let ${producedCell(id)}: std::cell::Cell<bool> = std::cell::Cell::new(false);`)
|
|
482
|
-
.join("\n");
|
|
483
|
-
const portScopeNodeInjections = [...typedNodes.entries()]
|
|
484
|
-
.map(([id, ref]) => {
|
|
485
|
-
// Own the cell's struct/scalar the same way the output assembly reads it (clone for
|
|
486
|
-
// struct/arr/opt/string; deref-copy for Copy scalars to stay clippy-clean), then serialize.
|
|
487
|
-
const isBareCopyScalar = ref.kind === "scalar" && (ref.scalar === "int" || ref.scalar === "float" || ref.scalar === "bool");
|
|
488
|
-
const owned = isBareCopyScalar ? `*${typedCell(id)}.borrow()` : `${typedCell(id)}.borrow().clone()`;
|
|
489
|
-
const ser = serializeTyped(owned, ref, plan);
|
|
490
|
-
return ` if ${producedCell(id)}.get() {\n s.push((${rustStrLit(id)}.to_string(), ${ser}));\n }`;
|
|
491
|
-
})
|
|
492
|
-
.join("\n");
|
|
493
|
-
const arms = cplan.ops.map((op) => emitTypedExecArm(op, comp.name, typedNodes, plan, producedCell)).join("\n");
|
|
494
|
-
// handler を 1 度も呼ばない typed component(cond のみ等)では `handlers` を `_handlers` に(unused-variables
|
|
495
|
-
// 回避)。componentRef と map(要素/batched handler)は handler を呼ぶ。
|
|
496
|
-
const usesHandlers = cplan.ops.some((o) => o.kind === "componentRef" || o.kind === "map");
|
|
497
|
-
const handlerParam = usesHandlers ? "handlers" : "_handlers";
|
|
498
|
-
const planExpr = comp.plan
|
|
499
|
-
? rustStraightlineInternals.emitPlanLiteral(comp.plan)
|
|
500
|
-
: "None";
|
|
501
|
-
// output を struct で組み立てて 1 回だけ serialize(動的 Value output tree は組まない)。
|
|
502
|
-
const outRef = comp.outputType !== undefined ? deriveTypeRef(comp.outputType, plan) : undefined;
|
|
503
|
-
const out = emitTypedValue(comp.output, outRef, typedNodes, plan);
|
|
504
|
-
const outRefFinal = out.ref ?? outRef;
|
|
505
|
-
const serialized = outRefFinal ? serializeTyped("__typed_out", outRefFinal, plan) : "__typed_out";
|
|
506
|
-
const opsLiteral = rustStraightlineInternals.emitOpsLiteral(cplan);
|
|
507
|
-
return `// run_typed_${fn} — STRUCT-NATIVE exec (bc#48 de-box): the plan is driven by the run_plan
|
|
508
|
-
// primitive (plan SSoT) with per-node exec arms that marshal the handler result DIRECTLY into the
|
|
509
|
-
// node's outType struct at the boundary and store the STRUCT in a typed cell (RefCell<T*>) — the
|
|
510
|
-
// dynamic Value node-result scope (Vec<(String, Value)> results) and the Value output tree are
|
|
511
|
-
// NEVER built. Downstream ref/field access reads the structs (.field, not obj_get); the output is
|
|
512
|
-
// assembled as a struct and serialized to a Value ONLY at the final return boundary.
|
|
513
|
-
// Called by Bind for '${comp.name}'. (bc handlers return a Value mock = the raw->typed boundary; in
|
|
514
|
-
// graphddb #60 the handler returns raw and the same marshal-at-boundary keeps it struct-native.)
|
|
515
|
-
fn run_typed_${fn}<H: ComponentExec>(
|
|
516
|
-
${handlerParam}: &mut H,
|
|
517
|
-
input: &[(String, Value)],
|
|
518
|
-
) -> Result<Value, BehaviorError> {
|
|
519
|
-
let ops: Vec<OpSpec> = ${opsLiteral};
|
|
520
|
-
${cellDecls ? cellDecls + "\n" : ""}${producedDecls ? producedDecls + "\n" : ""} // port_scope = input params + accumulated PRODUCED typed node results (serialized from the struct
|
|
521
|
-
// cells — the struct-space analogue of the straight-line base_scope). #74: this lets a child/relation
|
|
522
|
-
// node port ref a parent typed node's field. Node results live in the cells above; they are
|
|
523
|
-
// serialized into this ephemeral boundary Value on demand (no persistent dynamic node-result scope).
|
|
524
|
-
let port_scope = || -> Vec<(String, Value)> {
|
|
525
|
-
let mut s = input.to_vec();
|
|
526
|
-
${portScopeNodeInjections ? portScopeNodeInjections + "\n" : ""} s
|
|
527
|
-
};
|
|
528
|
-
let _ = &port_scope;
|
|
529
|
-
let mut pending_err: Option<BehaviorError> = None;
|
|
530
|
-
|
|
531
|
-
let run = {
|
|
532
|
-
let err_cell = &mut pending_err;
|
|
533
|
-
let exec = |op: &OpSpec, _bound: Option<&Value>| -> ExecOutcome {
|
|
534
|
-
if err_cell.is_some() {
|
|
535
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
536
|
-
}
|
|
537
|
-
let i = match ops.iter().position(|o| o.id == op.id) {
|
|
538
|
-
Some(i) => i,
|
|
539
|
-
None => {
|
|
540
|
-
*err_cell = Some(no_typed_exec_arm(&op.id));
|
|
541
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
let _ = i;
|
|
545
|
-
${arms}
|
|
546
|
-
*err_cell = Some(no_typed_exec_arm(&op.id));
|
|
547
|
-
ExecOutcome::Error("aborted".to_string())
|
|
548
|
-
};
|
|
549
|
-
run_plan(${planExpr}, &ops, exec)
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
// exec 内で立てた BehaviorError は run_plan が返す合成 OP_FAILED に優先する。
|
|
553
|
-
if let Some(e) = pending_err {
|
|
554
|
-
return Err(e);
|
|
555
|
-
}
|
|
556
|
-
let _run = run?;
|
|
557
|
-
|
|
558
|
-
// output assembly: struct field 直参照 + struct/Vec literal(動的 Value output tree は組まない)。
|
|
559
|
-
let __typed_out = ${out.expr};
|
|
560
|
-
Ok(${serialized})
|
|
561
|
-
}`;
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* emitTypedExecArm — 1 ノードの struct-native exec 分岐。componentRef: ports を native/primitive で評価
|
|
565
|
-
* (port_scope 読み)→ handlers.exec_ctx → 結果を **outType struct へ marshal**(raw→typed 境界)→ cell に
|
|
566
|
-
* 格納。map/cond は struct-native では未対応(typed corpus は componentRef のみ)— fail-closed。
|
|
567
|
-
*/
|
|
568
|
-
function emitTypedExecArm(op, compName, typedNodes, plan, producedCell) {
|
|
569
|
-
if (op.kind === "map")
|
|
570
|
-
return emitTypedMapArm(op, compName, typedNodes, plan, producedCell);
|
|
571
|
-
if (op.kind === "cond")
|
|
572
|
-
return emitTypedCondArm(op, compName, typedNodes, plan, producedCell);
|
|
573
|
-
const portRows = op.ports.length === 0
|
|
574
|
-
? " Ok(Vec::new())"
|
|
575
|
-
: ` Ok(vec![\n${op.ports
|
|
576
|
-
.map((pt) => ` (${rustStrLit(pt.name)}.to_string(), (${pt.expr})?),`)
|
|
577
|
-
.join("\n")}\n ])`;
|
|
578
|
-
const ref = typedNodes.get(op.id);
|
|
579
|
-
// 結果 materialization: handler 結果(&v)を outType struct へ marshal して cell へ格納(de-box 境界)。
|
|
580
|
-
const materialize = emitStoreTypedNodeRust(op.id, "v", ref, 4, producedCell);
|
|
581
|
-
return ` if i == ${op.index} {
|
|
582
|
-
let eval_ports = || -> Result<Vec<(String, Value)>, behavior_contracts::ExprFailure> {
|
|
583
|
-
${portRows}
|
|
584
|
-
};
|
|
585
|
-
let ports = match eval_ports() {
|
|
586
|
-
Ok(p) => p,
|
|
587
|
-
Err(e) => {
|
|
588
|
-
*err_cell = Some(e.into());
|
|
589
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
let outcome = match handlers.exec_ctx(${rustStrLit(op.id)}, ${rustStrLit(op.component)}, &ports, None) {
|
|
593
|
-
Some(o) => o,
|
|
594
|
-
None => {
|
|
595
|
-
*err_cell = Some(unknown_component(${rustStrLit(op.component)}));
|
|
596
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
597
|
-
}
|
|
598
|
-
};
|
|
599
|
-
let v = match &outcome {
|
|
600
|
-
ExecOutcome::Ok(v) => v,
|
|
601
|
-
ExecOutcome::Error(_) => return outcome,
|
|
602
|
-
};
|
|
603
|
-
${materialize}
|
|
604
|
-
return ExecOutcome::Ok(Value::Null);
|
|
605
|
-
}`;
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* collectTypedNodes — component の body ノードで outType を持つもの(componentRef / map / cond)を
|
|
609
|
-
* id → 結果 TypeRef に index 化する(#66: map/cond も struct 空間で結果を保持)。
|
|
610
|
-
*/
|
|
611
|
-
function collectTypedNodes(comp, plan) {
|
|
612
|
-
const typedNodes = new Map();
|
|
613
|
-
for (const n of comp.body) {
|
|
614
|
-
const ot = n.outType;
|
|
615
|
-
if (ot !== undefined)
|
|
616
|
-
typedNodes.set(n.id, deriveTypeRef(ot, plan));
|
|
617
|
-
}
|
|
618
|
-
return typedNodes;
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* emitStoreTypedNodeRust — raw な `&Value` 式(`srcExpr`)を node の outType(`ref`)へ **fail-closed**
|
|
622
|
-
* de-box し、typed cell(RefCell<T>)へ格納する Rust match block を返す(indent = 4-space 段)。named→
|
|
623
|
-
* marshal_T*、scalar/arr/opt→materialize_node_*、注記無し→捨てる。componentRef / cond の結果格納で共有。
|
|
624
|
-
* これらは `?` を使わず(closure が Result を返さない)BehaviorError を err_cell へ写す。`nodeIdForFn` は
|
|
625
|
-
* materialize_node_* の名前生成に使う node id(cond/componentRef とも op.id)。
|
|
626
|
-
*/
|
|
627
|
-
function emitStoreTypedNodeRust(nodeId, srcExpr, ref, indent, producedCell) {
|
|
628
|
-
const pad = " ".repeat(indent);
|
|
629
|
-
if (ref === undefined) {
|
|
630
|
-
return `${pad}let _ = ${srcExpr};`;
|
|
631
|
-
}
|
|
632
|
-
const call = ref.kind === "named" ? `marshal_${ref.name}(${srcExpr})` : `materialize_node_${typedCell(nodeId)}(${srcExpr})`;
|
|
633
|
-
// #74: flag the node produced so a later port_scope() (from a child node's port eval) serializes it.
|
|
634
|
-
const prod = producedCell ? ` ${producedCell(nodeId)}.set(true);` : "";
|
|
635
|
-
return `${pad}match ${call} {
|
|
636
|
-
${pad} Ok(tv) => { *${typedCell(nodeId)}.borrow_mut() = tv;${prod} }
|
|
637
|
-
${pad} Err(e) => {
|
|
638
|
-
${pad} *err_cell = Some(e);
|
|
639
|
-
${pad} return ExecOutcome::Error("aborted".to_string());
|
|
640
|
-
${pad} }
|
|
641
|
-
${pad}}`;
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* emitTypedCondArm — typed cond の struct-native exec(#66)。cond は純 Expression(handler 非呼び)。
|
|
645
|
-
* `{cond:[if,then,else]}` を primitive(primitives::cond。短絡・strict-bool の意味論 SSoT)で 1 式評価し、
|
|
646
|
-
* 選択枝の raw Value を outType struct へ **その場で marshal**(de-box 境界)して cell へ格納する。動的 Value
|
|
647
|
-
* node-result scope は組まない(両枝は同じ outType へ材化される)。
|
|
648
|
-
*/
|
|
649
|
-
function emitTypedCondArm(op, _compName, typedNodes, _plan, producedCell) {
|
|
650
|
-
const ref = typedNodes.get(op.id);
|
|
651
|
-
const store = emitStoreTypedNodeRust(op.id, "&cond_raw", ref, 4, producedCell);
|
|
652
|
-
return ` if i == ${op.index} {
|
|
653
|
-
let cond_raw: Value = match ${op.expr} {
|
|
654
|
-
Ok(v) => v,
|
|
655
|
-
Err(e) => {
|
|
656
|
-
*err_cell = Some(e.into());
|
|
657
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
${store}
|
|
661
|
-
return ExecOutcome::Ok(Value::Null);
|
|
662
|
-
}`;
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* emitTypedMapArm — typed map の struct-native exec(#66)。node 結果型は {arr:elemType}(`outType`)。要素を
|
|
666
|
-
* 反復し、guard(primitives::cond・strict-bool SSoT)で skip、per-element ports を要素 scope(mscope = input
|
|
667
|
-
* params + `as`→要素値)で native/primitive 評価、handler を呼び、その **raw 結果を要素 struct へ marshal**
|
|
668
|
-
* (de-box 境界)して `Vec<elemStruct>` を組み、cell へ格納する(動的 Value collected list は作らない)。batched
|
|
669
|
-
* は guard 通過全要素 ports を 1 回の handler 呼びへ。into は over 要素へ handler 結果を zip-attach した augment
|
|
670
|
-
* 済み Value を **要素 struct へ marshal**(over 要素=入力境界 Value・into 値=handler raw の局所合流→marshal で
|
|
671
|
-
* de-box。動的 output tree は組まない)。plan.concurrency は外側の run_plan primitive が保存する(map=1 op)。
|
|
672
|
-
*/
|
|
673
|
-
function emitTypedMapArm(op, compName, typedNodes, _plan, producedCell) {
|
|
674
|
-
const ref = typedNodes.get(op.id);
|
|
675
|
-
if (ref === undefined || ref.kind !== "arr") {
|
|
676
|
-
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `rust-typed struct-native map: node '${op.id}' has no {arr:elemType} outType (got ${ref ? JSON.stringify(ref.kind) : "no outType"}). A typed map must annotate its result element type to de-box into a native Vec<T>; otherwise regenerate as untyped straight-line. Do not fall back to a dynamic Value collected list.`);
|
|
677
|
-
}
|
|
678
|
-
// #66 hardening: a typed map combining `when` (guard) AND `into` yields
|
|
679
|
-
// HETEROGENEOUS elements (skipped elements lack the `into` field) that cannot
|
|
680
|
-
// de-box into a single Vec<T>. Reject loudly at generation time rather than
|
|
681
|
-
// emitting a module that only fail-closes (MISSING_PROP) at runtime on a skip.
|
|
682
|
-
if (op.guard !== undefined && op.into !== undefined) {
|
|
683
|
-
throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `rust-typed struct-native map: node '${op.id}' combines 'when' (guard) and 'into', which produces heterogeneous elements (skipped elements lack the 'into' field) that cannot de-box into a single Vec<T>. Regenerate this map as untyped straight-line, or split guard and into. Do not emit a struct-native map that fail-closes only at runtime.`);
|
|
684
|
-
}
|
|
685
|
-
const elemRef = ref.elem;
|
|
686
|
-
const elemTy = renderTypeRef(elemRef);
|
|
687
|
-
const comp = rustStrLit(op.component);
|
|
688
|
-
const nodeId = rustStrLit(op.id);
|
|
689
|
-
const asKey = rustStrLit(op.as);
|
|
690
|
-
const elemMat = mapElemMaterializerNameRust(compName, op.id);
|
|
691
|
-
// mscope: input params(port_scope)に `as`→要素値 を足す(node-result Value scope は組まない)。
|
|
692
|
-
const mscope = ` let mscope = || -> Vec<(String, Value)> {
|
|
693
|
-
let mut s = ${PORT_SCOPE};
|
|
694
|
-
s.push((${asKey}.to_string(), el.clone()));
|
|
695
|
-
s
|
|
696
|
-
};
|
|
697
|
-
let _ = &mscope;`;
|
|
698
|
-
const guardBlock = op.guard === undefined
|
|
699
|
-
? ""
|
|
700
|
-
: `
|
|
701
|
-
let gkeep = {
|
|
702
|
-
${mscope}
|
|
703
|
-
match ${op.guard} {
|
|
704
|
-
Ok(v) => matches!(v, Value::Bool(true)),
|
|
705
|
-
Err(e) => {
|
|
706
|
-
*err_cell = Some(e.into());
|
|
707
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
};
|
|
711
|
-
if !gkeep {
|
|
712
|
-
continue;
|
|
713
|
-
}`;
|
|
714
|
-
const portRows = op.ports.length === 0
|
|
715
|
-
? "Ok(Vec::new())"
|
|
716
|
-
: `Ok(vec![\n${op.ports
|
|
717
|
-
.map((pt) => ` (${rustStrLit(pt.name)}.to_string(), (${pt.expr})?),`)
|
|
718
|
-
.join("\n")}\n ])`;
|
|
719
|
-
const portEval = `
|
|
720
|
-
let eval_ports = || -> Result<Vec<(String, Value)>, behavior_contracts::ExprFailure> {
|
|
721
|
-
${mscope}
|
|
722
|
-
${portRows}
|
|
723
|
-
};
|
|
724
|
-
let ports = match eval_ports() {
|
|
725
|
-
Ok(p) => p,
|
|
726
|
-
Err(e) => {
|
|
727
|
-
*err_cell = Some(e.into());
|
|
728
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
729
|
-
}
|
|
730
|
-
};`;
|
|
731
|
-
// guard 通過要素の handler raw 結果を貯める(into でも collected でも per-element raw)。
|
|
732
|
-
const collectLoop = op.batched
|
|
733
|
-
? ` let mut items: Vec<Value> = Vec::new();
|
|
734
|
-
for (mi, el) in over_arr.iter().enumerate() {${guardBlock}${portEval}
|
|
735
|
-
items.push(Value::Obj(ports));
|
|
736
|
-
kept_idx.push(mi);
|
|
737
|
-
}
|
|
738
|
-
let elem_raws: Vec<Value> = if items.is_empty() {
|
|
739
|
-
Vec::new()
|
|
740
|
-
} else {
|
|
741
|
-
let want = items.len();
|
|
742
|
-
let batch_ports = vec![("items".to_string(), Value::Arr(items))];
|
|
743
|
-
match handlers.exec_ctx(${nodeId}, ${comp}, &batch_ports, None) {
|
|
744
|
-
None => {
|
|
745
|
-
*err_cell = Some(unknown_component(${comp}));
|
|
746
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
747
|
-
}
|
|
748
|
-
Some(ExecOutcome::Error(e)) => return ExecOutcome::Error(e),
|
|
749
|
-
Some(ExecOutcome::Ok(v)) => match v {
|
|
750
|
-
Value::Arr(r) if r.len() == want => r,
|
|
751
|
-
_ => {
|
|
752
|
-
*err_cell = Some(behavior_contracts::BehaviorError::new(
|
|
753
|
-
"MAP_BATCH_RESULT_MISMATCH",
|
|
754
|
-
format!("map '${op.id}': batched handler must return a list aligned to items (want {want})"),
|
|
755
|
-
));
|
|
756
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
757
|
-
}
|
|
758
|
-
},
|
|
759
|
-
}
|
|
760
|
-
};`
|
|
761
|
-
: ` let mut elem_raws: Vec<Value> = Vec::with_capacity(over_arr.len());
|
|
762
|
-
for (mi, el) in over_arr.iter().enumerate() {${guardBlock}${portEval}
|
|
763
|
-
match handlers.exec_ctx(${nodeId}, ${comp}, &ports, Some(el)) {
|
|
764
|
-
None => {
|
|
765
|
-
*err_cell = Some(unknown_component(${comp}));
|
|
766
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
767
|
-
}
|
|
768
|
-
Some(ExecOutcome::Error(e)) => return ExecOutcome::Error(e),
|
|
769
|
-
Some(ExecOutcome::Ok(v)) => elem_raws.push(v),
|
|
770
|
-
}
|
|
771
|
-
kept_idx.push(mi);
|
|
772
|
-
}`;
|
|
773
|
-
// 要素 struct へ **fail-closed** de-box して Vec<elemStruct> を組み、cell へ格納(動的 Value list は作らない)。
|
|
774
|
-
const buildTyped = op.into === undefined
|
|
775
|
-
? ` let mut typed: Vec<${elemTy}> = Vec::with_capacity(elem_raws.len());
|
|
776
|
-
for er in elem_raws.iter() {
|
|
777
|
-
match ${elemMat}(er) {
|
|
778
|
-
Ok(tv) => typed.push(tv),
|
|
779
|
-
Err(e) => {
|
|
780
|
-
*err_cell = Some(e);
|
|
781
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
*${typedCell(op.id)}.borrow_mut() = typed;
|
|
786
|
-
${producedCell(op.id)}.set(true);`
|
|
787
|
-
: ` let mut typed: Vec<${elemTy}> = Vec::with_capacity(over_arr.len());
|
|
788
|
-
let mut mk = 0usize;
|
|
789
|
-
for (mj, oel) in over_arr.iter().enumerate() {
|
|
790
|
-
let elem_val: Value = if mk < kept_idx.len() && kept_idx[mk] == mj {
|
|
791
|
-
let merged = match oel {
|
|
792
|
-
Value::Obj(pairs) => {
|
|
793
|
-
let mut next: Vec<(String, Value)> = pairs.clone();
|
|
794
|
-
if let Some(slot) = next.iter_mut().find(|(kk, _)| kk == ${rustStrLit(op.into ?? "")}) {
|
|
795
|
-
slot.1 = elem_raws[mk].clone();
|
|
796
|
-
} else {
|
|
797
|
-
next.push((${rustStrLit(op.into ?? "")}.to_string(), elem_raws[mk].clone()));
|
|
798
|
-
}
|
|
799
|
-
Value::Obj(next)
|
|
800
|
-
}
|
|
801
|
-
_ => {
|
|
802
|
-
*err_cell = Some(behavior_contracts::BehaviorError::new(
|
|
803
|
-
"MAP_INTO_ELEMENT_NOT_OBJECT",
|
|
804
|
-
format!("map '${op.id}': 'into' requires object elements (element {mj} is not an object)"),
|
|
805
|
-
));
|
|
806
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
mk += 1;
|
|
810
|
-
merged
|
|
811
|
-
} else {
|
|
812
|
-
oel.clone()
|
|
813
|
-
};
|
|
814
|
-
match ${elemMat}(&elem_val) {
|
|
815
|
-
Ok(tv) => typed.push(tv),
|
|
816
|
-
Err(e) => {
|
|
817
|
-
*err_cell = Some(e);
|
|
818
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
*${typedCell(op.id)}.borrow_mut() = typed;
|
|
823
|
-
${producedCell(op.id)}.set(true);`;
|
|
824
|
-
return ` if i == ${op.index} {
|
|
825
|
-
let over_arr: Vec<Value> = match ${op.over} {
|
|
826
|
-
Ok(Value::Arr(a)) => a,
|
|
827
|
-
Ok(_) => {
|
|
828
|
-
*err_cell = Some(behavior_contracts::BehaviorError::new(
|
|
829
|
-
"MAP_OVER_NOT_ARRAY",
|
|
830
|
-
"map '${op.id}': 'over' did not evaluate to an array".to_string(),
|
|
831
|
-
));
|
|
832
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
833
|
-
}
|
|
834
|
-
Err(e) => {
|
|
835
|
-
*err_cell = Some(e.into());
|
|
836
|
-
return ExecOutcome::Error("aborted".to_string());
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
let mut kept_idx: Vec<usize> = Vec::new();
|
|
840
|
-
${collectLoop}
|
|
841
|
-
${buildTyped}
|
|
842
|
-
return ExecOutcome::Ok(Value::Null);
|
|
843
|
-
}`;
|
|
844
|
-
}
|
|
845
|
-
/** typed map の要素 fail-closed 材化関数名(component 名で namespace・rust)。 */
|
|
846
|
-
function mapElemMaterializerNameRust(compName, nodeId) {
|
|
847
|
-
return `materialize_map_elem_${rustStraightlineInternals.sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* emitNodeMaterializers — scalar/arr/opt を outType に持つ typed **node** 用の材化関数
|
|
851
|
-
* `materialize_node_cell_*(&Value) -> Result<T, BehaviorError>` を emit する(named node は marshal_T* を
|
|
852
|
-
* 直接使うので不要)。exec arm から `?` なしで呼べる名前付き関数へ切り出す(closure 制約回避 + de-box)。
|
|
853
|
-
*/
|
|
854
|
-
function emitNodeMaterializers(comp, typedNodes, plan) {
|
|
855
|
-
const parts = [];
|
|
856
|
-
for (const [id, ref] of typedNodes.entries()) {
|
|
857
|
-
if (ref.kind === "named")
|
|
858
|
-
continue;
|
|
859
|
-
const ty = renderTypeRef(ref);
|
|
860
|
-
const body = emitFieldMaterialize("raw", ref, plan);
|
|
861
|
-
parts.push(`#[allow(dead_code)]\nfn materialize_node_${typedCell(id)}(raw: &Value) -> Result<${ty}, BehaviorError> {\n Ok(${indentBlock(body, 1).trimStart()})\n}`);
|
|
862
|
-
}
|
|
863
|
-
return parts.join("\n\n");
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* emitMapElemMaterializersRust — typed map node(outType {arr:elemType})の要素 fail-closed 材化関数
|
|
867
|
-
* `materialize_map_elem_<comp>_<id>(raw: &Value) -> Result<elemT, BehaviorError>` を emit する(#66)。
|
|
868
|
-
* emitFieldMaterialize を再利用し、marshaller と同型の TYPE_MISMATCH/MISSING_PROP で loud reject する。
|
|
869
|
-
*/
|
|
870
|
-
function emitMapElemMaterializersRust(comp, plan) {
|
|
871
|
-
const parts = [];
|
|
872
|
-
for (const n of comp.body) {
|
|
873
|
-
if (!("map" in n))
|
|
874
|
-
continue;
|
|
875
|
-
const ot = n.outType;
|
|
876
|
-
if (ot === undefined)
|
|
877
|
-
continue;
|
|
878
|
-
const ref = deriveTypeRef(ot, plan);
|
|
879
|
-
if (ref.kind !== "arr")
|
|
880
|
-
continue;
|
|
881
|
-
const elemRef = ref.elem;
|
|
882
|
-
const ty = renderTypeRef(elemRef);
|
|
883
|
-
const fn = mapElemMaterializerNameRust(comp.name, n.id);
|
|
884
|
-
// named 要素は marshal_T*(raw) を直接返す(clippy::needless_question_mark 回避)。それ以外は
|
|
885
|
-
// materialize block を Ok(...) で包む(emitFieldMaterialize は末尾式・error は内部 return)。
|
|
886
|
-
const inner = elemRef.kind === "named"
|
|
887
|
-
? `marshal_${elemRef.name}(raw)`
|
|
888
|
-
: `Ok(${indentBlock(emitFieldMaterialize("raw", elemRef, plan), 1).trimStart()})`;
|
|
889
|
-
parts.push(`#[allow(dead_code)]\nfn ${fn}(raw: &Value) -> Result<${ty}, BehaviorError> {\n ${inner}\n}`);
|
|
890
|
-
}
|
|
891
|
-
return parts.join("\n\n");
|
|
892
|
-
}
|
|
893
|
-
// ── module 綴じ(straight-line 本体を流用 + typed 層)──────────────────────────────
|
|
894
|
-
function hasAnyTypeAnnotation(ctx) {
|
|
895
|
-
for (const comp of ctx.ir.components) {
|
|
896
|
-
if (comp.outputType !== undefined)
|
|
897
|
-
return true;
|
|
898
|
-
for (const n of comp.body)
|
|
899
|
-
if (n.outType !== undefined)
|
|
900
|
-
return true;
|
|
901
|
-
}
|
|
902
|
-
return false;
|
|
903
|
-
}
|
|
904
|
-
/** typed component(run_typed_* 経路で駆動するもの)か。 */
|
|
905
|
-
function isTypedComponent(comp) {
|
|
906
|
-
if (comp.outputType !== undefined)
|
|
907
|
-
return true;
|
|
908
|
-
return comp.body.some((n) => n.outType !== undefined);
|
|
909
|
-
}
|
|
910
|
-
/** marshaller helper(BehaviorError 生成)。生成モジュールに 1 度だけ emit。 */
|
|
911
|
-
function emitMarshalHelpers() {
|
|
912
|
-
return `// marshaller helpers — de-box failures map to BehaviorError (fail-closed, TYPE_MISMATCH / MISSING_PROP).
|
|
913
|
-
// (Some are unused in modules whose typed corpus never hits that failure shape — generated toolkit.)
|
|
914
|
-
#[allow(dead_code)]
|
|
915
|
-
fn marshal_type_error(prefix: &str, got: &str) -> BehaviorError {
|
|
916
|
-
BehaviorError::new("TYPE_MISMATCH", format!("{prefix}{got}"))
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
#[allow(dead_code)]
|
|
920
|
-
fn marshal_missing_prop(prefix: &str, key: &str) -> BehaviorError {
|
|
921
|
-
BehaviorError::new("MISSING_PROP", format!("{prefix}{key}"))
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// input_ref — reads a single leaf from the input params Value (a genuine boundary read, NOT a
|
|
925
|
-
// reconstruction of a dynamic Value node-result tree). Used only when an output directly references
|
|
926
|
-
// an input param (rare in the bc typed corpus). Best-effort: the type plan guarantees the shape.
|
|
927
|
-
#[allow(dead_code)]
|
|
928
|
-
fn input_ref(path: &[&str], scope: &[(String, Value)]) -> Value {
|
|
929
|
-
if path.is_empty() {
|
|
930
|
-
return Value::Null;
|
|
931
|
-
}
|
|
932
|
-
let mut cur = match scope.iter().find(|(k, _)| k == path[0]) {
|
|
933
|
-
Some((_, v)) => v.clone(),
|
|
934
|
-
None => return Value::Null,
|
|
935
|
-
};
|
|
936
|
-
for seg in &path[1..] {
|
|
937
|
-
match cur.obj_get(seg) {
|
|
938
|
-
Some(v) => cur = v.clone(),
|
|
939
|
-
None => return Value::Null,
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
cur
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
fn no_typed_exec_arm(id: &str) -> BehaviorError {
|
|
946
|
-
BehaviorError::new("UNKNOWN_NODE_KIND", format!("op '{id}' has no generated typed exec arm (fail-closed)"))
|
|
947
|
-
}`;
|
|
948
|
-
}
|
|
949
|
-
/** Default 導出不能な named struct(null 単独型 Value field を持つもの)だけ手書き Default を emit。 */
|
|
950
|
-
function emitDefaults(plan) {
|
|
951
|
-
return plan.decls
|
|
952
|
-
.filter((d) => !isDefaultable({ kind: "named", name: d.name }, plan))
|
|
953
|
-
.map((d) => {
|
|
954
|
-
const fields = d.fields
|
|
955
|
-
.map((f) => ` ${rustFieldName(f.name)}: ${defaultExpr(f.type)},`)
|
|
956
|
-
.join("\n");
|
|
957
|
-
return `impl Default for ${d.name} {\n fn default() -> Self {\n ${d.name} {\n${fields}\n }\n }\n}`;
|
|
958
|
-
})
|
|
959
|
-
.join("\n\n");
|
|
960
|
-
}
|
|
961
|
-
/** TypeRef の Rust default 値式(Default::default() を持たない Value 型に対応するため明示)。 */
|
|
962
|
-
function defaultExpr(ref) {
|
|
963
|
-
switch (ref.kind) {
|
|
964
|
-
case "scalar":
|
|
965
|
-
switch (ref.scalar) {
|
|
966
|
-
case "string":
|
|
967
|
-
return "String::new()";
|
|
968
|
-
case "int":
|
|
969
|
-
return "0i64";
|
|
970
|
-
case "float":
|
|
971
|
-
return "0f64";
|
|
972
|
-
case "bool":
|
|
973
|
-
return "false";
|
|
974
|
-
case "null":
|
|
975
|
-
return "Value::Null";
|
|
976
|
-
}
|
|
977
|
-
break;
|
|
978
|
-
case "opt":
|
|
979
|
-
return "None";
|
|
980
|
-
case "arr":
|
|
981
|
-
return "Vec::new()";
|
|
982
|
-
case "named":
|
|
983
|
-
return `${ref.name}::default()`;
|
|
984
|
-
}
|
|
985
|
-
throw new Error(`typed rust: defaultExpr fell through for ${JSON.stringify(ref)}`);
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* injectTypedDispatch — rust-straightline 本体を typed-aware にする(typed IR 専用の書き換え。untyped IR は
|
|
989
|
-
* この関数を通らない=byte-identical 後方互換)。2 点だけ触る:
|
|
990
|
-
* 1. `Bound::call` の `match name` で **typed component の arm を run_typed_* へ差し替える**(run_* 呼びを
|
|
991
|
-
* run_typed_* 呼びに置換 — 重複 arm を作らない=unreachable_patterns 回避)。
|
|
992
|
-
* 2. 差し替えで unreachable になる straight-line `fn run_<typed>` に `#[allow(dead_code)]` を付す(byte 上は
|
|
993
|
-
* 定義のまま・struct-native 経路が SSoT。dead_code lint 回避)。
|
|
994
|
-
* 定数(IR_FINGERPRINT/COMPONENT_NAMES)・helper・untyped component の run_* と arm は byte 不変
|
|
995
|
-
* (可搬 IR は straight-line 本体ごと埋め込まない)。
|
|
996
|
-
*/
|
|
997
|
-
function injectTypedDispatch(code, ctx) {
|
|
998
|
-
let out = code;
|
|
999
|
-
for (const c of ctx.ir.components) {
|
|
1000
|
-
if (!isTypedComponent(c))
|
|
1001
|
-
continue;
|
|
1002
|
-
const fn = rustStraightlineInternals.sanitize(c.name);
|
|
1003
|
-
// (1) dispatch arm 差し替え(run_<fn> → run_typed_<fn>。1 度だけ)。
|
|
1004
|
-
const arm = `${rustStrLit(c.name)} => run_${fn}(&mut self.handlers, input),`;
|
|
1005
|
-
const typedArm = `${rustStrLit(c.name)} => run_typed_${fn}(&mut self.handlers, input),`;
|
|
1006
|
-
if (!out.includes(arm)) {
|
|
1007
|
-
throw new Error(`rust-typed emit: could not find the straight-line dispatch arm for typed component '${c.name}'`);
|
|
1008
|
-
}
|
|
1009
|
-
out = out.replace(arm, typedArm);
|
|
1010
|
-
// (2) 差し替えで dead になる straight-line 定義に allow を付す。dead な本体(typed dispatch が SSoT に
|
|
1011
|
-
// なるので実行されない)に対し dead_code だけでなく、body 内で発火し得る lint(未使用 handler・
|
|
1012
|
-
// map の static-string format! など。#66 で map/cond typed component も dead 化するため)も抑止する。
|
|
1013
|
-
const def = `fn run_${fn}<H: ComponentExec>(`;
|
|
1014
|
-
if (!out.includes(def)) {
|
|
1015
|
-
throw new Error(`rust-typed emit: could not find the straight-line fn definition for typed component '${c.name}'`);
|
|
1016
|
-
}
|
|
1017
|
-
out = out.replace(def, `#[allow(dead_code, unused_variables, clippy::useless_format)]\n${def}`);
|
|
1018
|
-
}
|
|
1019
|
-
return out;
|
|
1020
|
-
}
|
|
1021
|
-
function emit(ctx) {
|
|
1022
|
-
// rust-straightline のモジュール全文を同梱する(untyped component の run_*/bind = A2 SSoT。byte 不変で
|
|
1023
|
-
// 後方互換)。typed component は **struct-native な run_typed_* へ差し替える**(Bound::call の dispatch
|
|
1024
|
-
// 先頭に typed 相談を注入)。straight-line 本体・run_*・helper・定数は byte 不変(IR は非埋め込み)。
|
|
1025
|
-
const typed = hasAnyTypeAnnotation(ctx);
|
|
1026
|
-
if (!typed)
|
|
1027
|
-
return rustStraightlineEmitter.emit(ctx);
|
|
1028
|
-
const plan = buildTypePlan(ctx.ir);
|
|
1029
|
-
const decls = rustTypedMaterializer.emitTypeDecls(plan);
|
|
1030
|
-
const defaults = emitDefaults(plan);
|
|
1031
|
-
const marshallers = rustTypedMaterializer.emitMarshallers(plan);
|
|
1032
|
-
const serializers = emitSerializers(plan);
|
|
1033
|
-
const marshalHelpers = emitMarshalHelpers();
|
|
1034
|
-
// node 材化関数(scalar/arr/opt outType node 用)を全 typed component から集める(#66: cond も対象。
|
|
1035
|
-
// map は要素材化 materialize_map_elem_* が別途担うので除外)。
|
|
1036
|
-
const nodeMaterializers = [];
|
|
1037
|
-
for (const comp of ctx.ir.components) {
|
|
1038
|
-
if (!isTypedComponent(comp))
|
|
1039
|
-
continue;
|
|
1040
|
-
const typedNodes = new Map();
|
|
1041
|
-
for (const n of comp.body) {
|
|
1042
|
-
if ("map" in n)
|
|
1043
|
-
continue;
|
|
1044
|
-
const ot = n.outType;
|
|
1045
|
-
if (ot !== undefined)
|
|
1046
|
-
typedNodes.set(n.id, deriveTypeRef(ot, plan));
|
|
1047
|
-
}
|
|
1048
|
-
const nm = emitNodeMaterializers(comp, typedNodes, plan);
|
|
1049
|
-
if (nm)
|
|
1050
|
-
nodeMaterializers.push(nm);
|
|
1051
|
-
}
|
|
1052
|
-
// typed map の要素 fail-closed 材化関数(#66)。
|
|
1053
|
-
const mapElemMaterializers = [];
|
|
1054
|
-
for (const comp of ctx.ir.components) {
|
|
1055
|
-
if (!isTypedComponent(comp))
|
|
1056
|
-
continue;
|
|
1057
|
-
const me = emitMapElemMaterializersRust(comp, plan);
|
|
1058
|
-
if (me)
|
|
1059
|
-
mapElemMaterializers.push(me);
|
|
1060
|
-
}
|
|
1061
|
-
const runners = ctx.ir.components
|
|
1062
|
-
.filter((c) => isTypedComponent(c))
|
|
1063
|
-
.map((c) => emitTypedRunner(c, plan))
|
|
1064
|
-
.join("\n\n");
|
|
1065
|
-
const banner = `// ── typed layer (bc#48 / Layer B2 — Rust REAL de-box, STRUCT-NATIVE exec) ────────────
|
|
1066
|
-
// Unlike the TS oracle (type erasure, no runtime de-box), the Rust typed layer executes typed
|
|
1067
|
-
// components in STRUCT space (the largest de-box win): run_typed_* drives the plan with the
|
|
1068
|
-
// run_plan primitive (plan SSoT) and per-node exec arms that marshal the handler result DIRECTLY
|
|
1069
|
-
// into the node's outType struct at the boundary (raw->typed) and store the STRUCT in a typed cell
|
|
1070
|
-
// (RefCell<T*>). The dynamic Value node-result scope (Vec<(String, Value)> results) and the Value
|
|
1071
|
-
// output tree are NEVER built. ref/field access reads the structs (.field, not obj_get); the output
|
|
1072
|
-
// is assembled as a struct and serialized to a Value ONLY at the final return boundary.
|
|
1073
|
-
// Bind routes typed components here; untyped components/IR stay byte-identical to rust-straightline.
|
|
1074
|
-
use std::cell::RefCell;`;
|
|
1075
|
-
const sections = [
|
|
1076
|
-
banner,
|
|
1077
|
-
decls || undefined,
|
|
1078
|
-
defaults || undefined,
|
|
1079
|
-
marshalHelpers,
|
|
1080
|
-
marshallers
|
|
1081
|
-
? `// raw -> typed marshallers (§4.4): de-box a dynamic Value into a concrete struct, field by field.\n${marshallers}`
|
|
1082
|
-
: undefined,
|
|
1083
|
-
nodeMaterializers.length
|
|
1084
|
-
? `// scalar/arr/opt node materializers: concrete de-box for typed nodes whose outType is not a named obj.\n${nodeMaterializers.join("\n\n")}`
|
|
1085
|
-
: undefined,
|
|
1086
|
-
mapElemMaterializers.length
|
|
1087
|
-
? `// typed map element materializers (#66): FAIL-CLOSED de-box of each map element into the Vec<T> element type.\n${mapElemMaterializers.join("\n\n")}`
|
|
1088
|
-
: undefined,
|
|
1089
|
-
serializers
|
|
1090
|
-
? `// typed -> Value serializers (final return boundary only): canonical observation for the equivalence pin.\n${serializers}`
|
|
1091
|
-
: undefined,
|
|
1092
|
-
`// struct-native typed runners: the REAL de-box exec path (called by Bind for typed components).\n${runners}`,
|
|
1093
|
-
];
|
|
1094
|
-
const typedBody = sections.filter((s) => !!s).join("\n\n");
|
|
1095
|
-
// typed body が実際に参照する RunPlan-path import を scan で確定し、base straight-line モジュールに
|
|
1096
|
-
// それだけを force する(bc#75: 逐次 base は run_plan/OpSpec/RelationKind/json! を落とすが、struct-native
|
|
1097
|
-
// typed runner は参照する)。typed body は我々の生成した rust なので token scan は安全(過剰 import は
|
|
1098
|
-
// clippy unused で落ちるため granular に絞る)。
|
|
1099
|
-
const req = {
|
|
1100
|
-
runPlan: /\brun_plan\(|\bOpSpec\b|ExecOutcome\b/.test(typedBody),
|
|
1101
|
-
relationKind: /\bRelationKind\b/.test(typedBody),
|
|
1102
|
-
json: /\bjson!\(/.test(typedBody),
|
|
1103
|
-
};
|
|
1104
|
-
setRustForceRunPlanImports(req);
|
|
1105
|
-
const straightlineCode = rustStraightlineEmitter.emit(ctx);
|
|
1106
|
-
setRustForceRunPlanImports(null);
|
|
1107
|
-
const patched = injectTypedDispatch(straightlineCode, ctx);
|
|
1108
|
-
// straight-line 本体は改行で終わる。1 空行を挟んで typed 層、末尾は改行 1 つ。
|
|
1109
|
-
return `${patched.replace(/\n+$/, "\n")}\n${typedBody}\n`;
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* rustTypedEmitter — 言語識別子 `rust-typed` の emitter plugin(bc#48 B2, 実 de-box)。
|
|
1113
|
-
* rust-straightline 本体(byte 同一)+ typed 層(struct/marshaller/serializer/struct-native runner)。
|
|
1114
|
-
* `outType` 無し IR は rust-straightline と byte-identical(後方互換)。
|
|
1115
|
-
*/
|
|
1116
|
-
export const rustTypedEmitter = {
|
|
1117
|
-
language: "rust-typed",
|
|
1118
|
-
fileExtension: ".rs",
|
|
1119
|
-
defaultRuntimeImport: "behavior_contracts",
|
|
1120
|
-
filenameHint: "behaviors_typed_generated.rs",
|
|
1121
|
-
emit,
|
|
1122
|
-
};
|
|
1123
|
-
/**
|
|
1124
|
-
* rustTypedInternals — shared Rust typed-emit helpers the RAW-ABI emitter (bc#76,
|
|
1125
|
-
* emit-raw-abi-rust.ts) reuses so boxed and raw paths keep one implementation of the
|
|
1126
|
-
* type-plan/naming/serializer discipline (only the marshaller data plane and the handler
|
|
1127
|
-
* dispatch differ between them).
|
|
1128
|
-
*/
|
|
1129
|
-
export const rustTypedInternals = {
|
|
1130
|
-
sanitize: rustStraightlineInternals.sanitize,
|
|
1131
|
-
rustFieldName,
|
|
1132
|
-
rustStrLit,
|
|
1133
|
-
renderTypeRef,
|
|
1134
|
-
rustScalar,
|
|
1135
|
-
typedCell,
|
|
1136
|
-
typedFieldAccess,
|
|
1137
|
-
isTypedComponent,
|
|
1138
|
-
hasAnyTypeAnnotation,
|
|
1139
|
-
collectTypedNodes,
|
|
1140
|
-
emitTypeDecls,
|
|
1141
|
-
emitDefaults,
|
|
1142
|
-
emitSerializers,
|
|
1143
|
-
emitMarshallers,
|
|
1144
|
-
emitMarshalHelpers,
|
|
1145
|
-
materializeExpr,
|
|
1146
|
-
serializeTyped,
|
|
1147
|
-
emitTypedValue,
|
|
1148
|
-
indentBlock,
|
|
1149
|
-
emitOpsLiteral: rustStraightlineInternals.emitOpsLiteral,
|
|
1150
|
-
emitPlanLiteral: rustStraightlineInternals.emitPlanLiteral,
|
|
1151
|
-
rustStraightlineEmitter,
|
|
1152
|
-
rustStraightlineInternals,
|
|
1153
|
-
setRustForceRunPlanImports,
|
|
1154
|
-
};
|
|
1155
|
-
//# sourceMappingURL=emit-straightline-typed-rust.js.map
|