// UsingResolveSSOTBox — SSOT for using/modules resolution (Phase 22.1) // Contract (MVP): // - Pure function. IO(filesystem access)禁止。与えられた ctx のみを参照。 // - 優先順: modules(nyash.toml 明示) > relative 推定(cwd → using_paths の順) > 見つからない(null) // - relative 推定は ctx.relative_hint=="1" が有効時のみ(既定OFF/挙動不変)。 // - 曖昧(複数候補)の最終判断は Runner 側。strict=1 時は legacy へ委譲する(本箱は null を返すのが安全)。 // Extension points: // - ctx.modules: Map(厳密一致) // - ctx.using_paths: Array(将来のヒント/本箱では純粋合成のみ) // - ctx.cwd: String(相対の基準) // Quick README (Phase 25.1 using設計) // - 役割: using 解決の唯一の窓口(SSOT)。parser/Stage‑B/Stage‑1 は IO や名前探索をここに委譲する。 // - I/F: // - resolve(name, ctx) -> path|null : modules/cwd/using_paths から純粋に合成して返す(IOなし)。 // - resolve_modules(modules_json, using_entries_json, ctx) -> modules_json_resolved|null : // modules_json(nyash.toml 相当)と using_entries_json(UsingCollector 出力)を突き合わせ、 // 「解決済み modules_json」(同名重複などの調停結果)を返す。IOなし。 // - resolve_prefix(using_entries_json, modules_json, ctx) -> prefix_string : // using_entries_json を modules_json を使ってパス解決し、ファイル読まずに // 「prefix 文字列(空でよい)を組み立てるための情報」を返すスコープ(MVPは空文字返しでOK)。 // - ポリシー: IO/ファイル読み込みは絶対にしない。より重い処理は entry/pipeline 側の責務。 static box UsingResolveSSOTBox { /// Resolve a module name to a file path string (or null when not found). /// name: requested module name (e.g., "hako.mir.builder.internal.lower_return_int") /// ctx : optional map for extra hints. Supported keys (all optional): /// - modules: Map (exact name → path) /// - using_paths: Array (search bases; no IO here, used only for future hints) /// - cwd: String (caller context dir) method resolve(name, ctx) { if name == null { return null } // Strictly pure: do not access filesystem here. Consume only provided hints. // 1) modules mapping has priority if ctx != null { local m = ctx.get("modules"); if m != null { local hit = m.get(name); if hit != null { return hit } } // 2) Relative hint (optional): synthesize a likely path using using_paths/cwd // Gate via ctx.relative_hint == "1" to avoid behavior changes unless explicitly enabled. local rh = ctx.get("relative_hint"); if rh != null && rh == "1" { local leaf = me._dot_to_slash(name) + ".hako"; // prefer cwd local cwd = ctx.get("cwd"); if cwd != null { return me._join_path(cwd, leaf) } local ups = ctx.get("using_paths"); if ups != null { local i = 0; while i < ups.size() { local base = ups.get(i); return me._join_path(base, leaf) i = i + 1 } } } } // No IO side effects in MVP return null } _dot_to_slash(s) { return s.replace(".", "/") } _join_path(base, leaf) { if base == null { return leaf } if base.endsWith("/") { return base + leaf } return base + "/" + leaf } // Merge modules_json + using_entries_json into resolved modules table (pure, no IO). resolve_modules(modules_json, using_entries_json, ctx) { // MVP: just echo back modules_json (keep behavior unchanged) to establish interface. return modules_json } // Build prefix string from using_entries_json with modules_json (pure, no IO). resolve_prefix(using_entries_json, modules_json, ctx) { // MVP: no file read, so prefix is empty. Interface is reserved for Stage‑1 entry. return "" } }