Phase 25.1b: Step2完了(FuncBodyBasicLowerBox導入)

Step2実装内容:
- FuncBodyBasicLowerBox導入(defs専用下請けモジュール)
- _try_lower_local_if_return実装(Local+単純if)
- _inline_local_ints実装(軽い正規化)
- minimal lowers統合(Return/BinOp/IfCompare/MethodArray系)

Fail-Fast体制確立:
- MirBuilderBox: defs_onlyでも必ずタグ出力
- [builder/selfhost-first:unsupported:defs_only]
- [builder/selfhost-first:unsupported:no_match]

Phase構造整備:
- Phase 25.1b README新設(Step0-3計画)
- Phase 25.2b README新設(次期計画)
- UsingResolverBox追加(using system対応準備)

スモークテスト:
- stage1_launcher_program_to_mir_canary_vm.sh追加

Next: Step3 LoopForm対応

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-15 22:32:13 +09:00
parent 6856922374
commit 7ca7f646de
31 changed files with 1670 additions and 323 deletions

View File

@ -77,24 +77,64 @@ Update (2025-11-15 — Phase 25.1: Stage1 CLI first commands wired)
Update (2025-11-15 — Phase 25.1a: Stage1 build pipeline hotfix progress) Update (2025-11-15 — Phase 25.1a: Stage1 build pipeline hotfix progress)
- Context: - Context:
- Stage1 CLIlauncher.hakoと selfhost AOT パイプラインbuild_stage1.shは設計上つながっているが、Program→MIR 変換フェーズでの selfhost builder 実行がまだ不安定 - Stage1 CLIlauncher.hakoと selfhost AOT パイプラインbuild_stage1.shは設計上つながっているが、現状 selfhost builder (MirBuilderBox on VM) は Stage1 CLI の Program(JSON) をフルで扱えず 171 bytes の stub MIR を返してしまうため、provider-first を既定としている
- CLI 側の `.hako → Program(JSON v0) → MIR(JSON) → EXE` ロジックは Ny コードとして実装済みで、Rust 側の Program→MIRenv.mirbuilder.emitも修復済み。 - CLI 側の `.hako → Program(JSON v0) → MIR(JSON) → EXE` ロジックは Ny コードとして実装済みで、Rust 側の Program→MIRenv.mirbuilder.emitも修復済み。
- Current symptoms (after first fixes): - Current symptoms (after first fixes):
- `tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako /tmp/launcher_mir.json`: - `tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako /tmp/launcher_mir.json`:
- StageB: `[emit:trace] Stage-B: SUCCESS - Generated Program(JSON)` まで成功Program(JSON v0) 自体は安定して取得可能)。 - StageB: `[emit:trace] Stage-B: SUCCESS - Generated Program(JSON)` まで成功Program(JSON v0) 自体は安定して取得可能)。
- selfhost builder 経路try_selfhost_builderは、当初の `Parse error: Unexpected token FN` から一歩前進し、現在は text-merge 後の巨大一時ファイルに対して `Parse error: Invalid expression at line <N>` が出ているprelude 連結のどこかに Stage3 と合わない断片が残っている状態)。 - selfhost builder 経路try_selfhost_builderは、当初の `Parse error: Unexpected token FN` → LoopForm/`init` 予約語衝突 → `self._is_on/1` 未解決 → `NewClosure` 未実装、という段階的なエラーが出ていたが、
- `lang/src/mir/builder/func_lowering.hako``local fn` をリネーム、
- `lang/src/shared/mir/loop_form_box.hako` / `lang/src/mir/builder/internal/lower_loop_count_param_box.hako``init``start_value` リネーム、
- `BuilderConfigBox._is_on` 呼び出しを `me._is_on` に統一し、
- `MirBuilderBox` / `MirBuilderMinBox``norm_if = fn(...)` を helper メソッド(`_norm_if_apply` / `_norm_json_if_needed`)に置き換えて lambda/closure を排除、
によって Stage3 パース/VM 実行の両方で builder Runner が launcher.hako を通せるようになった(`[OK] MIR JSON written (selfhost-first)` まで到達)。
- selfhost builder 経路try_selfhost_builder: - selfhost builder 経路try_selfhost_builder:
- `lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()``local func_len = func_jsons.length()` にリネームし、Stage3 パーサの予約語 `fn` による `Unexpected token FN, expected identifier` は解消済み - 現状 launcher.hako に対しては selfhost-first で Program→MIR が成功しているが、一般の lambda/closure (`fn(...) { ... }`) は VM 側で未サポートのままなので、selfhost builder のコードからは意図的に排除しているlambda の完全な意味論実装は後続フェーズに送る)
- それでも builder Runner 全体としては rc=1 のままで、`Invalid expression` が発生しているため、今後は merged prelude ハコを生成して問題行を特定するデバッグタスクが必要。
- provider 経路try_provider_emit → env.mirbuilder.emit: - provider 経路try_provider_emit → env.mirbuilder.emit:
- Rust 側の Program→MIR ルート修正後は `[mirbuilder/parse/error] undefined variable: args` が発生しなくなり、`launcher.hako` から MIR(JSON) を安定して生成できる状態delegate:provider 経路が暫定のメインパス)。 - Rust 側の Program→MIR ルート修正後は `[mirbuilder/parse/error] undefined variable: args` が発生しなくなり、`launcher.hako` から MIR(JSON) を安定して生成できる状態delegate:provider 経路が暫定のメインパス)。selfhost builder 実行が `self._is_on/1` で落ちても、最終的な MIR(JSON) は provider 経由で出力されている。
- legacy CLI 経路(--program-json-to-mir: - legacy CLI 経路(--program-json-to-mir:
- Program(JSON) を一時ファイルに書いて `nyash --program-json-to-mir` を叩くフォールバックは、Phase 25.1a では退避路扱いのまま(通常は provider 経路が先に成功する)。 - Program(JSON) を一時ファイルに書いて `nyash --program-json-to-mir` を叩くフォールバックは、Phase 25.1a では退避路扱いのまま(通常は provider 経路が先に成功する)。
- Plan (Phase 25.1a, updated): - Plan (Phase 25.1a, updated):
- Stage1 CLI ソースlauncher.hakoを Stage3 VM で素直に通るように整えるusing 解決と文法を selfhost 既存パターンに合わせる)タスクは継続。 - Stage1 CLI ソースlauncher.hakoを Stage3 VM で素直に通るように整えるusing 解決と文法を selfhost 既存パターンに合わせる)タスクは継続。
- `tools/hakorune_emit_mir.sh` の Program→MIR 部分については、provider 経路が安定していることを確認しつつ、selfhost builder 経路MirBuilderBox.emit_from_program_json_v0の parse error を一つずつ潰す - `tools/hakorune_emit_mir.sh` の Program→MIR 部分については、provider 経路が安定していることを確認しつつ、selfhost builder 経路MirBuilderBox.emit_from_program_json_v0が Stage1 CLI に対して stub MIR を返してしまう根本課題を Phase 25.1b で解く(現時点では `HAKO_SELFHOST_BUILDER_FIRST=0` を維持)
- 具体的には「builder prelude を text-merge した一時ハコをそのまま保存するデバッグ導線を追加し、そのファイルを直接 VM に食わせて `Invalid expression` 行を特定→元の .hako を修正」という手順で進める - デバッグ導線(マージ後 Hako を `/tmp/hako_builder_merged.hako` に保存 etc.)は既に整備済みで、次のフェーズで原因特定と修正を行う
- `tools/selfhost/build_stage1.sh` / `tools/selfhost_exe_stageb.sh` を使った `.hako → MIR(JSON) → EXE` スモークは、Phase 25.1a 中は provider-firstselfhost builder 既定OFFを維持しつつ、selfhost builder が安定したタイミングで `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻す。 - `.hako → MIR(JSON) → EXE` スモークは provider-first`HAKO_SELFHOST_BUILDER_FIRST=0`でグリーン。selfhost-first`=1`)は Stage1 CLI の Program(JSON) を完全には扱えないため stub MIR になり、EXE が空挙動になる。MirBuilderBox 側の対応後Phase 25.1b 以降)に再度 selfhost-first へ戻す予定。***
- Phase 25.1b の具体タスクselfhost builder 拡張):
- `Program.defs` を MirBuilder 側で反映し、`HakoCli` 内の各メソッドを MIR 関数として生成する。
- `func_lowering` / `call_resolve` 相当の処理を Hako 側でも実装し、`Call("cmd_emit_*")``Global` resolve できるようにする。
- Loop / branch / boxcall など Stage1 CLI で出現する全ステートメントを lowering するため、`lang/src/mir/builder/internal/*` の helper を統合。
- JSON 出力を jsonfrag ベースで構築し、functions 配列に複数関数を格納可能にする。
- 上記の完了後、selfhost-first を既定に戻し Stage1 CLI EXE の JSON stdout 契約Rust/llvmlite と同等)を仕上げる。
- ループについては LoopForm 正規化を前提とし、LoopForm の制約を満たさない形キャリア3変数以上・順序再配置不能などは selfhost builder では扱わず、タグ付き FailFast で検知するPHI ノード生成は既存 LoopForm/LowerLoop helper に一元化)。
- docs: `docs/development/roadmap/phases/phase-25.1b/README.md` を design deepdive 版に更新し、FuncLoweringBox / MirBuilderBox の現状把握と拡張方針FailFast ポリシー・LoopForm/PHI ポリシー・Step0〜6 の実装順序などを整理済み。Step0FailFast/観測導線)は 2025-11-15 実装済みdefs_only/no_match タグ+ `_lower_func_body` トレース、Step1 は `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` トグルによる main 必須チェックを inject_funcs に入れるところまで完了。Step2 の最初の差分として、`FuncBodyBasicLowerBox` を追加して Local/If/Return パターンを既存の minimal lowers で箱化し、`_lower_func_body` から段階的に呼び出せるようにしたLoop を含む場合は `[builder/funcs:unsupported:loop]` をトレース)。***
Update (2025-11-16 — StageB using resolver alias 化 & 環境導線)
- StageB entry (`compiler_stageb.hako`) の `using "hako.compiler.entry.bundle_resolver"` / `using "lang/src/compiler/entry/using_resolver_box.hako"` を module alias (`hako.compiler.entry.bundle_resolver`, `lang.compiler.entry.using_resolver`) に置き換え、Stage3 パーサが string literal using を弾いていた問題を解消。
- `tools/hakorune_emit_mir.sh` では `nyash.toml` `[modules]` を Python で読み取り `name=path``HAKO_STAGEB_MODULES_LIST``|||` 区切り)として export 済み。StageB 側では `Stage1UsingResolverBox` がこの env を `MapBox` 化し、`using lang.mir.builder.MirBuilderBox` などの module alias を元の .hako に展開してから parse する。
- 同じく `tools/hakorune_emit_mir.sh` 内の `HAKO_MIRBUILDER_IMPORTS` 生成ロジックを拡張し、`using ns.path.Type as Alias` だけでなく `using ns.path.Type` 形式からも末尾セグメント(例: `MirBuilderBox`)を alias として抽出するようにした。これにより StageB 経由で生成された Program(JSON v0) を `env.mirbuilder.emit` が処理するときに、`MirBuilderBox``BuildBox` といった static box 名が `undefined variable` にならず、imports 経由で Const(String) に解決されるようになった。
- Rust 側 JSON v0 ブリッジ(`src/runner/json_v0_bridge/lowering/expr.rs`)には `hostbridge` 名の変数を well-known グローバルとして扱う最小の分岐を追加し、`hostbridge.extern_invoke(...)` を含む Program(JSON v0) でも `undefined variable: hostbridge` エラーが出ないようにした(値は `Const(String(\"hostbridge\"))` を発行する placeholder とし、実際の extern dispatch は VM/ランタイム側で担保)。
- Stage1 CLI (`lang/src/runner/launcher.hako`) の emit/build コマンドを整理し、FileBox 操作を `_read_file` / `_write_file` helper に集約。Option 解析の重複を解消しつつ Stage3 friendly な `local` 宣言へ寄せた結果、`tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako /tmp/launcher_cli_mir.json` が provider delegate で安定して成功する状態を quick smoke (`phase251/stage1_launcher_program_to_mir_canary_vm`) でカバー。
- 検証:
- `HAKO_SELFHOST_TRACE=1 ./tools/hakorune_emit_mir.sh basic_test.hako /tmp/test_mir_stageb.json``[emit:trace] Stage-B: SUCCESS - Generated Program(JSON)` の後に `delegate:provider` で MIR を出力し、`[OK] MIR JSON written (delegate:provider)` で終了(`direct-emit` フォールバックに落ちない)。
- `HAKO_SELFHOST_TRACE=1 ./tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako /tmp/launcher_mir.json` でも `env.mirbuilder.emit``undefined variable: MirBuilderBox` / `hostbridge` を出さずに 0 exit し、MIR(JSON) を生成できることを確認。
- Next:
- Stage1 CLI (`lang/src/runner/launcher.hako`) 側の `using` も module alias に揃え、StageB から box 定義が Program(JSON) に落ちるようにする。
- selfhost builder Runner (`try_selfhost_builder`) は引き続き opt-in。StageB 改修により Program(JSON) 生成が安定したので、次のターンは builder prelude の VM 実行エラー(`self._is_on` 等)を潰し selfhost-first を既定ONに戻せるかを調査する。
Update (2025-11-15 — Phase 25.1a extension: ny-llvmc / llvmlite I/O semantics)
- Decision:
- Stage1 EXEny-llvmc 経由で生成されたバイナリ)が提供する `emit program-json` / `emit mir-json` の I/O 挙動は、llvmlite ハーネス(`NYASH_LLVM_USE_HARNESS=1` 経路)の挙動に揃える。
- 具体的には:
- stdout: JSON をそのまま吐く(`Result: 0` のようなハーネス用メッセージは既定OFFverbose専用に寄せる、エラー時は JSON ではなく明示的なエラーメッセージ非0 exit code。
- exit code: Rust CLI / llvmlite ハーネスと同じく、0=成功、非0=失敗Stage1 CLI 側の 9093 番台コードと整合)。
- Scope (Phase 25.1a 内でやる範囲):
- `tools/ny_mir_builder.sh` / ny-llvmc ハーネスで、Stage1 EXE`target/selfhost/hakorune` 等)が `emit program-json` / `emit mir-json` を呼び出す場合に、llvmlite ハーネスと同じ I/O 契約stdout に JSON、exit code で成否を表現)になるように調整する。
- `docs/development/runtime/cli-hakorune-stage1.md` に「Stage1 EXE の emit I/O 挙動は llvmlite ハーネスを参照する」ことを明記し、さらなる profile 切替dev/ci/lite 等)は Phase 25.2 以降のタスクとして扱う。
- Helper: `tools/selfhost/run_stage1_cli.sh` を追加し、Stage1 CLI 実行時に `NYASH_NYRT_SILENT_RESULT=1` / `NYASH_DISABLE_PLUGINS=1` / `NYASH_FILEBOX_MODE=core-ro` を既定ONにして呼び出せるようにした。これにより JSON stdout が ny-llvmc / llvmlite ハーネスと同一契約になり、`tools/selfhost_exe_stageb.sh --run` も Result 行を出さずに exit code だけで評価できる。
- 現在の状況2025-11-16 調査):
- Stage-B では `HakoCli` 本体や `cmd_emit_*` が Program(JSON v0) に落ちるようになったが、VM 側 selfhost builder は Stage1 CLI をほぼ no-op に潰してしまい、171 bytes の stub MIR しか出力していない。
- provider 経由 (`env.mirbuilder.emit`) は `.hako → Program(JSON) → MIR(JSON)` を安定して通すため、Phase 25.1a では `HAKO_SELFHOST_BUILDER_FIRST=0` を維持してこのルートを採用する。
- Stage1 EXE (`target/selfhost/hakorune`) の CLI コマンドは現在 `Result: 0` しか出力しないMIR が stub のため。selfhost builder の機能拡張後に JSON stdout を Rust/llvmlite と揃えるタスクを再開する。
Update (2025-11-14 — Phase 25 closure & Phase 25.2 deferral) Update (2025-11-14 — Phase 25 closure & Phase 25.2 deferral)
- Phase 25: - Phase 25:
@ -1465,3 +1505,9 @@ Pointers
- Harness: `tools/smokes/v2/lib/test_runner.sh` (marker extraction / debug tails) - Harness: `tools/smokes/v2/lib/test_runner.sh` (marker extraction / debug tails)
--- ---
Update (2025-11-15 — Stage1 CLI emit調査メモ)
- StageB emit を拡張FuncScannerBox/StageB 本体)して `static box` メソッドを defs に追加。暗黙 `me` もパラメータへ補完。
- Rust 側 JSON v0 ブリッジに `ExprV0::Null` variant を追加し、StageB が吐く Null リテラルを受理。
- `launcher.hako` の `while` を `loop` 構文へ置換StageB パーサ互換)。
- Stage1 CLI の Program(JSON) は attr付き defs まで含むようになったが、selfhost builder MirBuilderBox on VMがまだ HakoCli 全体を lowering できず stub MIR を返している。provider 経由では 62KB 超の MIR(JSON) が得られるので Phase 25.1a ではこちらを利用。

View File

@ -35,7 +35,14 @@ Parser/StageB
- Accept Stage3 syntax (Break/Continue/Try/Catch, etc.). Enabled in smokes for StageB runs. - Accept Stage3 syntax (Break/Continue/Try/Catch, etc.). Enabled in smokes for StageB runs.
- HAKO_STAGEB_FUNC_SCAN=1 - HAKO_STAGEB_FUNC_SCAN=1
- Devonly: inject a `defs` array into Program(JSON) with scanned method definitions for `box Main`. - Dev-only: inject a `defs` array into Program(JSON) with scanned method definitions for `box Main`.
- HAKO_STAGEB_MODULES_LIST
- StageB/Hakorune 向けの using 解決に使う `name=path` 連結文字列(`nyash.toml``[modules]` をシェル側で変換)。
- 例: `lang.mir.builder.MirBuilderBox=lang/src/mir/builder/MirBuilderBox.hako|||lang.compiler.build.build_box=lang/src/compiler/build/build_box.hako`
- HAKO_STAGEB_APPLY_USINGS=0|1
- `1` のとき、StageB は `lang.compiler.entry.using_resolver_box` を通じて Stage1 用の using を text merge する。`0` で旧挙動。
- HAKO_STAGEB_BODY_EXTRACT=0|1 - HAKO_STAGEB_BODY_EXTRACT=0|1
- Toggle StageB body extractor. When `0`, skip methodbody extraction and pass the full `--source` to `parse_program2`. Useful to avoid environmentspecific drift in extractors; default is `1` (enabled). - Toggle StageB body extractor. When `0`, skip methodbody extraction and pass the full `--source` to `parse_program2`. Useful to avoid environmentspecific drift in extractors; default is `1` (enabled).

View File

@ -8,12 +8,18 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- `.hako → Program(JSON v0) → MIR(JSON) → EXE` のうち、**Program(JSON v0) → MIR(JSON)** の導線を再構成し、`tools/hakorune_emit_mir.sh` / `tools/selfhost_exe_stageb.sh` / `tools/selfhost/build_stage1.sh` が代表ケースで成功するようにする。 - `.hako → Program(JSON v0) → MIR(JSON) → EXE` のうち、**Program(JSON v0) → MIR(JSON)** の導線を再構成し、`tools/hakorune_emit_mir.sh` / `tools/selfhost_exe_stageb.sh` / `tools/selfhost/build_stage1.sh` が代表ケースで成功するようにする。
- selfhost builder / provider / legacy CLI の 3 経路が混在した現状を見直し、**信頼できる1本の Program→MIR 経路**を中心に据える。 - selfhost builder / provider / legacy CLI の 3 経路が混在した現状を見直し、**信頼できる1本の Program→MIR 経路**を中心に据える。
## 進捗状況2025-11-16 時点)
- Stage-B と provider 経路で `.hako → Program(JSON) → MIR(JSON)` は安定。`hakorune_emit_mir.sh lang/src/runner/launcher.hako …``[OK] MIR JSON written (delegate:provider)`
- selfhost builder (`HAKO_SELFHOST_BUILDER_FIRST=1`) では、Stage1 CLI の Program(JSON) を完全に lowering できず 171 bytes の stub MIR を返すため、現状 selfhost-first を既定ONにできない。
- Stage1 EXE`/tmp/hakorune-dev`)は provider 経由の MIR を使えばリンク成功。ただし CLI I/O は JSON を出さず `Result: 0` のみ。JSON 出力契約に揃える作業は selfhost builder の修復後に実施予定。
## 現状の問題点2025-11-15 時点) ## 現状の問題点2025-11-15 時点)
- `tools/selfhost/build_stage1.sh`: - `tools/selfhost/build_stage1.sh`:
- 現在の entry: `lang/src/runner/launcher.hako`Stage1 CLI ランチャー)。 - 現在の entry: `lang/src/runner/launcher.hako`Stage1 CLI ランチャー)。
- 内部で `tools/selfhost_exe_stageb.sh``tools/hakorune_emit_mir.sh` を呼び出しているが、MIR 生成フェーズで失敗 - provider 経由 (`env.mirbuilder.emit`) では `.hako → Program(JSON) → MIR(JSON)` を安定して通せるが、selfhost builder (`MirBuilderBox` on VM) はまだ Stage1 CLI の Program(JSON) をフルで扱えず、最小パターンの stub MIR171 bytesを出力してしまう
- ログ: `[FAIL] Program→MIR delegate failed (provider+legacy)` - このため selfhost builder を既定ONにすると EXE が空挙動になるため、Phase 25.1a では暫定的に provider-first (`HAKO_SELFHOST_BUILDER_FIRST=0`) を維持しつつ、selfhost builder 側の機能範囲拡張を翌フェーズへ送っている
- `tools/hakorune_emit_mir.sh` — Program→MIR 部分: - `tools/hakorune_emit_mir.sh` — Program→MIR 部分:
1. StageB`compiler_stageb.hako`: 1. StageB`compiler_stageb.hako`:
@ -21,7 +27,8 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
2. selfhost builder 経路(`try_selfhost_builder`: 2. selfhost builder 経路(`try_selfhost_builder`:
- `builder_box=hako.mir.builder` で Runner を生成し、VM 経由で実行。 - `builder_box=hako.mir.builder` で Runner を生成し、VM 経由で実行。
- 当初は tmp ハコファイルに対して `Parse error: Unexpected token FN``Unexpected token ASSIGN` が発生し rc=1 で失敗していたが、`lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()``local func_len = ...` にリネームすることで `Unexpected token FN` 自体は解消済み。 - 当初は tmp ハコファイルに対して `Parse error: Unexpected token FN``Unexpected token ASSIGN` が発生し rc=1 で失敗していたが、`lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()``local func_len = ...` にリネームすることで `Unexpected token FN` 自体は解消済み。
- 現在は text-merge 後の巨大一時ファイルに対して `Parse error: Invalid expression at line <N>` が出ており、prelude 連結のどこかで Stage3 構文と合わない断片が残っている状態selfhost builder は引き続き要調査) - その後、`lang/src/shared/mir/loop_form_box.hako``lang/src/mir/builder/internal/lower_loop_count_param_box.hako` における `init` 予約語衝突(`local init = ...` / 引数名 `init`)を `start_value` 系にリネームし、LoopFormBox まわりの `Invalid expression` も解消済み
- 現在は selfhost builder Runner の実行フェーズで `VM error: Invalid instruction: global function: Unknown: self._is_on/1` が発生しており、MirBuilder 内部のトグルヘルパー `BuilderConfigBox._is_on` の呼び出し(`norm_if` ラムダ経由)が VM 側にまだ実装されていないために落ちている状態(構文ではなく実行時エラー)。
3. provider 経路(`try_provider_emit``env.mirbuilder.emit`: 3. provider 経路(`try_provider_emit``env.mirbuilder.emit`:
- 当初は `env.mirbuilder.emit` 実行時に `[mirbuilder/parse/error] undefined variable: args` により失敗していたが、Rust 側の Program→MIR ルート修正によりこのエラーは解消済み。現在は provider 経路経由で `launcher.hako` から MIR(JSON) を安定して生成できている。 - 当初は `env.mirbuilder.emit` 実行時に `[mirbuilder/parse/error] undefined variable: args` により失敗していたが、Rust 側の Program→MIR ルート修正によりこのエラーは解消済み。現在は provider 経路経由で `launcher.hako` から MIR(JSON) を安定して生成できている。
4. legacy CLI 経路(`--program-json-to-mir`: 4. legacy CLI 経路(`--program-json-to-mir`:
@ -32,6 +39,16 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- `using` の解決(`lang.compiler.build.build_box`)は nyash.toml に追加済みだが、 - `using` の解決(`lang.compiler.build.build_box`)は nyash.toml に追加済みだが、
- まだパーサが Stage3 構文/関数宣言の一部を受理できていない箇所があり、`Unexpected token ...` 系のエラーが残っている。 - まだパーサが Stage3 構文/関数宣言の一部を受理できていない箇所があり、`Unexpected token ...` 系のエラーが残っている。
## Update (2025-11-16 — StageB using resolverを module alias 化)
- `lang/src/compiler/entry/compiler_stageb.hako` で使用していたファイルパス形式の `using "..." as ...` を、`nyash.toml` `[modules]` に登録済みの module alias`hako.compiler.entry.bundle_resolver` / `lang.compiler.entry.using_resolver`)へ置き換えた。
- `tools/hakorune_emit_mir.sh` が export する `HAKO_STAGEB_MODULES_LIST` / `HAKO_STAGEB_APPLY_USINGS` を StageB が参照できるようになり、Stage3 パーサが `using` 行で失敗しなくなった(`HAKO_SELFHOST_TRACE=1 ./tools/hakorune_emit_mir.sh basic_test.hako /tmp/out.json``[emit:trace] Stage-B: SUCCESS ...` で止まらず `delegate:provider` まで進むことを確認)。
- これにより Phase 25.1a の Program→MIR ホットフィックスでは「StageB→provider delegate」が安定経路になり、selfhost builder の修復に専念できる状態になったStage1 CLI 側の `using` も module alias 化を継続)。
- 追加で、`tools/hakorune_emit_mir.sh` 側の `HAKO_MIRBUILDER_IMPORTS` 生成ロジックを拡張し、`using ns.path.Type`alias なし)の形式からも末尾セグメント(例: `MirBuilderBox`)を alias として抽出するようにした。これにより、`using lang.mir.builder.MirBuilderBox` を含む Stage1 CLI/Builder コードでも `env.mirbuilder.emit``undefined variable: MirBuilderBox` を出さずに Program(JSON v0) を lowering できるようになった。
- Stage1 CLI`lang/src/runner/launcher.hako`)の emit/build コマンドを helper (`_read_file` / `_write_file`) で整理し、Stage3 friendly な `local` 宣言・ログメッセージに統一。`hakorune_emit_mir.sh lang/src/runner/launcher.hako …` を provider delegate で再度通し、62KB 超の MIR(JSON) が得られることを quick smoke`phase251/stage1_launcher_program_to_mir_canary_vm.sh`)でカバー済み。
- Rust 側の JSON v0 ブリッジ(`src/runner/json_v0_bridge/lowering/expr.rs`)には `hostbridge` を well-known グローバルとして扱う最小の分岐を追加し、`hostbridge.extern_invoke(...)` を含む Program(JSON v0) でも `undefined variable: hostbridge` エラーで止まらないようにした(値は `Const(String("hostbridge"))` を発行する placeholder とし、実際の extern dispatch は VM/ランタイム側に委譲する)。
## フェーズ内タスク25.1a TODO ## フェーズ内タスク25.1a TODO
### A. Stage1 CLI ソースの VM 実行復旧 ### A. Stage1 CLI ソースの VM 実行復旧
@ -48,7 +65,12 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- [ ] `try_selfhost_builder` 内で生成される tmp ハコファイル(`__BUILDER_BOX__` 版)を最小ケースで切り出し、単体で parse/実行できるように修正。 - [ ] `try_selfhost_builder` 内で生成される tmp ハコファイル(`__BUILDER_BOX__` 版)を最小ケースで切り出し、単体で parse/実行できるように修正。
- [ ] `args` 未定義エラーや `Invalid expression` の原因となっている記述を特定し、Runner 側の `Main.main(args)` などを正しく宣言する。 - [ ] `args` 未定義エラーや `Invalid expression` の原因となっている記述を特定し、Runner 側の `Main.main(args)` などを正しく宣言する。
- [ ] Stage3 構文の使用を必要最小限に抑え、selfhost builder 用 Runner のコードをシンプルに保つ。 - [ ] Stage3 構文の使用を必要最小限に抑え、selfhost builder 用 Runner のコードをシンプルに保つ。
- [x] Stage3 パーサで予約語となった `fn` をローカル変数名として使っている箇所(例: `lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()`)をリネームし、`Unexpected token FN, expected identifier` を根本的に解消する。 - [x] Stage3 パーサで予約語となった `fn` をローカル変数名として使っている箇所(例: `lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()`)をリネームし、`Unexpected token FN, expected identifier` を根本的に解消する。
- [x] selfhost builder/min で使っていた `fn``norm_if` クロージャ)を helper メソッドに置き換え、lambda 構文を排除。Stage3 VM が `NewClosure` を扱わなくても builder が進むようにした。
- [x] VM 側で prelude/text-merge 後の Hako コードを落とすデバッグ用トグルを追加(`NYASH_VM_DUMP_MERGED_HAKO=1` / `NYASH_VM_DUMP_MERGED_HAKO_PATH=<path>`。selfhost builder 実行時は `HAKO_SELFHOST_DUMP_MERGED_HAKO=1` / `HAKO_SELFHOST_DUMP_MERGED_HAKO_PATH=/tmp/hako_builder_merged.hako` を通じて `/tmp/hako_builder_merged.hako` にマージ後の一時ハコを保存し、`Invalid expression at line <N>` の行を直接観察できるようにする。
- 進捗メモ2025-11-15:
- `HAKO_SELFHOST_BUILDER_FIRST=1 HAKO_SELFHOST_DUMP_MERGED_HAKO_PATH=/tmp/hako_builder_merged.hako tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako …` で 275KB の merged Hako が採取済み。`BuilderConfigBox``_is_on` など Stage3 で落ちやすい箇所を直接確認できる。
- 同条件で `apps/selfhost-runtime/runner.hako``[OK] MIR JSON written (selfhost-first)` まで到達。stage3 parse 落ちは再現していないため、次回失敗したケースが出たら `/tmp/hako_builder_merged.hako` を差し替えて行番号を追跡する。
- [ ] `try_selfhost_builder`**第一候補** とし、代表ケースlauncher.hako 等)で常にここが成功することを確認。 - [ ] `try_selfhost_builder`**第一候補** とし、代表ケースlauncher.hako 等)で常にここが成功することを確認。
- [ ] `HAKO_SELFHOST_BUILDER_FIRST=1``tools/hakorune_emit_mir.sh` を叩いたときに `[OK] MIR JSON written (selfhost-first)` まで到達することをスモークで確認。 - [ ] `HAKO_SELFHOST_BUILDER_FIRST=1``tools/hakorune_emit_mir.sh` を叩いたときに `[OK] MIR JSON written (selfhost-first)` まで到達することをスモークで確認。
@ -63,10 +85,23 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- [x] `NYASH_LLVM_SKIP_BUILD=1 tools/selfhost/build_stage1.sh --out /tmp/hakorune-dev` が 0 exit すること(現状は selfhost builder を既定OFFにし、provider ルートで MIR を生成)。 - [x] `NYASH_LLVM_SKIP_BUILD=1 tools/selfhost/build_stage1.sh --out /tmp/hakorune-dev` が 0 exit すること(現状は selfhost builder を既定OFFにし、provider ルートで MIR を生成)。
- [ ] 生成された `/tmp/hakorune-dev` について: - [ ] 生成された `/tmp/hakorune-dev` について:
- [ ] `./hakorune-dev emit program-json apps/selfhost-minimal/main.hako` が Program(JSON v0) を出力すること - [ ] `Stage1 CLI emit` 系は現在 Result:0 のみで JSON を出さないMIR が stub のためので、selfhost builder が機能するまで provider MIR を直接使う(`tools/selfhost/run_stage1_cli.sh` に JSON を書かせるタスクは後続)
- [ ] `./hakorune-dev emit mir-json apps/selfhost-minimal/main.hako` が MIR(JSON) を出力すること。
- [ ] `./hakorune-dev build exe -o /tmp/hako_min apps/selfhost-minimal/main.hako` で簡単な EXE が生成され、実行して 0 exit を返すこと。 - [ ] `./hakorune-dev build exe -o /tmp/hako_min apps/selfhost-minimal/main.hako` で簡単な EXE が生成され、実行して 0 exit を返すこと。
- [x] Stage1 CLI を実行する補助スクリプト `tools/selfhost/run_stage1_cli.sh` を追加し、`NYASH_NYRT_SILENT_RESULT=1` / `NYASH_DISABLE_PLUGINS=1` / `NYASH_FILEBOX_MODE=core-ro` を既定ONにした状態で CLI を呼び出せるようにしたllvmlite ハーネスと同じ JSON stdout 契約を満たすため)。
- [ ] `tools/selfhost_exe_stageb.sh` についても同様に `.hako → EXE` のスモークを通しておく(少なくとも launcher.hako / apps/selfhost-minimal/main.hako の2ケース - [ ] `tools/selfhost_exe_stageb.sh` についても同様に `.hako → EXE` のスモークを通しておく(少なくとも launcher.hako / apps/selfhost-minimal/main.hako の2ケース
- 進捗メモ2025-11-15:
- `FuncScannerBox` を拡張し、`static box` メソッドを `defs` に追加(暗黙 `me` もパラメータに補完)。
- Rust 側 JSON v0 ブリッジに `ExprV0::Null` variant を追加して StageB からの Null リテラルを受理。
- `launcher.hako``while``loop` 構文へ置換し、StageB パーサ互換に揃えた。
- 依然として using 依存(例: `lang.mir.builder.MirBuilderBox`)を StageB emit が解決できず、`env.mirbuilder.emit``undefined variable: MirBuilderBox` で停止。BundleResolver / using resolver を StageB 経路に統合し、依存 Box を Program(JSON) に連結するのが次タスク。
### E. 次フェーズ25.1b)に送る selfhost builder 強化項目
- `Program.defs` を MirBuilder 側でも処理し、`HakoCli.run` / `cmd_emit_*` / `cmd_build_*` などのメソッドを MIR 関数として生成する(現状は main 1 本のみ)。
- `func_lowering` / `call_resolve` 相当の処理を Hako 側に移植し、`Call("cmd_emit_mir_json")``Global` resolved call になるようにする。
- Loop / branch / compare / Array・Map 操作など Stage1 CLI で出現するステートメントを包括的に lowering するため、`lang/src/mir/builder/internal/*` の helper を本番経路に組み込む。
- JSON 出力を `jsonfrag` ベースで構造的に生成し、functions 配列に複数関数を格納できるようにする(文字列連結のみの暫定実装を置き換える)。
- 上記を満たした段階で `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻し、Stage1 CLI バイナリの I/Ostdout JSON + exit codeを Rust/llvmlite と同じ契約に揃える。
## 25.1 / 25.1a / 25.2 の関係 ## 25.1 / 25.1a / 25.2 の関係

View File

@ -0,0 +1,204 @@
# Phase 25.1b — Selfhost Builder Parity (Planning → Design DeepDive)
Status: planning-only実装着手前の設計・分析フェーズ
## ゴール
- Rust 側 Program→MIR (`env.mirbuilder.emit`) と Hakorune 側 selfhost builder (`MirBuilderBox.emit_from_program_json_v0`) の機能差を埋め、Stage1 CLIlauncher.hakoレベルの Program(JSON) を selfhost builder 単独で lowering できるようにする。
- `.hako → Program(JSON v0) → MIR(JSON)` のうち、「Program→MIR」を selfhost builder だけでも成立させ、provider 経路はあくまで退避路に下げていく。
- 最終的には `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻し、Stage1 CLI EXE の I/OJSON stdout + exit codeを Rust/llvmlite と同じ契約に揃える。
## 現状Phase 25.1a 時点)
### StageBProgram(JSON v0) emit
- `compiler_stageb.hako``defs` を含む Program(JSON v0) を出力できる:
- `HakoCli.run` / `HakoCli.cmd_emit_*` / `HakoCli.cmd_build_*` などのメソッドを `Program.defs` 配列として含む。
- `FuncScannerBox` `HAKO_STAGEB_FUNC_SCAN=1` により、`static box` メソッド(暗黙の `me` 引数付き)も defs に載る。
- using 解決:
- `Stage1UsingResolverBox``lang/src/compiler/entry/using_resolver_box.hako` `HAKO_STAGEB_MODULES_LIST``nyash.toml``[modules]` を参照し、`using lang.mir.builder.MirBuilderBox` 等をファイル結合前に解決。
- StageB entry 側は string literal using を廃止し、`using lang.compiler.entry.using_resolver as Stage1UsingResolverBox` のように module alias を使用する。
### Rust provider (`env.mirbuilder.emit`)
- `program_json_to_mir_json_with_imports`:
- `Program.body``Program.defs` の両方を受理し、`FuncDefV0` から `func_map` を構築して Call を解決。
- `HAKO_MIRBUILDER_IMPORTS` 経由で `MirBuilderBox` / `BuildBox` などの static box 名をインポートし、`Const(String(alias))` として扱う。
- JSON v0 ブリッジ:
- `args` を暗黙パラメータとして扱う修正済み(`undefined variable: args` は解消)。
- `hostbridge` は wellknown 変数として `Const(String("hostbridge"))` を生成し、`hostbridge.extern_invoke(...)` を含む Program(JSON) でも undefined にならない。
- 結果:
- `launcher.hako` に対して ~62KB の MIR(JSON) を安定して生成できており、Phase 25.1a では provider 経路が事実上のメインルート。
### selfhost builder (`MirBuilderBox.emit_from_program_json_v0`)
- エントリ:
- 入口で `HAKO_MIR_BUILDER_FUNCS=1` のときに `FuncLoweringBox.lower_func_defs(program_json, program_json)` を呼び出し、defs 専用の MIR 関数群を文字列として受け取る(`func_defs_mir`)。
- その後、`internal` 配下の多数の `Lower*Box.try_lower` を順番に適用し、Program 全体を 1 関数(`main`)ベースの MIR(JSON) に落とす。
- `FuncLoweringBox` の現状:
- `lower_func_defs` は Program(JSON) から `defs` 配列をパースし、各 def ごとに `_lower_func_body` を呼ぶ。
- `_lower_func_body` がサポートするのは **単一の Return を持つ最小パターンのみ**:
- `Return(Int)`
- `Return(Binary(op, lhs, rhs))``+,-,*,/` のみ、かつ `Int/Var` 組み合わせ限定)
- `Return(Call(name, args))`Call 名は `func_map` を用いて `Box.method` に解決)
- 複数ステートメント、`If``Loop`、メソッドチェインなど Stage1 CLI に実際に現れる構造はすべて `null` でスキップされる。
- `MirBuilderBox` の挙動:
- 何らかの `Lower*Box` が Program 全体にマッチした場合は、その MIR(JSON) に対して `_norm_if_apply` を通し、`FuncLoweringBox.inject_funcs` で defs 分の MIR 関数を **追加注入** する。
- どの `Lower*Box` もマッチしないが `func_defs_mir` が非空の場合は、`"{\"functions\":[" + defs + "]}"` という最小モジュールを組み立てて `_norm_if_apply` に渡す。
- このケースでは main 関数を含まない defs だけの MIR モジュールになり、ny-llvmc 側でエントリポイント不足や空挙動 EXE を生む原因になる。
- `func_defs_mir` も空で internal lowers も不発の場合は `null` を返し、最後に provider delegate`env.mirbuilder.emit`)へフォールバックする。
### Stage1 EXE / build_stage1.sh の現状
- `tools/selfhost/build_stage1.sh`:
- 既定値 `HAKO_SELFHOST_BUILDER_FIRST=0`provider-firstでは、Stage1 EXE ビルドは成功し、`emit program-json` / `emit mir-json` / `build exe` の I/O も Rust/llvmlite と同等の JSON+exit code 契約を満たす。
- `HAKO_SELFHOST_BUILDER_FIRST=1`selfhost-firstでは、Stage1 CLI のような複雑な Program(JSON) に対して selfhost builder が「defs のみ」MIR か mini stub MIR を返し、結果として「Result: 0 だけ出す空 EXE」になる。
- スモーク:
- `tools/smokes/v2/profiles/quick/core/phase251/stage1_launcher_program_to_mir_canary_vm.sh` は provider-first ルートのみをカバーしており、selfhost builder 単独経路のギャップは検出できていない(今後 canary を追加する必要がある)。
## 25.1b のスコープ(案)
- selfhost builder 本体Hakorune 側):
- `Program.defs` の処理を実装し、`box + method` ごとに MIR 関数を生成する。
- 例: `HakoCli.run` / `HakoCli.cmd_emit_program_json` / `HakoCli.cmd_emit_mir_json` / `HakoCli.cmd_build_exe` 等。
- `call` / `BoxCall` / `ExternCall` の解決(`func_lowering` + call resolve 相当)を Hakorune 側にも実装し、`Call("cmd_emit_mir_json")``Global` callee に解決できるようにする。
- Loop / branch / compare / Array/Map 操作など Stage1 CLI で出現する構造を包括的に lowering するため、`lang/src/mir/builder/internal/*` の helper を本番経路に統合する。
- JSON 出力:
- 現状の `"{\"functions\":[...]}\"` ベタ書きから、jsonfrag 等を用いた構造的出力に切り替え、複数関数を同一モジュールに含められるようにする。
- 既存の mini パターン用 JSON 組み立てとの互換性を維持しつつ、Stage1 CLI で必要な関数数に耐えられる形に拡張する。
- 運用ポリシー:
- Phase 25.1b 中は `HAKO_SELFHOST_BUILDER_FIRST=0` のままprovider-firstとし、selfhost builder が Stage1 CLI を lowering し切れることを確認した時点で `=1` への切り替えを検討する。
- lambda/FunctionBox (`NewClosure` 等) は本フェーズでは扱わず、従来どおり builder からは排除したままにする(別フェーズ 25.2b に委ねる)。
## Guardrails / ポリシー
- Rust Freeze Policy:
- Rust 側の Program→MIR 実装には原則手を入れず、selfhost builder は「Rust の既存挙動に合わせる」方向で実装する。
- 変更は Hakorune 側 (`lang/src/mir/builder/*`) とツール (`tools/hakorune_emit_mir.sh`) に閉じる。
- FailFast:
- selfhost builder が Program(JSON) の一部に対応していない場合は、明確なタグ付きで失敗させる(例: `[builder/selfhost-first:unsupported:Match]`ようにし、silent stub には戻さない。
- provider 経路は退避路として残しつつ、Stage1 CLI の代表ケースでは selfhost builder が先に成功することを目標にする。
### LoopForm / PHI ポリシー(重要メモ)
- ループを含む関数本体を selfhost builder で扱うときは、**LoopForm 正規化を前提にする**:
- 可能な限り `docs/guides/loopform.md` で定義された「キャリア1個の φ」モデルに従う。
- break/continue を含むループは、LoopForm の制約更新変数最大2個・セグメント整列などを満たす範囲でのみ lowering 対象にする。
- MirBuilder 側で「生の while/for を直接 MIR の PHI に落とす」ような adhoc 実装は行わない:
- PHI ノードの生成・配置は既存の LoopForm/LowerLoop 系 helper`loop_scan_box.hako``lower_loop_*_box.hako` などに一元化し、builder 本体はそれを利用する立場にとどめる。
- LLVM harness 側の PHI 不変条件ブロック先頭グルーピングwelltyped incomingを崩さない。
- Phase 25.1b では:
- まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/forから対応し、
- LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの`[builder/selfhost-first:unsupported:loopform]` タグなどで FailFast する。
## Next Steps実装フェーズに入るときの TODO
1. 調査:
- Rust 側 `program_json_to_mir_json_with_imports` の挙動をトレースし、どの AST ノードがどの MIR に降りているかを整理(特に defs/call/loop/boxcall
- selfhost builder の現行 JSON 生成経路を洗い出し、stub を生成している箇所を特定。
2. 設計:
- `Program.defs` → MIR 関数生成のインタフェース(必要なフィールドと lowering 手順)を定義。
- call resolve 用の軽量マップ(`name -> qualified`)を selfhost builder に導入する。
3. 実装:
- defs 対応・call resolve・loop/branch lowering を段階的に導入しつつ、各ステップで mini スモークを追加。
- jsonfrag ベースの出力に切り替えながら、既存の mini テストを全て通ることを確認。
4. 検証:
- `tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako …``HAKO_SELFHOST_BUILDER_FIRST=1` で実行し、62KB クラスの MIR(JSON) が selfhost builder 単独で得られることを確認。
- `tools/selfhost/build_stage1.sh` を selfhost-first でビルドし、Stage1 CLI EXE が `emit`/`build` コマンドを正しく実行できるかJSON stdout + exit codeをスモークで検証。
## 設計 TODOFuncLoweringBox / MirBuilderBox 拡張の方向性)
※ ここから先は「具体的にどこをどう広げるか」の設計メモ。実装はまだ行わない。
1. FuncLowering の対応範囲拡張
- `_lower_func_body` を Stage1 CLI で実際に使われているパターンまで広げる:
- 単一 Return だけでなく、ローカル変数定義if 分岐loop を含む「典型的な CLI ハンドラ」の形をサポート。
- `MethodCall``args.size()` / `args.get(i)` / `FileBox.open/read/write` / `ArrayBox.push` など)を MIR `mir_call``call` に落とす処理を追加。
- `func_map` / `resolve_call_target` を用いて、`HakoCli.cmd_emit_*` / `cmd_build_exe` などの内部 Call を `Global("HakoCli.cmd_emit_*")` 系に正規化。
2. MirBuilder 本体の出力構造
- これまでの「Program 全体を 1 関数 main に落とす」前提から、「Program.body + defs を multifunction MIR モジュールに落とす」前提へシフト:
- `Program.body` からはエントリ `Main.main/1` 相当の MIR 関数を生成。
- `Program.defs` からは `HakoCli.*` などの補助関数を生成。
- `_norm_if_apply` / `inject_funcs` の役割を整理し、「main 関数を含まない defsonly モジュール」を返さないように FailFast する。
3. FailFast とデバッグ
- Stage1 CLI のような大きな Program(JSON) に対して selfhost builder が未対応の場合は:
- `[builder/selfhost-first:unsupported:func_body]` などのタグ付きで明示的に失敗。
- provider 経路(`env.mirbuilder.emit`)へのフォールバックは維持するが、「空 EXE になる stub MIR」は生成しない方針に切り替える。
- `HAKO_SELFHOST_TRACE=1` 時に、FuncLowering がどの def でどこまで lowering できたかをログに出す。
4. 検証計画
- selfhostfirst canary:
- `stage1_launcher_program_to_mir_canary_vm.sh` の selfhostfirst 版を追加し、`HAKO_SELFHOST_BUILDER_FIRST=1` `MirBuilderBox.emit_from_program_json_v0` だけで 60KB 級の MIR(JSON) を生成できることを確認。
- Stage1 build:
- `tools/selfhost/build_stage1.sh` を selfhost-first で回し、生成された Stage1 EXE に対して `emit program-json` / `emit mir-json` / `build exe` スモークを追加。
このファイルは引き続き「Phase 25.1b の計画メモ(設計ディープダイブ)」として扱い、実装は Phase 25.1a の安定化完了後に、小さな差分に分割して順次進める。***
---
## 実装計画(順番にやる TODO
備考: ここでは Phase 25.1b を「複数の最小ステップ」に分解して、順番/ゴール/ガードを具体的にメモしておくにゃ。
### Step 0 — FailFast・観測を揃える
- Status: implemented (2025-11-15). `MirBuilderBox` now tags `defs_only` / `no_match` failures and aborts, and `FuncLoweringBox` logs unsupported defs when `HAKO_SELFHOST_TRACE=1`.
- 目的: 既存の selfhost builder がどこで諦めているかを正確に観測し、stub MIR を返さずに Fail させる導線を整える。
- 作業:
- `MirBuilderBox.emit_from_program_json_v0`
- `func_defs_mir` だけが非空だった場合でも黙って `{ "functions": [defs] }` を返さず、`[builder/selfhost-first:unsupported:defs_only]` を出す。
- internal lowers がすべて `null` の場合、`[builder/selfhost-first:unsupported:<reason>]` のタグを付与。
- `FuncLoweringBox.lower_func_defs`
- どの関数名で `_lower_func_body``null` を返したかを `HAKO_SELFHOST_TRACE=1` でログ出力。
- `tools/hakorune_emit_mir.sh`
- 既存の head/tail ログ出力で `[builder/selfhost-first:*]` タグがそのまま表示されることを確認済み(追加改修なし)。
- 成果物:
- selfhost-first で Stage1 CLI を通したときに、どの関数/構造がまだ未サポートなのかがログで推測できる状態。
### Step 1 — defs injection の再設計
- Status: initial-implementedmain 必須チェックはトグル付き; multi-function への完全移行は後続 Step
- 目的: `FuncLoweringBox.inject_funcs` で main 関数の有無を意識し、multi-function モジュールの土台を整える。
- 作業:
- `inject_funcs` 内で `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` のときに `"name":"main"` を含まない `functions` 配列を拒否し、`[builder/funcs:fail:no-main]` をトレース出力して injection をスキップする(既定では OFF なので既存挙動は維持)。
- 将来フェーズで、`Program.body` から生成した main と defs を「2段構え」でマージする APImain 名の受け渡しなど)を追加する。
- 成果物(現段階):
- Env トグルを有効化した状態では「main を含まない MIR に defs だけ差し込む」ケースを検知できるようになり、Stage1 実装時に安全に stricter モードへ移行する足場ができた。
### Step 2 — `_lower_func_body` の拡張ローカルif
- 目的: Stage1 CLI の `HakoCli.cmd_emit_program_json` のような「Local / Assign / If / Return だけで構成された関数」を selfhost builder で lowering できるようにする。
- 作業:
- Body を JsonFrag で走査し、ローカル導入・代入・分岐を MIR ブロックへ展開する最小ロジックを FuncLoweringBox に追加。
- Loop が出た時は `[builder/funcs:unsupported:loop]`(仮タグ)を出して Fail-FastStep 3 で LoopForm 対応を行う)。
- 成果物:
- Loop を含まない defs は selfhost builder で MIR 関数にできるようになり、Stage1 CLI の emit/build ハンドラの半分程度を selfhost パスで賄える。
### Step 3 — LoopLoopFormの受理
- 目的: LoopForm 正規化済みの while/for を MIR に落とす。
- 作業:
- `lower_loop_simple_box` / `lower_loop_sum_bc_box` など既存 helper を FuncLoweringBox 側からも利用できるようにし、LoopForm のキャリアを MIR へ展開。
- LoopForm の制約を満たさないケースは `[builder/funcs:unsupported:loopform]` で Fail-Fast。
- 成果物:
- `cmd_build_exe``loop(i < argc)` 等、Stage1 CLI の代表的な while/for パターンを selfhost builder で通せる。
### Step 4 — MethodCall / ExternCall パリティ
- 目的: `hostbridge.extern_invoke` / `FileBox` / `ArrayBox` など Stage1 CLI で多用される呼び出しを selfhost builder でも再現。
- 作業:
- `FuncLoweringBox` に MethodCall/ExternCall の lowering ルートを追加し、`mir_call Method` もしくは `extern_call` を生成。
- `func_map` により `me.cmd_build_exe` のような self-call も Box.method/N に resolve未対応の呼び出しは `[builder/funcs:unsupported:call]` で Fail
- 成果物:
- Stage1 CLI の主要メソッド体が selfhost builder で MIR 関数化できる。
### Step 5 — call resolve / methodize 後処理
- 目的: 生成済み MIR に対して call resolver / methodize pass をかけ、Rust provider と同じ命名・呼び出し形式を実現。
- 作業:
- `HAKO_MIR_BUILDER_CALL_RESOLVE=1` を本格利用し、call の `Const("Box.method/N")``mir_call` に変換。Stage1 CLI で `mir_call Method` を使うケースをテストし、Methodize との組み合わせでも崩れないことを確認。
### Step 6 — selfhost-first canary / build_stage1.sh
- 目的: selfhost builder を既定 ON に戻す準備。
- 作業:
- `stage1_launcher_program_to_mir_canary_vm.sh` の selfhost-first 版を追加して selfhost builder 単独で 60KB 級 MIR を生成できることを検証。
- `tools/selfhost/build_stage1.sh` を selfhost-first で回し、selfhost builder 由来の MIR で `emit program-json` / `emit mir-json` / `build exe` が通ることを確認。
- 問題が無ければ `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻す別PRでも可
作業順は Step 0 → 1 → 2 → 3 → 4 → 5 → 6 を想定、各ステップで必ず docsこのファイルCURRENT_TASKとスモーク/テストを更新する。***

View File

@ -0,0 +1,37 @@
# Phase 25.2b — Lambda / FunctionBox Semantics (Planning)
Status: planning-only将来フェーズ用のメモ実装はまだ行わない
## ゴール
- Phase 25.1b で selfhost builder の Program→MIR パリティが整ったあとに、Hakoru­ne 言語としてのラムダ式(`fn(x, y) { ... }` / `fn(x) expr`)の意味論を Rust VM / MIR / FunctionBox まで一貫させる。
- MirBuilder / selfhost コードから暫定的に排除している `fn` ベースの helper`norm_if` など)を、正式なクロージャ意味論の上に戻せるようにする。
- Stage1/Stage0 の責務分離を崩さずに、「lambda を使う Hako コード」が hv1 VM / AOT でも安全に実行できる状態を作る。
## 現状Phase 25.1b 前提)
- 構文AST:
- `docs/reference/language/LANGUAGE_REFERENCE_2025.md``fn(x, y) { ... }` / `fn(x) expr` のラムダ構文が記載済み。
- Stage3 パーサは `fn` キーワードを認識し、AST 上では Lambda ノードを持っている(`exprs_lambda.rs` 前提)。
- MIR:
- `src/mir/instruction.rs``MirInstruction::NewClosure { dst, params, body, captures, me }` が定義済み。
- `src/mir/builder/exprs_lambda.rs` は AST ラムダを `NewClosure` に降ろし、`captures` / `me` 情報を構築する実装がある。
- 実行VM:
- hv1 VM`src/backend/mir_interpreter`)は `NewClosure` / `Callee::Closure` をまだ実装していないcatch-all で InvalidInstruction
- `FunctionBox` / `ClosureEnv``src/boxes/function_box.rs` に存在し、手動で FunctionBox を作るテスト経路では動いている。
- selfhost 側:
- Phase 25.1a/b では selfhost builder から `fn` を排除し、lambda なしでも Stage1 CLI を扱える Program→MIR ルートに集中する。
## 25.2b のスコープ(案)
- Rust VM:
- `MirInstruction::NewClosure` を実装し、`FunctionBox` + `ClosureEnv` を構築する。
- `execute_callee_call``Callee::Closure` / `Callee::Value` の処理を追加し、第一級関数呼び出しをサポートする。
- Hakorune selfhost:
- selfhost builder (`MirBuilderBox` / `MirBuilderMinBox`) の helper の一部を lambda 版に戻し、`NewClosure` 経路が実際に踏まれるようにする(最初は dev トグル付きでもよい)。
- Stage1 コードでの lambda 利用ポリシー(どの層で許可するか)を docs に明記する。
- テスト:
- AST→MIR→VM で λ を含むケース単純関数、captures、`me` 利用など)をカバーする canary を追加する。
このフェーズは「selfhost builder パリティ25.1b)」完了後に着手する前提であり、本ドキュメントは計画メモのみとする。***

View File

@ -41,15 +41,28 @@ hakorune <command> [<subcommand>] [options] [-- script_args...]
## コマンド一覧MVP 案) ## コマンド一覧MVP 案)
| コマンド | 役割 | | コマンド | 役割 | Phase 25.1a 実装状況 |
|-----------------------------------|-------------------------------------------| |-----------------------------------|-------------------------------------------|----------------------|
| `run` | .hako をコンパイルして実行(既定 VM | | `run` | .hako をコンパイルして実行(既定 VM | プレースホルダ(`[hakorune] run: not implemented yet` |
| `build exe` | .hako からネイティブ EXE を AOT ビルド | | `build exe` | .hako からネイティブ EXE を AOT ビルド | 実装済みenv.codegen経由で `.o` → EXE を生成) |
| `emit program-json` | StageB で Program(JSON v0) を出力 | | `emit program-json` | StageB で Program(JSON v0) を出力 | 実装済み(`BuildBox.emit_program_json_v0` |
| `emit mir-json` | Program(JSON) → MIR(JSON) を出力 | | `emit mir-json` | Program(JSON) → MIR(JSON) を出力 | 実装済み(`MirBuilderBox.emit_from_program_json_v0` |
| `check` | 将来の構文/型/using チェック(予約) | | `check` | 将来の構文/型/using チェック(予約) | プレースホルダ(`[hakorune] check: not implemented yet` |
Phase 25.1 では、既存スクリプト置き換えに近い **`emit mir-json` / `build exe` を中心** に進め`run`/`check`設計レベルに留める Phase 25.1a では、**`emit program-json` / `emit mir-json` / `build exe` の 3 系列のみが実働コード** であり`run` / `check`メッセージを返して終了するプレースホルダのまま運用する。CLI の出口コード90〜93やログ形式は docs と実装を同期済み
### 実装ステータスPhase 25.1a
- `.hako → Program(JSON v0)`:
- StageB (`lang/src/compiler/entry/compiler_stageb.hako`) で `BuildBox.emit_program_json_v0` を呼び出し、`"version":0,"kind":"Program"` を持つ JSON を生成。
- `Stage1UsingResolverBox``HAKO_STAGEB_MODULES_LIST` により、`using lang.mir.builder.MirBuilderBox` などの module alias を解決してから parse する。
- `Program(JSON v0) → MIR(JSON)`:
- 既定は provider 経路(`env.mirbuilder.emit`)を利用。`HAKO_MIRBUILDER_IMPORTS``using ns.Type [as Alias]` から得た alias を JSON で渡し、Rust 側ブリッジが static box 参照(`MirBuilderBox`, `BuildBox` など)を `Const(String(alias))` で生成できるようにする。
- `hostbridge` 参照は JSON ブリッジ側で well-known グローバルとして扱い、`hostbridge.extern_invoke(...)` を含む CLI コードでも `undefined variable: hostbridge` にならないようにした。
- `.hako → EXE` (`build exe`):
- `env.codegen.emit_object`MIR→.o`env.codegen.link_object`.o→EXE`hostbridge.extern_invoke` で呼び出す。
- `--quiet` でログ抑制、`-o/--out` で出力 EXE パスを指定可能。C-API トグル(`NYASH_LLVM_USE_CAPI`, `HAKO_V1_EXTERN_PROVIDER_C_ABI`)が無効な場合は fail-fast。
- Stage1 バイナリ(`target/selfhost/hakorune`)を直接叩く際は `NYASH_NYRT_SILENT_RESULT=1` を付与し、stdout に JSON だけを流す運用を徹底する(`tools/selfhost/run_stage1_cli.sh` が環境セットとバイナリ検出を担当)。
## `run` コマンド ## `run` コマンド
@ -192,6 +205,24 @@ hakorune emit mir-json [-o <out>] [--quiet] <source.hako>
`--force-jsonfrag` / `--normalize-provider` などは、引き続き設計のみで未実装。 `--force-jsonfrag` / `--normalize-provider` などは、引き続き設計のみで未実装。
## I/O と実行補助スクリプト
- Stage1 EXE`target/selfhost/hakorune`)は NyRTnyash_kernel上で動作するため、既定ではプログラム終了時に `Result: <code>` が stdout に追記される。
- llvmlite ハーネスとの互換性を保つため、Stage1 CLI をスクリプトから呼び出す際は `NYASH_NYRT_SILENT_RESULT=1` を常に有効化し、JSON 出力を純粋に保つ。
- 補助スクリプト: `tools/selfhost/run_stage1_cli.sh`
- 役割: Stage1 EXE の場所を解決し(既定 `target/selfhost/hakorune`)、`NYASH_NYRT_SILENT_RESULT=1` / `NYASH_DISABLE_PLUGINS=1` / `NYASH_FILEBOX_MODE=core-ro` を既定ONにしたうえで CLI 引数をそのまま渡す。
- 使い方:
```bash
tools/selfhost/run_stage1_cli.sh emit program-json apps/tests/minimal.hako
tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit mir-json apps/tests/minimal.hako
```
- 直接 EXE を叩く場合も同じ環境変数を手動で設定すること(`NYASH_NYRT_SILENT_RESULT=1 ./target/selfhost/hakorune ...`)。
これにより、stdout は JSON のみを返し、終了コードで成否を判別できるllvmlite ハーネスと同一の契約)。
- 現状の制約2025-11-15 時点):
- `lang/src/runner/launcher.hako` から生成された Program(JSON) がまだ `Main.main` のトップレベル式(`new HakoCli().run(args)`)しか含んでおらず、`HakoCli` / `BuildBox` 定義が JSON に落ちていないため、selfhost EXE 側でも `emit program-json` / `emit mir-json` の実体が欠落している。
- `tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit program-json apps/tests/minimal.hako` は exit code 0 だが stdout が空のまま。StageB 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。
- using 解決は Stage0Rust Runnerと Stage1Hakoruneの二系統に分離する方針。Stage1 側は `lang.compiler.entry.using_resolver_box` で `nyash.toml` の `[modules]` を参照し、`HAKO_STAGEB_MODULES_LIST`shell 側で生成した `name=path` リスト)をキーに依存 Box を text merge する。Rust 側は既存の Runner using 実装を維持し、Stage1 経路はこの Box で独立した自己ホスト導線を持つ。
## `check` コマンド(予約) ## `check` コマンド(予約)
```text ```text
@ -239,3 +270,6 @@ hakorune check [options] <entry.hako>
- Stage1 / Stage1' の CLI インターフェース・代表挙動が一致することをゴールデン/スモークで確認する。 - Stage1 / Stage1' の CLI インターフェース・代表挙動が一致することをゴールデン/スモークで確認する。
このファイルは「Stage1 CLI の仕様 SSOT」として扱い、実装時は本仕様を先に更新→テスト→コードの順で進める。*** このファイルは「Stage1 CLI の仕様 SSOT」として扱い、実装時は本仕様を先に更新→テスト→コードの順で進める。***
- StageB using 未解決メモ:
- `lang.mir.builder.MirBuilderBox` などの `using` 依存を StageB emit が連結できておらず、`tools/selfhost/build_stage1.sh` では `undefined variable: MirBuilderBox` で停止する。BundleResolver / using resolver を StageB 経路に統合し、依存 Box 定義を Program(JSON) に含めるのが次タスク。

View File

@ -10,9 +10,10 @@
// - Recommended: Dev/CI は wrappertools/hakorune_emit_mir.sh経由で Program→MIR を行い、失敗時は GateC に自動委譲する。 // - Recommended: Dev/CI は wrappertools/hakorune_emit_mir.sh経由で Program→MIR を行い、失敗時は GateC に自動委譲する。
// Note: sh_core must be in [modules] for parser dependencies to resolve // Note: sh_core must be in [modules] for parser dependencies to resolve
using "hako.compiler.entry.bundle_resolver" as BundleResolver using hako.compiler.entry.bundle_resolver as BundleResolver
using lang.compiler.parser.box as ParserBox using lang.compiler.parser.box as ParserBox
using lang.compiler.entry.func_scanner as FuncScannerBox using lang.compiler.entry.func_scanner as FuncScannerBox
using lang.compiler.entry.using_resolver as Stage1UsingResolverBox
// Note: Runner resolves entry as Main.main by default. // Note: Runner resolves entry as Main.main by default.
// Provide static box Main with method main(args) as the entry point. // Provide static box Main with method main(args) as the entry point.
@ -499,6 +500,16 @@ static box Main {
body_src = merged_prefix + body_src body_src = merged_prefix + body_src
} }
{
local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS")
if apply_flag == null || ("" + apply_flag) != "0" {
local resolved_prefix = Stage1UsingResolverBox.resolve_for_source(body_src)
if resolved_prefix != null && resolved_prefix != "" {
body_src = resolved_prefix + "\n" + body_src
}
}
}
// 5) Normalize body: trim leading/trailing whitespaces/newlines // 5) Normalize body: trim leading/trailing whitespaces/newlines
{ {
local s = body_src local s = body_src
@ -523,7 +534,7 @@ static box Main {
// 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。 // 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。
local ast_json = p.parse_program2(body_src) local ast_json = p.parse_program2(body_src)
// 6.5) Dev-toggle: scan for function definitions (box Main { method <name>(...) {...} }) // 6.5) Dev-toggle: scan for function definitions (static box { method <name>(...) {...} })
// Toggle: HAKO_STAGEB_FUNC_SCAN=1 // Toggle: HAKO_STAGEB_FUNC_SCAN=1
// Policy: conservative minimal scanner for Phase 21.6 call canary (no nesting, basic only) // Policy: conservative minimal scanner for Phase 21.6 call canary (no nesting, basic only)
// Scope: method <name>(params) { ... } outside of main (same box Main) // Scope: method <name>(params) { ... } outside of main (same box Main)
@ -532,8 +543,8 @@ static box Main {
{ {
local func_scan = env.get("HAKO_STAGEB_FUNC_SCAN") local func_scan = env.get("HAKO_STAGEB_FUNC_SCAN")
if func_scan != null && ("" + func_scan) == "1" { if func_scan != null && ("" + func_scan) == "1" {
// Use FuncScannerBox to extract method definitions // Use FuncScannerBox to extract method definitions from all boxes
local methods = FuncScannerBox.scan_functions(src, "Main") local methods = FuncScannerBox.scan_all_boxes(src)
// Build defs JSON array // Build defs JSON array
if methods.length() > 0 { if methods.length() > 0 {

View File

@ -7,16 +7,96 @@
using lang.compiler.parser.box as ParserBox using lang.compiler.parser.box as ParserBox
static box FuncScannerBox { static box FuncScannerBox {
// Scan source for method definitions (excluding main) // Scan source for method definitions (excluding Main.main)
// Returns ArrayBox of method definitions
method scan_functions(source, box_name) { method scan_functions(source, box_name) {
return me._scan_methods(source, box_name, 1, 0)
}
// Scan all static box definitions and collect their methods.
method scan_all_boxes(source) {
local defs = new ArrayBox()
local s = "" + source
local n = s.length()
local i = 0
local in_str = 0
local esc = 0
local in_line = 0
local in_block = 0
loop(i < n) {
local ch = s.substring(i, i + 1)
if in_line == 1 {
if ch == "\n" { in_line = 0 }
i = i + 1
continue
}
if in_block == 1 {
if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" {
in_block = 0
i = i + 2
continue
}
i = i + 1
continue
}
if in_str == 1 {
if esc == 1 { esc = 0 i = i + 1 continue }
if ch == "\\" { esc = 1 i = i + 1 continue }
if ch == "\"" { in_str = 0 i = i + 1 continue }
i = i + 1
continue
}
if ch == "\"" { in_str = 1 i = i + 1 continue }
if ch == "/" && i + 1 < n {
local ch2 = s.substring(i + 1, i + 2)
if ch2 == "/" { in_line = 1 i = i + 2 continue }
if ch2 == "*" { in_block = 1 i = i + 2 continue }
}
if s.substring(i, i + 3) == "box" && me._kw_boundary_before(s, i) == 1 && me._kw_boundary_after(s, i + 3) == 1 {
local cursor = i + 3
cursor = me._skip_whitespace(s, cursor)
local name_start = cursor
loop(cursor < n) {
local ch_name = s.substring(cursor, cursor + 1)
if me._is_ident_char(ch_name) == 1 { cursor = cursor + 1 } else { break }
}
local box_name = s.substring(name_start, cursor)
if box_name == "" {
i = cursor
continue
}
cursor = me._skip_whitespace(s, cursor)
if cursor >= n || s.substring(cursor, cursor + 1) != "{" {
i = cursor
continue
}
local close_idx = me._find_matching_brace(s, cursor)
if close_idx < 0 { break }
local body = s.substring(cursor + 1, close_idx)
local skip_main = 0
if box_name == "Main" { skip_main = 1 }
local include_me = 0
if box_name != "Main" { include_me = 1 }
local defs_box = me._scan_methods(body, box_name, skip_main, include_me)
me._append_defs(defs, defs_box)
i = close_idx + 1
continue
}
i = i + 1
}
return defs
}
method _scan_methods(source, box_name, skip_main, include_me) {
local methods = new ArrayBox() local methods = new ArrayBox()
local s = "" + source local s = "" + source
local n = s.length() local n = s.length()
local i = 0 local i = 0
loop(i < n) { loop(i < n) {
// Search for "method " pattern // Search for "method "
local k = -1 local k = -1
{ {
local pat = "method " local pat = "method "
@ -28,7 +108,7 @@ static box FuncScannerBox {
} }
} }
if k < 0 { break } if k < 0 { break }
i = k + 7 // skip "method " i = k + 7
// Extract method name (alphanumeric until '(') // Extract method name (alphanumeric until '(')
local name_start = i local name_start = i
@ -44,8 +124,7 @@ static box FuncScannerBox {
if name_end < 0 { break } if name_end < 0 { break }
local method_name = s.substring(name_start, name_end) local method_name = s.substring(name_start, name_end)
// Skip main (already extracted as body) if skip_main == 1 && box_name == "Main" && method_name == "main" { i = name_end continue }
if method_name == "main" { i = name_end continue }
// Find '(' after name // Find '(' after name
local lparen = name_end local lparen = name_end
@ -75,6 +154,21 @@ static box FuncScannerBox {
// Extract params (minimal: comma-separated names) // Extract params (minimal: comma-separated names)
local params_str = s.substring(lparen + 1, rparen) local params_str = s.substring(lparen + 1, rparen)
local params = me._parse_params(params_str) local params = me._parse_params(params_str)
if include_me == 1 {
local first = ""
if params.length() > 0 { first = "" + params.get(0) }
if first != "me" {
local merged = new ArrayBox()
merged.push("me")
local pi2 = 0
local pn2 = params.length()
loop(pi2 < pn2) {
merged.push(params.get(pi2))
pi2 = pi2 + 1
}
params = merged
}
}
// Find opening '{' after ')' // Find opening '{' after ')'
local lbrace = -1 local lbrace = -1
@ -130,10 +224,7 @@ static box FuncScannerBox {
// Extract method body (inside braces) // Extract method body (inside braces)
local method_body = s.substring(lbrace + 1, rbrace) local method_body = s.substring(lbrace + 1, rbrace)
// Strip comments from method body
method_body = me._strip_comments(method_body) method_body = me._strip_comments(method_body)
// Trim method body
method_body = me._trim(method_body) method_body = me._trim(method_body)
// Parse method body to JSON (statement list) // Parse method body to JSON (statement list)
@ -144,7 +235,6 @@ static box FuncScannerBox {
body_json = p.parse_program2(method_body) body_json = p.parse_program2(method_body)
} }
// Store method definition
if body_json != null && body_json != "" { if body_json != null && body_json != "" {
local def = new MapBox() local def = new MapBox()
def.set("name", method_name) def.set("name", method_name)
@ -159,6 +249,101 @@ static box FuncScannerBox {
return methods return methods
} }
method _append_defs(dst, defs_box) {
if defs_box == null { return }
local i = 0
loop(i < defs_box.length()) {
dst.push(defs_box.get(i))
i = i + 1
}
}
method _skip_whitespace(s, idx) {
local i = idx
local n = s.length()
loop(i < n) {
local ch = s.substring(i, i + 1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { i = i + 1 } else { break }
}
return i
}
method _kw_boundary_before(s, idx) {
if idx <= 0 { return 1 }
local ch = s.substring(idx - 1, idx)
if me._is_ident_char(ch) == 1 { return 0 }
return 1
}
method _kw_boundary_after(s, idx) {
if idx >= s.length() { return 1 }
local ch = s.substring(idx, idx + 1)
if me._is_ident_char(ch) == 1 { return 0 }
return 1
}
method _is_ident_char(ch) {
if ch == null || ch == "" { return 0 }
if ch >= "0" && ch <= "9" { return 1 }
if ch >= "A" && ch <= "Z" { return 1 }
if ch >= "a" && ch <= "z" { return 1 }
if ch == "_" { return 1 }
return 0
}
method _find_matching_brace(s, open_idx) {
local n = s.length()
local i = open_idx
local depth = 0
local in_str = 0
local esc = 0
local in_line = 0
local in_block = 0
loop(i < n) {
local ch = s.substring(i, i + 1)
if in_line == 1 {
if ch == "\n" { in_line = 0 }
i = i + 1
continue
}
if in_block == 1 {
if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" {
in_block = 0
i = i + 2
continue
}
i = i + 1
continue
}
if in_str == 1 {
if esc == 1 { esc = 0 i = i + 1 continue }
if ch == "\\" { esc = 1 i = i + 1 continue }
if ch == "\"" { in_str = 0 i = i + 1 continue }
i = i + 1
continue
}
if ch == "\"" { in_str = 1 i = i + 1 continue }
if ch == "/" && i + 1 < n {
local ch2 = s.substring(i + 1, i + 2)
if ch2 == "/" { in_line = 1 i = i + 2 continue }
if ch2 == "*" { in_block = 1 i = i + 2 continue }
}
if ch == "{" {
depth = depth + 1
i = i + 1
continue
}
if ch == "}" {
depth = depth - 1
i = i + 1
if depth == 0 { return i - 1 }
continue
}
i = i + 1
}
return -1
}
// Helper: parse comma-separated parameter names // Helper: parse comma-separated parameter names
method _parse_params(params_str) { method _parse_params(params_str) {
local params = new ArrayBox() local params = new ArrayBox()
@ -202,7 +387,6 @@ static box FuncScannerBox {
local esc = 0 local esc = 0
local in_line = 0 local in_line = 0
local in_block = 0 local in_block = 0
loop(i < n) { loop(i < n) {
local ch = s.substring(i, i + 1) local ch = s.substring(i, i + 1)
if in_line == 1 { if in_line == 1 {
@ -223,7 +407,6 @@ static box FuncScannerBox {
i = i + 1 i = i + 1
continue continue
} }
// Not in string/comment
if ch == "\"" { out = out + ch in_str = 1 i = i + 1 continue } if ch == "\"" { out = out + ch in_str = 1 i = i + 1 continue }
if ch == "/" && i + 1 < n { if ch == "/" && i + 1 < n {
local ch2 = s.substring(i + 1, i + 2) local ch2 = s.substring(i + 1, i + 2)
@ -233,29 +416,24 @@ static box FuncScannerBox {
out = out + ch out = out + ch
i = i + 1 i = i + 1
} }
return out return out
} }
// Helper: trim whitespace from string // Helper: trim whitespace
method _trim(s) { method _trim(s) {
if s == null { return "" }
local str = "" + s local str = "" + s
local n = str.length() local n = str.length()
local b = 0 local b = 0
// left trim (space, tab, CR, LF)
loop(b < n) { loop(b < n) {
local ch = str.substring(b, b + 1) local ch = str.substring(b, b + 1)
if ch == " " || ch == "\t" || ch == "\r" || ch == "\n" { b = b + 1 } else { break } if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break }
} }
// right trim
local e = n local e = n
loop(e > b) { loop(e > b) {
local ch = str.substring(e - 1, e) local ch = str.substring(e - 1, e)
if ch == " " || ch == "\t" || ch == "\r" || ch == "\n" { e = e - 1 } else { break } if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break }
} }
if e > b { return str.substring(b, e) } if e > b { return str.substring(b, e) }
return "" return ""
} }

View File

@ -0,0 +1,150 @@
// Stage1UsingResolverBox — resolve `using` statements for Stage1 pipelines
// Responsibilities:
// - Collect line-based using declarations via UsingCollectorBox.
// - Resolve namespace targets against `nyash.toml` `[modules]` entries (fed via env).
// - Read the referenced .hako files and return a concatenated prefix for StageB.
//
// Env requirements:
// - HAKO_STAGEB_MODULES_LIST: `name=path` entries joined by "|||".
// - NYASH_ROOT: absolute path to repo root (for resolving relative paths).
using lang.compiler.parser.using.using_collector_box as UsingCollectorBox
using lang.compiler.parser.scan.parser_common_utils_box as ParserCommonUtilsBox
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box Stage1UsingResolverBox {
resolve_for_source(src) {
if src == null { return "" }
local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS")
if apply_flag != null && ("" + apply_flag) == "0" { return "" }
local entries = me._collect_using_entries(src)
if entries == null || entries.length() == 0 { return "" }
local modules = me._build_module_map()
local seen = new MapBox()
local prefix = ""
local i = 0
local n = entries.length()
loop(i < n) {
local entry = entries.get(i)
local name = "" + entry.get("name")
if name == "" { i = i + 1 continue }
local path = entry.get("path")
if path == null || ("" + path) == "" {
path = me._lookup_module_path(modules, name)
} else {
path = "" + path
}
if path == null || path == "" { i = i + 1 continue }
local abs_path = me._abs_path(path)
if abs_path == null || abs_path == "" { i = i + 1 continue }
if seen.get(abs_path) == "1" { i = i + 1 continue }
local code = me._read_file(abs_path)
if code == null { i = i + 1 continue }
seen.set(abs_path, "1")
prefix = prefix + "\n" + code + "\n"
i = i + 1
}
return prefix
}
// Collect entries from UsingCollectorBox JSON output.
_collect_using_entries(src) {
local json = UsingCollectorBox.collect(src)
if json == null || json == "" || json == "[]" { return null }
local out = new ArrayBox()
local pos = 0
local n = json.length()
loop(pos < n) {
local name_idx = JsonFragBox.index_of_from(json, "\"name\":\"", pos)
if name_idx < 0 { break }
local name = JsonFragBox.read_string_after(json, name_idx + 7)
local obj_end = JsonFragBox.index_of_from(json, "}", name_idx)
if obj_end < 0 { obj_end = n }
local path = null
local path_idx = JsonFragBox.index_of_from(json, "\"path\":\"", name_idx)
if path_idx >= 0 && path_idx < obj_end {
path = JsonFragBox.read_string_after(json, path_idx + 7)
}
local entry = new MapBox()
entry.set("name", name)
if path != null { entry.set("path", path) }
out.push(entry)
pos = obj_end + 1
}
return out
}
_build_module_map() {
local map = new MapBox()
local raw = env.get("HAKO_STAGEB_MODULES_LIST")
if raw == null { return map }
local str = "" + raw
if str == "" { return map }
local delim = "|||"
local start = 0
loop(true) {
local next = ParserCommonUtilsBox.index_of(str, start, delim)
local seg = ""
if next >= 0 { seg = str.substring(start, next) } else { seg = str.substring(start, str.length()) }
me._push_module_entry(map, seg)
if next < 0 { break }
start = next + delim.length()
}
return map
}
_push_module_entry(map, seg) {
if seg == null { return }
local line = ParserCommonUtilsBox.trim("" + seg)
if line == "" { return }
local eq_idx = ParserCommonUtilsBox.index_of(line, 0, "=")
if eq_idx < 0 { return }
local key = ParserCommonUtilsBox.trim(line.substring(0, eq_idx))
local val = ParserCommonUtilsBox.trim(line.substring(eq_idx + 1, line.length()))
if key == "" || val == "" { return }
map.set(key, val)
}
_lookup_module_path(map, name) {
if map == null { return null }
local val = map.get(name)
if val != null { return "" + val }
return null
}
_abs_path(path) {
if path == null { return null }
local p = "" + path
if p == "" { return null }
if ParserCommonUtilsBox.starts_with(p, 0, "/") == 1 { return p }
local root = env.get("NYASH_ROOT")
if root == null || root == "" { return p }
local base = "" + root
if base == "" { return p }
if base.substring(base.length() - 1, base.length()) == "/" { return base + p }
return base + "/" + p
}
_read_file(path) {
if path == null { return null }
@fb = new FileBox()
@ok = fb.open(path, "r")
if ok != 1 { return null }
@content = fb.read()
fb.close()
return content
}
}
static box Stage1UsingResolverBoxMain { main(args) { return 0 } }

View File

@ -41,6 +41,7 @@ entry.func_scanner = "entry/func_scanner.hako"
entry.compiler = "entry/compiler.hako" entry.compiler = "entry/compiler.hako"
entry.compiler_stageb = "entry/compiler_stageb.hako" entry.compiler_stageb = "entry/compiler_stageb.hako"
entry.bundle_resolver = "entry/bundle_resolver.hako" entry.bundle_resolver = "entry/bundle_resolver.hako"
entry.using_resolver = "entry/using_resolver_box.hako"
[dependencies] [dependencies]
"selfhost.shared" = "^1.0.0" "selfhost.shared" = "^1.0.0"

View File

@ -20,6 +20,24 @@ using "hako.mir.builder.internal.pattern_util" as PatternUtilBox
using lang.mir.builder.func_lowering as FuncLoweringBox using lang.mir.builder.func_lowering as FuncLoweringBox
static box MirBuilderBox { static box MirBuilderBox {
method _log_builder_fail(reason) {
local tag = "[builder/selfhost-first:unsupported:" + reason + "]"
print(tag)
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/selfhost-first:trace] fail_reason=" + reason)
}
}
// Helper to inject optional function defs + normalization
method _norm_if_apply(m, func_defs_mir) {
if m == null { return null }
local result = FuncLoweringBox.inject_funcs(m, func_defs_mir)
if env.get("HAKO_MIR_BUILDER_METHODIZE") == "1" {
result = FuncLoweringBox.methodize_calls_in_mir(result)
}
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(result) }
return result
}
// Availability probe (for canaries) // Availability probe (for canaries)
is_available() { is_available() {
// For now, availability means delegate toggle is present // For now, availability means delegate toggle is present
@ -59,20 +77,6 @@ static box MirBuilderBox {
func_defs_mir = FuncLoweringBox.lower_func_defs(s, s) func_defs_mir = FuncLoweringBox.lower_func_defs(s, s)
} }
} }
// Helper: optional normalization (dev toggle, default OFF) + func injection
local norm_if = function(m) {
if m == null { return null }
// Inject function definitions if available
local result = FuncLoweringBox.inject_funcs(m, func_defs_mir)
// Optional methodize (dev): rewrite legacy Call -> unified mir_call(Method)
if env.get("HAKO_MIR_BUILDER_METHODIZE") == "1" {
result = FuncLoweringBox.methodize_calls_in_mir(result)
}
// Optional normalize (dev)
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(result) }
return result
}
// Internal path既定ON — const(int)+ret, binop+ret ほか、registry 優先の lowering // Internal path既定ON — const(int)+ret, binop+ret ほか、registry 優先の lowering
// Disable with: HAKO_MIR_BUILDER_INTERNAL=0 // Disable with: HAKO_MIR_BUILDER_INTERNAL=0
{ {
@ -88,7 +92,7 @@ static box MirBuilderBox {
"{\"id\":0,\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":0}},{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":0}},{\"op\":\"compare\",\"operation\":\"<\",\"lhs\":1,\"rhs\":2,\"dst\":3},{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}]}," + "{\"id\":0,\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":0}},{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":0}},{\"op\":\"compare\",\"operation\":\"<\",\"lhs\":1,\"rhs\":2,\"dst\":3},{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}]}," +
"{\"id\":1,\"instructions\":[{\"op\":\"ret\",\"value\":1}]}," + "{\"id\":1,\"instructions\":[{\"op\":\"ret\",\"value\":1}]}," +
"{\"id\":2,\"instructions\":[{\"op\":\"ret\",\"value\":1}]}]}]}" "{\"id\":2,\"instructions\":[{\"op\":\"ret\",\"value\":1}]}]}]}"
return norm_if(mir) return me._norm_if_apply(mir, func_defs_mir)
} }
} }
// Optional: registry-driven lowering (scaffold). When HAKO_MIR_BUILDER_REGISTRY=1, // Optional: registry-driven lowering (scaffold). When HAKO_MIR_BUILDER_REGISTRY=1,
@ -138,7 +142,7 @@ static box MirBuilderBox {
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":4,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Method\\\",\\\"method\\\":\\\"" + method + "\\\",\\\"receiver\\\":1},\\\"args\\\":[" + args_text + "],\\\"effects\\\":[]}}," + "{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":4,\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Method\\\",\\\"method\\\":\\\"" + method + "\\\",\\\"receiver\\\":1},\\\"args\\\":[" + args_text + "],\\\"effects\\\":[]}}," +
"{\\\"op\\\":\\\"ret\\\",\\\"value\\\":4}]}]}]}" "{\\\"op\\\":\\\"ret\\\",\\\"value\\\":4}]}]}]}"
print("[mirbuilder/registry:return.method.arraymap]") print("[mirbuilder/registry:return.method.arraymap]")
return norm_if(mir) return me._norm_if_apply(mir, func_defs_mir)
} }
// Registry list汎用 // Registry list汎用
using "hako.mir.builder.pattern_registry" as PatternRegistryBox using "hako.mir.builder.pattern_registry" as PatternRegistryBox
@ -166,23 +170,23 @@ static box MirBuilderBox {
local i = 0; local n = names.length() local i = 0; local n = names.length()
loop(i < n) { loop(i < n) {
local nm = "" + names.get(i) local nm = "" + names.get(i)
if nm == "if.compare.intint" { local out = LowerIfCompareBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "if.compare.intint" { local out = LowerIfCompareBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "if.compare.fold.binints" { local out = LowerIfCompareFoldBinIntsBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "if.compare.fold.binints" { local out = LowerIfCompareFoldBinIntsBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "if.compare.fold.varint" { local out = LowerIfCompareFoldVarIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "if.compare.fold.varint" { local out = LowerIfCompareFoldVarIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "if.compare.varint" { local out = LowerIfCompareVarIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "if.compare.varint" { local out = LowerIfCompareVarIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "if.compare.varvar" { local out = LowerIfCompareVarVarBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "if.compare.varvar" { local out = LowerIfCompareVarVarBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.method.arraymap" { local out = LowerReturnMethodArrayMapBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.method.arraymap" { local out = LowerReturnMethodArrayMapBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.method.string.length" { local out = LowerReturnMethodStringLengthBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.method.string.length" { local out = LowerReturnMethodStringLengthBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.loop.strlen.sum" { local out = LowerReturnLoopStrlenSumBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.loop.strlen.sum" { local out = LowerReturnLoopStrlenSumBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.var.local" { local out = LowerReturnVarLocalBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.var.local" { local out = LowerReturnVarLocalBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.string" { local out = LowerReturnStringBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.string" { local out = LowerReturnStringBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.float" { local out = LowerReturnFloatBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.float" { local out = LowerReturnFloatBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.bool" { local out = LowerReturnBoolBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.bool" { local out = LowerReturnBoolBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.logical" { local out = LowerReturnLogicalBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.logical" { local out = LowerReturnLogicalBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.binop.varint" { local out = LowerReturnBinOpVarIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.binop.varint" { local out = LowerReturnBinOpVarIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.binop.varvar" { local out = LowerReturnBinOpVarVarBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.binop.varvar" { local out = LowerReturnBinOpVarVarBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.binop.intint" { local out = LowerReturnBinOpBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.binop.intint" { local out = LowerReturnBinOpBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
if nm == "return.int" { local out = LowerReturnIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return norm_if(out) } } if nm == "return.int" { local out = LowerReturnIntBox.try_lower(s); if out != null { if env.get("HAKO_MIR_BUILDER_DEBUG")=="1" || env.get("NYASH_CLI_VERBOSE")=="1" { print("[mirbuilder/registry:" + nm + "]") } return me._norm_if_apply(out, func_defs_mir) } }
i = i + 1 i = i + 1
} }
// Fall-through to chain if none matched // Fall-through to chain if none matched
@ -203,53 +207,53 @@ static box MirBuilderBox {
using "hako.mir.builder.internal.lower_loop_count_param" as LowerLoopCountParamBox using "hako.mir.builder.internal.lower_loop_count_param" as LowerLoopCountParamBox
using "hako.mir.builder.internal.lower_loop_simple" as LowerLoopSimpleBox using "hako.mir.builder.internal.lower_loop_simple" as LowerLoopSimpleBox
// Prefer New(Constructor) minimal first to avoid unresolved nested lowers in inline runs // Prefer New(Constructor) minimal first to avoid unresolved nested lowers in inline runs
{ local out_newc = LowerNewboxConstructorBox.try_lower(s); if out_newc != null { return norm_if(out_newc) } } { local out_newc = LowerNewboxConstructorBox.try_lower(s); if out_newc != null { return me._norm_if_apply(out_newc, func_defs_mir) } }
{ local out_arr_size = LowerMethodArraySizeBox.try_lower(s); if out_arr_size != null { return norm_if(out_arr_size) } } { local out_arr_size = LowerMethodArraySizeBox.try_lower(s); if out_arr_size != null { return me._norm_if_apply(out_arr_size, func_defs_mir) } }
{ local out_arr_push = LowerMethodArrayPushBox.try_lower(s); if out_arr_push != null { return norm_if(out_arr_push) } } { local out_arr_push = LowerMethodArrayPushBox.try_lower(s); if out_arr_push != null { return me._norm_if_apply(out_arr_push, func_defs_mir) } }
{ local out_arr_gs = LowerMethodArrayGetSetBox.try_lower(s); if out_arr_gs != null { return norm_if(out_arr_gs) } } { local out_arr_gs = LowerMethodArrayGetSetBox.try_lower(s); if out_arr_gs != null { return me._norm_if_apply(out_arr_gs, func_defs_mir) } }
{ local out_map_size = LowerMethodMapSizeBox.try_lower(s); if out_map_size != null { return norm_if(out_map_size) } } { local out_map_size = LowerMethodMapSizeBox.try_lower(s); if out_map_size != null { return me._norm_if_apply(out_map_size, func_defs_mir) } }
{ local out_map_gs = LowerMethodMapGetSetBox.try_lower(s); if out_map_gs != null { return norm_if(out_map_gs) } } { local out_map_gs = LowerMethodMapGetSetBox.try_lower(s); if out_map_gs != null { return me._norm_if_apply(out_map_gs, func_defs_mir) } }
{ local out_ls = LowerLoadStoreLocalBox.try_lower(s); if out_ls != null { return norm_if(out_ls) } } { local out_ls = LowerLoadStoreLocalBox.try_lower(s); if out_ls != null { return me._norm_if_apply(out_ls, func_defs_mir) } }
{ local out_toc = LowerTypeOpCheckBox.try_lower(s); if out_toc != null { return norm_if(out_toc) } } { local out_toc = LowerTypeOpCheckBox.try_lower(s); if out_toc != null { return me._norm_if_apply(out_toc, func_defs_mir) } }
{ local out_tca = LowerTypeOpCastBox.try_lower(s); if out_tca != null { return norm_if(out_tca) } } { local out_tca = LowerTypeOpCastBox.try_lower(s); if out_tca != null { return me._norm_if_apply(out_tca, func_defs_mir) } }
// Loop lowers (sum_bc/continue/break normalization) // Loop lowers (sum_bc/continue/break normalization)
// Allow skipping heavy loop lowers on plugin-less hosts: HAKO_MIR_BUILDER_SKIP_LOOPS=1 // Allow skipping heavy loop lowers on plugin-less hosts: HAKO_MIR_BUILDER_SKIP_LOOPS=1
{ {
local skip_loops = env.get("HAKO_MIR_BUILDER_SKIP_LOOPS") local skip_loops = env.get("HAKO_MIR_BUILDER_SKIP_LOOPS")
local do_loops = (skip_loops == null) || (("" + skip_loops) != "1") local do_loops = (skip_loops == null) || (("" + skip_loops) != "1")
if do_loops == 1 { if do_loops == 1 {
{ local out_loop2 = LowerLoopSumBcBox.try_lower(s); if out_loop2 != null { return norm_if(out_loop2) } } { local out_loop2 = LowerLoopSumBcBox.try_lower(s); if out_loop2 != null { return me._norm_if_apply(out_loop2, func_defs_mir) } }
} }
} }
{ local out_if2b = LowerIfNestedBox.try_lower(s); if out_if2b != null { return norm_if(out_if2b) } } { local out_if2b = LowerIfNestedBox.try_lower(s); if out_if2b != null { return me._norm_if_apply(out_if2b, func_defs_mir) } }
{ local out_if2 = LowerIfThenElseFollowingReturnBox.try_lower(s); if out_if2 != null { return norm_if(out_if2) } } { local out_if2 = LowerIfThenElseFollowingReturnBox.try_lower(s); if out_if2 != null { return me._norm_if_apply(out_if2, func_defs_mir) } }
{ local out_if = LowerIfCompareBox.try_lower(s); if out_if != null { return norm_if(out_if) } } { local out_if = LowerIfCompareBox.try_lower(s); if out_if != null { return me._norm_if_apply(out_if, func_defs_mir) } }
{ local out_ifb = LowerIfCompareFoldBinIntsBox.try_lower(s); if out_ifb != null { return norm_if(out_ifb) } } { local out_ifb = LowerIfCompareFoldBinIntsBox.try_lower(s); if out_ifb != null { return me._norm_if_apply(out_ifb, func_defs_mir) } }
{ local out_ifbv = LowerIfCompareFoldVarIntBox.try_lower(s); if out_ifbv != null { return norm_if(out_ifbv) } } { local out_ifbv = LowerIfCompareFoldVarIntBox.try_lower(s); if out_ifbv != null { return me._norm_if_apply(out_ifbv, func_defs_mir) } }
{ local out_ifvi = LowerIfCompareVarIntBox.try_lower(s); if out_ifvi != null { return norm_if(out_ifvi) } } { local out_ifvi = LowerIfCompareVarIntBox.try_lower(s); if out_ifvi != null { return me._norm_if_apply(out_ifvi, func_defs_mir) } }
{ local out_ifvv = LowerIfCompareVarVarBox.try_lower(s); if out_ifvv != null { return norm_if(out_ifvv) } } { local out_ifvv = LowerIfCompareVarVarBox.try_lower(s); if out_ifvv != null { return me._norm_if_apply(out_ifvv, func_defs_mir) } }
{ {
local skip_loops2 = env.get("HAKO_MIR_BUILDER_SKIP_LOOPS") local skip_loops2 = env.get("HAKO_MIR_BUILDER_SKIP_LOOPS")
local do_loops2 = (skip_loops2 == null) || (("" + skip_loops2) != "1") local do_loops2 = (skip_loops2 == null) || (("" + skip_loops2) != "1")
if do_loops2 == 1 { if do_loops2 == 1 {
{ local out_loopp = LowerLoopCountParamBox.try_lower(s); if out_loopp != null { return norm_if(out_loopp) } } { local out_loopp = LowerLoopCountParamBox.try_lower(s); if out_loopp != null { return me._norm_if_apply(out_loopp, func_defs_mir) } }
{ local out_loop = LowerLoopSimpleBox.try_lower(s); if out_loop != null { return norm_if(out_loop) } } { local out_loop = LowerLoopSimpleBox.try_lower(s); if out_loop != null { return me._norm_if_apply(out_loop, func_defs_mir) } }
} }
} }
{ local out_var = LowerReturnVarLocalBox.try_lower(s); if out_var != null { return norm_if(out_var) } } { local out_var = LowerReturnVarLocalBox.try_lower(s); if out_var != null { return me._norm_if_apply(out_var, func_defs_mir) } }
{ local out_str = LowerReturnStringBox.try_lower(s); if out_str != null { return norm_if(out_str) } } { local out_str = LowerReturnStringBox.try_lower(s); if out_str != null { return me._norm_if_apply(out_str, func_defs_mir) } }
{ local out_f = LowerReturnFloatBox.try_lower(s); if out_f != null { return norm_if(out_f) } } { local out_f = LowerReturnFloatBox.try_lower(s); if out_f != null { return me._norm_if_apply(out_f, func_defs_mir) } }
{ local out_log = LowerReturnLogicalBox.try_lower(s); if out_log != null { return norm_if(out_log) } } { local out_log = LowerReturnLogicalBox.try_lower(s); if out_log != null { return me._norm_if_apply(out_log, func_defs_mir) } }
{ local out_meth = LowerReturnMethodArrayMapBox.try_lower(s); if out_meth != null { return norm_if(out_meth) } } { local out_meth = LowerReturnMethodArrayMapBox.try_lower(s); if out_meth != null { return me._norm_if_apply(out_meth, func_defs_mir) } }
{ local out_meth_s = LowerReturnMethodStringLengthBox.try_lower(s); if out_meth_s != null { return norm_if(out_meth_s) } } { local out_meth_s = LowerReturnMethodStringLengthBox.try_lower(s); if out_meth_s != null { return me._norm_if_apply(out_meth_s, func_defs_mir) } }
{ local out_sum = LowerReturnLoopStrlenSumBox.try_lower(s); if out_sum != null { return norm_if(out_sum) } } { local out_sum = LowerReturnLoopStrlenSumBox.try_lower(s); if out_sum != null { return me._norm_if_apply(out_sum, func_defs_mir) } }
{ local out_bool = LowerReturnBoolBox.try_lower(s); if out_bool != null { return norm_if(out_bool) } } { local out_bool = LowerReturnBoolBox.try_lower(s); if out_bool != null { return me._norm_if_apply(out_bool, func_defs_mir) } }
{ local out_bvi = LowerReturnBinOpVarIntBox.try_lower(s); if out_bvi != null { return norm_if(out_bvi) } } { local out_bvi = LowerReturnBinOpVarIntBox.try_lower(s); if out_bvi != null { return me._norm_if_apply(out_bvi, func_defs_mir) } }
{ local out_bvv = LowerReturnBinOpVarVarBox.try_lower(s); if out_bvv != null { return norm_if(out_bvv) } } { local out_bvv = LowerReturnBinOpVarVarBox.try_lower(s); if out_bvv != null { return me._norm_if_apply(out_bvv, func_defs_mir) } }
{ local out_bin = LowerReturnBinOpBox.try_lower(s); if out_bin != null { return norm_if(out_bin) } } { local out_bin = LowerReturnBinOpBox.try_lower(s); if out_bin != null { return me._norm_if_apply(out_bin, func_defs_mir) } }
{ {
local out_int = LowerReturnIntBox.try_lower(s) local out_int = LowerReturnIntBox.try_lower(s)
if out_int != null { return norm_if(out_int) } if out_int != null { return me._norm_if_apply(out_int, func_defs_mir) }
} }
// Find Return marker (or If) // Find Return marker (or If)
// Case (If with Compare + Return(Int)/Return(Int) in branches) // Case (If with Compare + Return(Int)/Return(Int) in branches)
@ -341,7 +345,7 @@ static box MirBuilderBox {
"{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}]}," + "{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}]}," +
"{\"id\":1,\"instructions\":[{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":" + then_val + "}},{\"op\":\"ret\",\"value\":4}]}," + "{\"id\":1,\"instructions\":[{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":" + then_val + "}},{\"op\":\"ret\",\"value\":4}]}," +
"{\"id\":2,\"instructions\":[{\"op\":\"const\",\"dst\":5,\"value\":{\"type\":\"i64\",\"value\":" + else_val + "}},{\"op\":\"ret\",\"value\":5}]}]}]}" "{\"id\":2,\"instructions\":[{\"op\":\"const\",\"dst\":5,\"value\":{\"type\":\"i64\",\"value\":" + else_val + "}},{\"op\":\"ret\",\"value\":5}]}]}]}"
return norm_if(mir_if) return me._norm_if_apply(mir_if, func_defs_mir)
} }
} }
} }
@ -350,7 +354,7 @@ static box MirBuilderBox {
// NewBox(Constructor) minimal // NewBox(Constructor) minimal
{ {
local out_new = LowerNewboxConstructorBox.try_lower(s) local out_new = LowerNewboxConstructorBox.try_lower(s)
if out_new != null { return norm_if(out_new) } if out_new != null { return me._norm_if_apply(out_new, func_defs_mir) }
} }
// Fallback cases below: Return(Binary) and Return(Int) // Fallback cases below: Return(Binary) and Return(Int)
local k_ret = JsonFragBox.index_of_from(s, "\"type\":\"Return\"", 0) local k_ret = JsonFragBox.index_of_from(s, "\"type\":\"Return\"", 0)
@ -392,7 +396,7 @@ static box MirBuilderBox {
"{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs_val + "}}," + "{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs_val + "}}," +
"{\"op\":\"binop\",\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2,\"dst\":3}," + "{\"op\":\"binop\",\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2,\"dst\":3}," +
"{\"op\":\"ret\",\"value\":3}]}]}]}" "{\"op\":\"ret\",\"value\":3}]}]}]}"
return norm_if(mir_bin) return me._norm_if_apply(mir_bin, func_defs_mir)
} }
} }
} }
@ -408,7 +412,7 @@ static box MirBuilderBox {
if num != null { if num != null {
if env.get("HAKO_MIR_BUILDER_DEBUG") == "1" || env.get("NYASH_CLI_VERBOSE") == "1" { print("[mirbuilder/fallback:Return(Int) val=" + num + "]") } if env.get("HAKO_MIR_BUILDER_DEBUG") == "1" || env.get("NYASH_CLI_VERBOSE") == "1" { print("[mirbuilder/fallback:Return(Int) val=" + num + "]") }
local mir = "{\"functions\":[{\"name\":\"main\",\"params\":[],\"locals\":[],\"blocks\":[{\"id\":0,\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + num + "}},{\"op\":\"ret\",\"value\":1}]}]}]}" local mir = "{\"functions\":[{\"name\":\"main\",\"params\":[],\"locals\":[],\"blocks\":[{\"id\":0,\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + num + "}},{\"op\":\"ret\",\"value\":1}]}]}]}"
return norm_if(mir) return me._norm_if_apply(mir, func_defs_mir)
} }
} }
} else { } else {
@ -421,11 +425,9 @@ static box MirBuilderBox {
// Unsupported internal shape → try defs fallback (dev: HAKO_MIR_BUILDER_FUNCS=1) // Unsupported internal shape → try defs fallback (dev: HAKO_MIR_BUILDER_FUNCS=1)
print("[mirbuilder/internal/unsupported] only Return(Int|Binary(Int,Int)) supported in internal mode") print("[mirbuilder/internal/unsupported] only Return(Int|Binary(Int,Int)) supported in internal mode")
if func_defs_mir != null && func_defs_mir != "" { if func_defs_mir != null && func_defs_mir != "" {
// Assemble a minimal module from lowered defs me._log_builder_fail("defs_only")
local defs = func_defs_mir } else {
if defs.substring(0,1) == "," { defs = defs.substring(1, defs.length()) } me._log_builder_fail("no_match")
local mod = "{\\\"functions\\\":[" + defs + "]}"
return norm_if(mod)
} }
return null return null
} }
@ -436,7 +438,7 @@ static box MirBuilderBox {
// Call host provider via extern: env.mirbuilder.emit(program_json) // Call host provider via extern: env.mirbuilder.emit(program_json)
local args = new ArrayBox(); args.push(program_json) local args = new ArrayBox(); args.push(program_json)
local ret = hostbridge.extern_invoke("env.mirbuilder", "emit", args) local ret = hostbridge.extern_invoke("env.mirbuilder", "emit", args)
return norm_if(ret) return me._norm_if_apply(ret, func_defs_mir)
} }
// Provider not wired → FailFast tag // Provider not wired → FailFast tag
print("[mirbuilder/delegate/missing] no provider; enable HAKO_MIR_BUILDER_DELEGATE=1") print("[mirbuilder/delegate/missing] no provider; enable HAKO_MIR_BUILDER_DELEGATE=1")

View File

@ -16,30 +16,29 @@ using "hako.mir.builder.internal.lower_if_compare_varint" as LowerIfCompareVarIn
using "hako.mir.builder.internal.lower_if_compare_varvar" as LowerIfCompareVarVarBox using "hako.mir.builder.internal.lower_if_compare_varvar" as LowerIfCompareVarVarBox
static box MirBuilderBox { static box MirBuilderBox {
method _norm_json_if_needed(m) {
if m == null { return null }
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(m) }
return m
}
// Minimal entry // Minimal entry
method emit_from_program_json_v0(program_json, opts) { method emit_from_program_json_v0(program_json, opts) {
if program_json == null { print("[mirbuilder/min/input:null]"); return null } if program_json == null { print("[mirbuilder/min/input:null]"); return null }
local s = "" + program_json local s = "" + program_json
if !(s.contains("\"version\"")) || !(s.contains("\"kind\"")) { print("[mirbuilder/min/input:invalid]"); return null } if !(s.contains("\"version\"")) || !(s.contains("\"kind\"")) { print("[mirbuilder/min/input:invalid]"); return null }
// Small helper: optionally normalize MIR(JSON) via toggle (dev-only, default OFF)
local norm_if = function(m) {
if m == null { return null }
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(m) }
return m
}
// Try minimal patterns (lightweight only) // Try minimal patterns (lightweight only)
{ local out = LowerReturnMethodArrayMapBox.try_lower(s); if out != null { print("[mirbuilder/min:return.method.arraymap]"); return norm_if(out) } } { local out = LowerReturnMethodArrayMapBox.try_lower(s); if out != null { print("[mirbuilder/min:return.method.arraymap]"); return me._norm_json_if_needed(out) } }
{ local out_v = LowerReturnBinOpVarIntBox.try_lower(s); if out_v != null { print("[mirbuilder/min:return.binop.varint]"); return norm_if(out_v) } } { local out_v = LowerReturnBinOpVarIntBox.try_lower(s); if out_v != null { print("[mirbuilder/min:return.binop.varint]"); return me._norm_json_if_needed(out_v) } }
{ local out_b = LowerReturnBinOpBox.try_lower(s); if out_b != null { print("[mirbuilder/min:return.binop.intint]"); return norm_if(out_b) } } { local out_b = LowerReturnBinOpBox.try_lower(s); if out_b != null { print("[mirbuilder/min:return.binop.intint]"); return me._norm_json_if_needed(out_b) } }
{ local out_bvv = LowerReturnBinOpVarVarBox.try_lower(s); if out_bvv != null { print("[mirbuilder/min:return.binop.varvar]"); return norm_if(out_bvv) } } { local out_bvv = LowerReturnBinOpVarVarBox.try_lower(s); if out_bvv != null { print("[mirbuilder/min:return.binop.varvar]"); return me._norm_json_if_needed(out_bvv) } }
// Compare lowers: prefer fold/var-based before int-int to avoid greedy match // Compare lowers: prefer fold/var-based before int-int to avoid greedy match
{ local out_if_fv = LowerIfCompareFoldVarIntBox.try_lower(s); if out_if_fv != null { print("[mirbuilder/min:if.compare.fold.varint]"); return norm_if(out_if_fv) } } { local out_if_fv = LowerIfCompareFoldVarIntBox.try_lower(s); if out_if_fv != null { print("[mirbuilder/min:if.compare.fold.varint]"); return me._norm_json_if_needed(out_if_fv) } }
{ local out_if_fb = LowerIfCompareFoldBinIntsBox.try_lower(s); if out_if_fb != null { print("[mirbuilder/min:if.compare.fold.binints]"); return norm_if(out_if_fb) } } { local out_if_fb = LowerIfCompareFoldBinIntsBox.try_lower(s); if out_if_fb != null { print("[mirbuilder/min:if.compare.fold.binints]"); return me._norm_json_if_needed(out_if_fb) } }
{ local out_ifvi = LowerIfCompareVarIntBox.try_lower(s); if out_ifvi != null { print("[mirbuilder/min:if.compare.varint]"); return norm_if(out_ifvi) } } { local out_ifvi = LowerIfCompareVarIntBox.try_lower(s); if out_ifvi != null { print("[mirbuilder/min:if.compare.varint]"); return me._norm_json_if_needed(out_ifvi) } }
{ local out_ifvv = LowerIfCompareVarVarBox.try_lower(s); if out_ifvv != null { print("[mirbuilder/min:if.compare.varvar]"); return norm_if(out_ifvv) } } { local out_ifvv = LowerIfCompareVarVarBox.try_lower(s); if out_ifvv != null { print("[mirbuilder/min:if.compare.varvar]"); return me._norm_json_if_needed(out_ifvv) } }
{ local out_if = LowerIfCompareBox.try_lower(s); if out_if != null { print("[mirbuilder/min:if.compare.intint]"); return norm_if(out_if) } } { local out_if = LowerIfCompareBox.try_lower(s); if out_if != null { print("[mirbuilder/min:if.compare.intint]"); return me._norm_json_if_needed(out_if) } }
{ local out2 = LowerReturnIntBox.try_lower(s); if out2 != null { print("[mirbuilder/min:return.int]"); return norm_if(out2) } } { local out2 = LowerReturnIntBox.try_lower(s); if out2 != null { print("[mirbuilder/min:return.int]"); return me._norm_json_if_needed(out2) } }
print("[mirbuilder/min/unsupported]") print("[mirbuilder/min/unsupported]")
return null return null
} }

View File

@ -2,7 +2,7 @@
Responsibility Responsibility
- Convert StageB Program(JSON v0) into MIR(JSON v0) for VM/LLVM lines. - Convert StageB Program(JSON v0) into MIR(JSON v0) for VM/LLVM lines.
- Phase 20.34 starts with a delegate implementation to Runner and progressively internalizes ops. - Keep the boundary/contract stable and FailFast; no silent fallback to stub MIR.
Interface (stable) Interface (stable)
- `emit_from_program_json_v0(program_json: String, opts: Map|Null) -> String|Null` - `emit_from_program_json_v0(program_json: String, opts: Map|Null) -> String|Null`
@ -11,12 +11,24 @@ Interface (stable)
Tags (FailFast, stable) Tags (FailFast, stable)
- `[mirbuilder/input/null]` — input is null - `[mirbuilder/input/null]` — input is null
- `[mirbuilder/input/invalid]` — header missing (version/kind) - `[mirbuilder/input/invalid]` — header missing (version/kind)
- `[mirbuilder/delegate]` — delegate path selected (Runner `--program-json-to-mir`) - `[mirbuilder/internal/unsupported] ...` — Program(JSON) shape not yet supported by internal lowers
- `[builder/selfhost-first:unsupported:defs_only]` — only defs を lowering できる状態main なし)のため中止
- `[builder/selfhost-first:unsupported:no_match]` — internal lowers / defs のどちらにもマッチせず中止
- `[builder/funcs:fail:no-main]` — inject_funcs が main を含まない MIR に defs を差し込もうとしたため拒否(`HAKO_MIR_BUILDER_REQUIRE_MAIN=1` 時)
- `[mirbuilder/delegate]` — delegate path selectedRunner/extern provider 経由)
- `[mirbuilder/delegate/missing]` — delegate/provider not wired yet - `[mirbuilder/delegate/missing]` — delegate/provider not wired yet
Toggles (default OFF) Toggles
- `HAKO_MIR_BUILDER_DELEGATE=1`: Use Runner `--program-json-to-mir` as a temporary provider - `HAKO_MIR_BUILDER_INTERNAL=0/1` — internal lowers gate既定=1
- `HAKO_MIR_BUILDER_REGISTRY=0/1` — pattern registry gate既定=1
- `HAKO_MIR_BUILDER_DELEGATE=1` — use Runner/extern provider (`env.mirbuilder.emit`) 経由で Program→MIR
- `HAKO_MIR_BUILDER_FUNCS=1` — enable defs lowering via `FuncLoweringBox.lower_func_defs`
- `HAKO_MIR_BUILDER_METHODIZE=1` — enable call→mir_call(Method) rewrite after MIR 生成
- `HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE=1` — apply JsonFrag normalizer to selfhost/provider output
- `HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG=1` — devonly: minimal loop MIR を強制生成(テスト用)
- `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` — inject_funcs で `"name":"main"` を持たない MIR に defs を追加するのを禁止(既定=0
Notes Notes
- BoxFirst policy: boundary/contract first, then implementation. Keep tags stable; no silent fallback. - BoxFirst policy: define the interface and tags first, then evolve implementation behind the same contract.
- Large payloads: implementation may stream/json-scan later; initial version is stringbased. - Large payloads: implementation is currently string/JsonFragbased; later phases may stream or segment JSON, but I/F は維持する。
- Phase 25.1b では `FuncLoweringBox` との連携を拡張し、「main + defs」構造を前提とした multifunction MIR の土台を整えるdefsonly モジュールは FailFast

View File

@ -0,0 +1,344 @@
// FuncBodyBasicLowerBox — reuse minimal lowers for defs (Local/If/Return only)
// Scope: functions composed of Local / Assign / If / Return statements (no Loop)
// Policy: delegate to existing pattern lowers (ReturnInt, IfCompare, etc.) and
// rebind the resulting MIR JSON to the target box/method name + params.
using selfhost.shared.json.utils.json_frag as JsonFragBox
using "hako.mir.builder.internal.lower_return_int" as LowerReturnIntBox
using "hako.mir.builder.internal.lower_return_binop" as LowerReturnBinOpBox
using "hako.mir.builder.internal.lower_return_binop_varint" as LowerReturnBinOpVarIntBox
using "hako.mir.builder.internal.lower_return_binop_varvar" as LowerReturnBinOpVarVarBox
using "hako.mir.builder.internal.lower_return_method_array_map" as LowerReturnMethodArrayMapBox
using "hako.mir.builder.internal.lower_if_compare" as LowerIfCompareBox
using "hako.mir.builder.internal.lower_if_compare_fold_binints" as LowerIfCompareFoldBinIntsBox
using "hako.mir.builder.internal.lower_if_compare_fold_varint" as LowerIfCompareFoldVarIntBox
using "hako.mir.builder.internal.lower_if_compare_varint" as LowerIfCompareVarIntBox
using "hako.mir.builder.internal.lower_if_compare_varvar" as LowerIfCompareVarVarBox
static box FuncBodyBasicLowerBox {
lower(func_name, box_name, params_arr, body_json) {
if body_json == null { return null }
local s = "" + body_json
if !(s.contains("\"version\"")) || !(s.contains("\"kind\"")) { return null }
local lowered = me._try_lower_local_if_return(func_name, box_name, params_arr, s)
if lowered != null { return lowered }
s = me._inline_local_ints(s)
// Try minimal lowers (same order as MirBuilderMinBox, restricted set)
{ local out = LowerReturnMethodArrayMapBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:return.method.arraymap]") } }
{ local out = LowerReturnBinOpVarIntBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:return.binop.varint]") } }
{ local out = LowerReturnBinOpVarVarBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:return.binop.varvar]") } }
{ local out = LowerReturnBinOpBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:return.binop.intint]") } }
{ local out = LowerIfCompareFoldVarIntBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:if.compare.fold.varint]") } }
{ local out = LowerIfCompareFoldBinIntsBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:if.compare.fold.binints]") } }
{ local out = LowerIfCompareVarIntBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:if.compare.varint]") } }
{ local out = LowerIfCompareVarVarBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:if.compare.varvar]") } }
{ local out = LowerIfCompareBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:if.compare.intint]") } }
{ local out = LowerReturnIntBox.try_lower(s); if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:return.int]") } }
if s.contains("\"type\":\"Loop\"") && env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/funcs:unsupported:loop]")
}
return null
}
method _rebind(mir_json, func_name, box_name, params_arr, tag) {
if mir_json == null { return null }
local s = "" + mir_json
local target = me._build_func_name(box_name, func_name, params_arr)
if target == null { return null }
local name_idx = JsonFragBox.index_of_from(s, "\"name\":\"", 0)
if name_idx < 0 { return null }
local name_end = JsonFragBox.index_of_from(s, "\"", name_idx + 8)
if name_end < 0 { return null }
s = s.substring(0, name_idx) + "\"name\":\"" + target + "\"" + s.substring(name_end + 1, s.length())
local params_json = me._build_params_json(params_arr)
local params_idx = JsonFragBox.index_of_from(s, "\"params\":", 0)
if params_idx >= 0 {
local bracket_start = JsonFragBox.index_of_from(s, "[", params_idx)
if bracket_start >= 0 {
local bracket_end = me._find_matching_bracket(s, bracket_start)
if bracket_end >= bracket_start {
s = s.substring(0, params_idx) + "\"params\":" + params_json + s.substring(bracket_end + 1, s.length())
}
}
}
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print(tag + " -> " + target)
}
return s
}
method _build_func_name(box_name, func_name, params_arr) {
if box_name == null || func_name == null { return null }
local arity = 0
if params_arr != null { arity = JsonFragBox._str_to_int("" + params_arr.length()) }
local suffix = "/" + arity
return ("" + box_name) + "." + ("" + func_name) + suffix
}
method _build_params_json(params_arr) {
if params_arr == null { return "[]" }
local params_json = "["
local i = 0
local n = params_arr.length()
loop(i < n) {
if i > 0 { params_json = params_json + "," }
params_json = params_json + "\\\"" + ("" + params_arr.get(i)) + "\\\""
i = i + 1
}
params_json = params_json + "]"
return params_json
}
method _find_matching_bracket(text, start_idx) {
local s = "" + text
local n = s.length()
local depth = 0
local in_str = 0
local esc = 0
local i = start_idx
loop(i < n) {
local ch = s.substring(i, i + 1)
if in_str == 1 {
if esc == 1 { esc = 0 i = i + 1 continue }
if ch == "\\" { esc = 1 i = i + 1 continue }
if ch == "\"" { in_str = 0 i = i + 1 continue }
i = i + 1
continue
}
if ch == "\"" { in_str = 1 i = i + 1 continue }
if ch == "[" { depth = depth + 1 i = i + 1 continue }
if ch == "]" {
depth = depth - 1
if depth == 0 { return i }
i = i + 1
continue
}
i = i + 1
}
return -1
}
method _inline_local_ints(body_json) {
local s = "" + body_json
local locals = new ArrayBox()
local search = 0
loop(search < s.length()) {
local loc_idx = JsonFragBox.index_of_from(s, "\"type\":\"Local\"", search)
if loc_idx < 0 { break }
local name_idx = JsonFragBox.index_of_from(s, "\"name\":", loc_idx)
if name_idx < 0 { search = loc_idx + 1 continue }
local name = JsonFragBox.read_string_after(s, name_idx + 7)
if name == null || name == "" { search = loc_idx + 1 continue }
local expr_idx = JsonFragBox.index_of_from(s, "\"expr\":{", loc_idx)
if expr_idx < 0 { search = loc_idx + 1 continue }
local type_idx = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", expr_idx)
if type_idx < 0 { search = loc_idx + 1 continue }
local val_idx = JsonFragBox.index_of_from(s, "\"value\":", type_idx)
if val_idx < 0 { search = loc_idx + 1 continue }
local val = JsonFragBox.read_int_after(s, val_idx + 8)
if val == null { search = loc_idx + 1 continue }
local info = new MapBox()
info.set("name", name)
info.set("value", JsonFragBox._str_to_int("" + val))
locals.push(info)
search = loc_idx + 1
}
local i = 0
local n = locals.length()
loop(i < n) {
local info = locals.get(i)
local lname = "" + info.get("name")
local lval = "" + info.get("value")
local pattern = "{\"type\":\"Var\",\"name\":\"" + lname + "\"}"
local replacement = "{\"type\":\"Int\",\"value\":" + lval + "}"
loop(true) {
local idx = JsonFragBox.index_of_from(s, pattern, 0)
if idx < 0 { break }
s = s.substring(0, idx) + replacement + s.substring(idx + pattern.length(), s.length())
}
i = i + 1
}
return s
}
method _try_lower_local_if_return(func_name, box_name, params_arr, body_json) {
local local_info = me._extract_first_local(body_json)
if local_info == null { return null }
local if_idx = JsonFragBox.index_of_from(body_json, "\"type\":\"If\"", 0)
if if_idx < 0 { return null }
local cond = me._extract_compare(body_json, if_idx)
if cond == null { return null }
if cond.get("lhs_kind") != "var" || cond.get("lhs_name") != local_info.get("name") { return null }
if cond.get("rhs_kind") != "int" { return null }
local then_ret = me._extract_then_return(body_json, if_idx)
if then_ret == null { return null }
local tail_ret = me._extract_tail_return(body_json, if_idx)
if tail_ret == null { return null }
return me._emit_local_if(func_name, box_name, params_arr, local_info, cond, then_ret, tail_ret)
}
method _extract_first_local(body_json) {
local idx = JsonFragBox.index_of_from(body_json, "\"type\":\"Local\"", 0)
if idx < 0 { return null }
local name_idx = JsonFragBox.index_of_from(body_json, "\"name\":", idx)
if name_idx < 0 { return null }
local name = JsonFragBox.read_string_after(body_json, name_idx + 7)
if name == null || name == "" { return null }
local expr_idx = JsonFragBox.index_of_from(body_json, "\"expr\":{", idx)
if expr_idx < 0 { return null }
local type_idx = JsonFragBox.index_of_from(body_json, "\"type\":\"Int\"", expr_idx)
if type_idx < 0 { return null }
local val_idx = JsonFragBox.index_of_from(body_json, "\"value\":", type_idx)
if val_idx < 0 { return null }
local val = JsonFragBox.read_int_after(body_json, val_idx + 8)
if val == null { return null }
local info = new MapBox()
info.set("name", name)
info.set("value", JsonFragBox._str_to_int("" + val))
return info
}
method _extract_compare(body_json, if_idx) {
local cond_idx = JsonFragBox.index_of_from(body_json, "\"type\":\"Compare\"", if_idx)
if cond_idx < 0 { return null }
local op_idx = JsonFragBox.index_of_from(body_json, "\"op\":", cond_idx)
if op_idx < 0 { return null }
local op = JsonFragBox.read_string_after(body_json, op_idx + 5)
if op == null { return null }
if !(op == "<" || op == ">" || op == "<=" || op == ">=" || op == "==" || op == "!=") { return null }
local lhs_info = me._extract_operand(body_json, "\"lhs\":{", cond_idx)
local rhs_info = me._extract_operand(body_json, "\"rhs\":{", cond_idx)
if lhs_info == null || rhs_info == null { return null }
local info = new MapBox()
info.set("op", op)
info.set("lhs_kind", lhs_info.get("kind"))
info.set("lhs_name", lhs_info.get("name"))
info.set("lhs_value", lhs_info.get("value"))
info.set("rhs_kind", rhs_info.get("kind"))
info.set("rhs_name", rhs_info.get("name"))
info.set("rhs_value", rhs_info.get("value"))
return info
}
method _extract_operand(body_json, key, cond_idx) {
local idx = JsonFragBox.index_of_from(body_json, key, cond_idx)
if idx < 0 { return null }
local kind = null
local name = null
local value = null
local type_var = JsonFragBox.index_of_from(body_json, "\"type\":\"Var\"", idx)
local type_int = JsonFragBox.index_of_from(body_json, "\"type\":\"Int\"", idx)
if type_var >= 0 && (type_int < 0 || type_var < type_int) {
kind = "var"
local name_idx = JsonFragBox.index_of_from(body_json, "\"name\":", type_var)
if name_idx < 0 { return null }
name = JsonFragBox.read_string_after(body_json, name_idx + 7)
if name == null { return null }
} else if type_int >= 0 {
kind = "int"
local val_idx = JsonFragBox.index_of_from(body_json, "\"value\":", type_int)
if val_idx < 0 { return null }
value = JsonFragBox.read_int_after(body_json, val_idx + 8)
if value == null { return null }
value = JsonFragBox._str_to_int("" + value)
} else {
return null
}
local info = new MapBox()
info.set("kind", kind)
info.set("name", name)
info.set("value", value)
return info
}
method _extract_then_return(body_json, if_idx) {
local then_idx = JsonFragBox.index_of_from(body_json, "\"then\":", if_idx)
if then_idx < 0 { return null }
local ret_idx = JsonFragBox.index_of_from(body_json, "\"type\":\"Return\"", then_idx)
if ret_idx < 0 { return null }
return me._extract_return_expr(body_json, ret_idx)
}
method _extract_tail_return(body_json, if_idx) {
local ret_idx = JsonFragBox.index_of_from(body_json, "\"type\":\"Return\"", if_idx + 1)
if ret_idx < 0 { return null }
local ret_next = JsonFragBox.index_of_from(body_json, "\"type\":\"Return\"", ret_idx + 1)
if ret_next >= 0 { ret_idx = ret_next }
return me._extract_return_expr(body_json, ret_idx)
}
method _extract_return_expr(body_json, ret_idx) {
local var_idx = JsonFragBox.index_of_from(body_json, "\"type\":\"Var\"", ret_idx)
local int_idx = JsonFragBox.index_of_from(body_json, "\"type\":\"Int\"", ret_idx)
if var_idx >= 0 && (int_idx < 0 || var_idx < int_idx) {
local name_idx = JsonFragBox.index_of_from(body_json, "\"name\":", var_idx)
if name_idx < 0 { return null }
local name = JsonFragBox.read_string_after(body_json, name_idx + 7)
if name == null { return null }
local info = new MapBox()
info.set("kind", "var")
info.set("name", name)
return info
}
if int_idx >= 0 {
local val_idx = JsonFragBox.index_of_from(body_json, "\"value\":", int_idx)
if val_idx < 0 { return null }
local val = JsonFragBox.read_int_after(body_json, val_idx + 8)
if val == null { return null }
local info = new MapBox()
info.set("kind", "int")
info.set("value", JsonFragBox._str_to_int("" + val))
return info
}
return null
}
method _emit_local_if(func_name, box_name, params_arr, local_info, cond, then_ret, tail_ret) {
local target = me._build_func_name(box_name, func_name, params_arr)
local params_json = me._build_params_json(params_arr)
local local_val = local_info.get("value")
local rhs_val = cond.get("rhs_value")
local op = "" + cond.get("op")
local then_instr = ""
if then_ret.get("kind") == "var" && then_ret.get("name") == local_info.get("name") {
then_instr = "{\"op\":\"ret\",\"value\":1}"
} else if then_ret.get("kind") == "int" {
then_instr = "{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":" + then_ret.get("value") + "}},{\"op\":\"ret\",\"value\":4}"
} else {
return null
}
local else_instr = ""
if tail_ret.get("kind") == "var" && tail_ret.get("name") == local_info.get("name") {
else_instr = "{\"op\":\"ret\",\"value\":1}"
} else if tail_ret.get("kind") == "int" {
local else_reg = 4
if then_ret.get("kind") == "int" { else_reg = 5 }
else_instr = "{\"op\":\"const\",\"dst\":" + else_reg + ",\"value\":{\"type\":\"i64\",\"value\":" + tail_ret.get("value") + "}},{\"op\":\"ret\",\"value\":" + else_reg + "}"
} else {
return null
}
local blocks = "{\"id\":0,\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"i64\",\"value\":" + local_val + "}}," +
"{\"op\":\"const\",\"dst\":2,\"value\":{\"type\":\"i64\",\"value\":" + rhs_val + "}}," +
"{\"op\":\"compare\",\"operation\":\"" + op + "\",\"lhs\":1,\"rhs\":2,\"dst\":3}," +
"{\"op\":\"branch\",\"cond\":3,\"then\":1,\"else\":2}]}," +
"{\"id\":1,\"instructions\":[" + then_instr + "]}," +
"{\"id\":2,\"instructions\":[" + else_instr + "]}"
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[funcs/basic:local_if] -> " + target)
}
return "{\"functions\":[{\"name\":\"" + target + "\",\"params\":" + params_json + ",\"locals\":[],\"blocks\":[" + blocks + "]}]}"
}
}

View File

@ -5,6 +5,7 @@
// Output: Additional MIR functions + resolved Call targets // Output: Additional MIR functions + resolved Call targets
using selfhost.shared.json.utils.json_frag as JsonFragBox using selfhost.shared.json.utils.json_frag as JsonFragBox
using lang.mir.builder.func_body.basic_lower_box as FuncBodyBasicLowerBox
static box FuncLoweringBox { static box FuncLoweringBox {
// Lower function definitions to MIR // Lower function definitions to MIR
@ -14,6 +15,11 @@ static box FuncLoweringBox {
local s = "" + program_json local s = "" + program_json
local func_defs_mir = "" local func_defs_mir = ""
local trace_funcs = 0
{
local trace_env = env.get("HAKO_SELFHOST_TRACE")
if trace_env != null && ("" + trace_env) == "1" { trace_funcs = 1 }
}
// Check for "defs" key in Program JSON // Check for "defs" key in Program JSON
local defs_idx = JsonFragBox.index_of_from(s, "\"defs\":", 0) local defs_idx = JsonFragBox.index_of_from(s, "\"defs\":", 0)
@ -201,6 +207,8 @@ static box FuncLoweringBox {
local mir_func = me._lower_func_body(func_name, box_name, params_arr, body_json, func_map) local mir_func = me._lower_func_body(func_name, box_name, params_arr, body_json, func_map)
if mir_func != null && mir_func != "" { if mir_func != null && mir_func != "" {
func_jsons.push(mir_func) func_jsons.push(mir_func)
} else if trace_funcs == 1 {
print("[builder/funcs:skip] " + box_name + "." + func_name + " body unsupported")
} }
} }
} }
@ -308,6 +316,11 @@ static box FuncLoweringBox {
method _lower_func_body(func_name, box_name, params_arr, body_json, func_map) { method _lower_func_body(func_name, box_name, params_arr, body_json, func_map) {
local body_str = "" + body_json local body_str = "" + body_json
{
local basic = FuncBodyBasicLowerBox.lower(func_name, box_name, params_arr, body_str)
if basic != null { return basic }
}
// Check for Return statement // Check for Return statement
local ret_idx = JsonFragBox.index_of_from(body_str, "\"type\":\"Return\"", 0) local ret_idx = JsonFragBox.index_of_from(body_str, "\"type\":\"Return\"", 0)
if ret_idx < 0 { return null } if ret_idx < 0 { return null }
@ -645,6 +658,21 @@ static box FuncLoweringBox {
local funcs_idx = JsonFragBox.index_of_from(mir_str, "\"functions\":[", 0) local funcs_idx = JsonFragBox.index_of_from(mir_str, "\"functions\":[", 0)
if funcs_idx < 0 { return mir_json } if funcs_idx < 0 { return mir_json }
// Optional: require presence of a main function (guarded by env)
{
local req = env.get("HAKO_MIR_BUILDER_REQUIRE_MAIN")
if req != null && ("" + req) == "1" {
// Minimal check: look for a function with name "main"
local main_idx = JsonFragBox.index_of_from(mir_str, "\"name\":\"main\"", funcs_idx)
if main_idx < 0 {
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/funcs:fail:no-main]")
}
return mir_json
}
}
}
// Find first function's closing } // Find first function's closing }
local first_func_start = funcs_idx + 13 // skip "functions":[ local first_func_start = funcs_idx + 13 // skip "functions":[
local brace_depth = 0 local brace_depth = 0

View File

@ -12,8 +12,8 @@ static box BuilderConfigBox {
} }
// Trace controls // Trace controls
trace_enabled() { return self._is_on("HAKO_MIR_BUILDER_TRACE") } trace_enabled() { return me._is_on("HAKO_MIR_BUILDER_TRACE") }
debug_enabled() { return self._is_on("HAKO_MIR_BUILDER_DEBUG") || self._is_on("NYASH_CLI_VERBOSE") } debug_enabled() { return me._is_on("HAKO_MIR_BUILDER_DEBUG") || me._is_on("NYASH_CLI_VERBOSE") }
// Registry and internal lowers // Registry and internal lowers
internal_on() { internal_on() {
@ -29,10 +29,10 @@ static box BuilderConfigBox {
registry_only() { return env.get("HAKO_MIR_BUILDER_REGISTRY_ONLY") } registry_only() { return env.get("HAKO_MIR_BUILDER_REGISTRY_ONLY") }
// Loop/JsonFrag related // Loop/JsonFrag related
loop_jsonfrag_on() { return self._is_on("HAKO_MIR_BUILDER_LOOP_JSONFRAG") } loop_jsonfrag_on() { return me._is_on("HAKO_MIR_BUILDER_LOOP_JSONFRAG") }
loop_force_jsonfrag_on() { return self._is_on("HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG") } loop_force_jsonfrag_on() { return me._is_on("HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG") }
jsonfrag_normalize_on() { return self._is_on("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE") } jsonfrag_normalize_on() { return me._is_on("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE") }
skip_loops_on() { return self._is_on("HAKO_MIR_BUILDER_SKIP_LOOPS") } skip_loops_on() { return me._is_on("HAKO_MIR_BUILDER_SKIP_LOOPS") }
// Return-mode for loop adapter: "string" (default) | "map" // Return-mode for loop adapter: "string" (default) | "map"
loop_adapter_return_mode() { loop_adapter_return_mode() {

View File

@ -47,8 +47,8 @@ static box LoopOptsBox {
if BuilderConfigBox.jsonfrag_normalize_on() == 1 { mir = JsonFragNormalizerBox.normalize_all(mir) } if BuilderConfigBox.jsonfrag_normalize_on() == 1 { mir = JsonFragNormalizerBox.normalize_all(mir) }
local mode = BuilderConfigBox.loop_adapter_return_mode() local mode = BuilderConfigBox.loop_adapter_return_mode()
if mode == "map" { if mode == "map" {
local m = self.new_map() local m = me.new_map()
m = self.put(m, "mir", mir) m = me.put(m, "mir", mir)
return m return m
} }
return mir return mir
@ -74,8 +74,8 @@ static box LoopOptsBox {
// Return mode: default string, optionally wrap into Map // Return mode: default string, optionally wrap into Map
local mode = BuilderConfigBox.loop_adapter_return_mode() local mode = BuilderConfigBox.loop_adapter_return_mode()
if mode == "map" { if mode == "map" {
local m = self.new_map() local m = me.new_map()
m = self.put(m, "mir", mir) m = me.put(m, "mir", mir)
return m return m
} }
return mir return mir

View File

@ -20,26 +20,26 @@ static box LowerLoopCountParamBox {
local varname = LoopScanBox.find_loop_var_name(s, k_cmp) local varname = LoopScanBox.find_loop_var_name(s, k_cmp)
if varname == null { return null } if varname == null { return null }
// Local <varname> = (Int init | Var initName) // Local <varname> = (Int start_value | Var startName)
local k_local_i = JsonFragBox.index_of_from(s, "\"type\":\"Local\"", 0) local k_local_i = JsonFragBox.index_of_from(s, "\"type\":\"Local\"", 0)
if k_local_i < 0 { return null } if k_local_i < 0 { return null }
if JsonFragBox.index_of_from(s, "\"name\":\"" + varname + "\"", k_local_i) < 0 { return null } if JsonFragBox.index_of_from(s, "\"name\":\"" + varname + "\"", k_local_i) < 0 { return null }
local init = null local start_value = null
{ {
local k_init_int = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", k_local_i) local k_init_int = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", k_local_i)
local k_loop_next = JsonFragBox.index_of_from(s, "\"type\":\"Loop\"", k_local_i) local k_loop_next = JsonFragBox.index_of_from(s, "\"type\":\"Loop\"", k_local_i)
if k_init_int >= 0 && (k_loop_next < 0 || k_init_int < k_loop_next) { if k_init_int >= 0 && (k_loop_next < 0 || k_init_int < k_loop_next) {
local kv_i = JsonFragBox.index_of_from(s, "\"value\":", k_init_int); if kv_i >= 0 { init = JsonFragBox.read_int_after(s, kv_i + 8) } local kv_i = JsonFragBox.index_of_from(s, "\"value\":", k_init_int); if kv_i >= 0 { start_value = JsonFragBox.read_int_after(s, kv_i + 8) }
} else { } else {
local k_init_var = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", k_local_i) local k_init_var = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", k_local_i)
if k_init_var >= 0 && (k_loop_next < 0 || k_init_var < k_loop_next) { if k_init_var >= 0 && (k_loop_next < 0 || k_init_var < k_loop_next) {
local kn_i = JsonFragBox.index_of_from(s, "\"name\":\"", k_init_var); if kn_i < 0 { return null } local kn_i = JsonFragBox.index_of_from(s, "\"name\":\"", k_init_var); if kn_i < 0 { return null }
local vname = JsonFragBox.read_string_after(s, kn_i) local vname = JsonFragBox.read_string_after(s, kn_i)
if vname != null { init = PatternUtilBox.find_local_int_before(s, vname, k_local_i) } if vname != null { start_value = PatternUtilBox.find_local_int_before(s, vname, k_local_i) }
} }
} }
} }
if init == null { return null } if start_value == null { return null }
// Loop Compare normalize: accept < / <= / > / >= with Var(varname) on either side // Loop Compare normalize: accept < / <= / > / >= with Var(varname) on either side
// op: accept '<'/'<=' with i on lhs; '>'/'>=' with i on lhs (descending); swapped '>'/'>=' with i on rhs (ascending) // op: accept '<'/'<=' with i on lhs; '>'/'>=' with i on lhs (descending); swapped '>'/'>=' with i on rhs (ascending)
local k_op = JsonFragBox.index_of_from(s, "\"op\":", k_cmp); if k_op < 0 { return null } local k_op = JsonFragBox.index_of_from(s, "\"op\":", k_cmp); if k_op < 0 { return null }
@ -140,7 +140,7 @@ static box LowerLoopCountParamBox {
// Adapter集中JsonFrag化の踏み台 // Adapter集中JsonFrag化の踏み台
local opts = LoopOptsBox.new_map() local opts = LoopOptsBox.new_map()
opts = LoopOptsBox.put(opts, "mode", "count") opts = LoopOptsBox.put(opts, "mode", "count")
opts = LoopOptsBox.put(opts, "init", init) opts = LoopOptsBox.put(opts, "init", start_value)
opts = LoopOptsBox.put(opts, "limit", limit) opts = LoopOptsBox.put(opts, "limit", limit)
opts = LoopOptsBox.put(opts, "step", step) opts = LoopOptsBox.put(opts, "step", step)
opts = LoopOptsBox.put(opts, "cmp", cmp) opts = LoopOptsBox.put(opts, "cmp", cmp)

View File

@ -8,6 +8,7 @@ builder.func_lowering = "builder/func_lowering.hako"
builder.MirBuilderBox = "builder/MirBuilderBox.hako" builder.MirBuilderBox = "builder/MirBuilderBox.hako"
builder.MirBuilderMinBox = "builder/MirBuilderMinBox.hako" builder.MirBuilderMinBox = "builder/MirBuilderMinBox.hako"
builder.pattern_registry = "builder/pattern_registry.hako" builder.pattern_registry = "builder/pattern_registry.hako"
builder.func_body.basic_lower_box = "builder/func_body/basic_lower_box.hako"
# MIR builder internal modules # MIR builder internal modules
builder.internal.prog_scan_box = "builder/internal/prog_scan_box.hako" builder.internal.prog_scan_box = "builder/internal/prog_scan_box.hako"

View File

@ -29,13 +29,13 @@ static box HakoCli {
@cmd = "" + cmd_raw @cmd = "" + cmd_raw
if cmd == "run" { if cmd == "run" {
return self.cmd_run(args) return me.cmd_run(args)
} else if cmd == "build" { } else if cmd == "build" {
return self.cmd_build(args) return me.cmd_build(args)
} else if cmd == "emit" { } else if cmd == "emit" {
return self.cmd_emit(args) return me.cmd_emit(args)
} else if cmd == "check" { } else if cmd == "check" {
return self.cmd_check(args) return me.cmd_check(args)
} }
print("[hakorune] unknown command: " + cmd) print("[hakorune] unknown command: " + cmd)
@ -63,7 +63,7 @@ static box HakoCli {
@sub_raw = args.get(1) @sub_raw = args.get(1)
@sub = "" + sub_raw @sub = "" + sub_raw
if sub == "exe" { if sub == "exe" {
return self.cmd_build_exe(args) return me.cmd_build_exe(args)
} }
print("[hakorune] build: unknown subcommand: " + sub) print("[hakorune] build: unknown subcommand: " + sub)
@ -86,7 +86,7 @@ static box HakoCli {
@source_path = null @source_path = null
@i = 2 @i = 2
while i < argc { loop(i < argc) {
@arg_raw = args.get(i) @arg_raw = args.get(i)
@arg = "" + arg_raw @arg = "" + arg_raw
if arg == "-o" || arg == "--out" { if arg == "-o" || arg == "--out" {
@ -116,66 +116,53 @@ static box HakoCli {
return 91 return 91
} }
// Read Hako source local tag = "[hakorune] build exe"
@fb = new FileBox() local src = me._read_file(tag, source_path)
@ok = fb.open(source_path, "r") if src == null { return 91 }
if ok != 1 {
print("[hakorune] build exe: failed to open source " + source_path)
return 91
}
@src = fb.read()
fb.close()
if src == null || src == "" {
print("[hakorune] build exe: source is empty")
return 91
}
// .hako → Program(JSON v0) local prog = BuildBox.emit_program_json_v0(src, null)
@prog = BuildBox.emit_program_json_v0(src, null)
if prog == null { if prog == null {
print("[hakorune] build exe: BuildBox returned null") print("[hakorune] build exe: BuildBox returned null")
return 91 return 91
} }
@ps = "" + prog local ps = "" + prog
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 { if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
print("[hakorune] build exe: unexpected Program(JSON) output (missing version/kind)") print("[hakorune] build exe: unexpected Program(JSON) output (missing version/kind)")
return 91 return 91
} }
// Program(JSON v0) → MIR(JSON) local mir = MirBuilderBox.emit_from_program_json_v0(ps, null)
@mir = MirBuilderBox.emit_from_program_json_v0(ps, null)
if mir == null { if mir == null {
print("[hakorune] build exe: MirBuilderBox returned null") print("[hakorune] build exe: MirBuilderBox returned null")
return 91 return 91
} }
@ms = "" + mir local ms = "" + mir
// MIR(JSON) → object via env.codegen.emit_object local emit_args = new ArrayBox()
@emit_args = new ArrayBox()
emit_args.push(ms) emit_args.push(ms)
@obj = hostbridge.extern_invoke("env.codegen", "emit_object", emit_args) local obj = hostbridge.extern_invoke("env.codegen", "emit_object", emit_args)
if obj == null || ("" + obj) == "" { if obj == null || ("" + obj) == "" {
print("[hakorune] build exe: env.codegen.emit_object failed") print("[hakorune] build exe: env.codegen.emit_object failed")
return 91 return 91
} }
@obj_path = "" + obj local obj_path = "" + obj
// object → EXE via env.codegen.link_object local link_args = new ArrayBox()
@link_args = new ArrayBox()
link_args.push(obj_path) link_args.push(obj_path)
if out_path != null && out_path != "" { if out_path != null && out_path != "" {
link_args.push(out_path) link_args.push(out_path)
} }
@exe = hostbridge.extern_invoke("env.codegen", "link_object", link_args) local exe = hostbridge.extern_invoke("env.codegen", "link_object", link_args)
if exe == null || ("" + exe) == "" { if exe == null || ("" + exe) == "" {
print("[hakorune] build exe: env.codegen.link_object failed") print("[hakorune] build exe: env.codegen.link_object failed")
return 91 return 91
} }
@exe_path = "" + exe local exe_path = "" + exe
if quiet != 1 { if quiet != 1 {
print("[hakorune] build exe: " + exe_path) print("[hakorune] build exe: " + exe_path)
} }
return 0 return 0
} }
@ -192,9 +179,9 @@ static box HakoCli {
@sub_raw = args.get(1) @sub_raw = args.get(1)
@sub = "" + sub_raw @sub = "" + sub_raw
if sub == "mir-json" { if sub == "mir-json" {
return self.cmd_emit_mir_json(args) return me.cmd_emit_mir_json(args)
} else if sub == "program-json" { } else if sub == "program-json" {
return self.cmd_emit_program_json(args) return me.cmd_emit_program_json(args)
} }
print("[hakorune] emit: unknown subcommand: " + sub) print("[hakorune] emit: unknown subcommand: " + sub)
@ -212,20 +199,20 @@ static box HakoCli {
return 92 return 92
} }
@path = null local path = null
@out_path = null local out_path = null
@quiet = 0 local quiet = 0
@i = 2 local i = 2
while i < argc { loop(i < argc) {
@arg_raw = args.get(i) local arg_raw = args.get(i)
@arg = "" + arg_raw local arg = "" + arg_raw
if arg == "-o" || arg == "--out" { if arg == "-o" || arg == "--out" {
if i + 1 >= argc { if i + 1 >= argc {
print("[hakorune] emit program-json: -o/--out requires a path") print("[hakorune] emit program-json: -o/--out requires a path")
return 92 return 92
} }
@op_raw = args.get(i + 1) local op_raw = args.get(i + 1)
out_path = "" + op_raw out_path = "" + op_raw
i = i + 2 i = i + 2
} else if arg == "--quiet" { } else if arg == "--quiet" {
@ -247,46 +234,26 @@ static box HakoCli {
return 92 return 92
} }
// Read Hako source from file local tag = "[hakorune] emit program-json"
@fb = new FileBox() local src = me._read_file(tag, path)
@ok = fb.open(path, "r") if src == null { return 92 }
if ok != 1 {
print("[hakorune] emit program-json: failed to open " + path)
return 92
}
@src = fb.read()
fb.close()
if src == null || src == "" {
print("[hakorune] emit program-json: source is empty")
return 92
}
// Compile to Program(JSON v0) via BuildBox local prog = BuildBox.emit_program_json_v0(src, null)
@prog = BuildBox.emit_program_json_v0(src, null)
if prog == null { if prog == null {
print("[hakorune] emit program-json: BuildBox returned null") print("[hakorune] emit program-json: BuildBox returned null")
return 92 return 92
} }
// Minimal validation: require Program(version 0) local ps = "" + prog
@ps = "" + prog
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 { if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
print("[hakorune] emit program-json: unexpected output (missing version/kind)") print("[hakorune] emit program-json: unexpected output (missing version/kind)")
return 92 return 92
} }
if out_path != null && out_path != "" { if out_path != null && out_path != "" {
@out_fb = new FileBox() if me._write_file(tag, out_path, ps, quiet) != 1 {
@ok2 = out_fb.open(out_path, "w")
if ok2 != 1 {
print("[hakorune] emit program-json: failed to open output " + out_path)
return 92 return 92
} }
out_fb.write(ps)
out_fb.close()
if quiet != 1 {
print("[hakorune] emit program-json: written " + out_path)
}
} else { } else {
print(ps) print(ps)
} }
@ -298,23 +265,23 @@ static box HakoCli {
// - Program(JSON v0) から MIR(JSON) を生成する経路に加えて、 // - Program(JSON v0) から MIR(JSON) を生成する経路に加えて、
// .hako ソースから直接 Program(JSON v0)→MIR(JSON) まで進める経路もサポートする。 // .hako ソースから直接 Program(JSON v0)→MIR(JSON) まで進める経路もサポートする。
method cmd_emit_mir_json(args){ method cmd_emit_mir_json(args){
@argc = 0 local argc = 0
if args { argc = args.size() } if args { argc = args.size() }
@prog_path = null local prog_path = null
@source_path = null local source_path = null
@out_path = null local out_path = null
@quiet = 0 local quiet = 0
@i = 2 local i = 2
while i < argc { loop(i < argc) {
@arg_raw = args.get(i) local arg_raw = args.get(i)
@arg = "" + arg_raw local arg = "" + arg_raw
if arg == "--from-program-json" { if arg == "--from-program-json" {
if i + 1 >= argc { if i + 1 >= argc {
print("[hakorune] emit mir-json: --from-program-json requires a path") print("[hakorune] emit mir-json: --from-program-json requires a path")
return 92 return 92
} }
@p_raw = args.get(i + 1) local p_raw = args.get(i + 1)
prog_path = "" + p_raw prog_path = "" + p_raw
i = i + 2 i = i + 2
} else if arg == "-o" || arg == "--out" { } else if arg == "-o" || arg == "--out" {
@ -322,14 +289,13 @@ static box HakoCli {
print("[hakorune] emit mir-json: -o/--out requires a path") print("[hakorune] emit mir-json: -o/--out requires a path")
return 92 return 92
} }
@op_raw = args.get(i + 1) local op_raw = args.get(i + 1)
out_path = "" + op_raw out_path = "" + op_raw
i = i + 2 i = i + 2
} else if arg == "--quiet" { } else if arg == "--quiet" {
quiet = 1 quiet = 1
i = i + 1 i = i + 1
} else { } else {
// Interpret as <source.hako> when not already set
if source_path == null { if source_path == null {
source_path = arg source_path = arg
i = i + 1 i = i + 1
@ -340,54 +306,32 @@ static box HakoCli {
} }
} }
// Prevent ambiguous usage
if prog_path != null && prog_path != "" && source_path != null && source_path != "" { if prog_path != null && prog_path != "" && source_path != null && source_path != "" {
print("[hakorune] emit mir-json: specify either --from-program-json or <source.hako>, not both") print("[hakorune] emit mir-json: specify either --from-program-json or <source.hako>, not both")
return 92 return 92
} }
@prog_json = null if (prog_path == null || prog_path == "") && (source_path == null || source_path == "") {
print("[hakorune] emit mir-json: require --from-program-json <file> or <source.hako>")
return 92
}
local tag = "[hakorune] emit mir-json"
local prog_json = null
if prog_path != null && prog_path != "" { if prog_path != null && prog_path != "" {
// Read Program(JSON v0) from file local prog_text = me._read_file(tag, prog_path)
@fb = new FileBox() if prog_text == null { return 92 }
@ok = fb.open(prog_path, "r") prog_json = prog_text
if ok != 1 {
print("[hakorune] emit mir-json: failed to open " + prog_path)
return 92
}
@p = fb.read()
fb.close()
if p == null || p == "" {
print("[hakorune] emit mir-json: program JSON is empty")
return 92
}
prog_json = p
} else { } else {
// Expect .hako source path local src = me._read_file(tag, source_path)
if source_path == null || source_path == "" { if src == null { return 92 }
print("[hakorune] emit mir-json: require --from-program-json <file> or <source.hako>") local prog = BuildBox.emit_program_json_v0(src, null)
return 92
}
@fb2 = new FileBox()
@ok2 = fb2.open(source_path, "r")
if ok2 != 1 {
print("[hakorune] emit mir-json: failed to open source " + source_path)
return 92
}
@src = fb2.read()
fb2.close()
if src == null || src == "" {
print("[hakorune] emit mir-json: source is empty")
return 92
}
// Compile to Program(JSON v0) via BuildBox
@prog = BuildBox.emit_program_json_v0(src, null)
if prog == null { if prog == null {
print("[hakorune] emit mir-json: BuildBox returned null") print("[hakorune] emit mir-json: BuildBox returned null")
return 92 return 92
} }
@ps = "" + prog local ps = "" + prog
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 { if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
print("[hakorune] emit mir-json: unexpected Program(JSON) output (missing version/kind)") print("[hakorune] emit mir-json: unexpected Program(JSON) output (missing version/kind)")
return 92 return 92
@ -395,27 +339,17 @@ static box HakoCli {
prog_json = ps prog_json = ps
} }
// Convert Program(JSON v0) → MIR(JSON) via MirBuilderBox local mir = MirBuilderBox.emit_from_program_json_v0(prog_json, null)
@mir = MirBuilderBox.emit_from_program_json_v0(prog_json, null)
if mir == null { if mir == null {
print("[hakorune] emit mir-json: MirBuilderBox returned null") print("[hakorune] emit mir-json: MirBuilderBox returned null")
return 92 return 92
} }
// Success: emit MIR(JSON) to stdout or file local ms = "" + mir
@ms = "" + mir
if out_path != null && out_path != "" { if out_path != null && out_path != "" {
@out_fb = new FileBox() if me._write_file(tag, out_path, ms, quiet) != 1 {
@ok3 = out_fb.open(out_path, "w")
if ok3 != 1 {
print("[hakorune] emit mir-json: failed to open output " + out_path)
return 92 return 92
} }
out_fb.write(ms)
out_fb.close()
if quiet != 1 {
print("[hakorune] emit mir-json: written " + out_path)
}
} else { } else {
print(ms) print(ms)
} }
@ -423,18 +357,56 @@ static box HakoCli {
return 0 return 0
} }
// hakorune check <entry.hako> // hakorune check <entry.hako>
// - Reserved for future static checks (syntax/using/subset). // - Reserved for future static checks (syntax/using/subset).
method cmd_check(args){ method cmd_check(args){
print("[hakorune] check: not implemented yet") print("[hakorune] check: not implemented yet")
return 93 return 93
} }
// Helpers for file I/O (Phase 25.1a: consolidate StageB friendly patterns)
method _read_file(tag, path){
if path == null || path == "" {
print(tag + ": source path is required")
return null
}
local fb = new FileBox()
local ok = fb.open(path, "r")
if ok != 1 {
print(tag + ": failed to open " + path)
return null
}
local content = fb.read()
fb.close()
if content == null || content == "" {
print(tag + ": source is empty")
return null
}
return "" + content
}
method _write_file(tag, path, content, quiet){
if path == null || path == "" { return 1 }
local fb = new FileBox()
local ok = fb.open(path, "w")
if ok != 1 {
print(tag + ": failed to open output " + path)
return 0
}
fb.write(content)
fb.close()
if quiet != 1 {
print(tag + ": written " + path)
}
return 1
}
} }
static box Main { static box Main {
// Main entrypoint for Stage1 hakorune CLI. // Main entrypoint for Stage1 hakorune CLI.
// args: raw argv array (excluding executable name). // args: raw argv array (excluding executable name).
main(args){ method main(args){
@cli = new HakoCli() @cli = new HakoCli()
return cli.run(args) return cli.run(args)
} }

View File

@ -12,7 +12,7 @@ static box LoopFormBox {
// i = i + 1; // i = i + 1;
// } // }
// Builds LoopForm structure with Header/Latch PHI + Exit PHI (continue/break aware) // Builds LoopForm structure with Header/Latch PHI + Exit PHI (continue/break aware)
loop_counter(limit, skip_value, break_value) { method loop_counter(limit, skip_value, break_value) {
if skip_value == null { skip_value = 2 } if skip_value == null { skip_value = 2 }
if break_value == null { break_value = limit } if break_value == null { break_value = limit }
@ -109,7 +109,7 @@ static box LoopFormBox {
// body: r12 = i + step, jump latch // body: r12 = i + step, jump latch
// latch: jump header (PHI incoming from body) // latch: jump header (PHI incoming from body)
// exit: ret r10 // exit: ret r10
loop_count(limit) { method loop_count(limit) {
// Preheader // Preheader
local pre = new ArrayBox() local pre = new ArrayBox()
pre.push(MirSchemaBox.inst_const(1, 0)) pre.push(MirSchemaBox.inst_const(1, 0))
@ -148,10 +148,10 @@ static box LoopFormBox {
// loop_count_param — counting loop with init/step parameters // loop_count_param — counting loop with init/step parameters
// Returns final i value, starting from init, incremented by step while i < limit // Returns final i value, starting from init, incremented by step while i < limit
loop_count_param(init, limit, step) { method build_loop_count_param(start_value, limit, step) {
// Preheader // Preheader
local pre = new ArrayBox() local pre = new ArrayBox()
pre.push(MirSchemaBox.inst_const(1, init)) pre.push(MirSchemaBox.inst_const(1, start_value))
pre.push(MirSchemaBox.inst_const(2, limit)) pre.push(MirSchemaBox.inst_const(2, limit))
pre.push(MirSchemaBox.inst_const(3, step)) pre.push(MirSchemaBox.inst_const(3, step))
pre.push(MirSchemaBox.inst_jump(1)) pre.push(MirSchemaBox.inst_jump(1))
@ -185,13 +185,13 @@ static box LoopFormBox {
// Extended param variant: allow custom compare op ("Lt"/"Le"/"Gt"/"Ge") via opts // Extended param variant: allow custom compare op ("Lt"/"Le"/"Gt"/"Ge") via opts
// and negative step (handled by passing negative string for step) // and negative step (handled by passing negative string for step)
loop_count_param_ex(init, limit, step, cmp) { method build_loop_count_param_ex(start_value, limit, step, cmp) {
local cmpop = "" + cmp local cmpop = "" + cmp
if cmpop == null || cmpop == "" { cmpop = "Lt" } if cmpop == null || cmpop == "" { cmpop = "Lt" }
// Preheader // Preheader
local pre = new ArrayBox() local pre = new ArrayBox()
pre.push(MirSchemaBox.inst_const(1, init)) pre.push(MirSchemaBox.inst_const(1, start_value))
pre.push(MirSchemaBox.inst_const(2, limit)) pre.push(MirSchemaBox.inst_const(2, limit))
pre.push(MirSchemaBox.inst_const(3, step)) pre.push(MirSchemaBox.inst_const(3, step))
pre.push(MirSchemaBox.inst_jump(1)) pre.push(MirSchemaBox.inst_jump(1))
@ -227,18 +227,18 @@ static box LoopFormBox {
// mode: // mode:
// - "count" : counting loop that returns final i (uses loop_count) // - "count" : counting loop that returns final i (uses loop_count)
// - "sum_bc" : sum with break/continue sentinels (uses loop_counter) // - "sum_bc" : sum with break/continue sentinels (uses loop_counter)
build(mode, limit, skip_value, break_value) { method build(mode, limit, skip_value, break_value) {
local m = "" + mode local m = "" + mode
if m == "count" { if m == "count" {
return me.loop_count(limit) return me.loop_count(limit)
} }
if m == "count_param" { if m == "count_param" {
// Here, skip_value carries init and break_value carries step (temporary param slots) // Here, skip_value carries init and break_value carries step (temporary param slots)
local init = skip_value local start_value = skip_value
local step = break_value local step = break_value
if init == null { init = 0 } if start_value == null { start_value = 0 }
if step == null { step = 1 } if step == null { step = 1 }
return me.loop_count_param(init, limit, step) return me.build_loop_count_param(start_value, limit, step)
} }
if m == "sum_bc" { if m == "sum_bc" {
if skip_value == null { skip_value = 2 } if skip_value == null { skip_value = 2 }
@ -250,20 +250,20 @@ static box LoopFormBox {
} }
// Map-based builder: build2({ mode, init, limit, step, skip, break }) // Map-based builder: build2({ mode, init, limit, step, skip, break })
build2(opts) { method build2(opts) {
if opts == null { return null } if opts == null { return null }
local mode = "" + opts.get("mode") local mode = "" + opts.get("mode")
local init = opts.get("init") local start_value = opts.get("init")
local limit = opts.get("limit") local limit = opts.get("limit")
local step = opts.get("step") local step = opts.get("step")
local skip_v = opts.get("skip") local skip_v = opts.get("skip")
local break_v = opts.get("break") local break_v = opts.get("break")
if mode == "count" { if mode == "count" {
if init == null { init = 0 } if start_value == null { start_value = 0 }
if step == null { step = 1 } if step == null { step = 1 }
local cmp = opts.get("cmp") // optional: "Lt"/"Le"/"Gt"/"Ge" local cmp = opts.get("cmp") // optional: "Lt"/"Le"/"Gt"/"Ge"
if cmp == null { cmp = "Lt" } if cmp == null { cmp = "Lt" }
return me.loop_count_param_ex(init, limit, step, cmp) return me.build_loop_count_param_ex(start_value, limit, step, cmp)
} }
if mode == "sum_bc" { return me.loop_counter(limit, skip_v, break_v) } if mode == "sum_bc" { return me.loop_counter(limit, skip_v, break_v) }
print("[loopform/unsupported-mode] " + mode) print("[loopform/unsupported-mode] " + mode)

View File

@ -131,6 +131,7 @@ path = "lang/src/shared/common/string_helpers.hako"
"lang.compiler.entry.compiler" = "lang/src/compiler/entry/compiler.hako" "lang.compiler.entry.compiler" = "lang/src/compiler/entry/compiler.hako"
"lang.compiler.entry.compiler_stageb" = "lang/src/compiler/entry/compiler_stageb.hako" "lang.compiler.entry.compiler_stageb" = "lang/src/compiler/entry/compiler_stageb.hako"
"lang.compiler.entry.bundle_resolver" = "lang/src/compiler/entry/bundle_resolver.hako" "lang.compiler.entry.bundle_resolver" = "lang/src/compiler/entry/bundle_resolver.hako"
"lang.compiler.entry.using_resolver" = "lang/src/compiler/entry/using_resolver_box.hako"
"lang.compiler.emit.mir_emitter_box" = "lang/src/compiler/emit/mir_emitter_box.hako" "lang.compiler.emit.mir_emitter_box" = "lang/src/compiler/emit/mir_emitter_box.hako"
"lang.compiler.emit.common.json_emit_box" = "lang/src/compiler/emit/common/json_emit_box.hako" "lang.compiler.emit.common.json_emit_box" = "lang/src/compiler/emit/common/json_emit_box.hako"
"lang.compiler.emit.common.mir_emit_box" = "lang/src/compiler/emit/common/mir_emit_box.hako" "lang.compiler.emit.common.mir_emit_box" = "lang/src/compiler/emit/common/mir_emit_box.hako"
@ -148,6 +149,7 @@ path = "lang/src/shared/common/string_helpers.hako"
"lang.mir.builder.MirBuilderBox" = "lang/src/mir/builder/MirBuilderBox.hako" "lang.mir.builder.MirBuilderBox" = "lang/src/mir/builder/MirBuilderBox.hako"
"lang.mir.builder.MirBuilderMinBox" = "lang/src/mir/builder/MirBuilderMinBox.hako" "lang.mir.builder.MirBuilderMinBox" = "lang/src/mir/builder/MirBuilderMinBox.hako"
"lang.mir.builder.pattern_registry" = "lang/src/mir/builder/pattern_registry.hako" "lang.mir.builder.pattern_registry" = "lang/src/mir/builder/pattern_registry.hako"
"lang.mir.builder.func_body.basic_lower_box" = "lang/src/mir/builder/func_body/basic_lower_box.hako"
"lang.mir.builder.internal.prog_scan_box" = "lang/src/mir/builder/internal/prog_scan_box.hako" "lang.mir.builder.internal.prog_scan_box" = "lang/src/mir/builder/internal/prog_scan_box.hako"
"lang.mir.builder.internal.lower_load_store_local_box" = "lang/src/mir/builder/internal/lower_load_store_local_box.hako" "lang.mir.builder.internal.lower_load_store_local_box" = "lang/src/mir/builder/internal/lower_load_store_local_box.hako"
"lang.mir.builder.internal.lower_typeop_cast_box" = "lang/src/mir/builder/internal/lower_typeop_cast_box.hako" "lang.mir.builder.internal.lower_typeop_cast_box" = "lang/src/mir/builder/internal/lower_typeop_cast_box.hako"

View File

@ -106,6 +106,9 @@ pub enum ParseError {
#[error("Invalid statement at line {line}")] #[error("Invalid statement at line {line}")]
InvalidStatement { line: usize }, InvalidStatement { line: usize },
#[error("Unsupported identifier '{name}' at line {line}")]
UnsupportedIdentifier { name: String, line: usize },
#[error("Circular dependency detected between static boxes: {cycle}")] #[error("Circular dependency detected between static boxes: {cycle}")]
CircularDependency { cycle: String }, CircularDependency { cycle: String },
@ -215,6 +218,17 @@ impl NyashParser {
let mut tokenizer = crate::tokenizer::NyashTokenizer::new(pre); let mut tokenizer = crate::tokenizer::NyashTokenizer::new(pre);
let tokens = tokenizer.tokenize()?; let tokens = tokenizer.tokenize()?;
for tok in &tokens {
if let TokenType::IDENTIFIER(name) = &tok.token_type {
if name == "self" {
return Err(ParseError::UnsupportedIdentifier {
name: name.clone(),
line: tok.line,
});
}
}
}
let mut parser = Self::new(tokens); let mut parser = Self::new(tokens);
parser.debug_fuel = fuel; parser.debug_fuel = fuel;
let result = parser.parse(); let result = parser.parse();

View File

@ -86,6 +86,7 @@ pub(super) enum ExprV0 {
Bool { Bool {
value: bool, value: bool,
}, },
Null,
Binary { Binary {
op: String, op: String,
lhs: Box<ExprV0>, lhs: Box<ExprV0>,

View File

@ -65,6 +65,21 @@ impl<'a> VarScope for MapVars<'a> {
self.vars.insert(name.to_string(), dst); self.vars.insert(name.to_string(), dst);
return Ok(Some(dst)); return Ok(Some(dst));
} }
// Phase 25.1a: Treat `hostbridge` as a well-known global for bridge lowering.
// The actual extern dispatch is handled at runtime; here we only need a stable
// placeholder value so that Program(JSON) containing hostbridge.extern_invoke(...)
// can be lowered without "undefined variable" errors.
if name == "hostbridge" {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::Const {
dst,
value: ConstValue::String("hostbridge".into()),
});
}
self.vars.insert(name.to_string(), dst);
return Ok(Some(dst));
}
if name == "me" { if name == "me" {
if env.allow_me_dummy { if env.allow_me_dummy {
let dst = f.next_value_id(); let dst = f.next_value_id();
@ -163,6 +178,16 @@ pub(super) fn lower_expr_with_scope<S: VarScope>(
} }
Ok((dst, cur_bb)) Ok((dst, cur_bb))
} }
ExprV0::Null => {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::Const {
dst,
value: ConstValue::Null,
});
}
Ok((dst, cur_bb))
}
ExprV0::Binary { op, lhs, rhs } => { ExprV0::Binary { op, lhs, rhs } => {
let (l, cur_after_l) = lower_expr_with_scope(env, f, cur_bb, lhs, vars)?; let (l, cur_after_l) = lower_expr_with_scope(env, f, cur_bb, lhs, vars)?;
let (r, cur_after_r) = lower_expr_with_scope(env, f, cur_after_l, rhs, vars)?; let (r, cur_after_r) = lower_expr_with_scope(env, f, cur_after_l, rhs, vars)?;

View File

@ -155,6 +155,34 @@ impl NyashRunner {
crate::runner::modes::common_util::hako::strip_local_decl(&code_final); crate::runner::modes::common_util::hako::strip_local_decl(&code_final);
} }
// Optional: dump merged Hako source after using/prelude merge and Hako normalization.
// Guarded by env; defaultはOFFPhase 25.1a selfhost builder デバッグ用)。
if std::env::var("NYASH_VM_DUMP_MERGED_HAKO")
.ok()
.as_deref()
== Some("1")
{
let default_path = {
let mut tmp = std::env::temp_dir();
tmp.push("nyash_merged_vm.hako");
tmp
};
let path = std::env::var("NYASH_VM_DUMP_MERGED_HAKO_PATH")
.ok()
.filter(|p| !p.is_empty())
.unwrap_or_else(|| default_path.to_string_lossy().into_owned());
if let Err(e) = fs::write(&path, &code_final) {
if trace {
eprintln!("[vm/merged-hako] failed to write {}: {}", path, e);
}
} else if trace
|| crate::config::env::env_bool("NYASH_VM_DUMP_MERGED_HAKO_LOG")
{
eprintln!("[vm/merged-hako] dumped merged code to {}", path);
}
}
if trace && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into()) if trace && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into())
|| std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into())) || std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into()))
{ {

View File

@ -44,21 +44,106 @@ CODE_TMP=$(mktemp --suffix=.hako)
trap 'rm -f "$CODE_TMP" || true' EXIT trap 'rm -f "$CODE_TMP" || true' EXIT
cp "$IN" "$CODE_TMP" cp "$IN" "$CODE_TMP"
# Stage1 using resolver: prepare modules list from nyash.toml
if [ -z "${HAKO_STAGEB_MODULES_LIST:-}" ]; then
HAKO_STAGEB_MODULES_LIST=$(
python3 - "$ROOT" <<'PY' 2>/dev/null
import sys
from pathlib import Path
root = Path(sys.argv[1])
toml_path = root / "nyash.toml"
try:
lines = toml_path.read_text(encoding="utf-8").splitlines()
except Exception:
print("", end="")
raise SystemExit(0)
collect = False
entries = []
for raw in lines:
line = raw.strip()
if not line or line.startswith("#"):
continue
if line.startswith("["):
collect = (line == "[modules]")
continue
if not collect:
continue
if "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip().strip('"')
value = value.split("#", 1)[0].strip()
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
if not key or not value:
continue
entries.append(f"{key}={value}")
print("|||".join(entries), end="")
PY
) || HAKO_STAGEB_MODULES_LIST=""
export HAKO_STAGEB_MODULES_LIST
fi
export HAKO_STAGEB_APPLY_USINGS="${HAKO_STAGEB_APPLY_USINGS:-1}"
# Phase 21.8: Extract using imports and build JSON map for MirBuilder # Phase 21.8: Extract using imports and build JSON map for MirBuilder
# This enables MirBuilder to recognize static box references like MatI64, IntArrayCore # This enables MirBuilder to recognize static box references like MatI64, IntArrayCore, MirBuilderBox.
IMPORTS_JSON="{}" IMPORTS_JSON="{}"
if [ "${NYASH_ENABLE_USING:-1}" = "1" ] || [ "${HAKO_ENABLE_USING:-1}" = "1" ]; then if [ "${NYASH_ENABLE_USING:-1}" = "1" ] || [ "${HAKO_ENABLE_USING:-1}" = "1" ]; then
# Extract "using X as Y" lines and build JSON map {"Y":"Y"} # Delegate parsing to Python for robustness:
# Example: "using nyash.core.numeric.matrix_i64 as MatI64" -> "MatI64":"MatI64" # - Handle `using ns.path.Type as Alias` → Alias
USING_LINES=$(grep -E '^\s*using\s+.*\s+as\s+' "$CODE_TMP" || true) # - Handle alias-less `using ns.path.Type` → Type
if [ -n "$USING_LINES" ]; then # - Ignore path-style using (`"path.hako"`, `./`, `/`, `*.hako`)
IMPORTS_JSON=$(echo "$USING_LINES" | \ IMPORTS_JSON=$(python3 - "$CODE_TMP" <<'PY' 2>/dev/null || echo "{}"
sed -E 's/^\s*using\s+.*\s+as\s+([A-Za-z0-9_]+).*/"\1":"\1"/' | \ import sys, json
paste -sd ',' | \ path = sys.argv[1]
sed 's/^/{/' | sed 's/$/}/') try:
fi text = open(path, "r", encoding="utf-8").read().splitlines()
# Fallback to empty object if extraction failed or produced invalid JSON except Exception:
if [ -z "$IMPORTS_JSON" ] || [ "$IMPORTS_JSON" = "{}" ]; then print("{}", end="")
raise SystemExit(0)
imports = {}
for raw in text:
line = raw.lstrip()
if not line.startswith("using "):
continue
# Strip trailing line comment
if "//" in line:
line = line.split("//", 1)[0]
line = line.strip()
if not line.startswith("using "):
continue
body = line[len("using "):].strip().rstrip(";").strip()
if not body:
continue
alias = None
# Split by " as "
lower = body
idx = lower.find(" as ")
if idx >= 0:
target = body[:idx].strip()
alias = body[idx + 4 :].strip()
else:
target = body
if not target:
continue
# Skip path-style targets
if target.startswith('"') or target.startswith("./") or target.startswith("/") or target.endswith(".hako") or target.endswith(".nyash"):
continue
if alias is None:
# Derive alias from last segment of namespace
alias = target.split(".")[-1].strip()
if not alias:
continue
# Very small ident check (letters/digits/_)
if not all((c.isalnum() or c == "_") for c in alias):
continue
imports[alias] = alias
print(json.dumps(imports), end="")
PY
)
if [ -z "$IMPORTS_JSON" ]; then
IMPORTS_JSON="{}" IMPORTS_JSON="{}"
fi fi
fi fi
@ -220,7 +305,7 @@ fi
# Run Stage-B with temp file (avoid subshell CODE variable expansion) # Run Stage-B with temp file (avoid subshell CODE variable expansion)
PROG_JSON_RAW=$(cd "$ROOT" && \ PROG_JSON_RAW=$(cd "$ROOT" && \
NYASH_JSON_ONLY=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_JSON_ONLY=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \ HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} \ NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} \
"$NYASH_BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$CODE_TMP")" 2>&1) "$NYASH_BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$CODE_TMP")" 2>&1)
@ -260,7 +345,7 @@ set -e
if [ $rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then if [ $rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then
# Stage-B not available - fall back to legacy CLI path directly # Stage-B not available - fall back to legacy CLI path directly
# Skip the intermediate Program(JSON) step and emit MIR directly # Skip the intermediate Program(JSON) step and emit MIR directly
if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \ if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \ HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \ HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \ NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \
@ -276,7 +361,7 @@ fi
# Quick validation for Program(JSON v0) # Quick validation for Program(JSON v0)
if ! printf '%s' "$PROG_JSON_OUT" | grep -q '"kind"\s*:\s*"Program"'; then if ! printf '%s' "$PROG_JSON_OUT" | grep -q '"kind"\s*:\s*"Program"'; then
# Invalid Program JSON - fall back to direct emit # Invalid Program JSON - fall back to direct emit
if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \ if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \ HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \ HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \ NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \
@ -559,6 +644,8 @@ HCODE
NYASH_USE_NY_COMPILER=0 HAKO_USE_NY_COMPILER=0 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_USE_NY_COMPILER=0 HAKO_USE_NY_COMPILER=0 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
NYASH_MACRO_DISABLE=1 HAKO_MACRO_DISABLE=1 \ NYASH_MACRO_DISABLE=1 HAKO_MACRO_DISABLE=1 \
HAKO_BUILDER_PROGRAM_JSON="$prog_json" \ HAKO_BUILDER_PROGRAM_JSON="$prog_json" \
NYASH_VM_DUMP_MERGED_HAKO="${NYASH_VM_DUMP_MERGED_HAKO:-${HAKO_SELFHOST_DUMP_MERGED_HAKO:-0}}" \
NYASH_VM_DUMP_MERGED_HAKO_PATH="${NYASH_VM_DUMP_MERGED_HAKO_PATH:-${HAKO_SELFHOST_DUMP_MERGED_HAKO_PATH:-}}" \
"$NYASH_BIN" --backend vm "$tmp_hako" 2>"$tmp_stderr" | tee "$tmp_stdout" >/dev/null) "$NYASH_BIN" --backend vm "$tmp_hako" 2>"$tmp_stderr" | tee "$tmp_stdout" >/dev/null)
local rc=$? local rc=$?
set -e set -e
@ -850,7 +937,7 @@ if try_provider_emit "$PROG_JSON_OUT" "$OUT"; then
fi fi
# 最終フォールバック: 旧CLI変換環境でv1を促す # 最終フォールバック: 旧CLI変換環境でv1を促す
if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \ if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \ HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \ HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \ NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \

