/** * straightline.ts — 共通 Generator の「直線 emit」枠(bc#37 / Layer A1)。 * * typed-codegen.md §4.1(主経路・脱解釈・型なし)の language-neutral な骨組み。 * 既存のリテラル焼き込み emit(emit-typescript.ts の `bind` — endpoint 3-literal)と * **併存**する additive な第2モード。component ごとに「plan を直呼び・各ノードの port 評価と * handler 呼びを直線展開した」ネイティブ関数を emit する枠を提供する。 * * この枠が消すもの(AC1): **`runBehavior` の tree-walk**—— * - `nodeKind` ディスパッチ(body ノード種の実行時分岐) * - `evalPorts` の汎用ループ(未知 ports を毎回 `evaluateExpression` で歩く) * - 汎用 `exec` closure + 汎用 output 評価 * これらを **IR から静的に展開した per-node の直線コード**に置き換える。プランのステージ * 実行・Skip 伝播・Policy Kind は runtime primitive `runPlan` として維持する(意味論の * SSoT を一本に保つ — typed-codegen.md §4.1 の「plan は primitive 維持」)。式演算子の * 意味論も再実装せず、A0(#36)の式演算子 primitive(`ref`/`concat`/… を評価する SSoT * ラッパ)を直呼びする。よって生成コードは: * 1. `nodeKind` で分岐しない(各ノードの exec 分岐は生成時に確定・ハードコード)。 * 2. `evalPorts` で未知 ports を歩かない(各 port を名前付き primitive 呼びに展開)。 * 3. `runBehavior` を内部で呼ばない(=薄い束ねではない)。 * * スコープ(A1 = read hot path): `componentRef` + `cond` + 単純 Expression Port のみ。 * `map`/`batched`/`concurrency`(A3)・hydration/relation stitching(A4)は **fail-closed** * ({@link assertStraightlineSupported} が `GeneratorFailure(UNSUPPORTED_NODE_STRAIGHTLINE)`)。 * 黙って誤コード生成・部分生成をしない(スコープ境界を fail-closed に — AC5)。 * * 言語 plugin IF(A2 で go/rust/python/php が同じ枠に乗る): {@link StraightlineDialect} が * 「式ノード → その言語の primitive 呼び文字列」と「per-node/handler/output/module の綴じ方」を * 供給する。language-neutral な走査(body 検査・op literal・scope 合成・fail-closed 判定)は * ここで一度だけ実装する。 */ import type { ComponentGraphIR, Component, BodyNode } from "../behavior.js"; import type { EmitContext } from "./core.js"; /** 直線 emit が扱える body ノードか(componentRef + cond + map + fanout — A3 以降 / #135)。 */ export declare function isStraightlineNode(n: BodyNode): boolean; /** * assertStraightlineSupported — IR 全体が直線 emit のスコープに収まるか検査し、外れるノード種を * 含むなら LOUD reject する(AC)。部分生成はしない。A3 時点で componentRef/cond/map は全て * covered。将来の hydration/relation stitching(A4 #43)を静的展開する固有ノードが導入された * 場合は、ここに fail-closed 判定を足す(現行 IR schema には無いため、未知ノード= * componentRef/cond/map のいずれでもない形のみを拒否する)。 */ export declare function assertStraightlineSupported(ir: ComponentGraphIR): void; /** 直線化された1ノードの実行分岐(言語中立の記述。dialect が本体を綴じる)。 */ export type StraightlineOp = { kind: "componentRef"; /** body 内 index(ops リテラルの index と一致)。 */ index: number; id: string; /** catalog 名(handler 解決キー)。 */ component: string; /** port 名 → 評価式(dialect が生成した primitive 呼び文字列)。宣言順保存。 */ ports: { name: string; expr: string; }[]; } | { kind: "cond"; index: number; id: string; /** `{cond:[if,then,else]}` を1式として評価する式(dialect 生成)。 */ expr: string; } | { kind: "map"; index: number; id: string; /** 各要素で呼ぶ catalog 名(handler 解決キー)。**純式 map(#222)には無い** — 要素本体は式で、 * handler を呼ばないので解決キーが存在しない(形の判定は `mapTransformExpr`)。 */ component?: string; /** 純式 map の要素式(dialect 生成・要素 scope で評価)。`component` と排他。 */ transform?: string; /** 要素束縛名(`map.as`。生成コードは per-element scope に `as`→要素値 を足す)。 */ as: string; /** * `over` 配列を評価する式(component scope で。dialect 生成)。要素反復の前に1回評価する。 * scope は component の scopeVar(要素束縛前)で解決される。 */ over: string; /** * per-element guard を `{cond:[when,true,false]}` へ lower した式(dialect 生成、要素 scope で * 評価)。`when` 未指定なら undefined(全要素採用)。strict-bool 意味論は cond primitive/native。 */ guard?: string; /** 各 port を要素 scope で評価する式(dialect 生成)。宣言順保存。batched でも同じ。 */ ports: { name: string; expr: string; }[]; /** zip-attach キー(`map.into`)。未指定なら結果は collected リスト。 */ into?: string; /** * ノードの relationKind(`map.relationKind`)。connection のとき、guard-skip 要素の `into` は * 空 connection(unproducedValue("connection"))で埋める(#182 — writeUnproduced と同一 SSoT)。 */ relationKind?: "connection"; /** バッチ fan-out(`map.batched`)。true なら guard 通過全要素の ports を1回の handler 呼びへ。 */ batched: boolean; } | { kind: "fanout"; index: number; id: string; /** 各 id 要素を解決する batched catalog 名(handler 解決キー)。 */ component: string; /** 要素束縛名(`fanout.as`。生成コードは per-element scope に `as`→要素値 を足す)。 */ as: string; /** `over`(id-list)を評価する式(component scope、要素束縛前。dialect 生成)。 */ over: string; /** 各 item port を要素 scope で評価する式(dialect 生成)。宣言順保存。 */ ports: { name: string; expr: string; }[]; /** first-seen dedupe の基準となる結果要素 port(フィールド名)。 */ dedupeKey: string; /** dangling(null/欠落 body)の扱い: `"dangling"`=drop / `"none"`=保持。 */ drop: "dangling" | "none"; /** 結果要素から除去する暗黙ソースフィールド名(省略時は strip しない)。 */ implicitSource?: string; }; /** * 直線 emit の言語 dialect。language-neutral な走査({@link buildComponentPlan})が呼ぶ * seam。式ノード → primitive 呼び文字列({@link emitExprCalls} を各言語の primitive 命名で * 具体化)と、per-node/handler/output/module の綴じ方を供給する。 */ export interface StraightlineDialect { /** * Expression IR ノードを、この言語で「scope を受けて Value を返す」primitive 呼び式に * emit する。leaf/演算子ノードを A0 primitive(`ref`/`concat`/…)へ落とす。 * `scopeVar` は生成コード内の scope 変数名。 */ emitExpr(node: unknown, scopeVar: string): string; /** * map ノード本体(over/guard/ports)の Expression IR を評価するときの scope 変数名。 * 各 dialect の map 展開が per-element scope をこの名前でローカルに組む(例 TS `mscope()`)。 * 要素束縛(`map.as`)を含んだ scope をこの名前で読める前提で {@link buildComponentPlan} が * ports/guard の式を emit する。over は component の scopeVar(要素束縛前)で評価する。 */ mapScopeVar: string; /** * component 1つぶんの直線関数(ops リテラル・per-node exec 分岐・output 評価・runPlan 呼び)を * 綴じる。{@link emitModule} が各 component ぶん呼ぶ。 */ emitComponent(plan: ComponentPlan, ctx: EmitContext): string; /** モジュール全体(ヘッダ・import・定数・load-time 検査・各 component)を綴じる。 */ emitModule(plans: ComponentPlan[], ctx: EmitContext): string; /** * module 単位の emit 開始 hook(optional)。plan 構築(= dialect.emitExpr が走る)より * **前に**呼ばれる。go/rust は生成中 module の runtime-json 使用トラッカーをここで reset する * (import 要否の構造的判定用 — 式 emit は plan 構築時に走るため emitModule 内 reset では遅い)。 */ beginModule?(): void; } /** 1 component ぶんの直線化計画(language-neutral)。 */ export interface ComponentPlan { name: string; component: Component; /** body ノード → OpSpec リテラル(parent は index に解決済み)。 */ opsLiteral: OpLiteral[]; /** 各ノードの直線 exec 分岐。 */ ops: StraightlineOp[]; } /** OpSpec のリテラル形(runPlan に渡す ops 配列の1要素。behavior.ts buildOps と同規律)。 */ export interface OpLiteral { id: string; parent: number | null; bindField?: string; relationKind?: "connection"; policy?: "fail" | "retry" | "continue"; } /** * buildComponentPlan — 1 component を language-neutral な {@link ComponentPlan} に落とす。 * behavior.ts の `buildOps`(parent の id→index 解決)を鏡映しつつ、各ノードの exec 分岐を * dialect の `emitExpr` で直線コード片へ展開する。ここが「nodeKind ディスパッチ」を * **生成時に一度だけ**行い、生成コードには分岐を残さない核。 */ export declare function buildComponentPlan(comp: Component, dialect: StraightlineDialect, scopeVar: string): ComponentPlan; /** * emitStraightlineModule — dialect を使って IR 全体の直線モジュールを綴じる(公開 entry)。 * A1 スコープ検査(fail-closed)を先に走らせる。 */ export declare function emitStraightlineModule(ctx: EmitContext, dialect: StraightlineDialect, scopeVar: string): string; /** * sequentialOrder — component の plan が「厳密逐次」なら実行順(op index 列)を返す。 * - plan 無し → 1 op = 1 stage の逐次 fallback(宣言順)= [0..n-1]。 * - plan 有り → 全 group が高々 1 要素・全 op をちょうど 1 回被覆・index が範囲内のとき * group 順を flatten した列。多 op stage(実並行の可能性)・被覆不正(実行時 INVALID_PLAN * の意味論を RunPlan に保存させる)は null。 */ export declare function sequentialOrder(comp: Component): number[] | null; /** * concurrencyPlan (bc#87) — the component's plan is a valid staged plan WITH at least one * real-concurrency stage (a stage with 2+ mutually-independent members). Returns the staged * execution plan (each stage = op indices, ASCENDING — matching plan.go/plan.rs's `sort` before * commit) + the static concurrency bound, so the emitter can lay down explicit static parallel * orchestration (goroutine/scoped-thread per member, bounded by `concurrency`). The staging, the * bound, and WHICH ops run in parallel are ALL fully static (baked from `plan.groups`). * * Returns null (keep the runtime RunPlan/run_plan path — this analyzer does not cover it) when: * - the plan is strictly sequential (`sequentialOrder != null` already covers it), * - coverage is invalid (out-of-range / duplicate / under-cover) — that INVALID_PLAN semantics * stays runtime, * - a multi-op stage has an INTRA-STAGE parent/child dependency (a §1-violating plan; run_behavior * falls back to sequential there, but we do NOT statically claim it — fail-closed conservative), * - concurrency <= 1 (a bounded-parallel stage with bound 1 degenerates to sequential; run_behavior * keeps sequential dispatch — no static parallel orchestration to emit, stays sequential-eligible * via sequentialOrder only if all stages are single-op; a multi-op stage at bound 1 is left to the * runtime so we never emit a "parallel" form that is actually serial). * * The emitted parallel form is byte-equivalent to run_behavior BECAUSE the deterministic protocol is * reproduced exactly: PREFLIGHT (ascending, from settled prior-stage results) → DISPATCH (bounded, * the goroutine/thread is the ONLY runtime element) → COMMIT (ascending: interpret Policy Kind + * materialize). Error precedence under bounded concurrency is thus the LOWEST-index failing member of * the stage — never a race — because interpretation is committed in ascending index order. */ export interface StagePlan { /** stages in declaration order; each stage's op indices sorted ASCENDING (commit order). */ stages: number[][]; /** flattened execution order (stages concatenated) — the order node-result locals are declared in. */ order: number[]; /** static concurrency bound (plan.concurrency; >= 2 when any stage is real-parallel). */ concurrency: number; } export declare function concurrencyPlan(comp: Component): StagePlan | null; /** An execution plan for a covered read: strictly sequential (order only) OR staged with real * concurrency (bc#87). `parallelStages` maps a stage's first index → the full ascending stage member * list when that stage is real-parallel (2+ members); single-op stages are absent. */ export interface ExecPlan { order: number[]; concurrency: number; /** op index → the ascending member list of its stage IFF that stage is real-parallel (else absent). */ parallelStageOf: Map; } /** execPlan — the unified covered-read execution plan (bc#87): sequential OR staged-with-concurrency. * Returns null when neither (the component is not a covered read shape). */ export declare function execPlan(comp: Component): ExecPlan | null; /** 逐次直線化された 1 op の静的メタ(skip 可能性・policy)。 */ export interface SequentialOpMeta { /** 実行時に「未生成」で終わり得るか(parent skip 連鎖 / bindField null-binding / continue 失敗)。 */ canSkip: boolean; /** parent が自分より後に実行される(不正 plan への防御)→ 無条件 skip。 */ alwaysSkip: boolean; /** 静的解決済み policy。unknown は {invalid: 生値}(実行時 UNKNOWN_POLICY を保存)。 */ policy: "fail" | "retry" | "continue" | { invalid: string; }; } /** * opsLiteralOf — body ノード列 → `OpLiteral[]`(**この導出の唯一の実装**)。 * * ノード種ごとに注記の在り処が違う(componentRef は node 直下、map は `map.`、fanout は `fanout.`)ので、 * 「componentRef だけ読む」写しを別に持つと map/fanout の `policy` / `parent` が黙って落ちる — それは * {@link analyzeSequentialOps} の `canSkip`(= produced-aware 出力の駆動源)を面ごとに食い違わせる。 * straight-line ビルダと typed-native emitter は**この 1 本**を読む。 */ export declare function opsLiteralOf(comp: Component): OpLiteral[]; /** * analyzeSequentialOps — 実行順 `order` の下で各 op の skip 可能性と policy を静的解決する。 * 判定は plan.ts preflightOp / interpretOutcome の鏡映: * - policy 検証は skip 判定より先(UNKNOWN_POLICY は skip でも投げる)— 生成時に静的化。 * - parent が ok でない(skip 済み)なら skip。bindField があれば null/欠落 binding で skip。 * - continue policy の失敗は「未生成」(skip 扱い・下流へ連鎖)。 */ export declare function analyzeSequentialOps(opsLiteral: OpLiteral[], order: number[], comp: Component): SequentialOpMeta[]; /** * StaticExpr — Expression IR ノードの静的形状(bc#75)。emitter はこの分類でネイティブ inline と * primitive SSoT 経路を分ける。`dynamic` は「生成時に形が確定しない/fail-closed 演算子意味論を * SSoT に残す」ノード(overflow 算術・短絡 and/or/cond/coalesce・obj/arr 構築・比較・len・ * リテラルラッパ・bare number 等)。 */ export type StaticExpr = { kind: "str"; value: string; } | { kind: "bool"; value: boolean; } | { kind: "null"; } | { kind: "ref"; opt: boolean; path: string[]; } | { kind: "concat"; parts: StaticExpr[]; } | { kind: "dynamic"; node: unknown; }; /** * classifyExpr — 静的 inline 可能な形状(string/bool/null リテラル・静的 path の ref/refOpt・ * それらからなる concat)を分類する。それ以外(bare number 含む — checked int/float リテラル * 規則は evaluate SSoT)は dynamic。concat は arity>=2 が静的に確定しているときだけ inline * (arity<2 の実行時 INVALID_NODE 意味論は evaluate SSoT に保存)。 */ export declare function classifyExpr(node: unknown): StaticExpr; /** * isScopeFree — ノード(primitive 経路に残る dynamic 式)が scope を読み得ないか。 * scope を読む唯一の経路は ref/refOpt なので、単一キー {ref:…}/{refOpt:…} の object が * どこにも現れなければ scope-free(emitter は空 scope 定数を渡せる)。obj の field 名が * 偶然 "ref" のケースは過大近似で「scope を読むかもしれない」に倒れる(常に安全側 — * 本物の scope を渡すだけで意味論は不変)。 */ export declare function isScopeFree(node: unknown): boolean; /** StaticExpr(分類済み)自体が scope を読み得るか(inline ref はローカル読みに解決されるので除外し、dynamic 部分だけを見る)。 */ export declare function staticExprNeedsScope(e: StaticExpr): boolean; /** * componentNeedsScope — component のどこか(port / cond / map over/guard/ports / output)に * 「本物の scope を読む dynamic 式」が残るか。false なら emitter は動的 scope オブジェクトを * 一切実体化しない(inline ref はローカル直読み・scope-free primitive は空 scope 定数)。 */ export declare function componentNeedsScope(plan: ComponentPlan): boolean; export { narrowedNonNullPaths, nodeNarrowedPaths } from "../behavior.js"; //# sourceMappingURL=straightline.d.ts.map