View File

@ -71,3 +71,15 @@ How it works
Notes Notes
- This Stage1 binary is a minimal Ny Executor and not yet a full CLI replacement. - This Stage1 binary is a minimal Ny Executor and not yet a full CLI replacement.
- Full CLI / mode selection for Stage1 will be implemented in later phases; this script focuses on establishing the binary layout and build wiring. - Full CLI / mode selection for Stage1 will be implemented in later phases; this script focuses on establishing the binary layout and build wiring.
Helper — Stage1 CLI Runner
- `tools/selfhost/run_stage1_cli.sh`
- Wraps a Stage1 binary (default `target/selfhost/hakorune`) with the required runtime env:
- `NYASH_NYRT_SILENT_RESULT=1`Result 行を抑止して JSON stdout を維持)
- `NYASH_DISABLE_PLUGINS=1`, `NYASH_FILEBOX_MODE=core-ro`FileBox などのコア実装を強制)
- Pass arguments verbatim to the Stage1 CLI:
```bash
tools/selfhost/run_stage1_cli.sh emit program-json apps/tests/minimal.hako
tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit mir-json apps/tests/minimal.hako
```
- Use this helper (or set the env vars manually) whenever CLI output is consumed by scripts, so that stdout matches llvmlite harness expectations.

View File

@ -0,0 +1,73 @@
#!/usr/bin/env bash
# run_stage1_cli.sh — helper to invoke Stage1 hakorune CLI with required env
#
# Responsibilities
# - Locate the Stage1 binary (default: target/selfhost/hakorune).
# - Ensure NyRT emits clean JSON/stdout by default (NYASH_NYRT_SILENT_RESULT=1).
# - Apply minimal runtime defaults so FileBox/hostbridge work without plugins.
# - Forward all remaining arguments to the Stage1 binary and propagate exit code.
set -euo pipefail
usage() {
cat <<'USAGE' >&2
Usage: tools/selfhost/run_stage1_cli.sh [--bin <path>] [--] <hakorune-args...>
Defaults:
--bin <path> : default target/selfhost/hakorune
env overrides :
NYASH_NYRT_SILENT_RESULT=1 (when unset)
NYASH_DISABLE_PLUGINS=1 (when unset)
NYASH_FILEBOX_MODE=core-ro (when unset)
Examples:
tools/selfhost/run_stage1_cli.sh emit program-json apps/tests/minimal.hako
tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit mir-json apps/tests/minimal.hako
USAGE
}
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
BIN="${ROOT_DIR}/target/selfhost/hakorune"
while [[ $# -gt 0 ]]; do
case "$1" in
--bin)
if [[ $# -lt 2 ]]; then
echo "[run-stage1] --bin requires a path" >&2
exit 2
fi
BIN="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
*)
break
;;
esac
done
if [[ ! -x "$BIN" ]]; then
echo "[run-stage1] Stage1 binary not found at $BIN" >&2
echo " Build via: tools/selfhost/build_stage1.sh" >&2
exit 1
fi
if [[ $# -lt 1 ]]; then
echo "[run-stage1] missing hakorune CLI arguments" >&2
usage
exit 2
fi
# Default env toggles for Stage1 CLI execution
export NYASH_NYRT_SILENT_RESULT="${NYASH_NYRT_SILENT_RESULT:-1}"
export NYASH_DISABLE_PLUGINS="${NYASH_DISABLE_PLUGINS:-1}"
export NYASH_FILEBOX_MODE="${NYASH_FILEBOX_MODE:-core-ro}"
exec "$BIN" "$@"

View File

@ -43,7 +43,12 @@ echo "[link] EXE: $OUT"
if [[ "$DO_RUN" = "1" ]]; then if [[ "$DO_RUN" = "1" ]]; then
set +e set +e
"$OUT"; rc=$? _silent="${NYASH_NYRT_SILENT_RESULT:-}"
if [[ -n "$_silent" ]]; then
"$OUT"; rc=$?
else
NYASH_NYRT_SILENT_RESULT=1 "$OUT"; rc=$?
fi
set -e set -e
echo "[run] exit=$rc" echo "[run] exit=$rc"
fi fi

View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# stage1_launcher_program_to_mir_canary_vm.sh
# - Canary for Phase 25.1a: ensure StageB + provider delegate can emit MIR(JSON)
# for the Stage1 CLI launcher source (lang/src/runner/launcher.hako).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || cd "$SCRIPT_DIR/../../../../../../.." && pwd)"
if [ ! -f "$ROOT_DIR/lang/src/runner/launcher.hako" ]; then
echo "[SKIP] stage1_launcher_program_to_mir_canary_vm (launcher.hako missing)"
exit 0
fi
source "$ROOT_DIR/tools/smokes/v2/lib/test_runner.sh" || true
require_env || { echo "[SKIP] env not ready"; exit 0; }
SRC="$ROOT_DIR/lang/src/runner/launcher.hako"
OUT_JSON="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$OUT_JSON" "$LOG_OUT" || true' EXIT
set +e
HAKO_SELFHOST_BUILDER_FIRST=0 \
NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$SRC" "$OUT_JSON" >"$LOG_OUT" 2>&1
rc=$?
set -e
if [ $rc -ne 0 ] || [ ! -s "$OUT_JSON" ]; then
echo "[FAIL] stage1_launcher_program_to_mir_canary_vm (Program→MIR failed rc=$rc)" >&2
sed -n '1,80p' "$LOG_OUT" >&2 || true
exit 1
fi
echo "[PASS] stage1_launcher_program_to_mir_canary_vm"
exit 0