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:
@ -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 CLI(launcher.hako)と selfhost AOT パイプライン(build_stage1.sh)は設計上つながっているが、Program→MIR 変換フェーズでの selfhost builder 実行がまだ不安定。
|
- Stage1 CLI(launcher.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→MIR(env.mirbuilder.emit)も修復済み。
|
- CLI 側の `.hako → Program(JSON v0) → MIR(JSON) → EXE` ロジックは Ny コードとして実装済みで、Rust 側の Program→MIR(env.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`:
|
||||||
- Stage‑B: `[emit:trace] Stage-B: SUCCESS - Generated Program(JSON)` まで成功(Program(JSON v0) 自体は安定して取得可能)。
|
- Stage‑B: `[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 連結のどこかに Stage‑3 と合わない断片が残っている状態)。
|
- 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 を排除、
|
||||||
|
によって Stage‑3 パース/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()` にリネームし、Stage‑3 パーサの予約語 `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)を Stage‑3 VM で素直に通るように整える(using 解決と文法を selfhost 既存パターンに合わせる)タスクは継続。
|
- Stage1 CLI ソース(launcher.hako)を Stage‑3 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-first(selfhost 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 では扱わず、タグ付き Fail‑Fast で検知する(PHI ノード生成は既存 LoopForm/LowerLoop helper に一元化)。
|
||||||
|
- docs: `docs/development/roadmap/phases/phase-25.1b/README.md` を design deep‑dive 版に更新し、FuncLoweringBox / MirBuilderBox の現状把握と拡張方針(Fail‑Fast ポリシー・LoopForm/PHI ポリシー・Step0〜6 の実装順序など)を整理済み。Step0(Fail‑Fast/観測導線)は 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 — Stage‑B using resolver alias 化 & 環境導線)
|
||||||
|
- Stage‑B 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`) に置き換え、Stage‑3 パーサが string literal using を弾いていた問題を解消。
|
||||||
|
- `tools/hakorune_emit_mir.sh` では `nyash.toml` `[modules]` を Python で読み取り `name=path` を `HAKO_STAGEB_MODULES_LIST`(`|||` 区切り)として export 済み。Stage‑B 側では `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 として抽出するようにした。これにより Stage‑B 経由で生成された 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 解析の重複を解消しつつ Stage‑3 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 に揃え、Stage‑B から box 定義が Program(JSON) に落ちるようにする。
|
||||||
|
- selfhost builder Runner (`try_selfhost_builder`) は引き続き opt-in。Stage‑B 改修により 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 EXE(ny-llvmc 経由で生成されたバイナリ)が提供する `emit program-json` / `emit mir-json` の I/O 挙動は、llvmlite ハーネス(`NYASH_LLVM_USE_HARNESS=1` 経路)の挙動に揃える。
|
||||||
|
- 具体的には:
|
||||||
|
- stdout: JSON をそのまま吐く(`Result: 0` のようなハーネス用メッセージは既定OFF/verbose専用に寄せる)、エラー時は JSON ではなく明示的なエラーメッセージ+非0 exit code。
|
||||||
|
- exit code: Rust CLI / llvmlite ハーネスと同じく、0=成功、非0=失敗(Stage1 CLI 側の 90–93 番台コードと整合)。
|
||||||
|
- 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調査メモ)
|
||||||
|
- Stage‐B emit を拡張(FuncScannerBox/Stage‐B 本体)して `static box` メソッドを defs に追加。暗黙 `me` もパラメータへ補完。
|
||||||
|
- Rust 側 JSON v0 ブリッジに `ExprV0::Null` variant を追加し、Stage‐B が吐く Null リテラルを受理。
|
||||||
|
- `launcher.hako` の `while` を `loop` 構文へ置換(Stage‐B パーサ互換)。
|
||||||
|
- Stage1 CLI の Program(JSON) は attr付き defs まで含むようになったが、selfhost builder (MirBuilderBox on VM)がまだ HakoCli 全体を lowering できず stub MIR を返している。provider 経由では 62KB 超の MIR(JSON) が得られるので Phase 25.1a ではこちらを利用。
|
||||||
|
|||||||
@ -35,7 +35,14 @@ Parser/Stage‑B
|
|||||||
- Accept Stage‑3 syntax (Break/Continue/Try/Catch, etc.). Enabled in smokes for Stage‑B runs.
|
- Accept Stage‑3 syntax (Break/Continue/Try/Catch, etc.). Enabled in smokes for Stage‑B runs.
|
||||||
|
|
||||||
- HAKO_STAGEB_FUNC_SCAN=1
|
- HAKO_STAGEB_FUNC_SCAN=1
|
||||||
- Dev‑only: 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
|
||||||
|
- Stage‑B/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` のとき、Stage‑B は `lang.compiler.entry.using_resolver_box` を通じて Stage1 用の using を text merge する。`0` で旧挙動。
|
||||||
|
|
||||||
- HAKO_STAGEB_BODY_EXTRACT=0|1
|
- HAKO_STAGEB_BODY_EXTRACT=0|1
|
||||||
- Toggle Stage‑B body extractor. When `0`, skip method‑body extraction and pass the full `--source` to `parse_program2`. Useful to avoid environment‑specific drift in extractors; default is `1` (enabled).
|
- Toggle Stage‑B body extractor. When `0`, skip method‑body extraction and pass the full `--source` to `parse_program2`. Useful to avoid environment‑specific drift in extractors; default is `1` (enabled).
|
||||||
|
|||||||
@ -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 MIR(171 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. Stage‑B(`compiler_stageb.hako`):
|
1. Stage‑B(`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 連結のどこかで Stage‑3 構文と合わない断片が残っている状態(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 に追加済みだが、
|
||||||
- まだパーサが Stage‑3 構文/関数宣言の一部を受理できていない箇所があり、`Unexpected token ...` 系のエラーが残っている。
|
- まだパーサが Stage‑3 構文/関数宣言の一部を受理できていない箇所があり、`Unexpected token ...` 系のエラーが残っている。
|
||||||
|
|
||||||
|
## Update (2025-11-16 — Stage‑B 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` を Stage‑B が参照できるようになり、Stage‑3 パーサが `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 ホットフィックスでは「Stage‑B→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`) で整理し、Stage‑3 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 実行復旧
|
||||||
@ -49,6 +66,11 @@ Status: hotfix-in-progress(緊急タスク/配線修正フェーズ)
|
|||||||
- [ ] `args` 未定義エラーや `Invalid expression` の原因となっている記述を特定し、Runner 側の `Main.main(args)` などを正しく宣言する。
|
- [ ] `args` 未定義エラーや `Invalid expression` の原因となっている記述を特定し、Runner 側の `Main.main(args)` などを正しく宣言する。
|
||||||
- [ ] Stage‑3 構文の使用を必要最小限に抑え、selfhost builder 用 Runner のコードをシンプルに保つ。
|
- [ ] Stage‑3 構文の使用を必要最小限に抑え、selfhost builder 用 Runner のコードをシンプルに保つ。
|
||||||
- [x] Stage‑3 パーサで予約語となった `fn` をローカル変数名として使っている箇所(例: `lang/src/mir/builder/func_lowering.hako` の `local fn = func_jsons.length()`)をリネームし、`Unexpected token FN, expected identifier` を根本的に解消する。
|
- [x] Stage‑3 パーサで予約語となった `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 構文を排除。Stage‑3 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` など Stage‑3 で落ちやすい箇所を直接確認できる。
|
||||||
|
- 同条件で `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 を追加して Stage‑B からの Null リテラルを受理。
|
||||||
|
- `launcher.hako` の `while` を `loop` 構文へ置換し、Stage‑B パーサ互換に揃えた。
|
||||||
|
- 依然として using 依存(例: `lang.mir.builder.MirBuilderBox`)を Stage‑B emit が解決できず、`env.mirbuilder.emit` が `undefined variable: MirBuilderBox` で停止。BundleResolver / using resolver を Stage‑B 経路に統合し、依存 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/O(stdout JSON + exit code)を Rust/llvmlite と同じ契約に揃える。
|
||||||
|
|
||||||
## 25.1 / 25.1a / 25.2 の関係
|
## 25.1 / 25.1a / 25.2 の関係
|
||||||
|
|
||||||
|
|||||||
204
docs/development/roadmap/phases/phase-25.1b/README.md
Normal file
204
docs/development/roadmap/phases/phase-25.1b/README.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Phase 25.1b — Selfhost Builder Parity (Planning → Design Deep‑Dive)
|
||||||
|
|
||||||
|
Status: planning-only(実装着手前の設計・分析フェーズ)
|
||||||
|
|
||||||
|
## ゴール
|
||||||
|
|
||||||
|
- Rust 側 Program→MIR (`env.mirbuilder.emit`) と Hakorune 側 selfhost builder (`MirBuilderBox.emit_from_program_json_v0`) の機能差を埋め、Stage1 CLI(launcher.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/O(JSON stdout + exit code)を Rust/llvmlite と同じ契約に揃える。
|
||||||
|
|
||||||
|
## 現状(Phase 25.1a 時点)
|
||||||
|
|
||||||
|
### Stage‑B(Program(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` 等をファイル結合前に解決。
|
||||||
|
- Stage‑B 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` は well‑known 変数として `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`) に閉じる。
|
||||||
|
- Fail‑Fast:
|
||||||
|
- 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 に落とす」ような ad‑hoc 実装は行わない:
|
||||||
|
- PHI ノードの生成・配置は既存の LoopForm/LowerLoop 系 helper(`loop_scan_box.hako`/`lower_loop_*_box.hako` など)に一元化し、builder 本体はそれを利用する立場にとどめる。
|
||||||
|
- LLVM harness 側の PHI 不変条件(ブロック先頭グルーピング/well‑typed incoming)を崩さない。
|
||||||
|
- Phase 25.1b では:
|
||||||
|
- まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/for)から対応し、
|
||||||
|
- LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの)は `[builder/selfhost-first:unsupported:loopform]` タグなどで Fail‑Fast する。
|
||||||
|
|
||||||
|
## 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)をスモークで検証。
|
||||||
|
|
||||||
|
## 設計 TODO(FuncLoweringBox / 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 を multi‑function MIR モジュールに落とす」前提へシフト:
|
||||||
|
- `Program.body` からはエントリ `Main.main/1` 相当の MIR 関数を生成。
|
||||||
|
- `Program.defs` からは `HakoCli.*` などの補助関数を生成。
|
||||||
|
- `_norm_if_apply` / `inject_funcs` の役割を整理し、「main 関数を含まない defs‑only モジュール」を返さないように Fail‑Fast する。
|
||||||
|
|
||||||
|
3. Fail‑Fast とデバッグ
|
||||||
|
- 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. 検証計画
|
||||||
|
- selfhost‑first canary:
|
||||||
|
- `stage1_launcher_program_to_mir_canary_vm.sh` の selfhost‑first 版を追加し、`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 — Fail‑Fast・観測を揃える
|
||||||
|
- 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-implemented(main 必須チェックはトグル付き; 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段構え」でマージする API(main 名の受け渡しなど)を追加する。
|
||||||
|
- 成果物(現段階):
|
||||||
|
- 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-Fast(Step 3 で LoopForm 対応を行う)。
|
||||||
|
- 成果物:
|
||||||
|
- Loop を含まない defs は selfhost builder で MIR 関数にできるようになり、Stage1 CLI の emit/build ハンドラの半分程度を selfhost パスで賄える。
|
||||||
|
|
||||||
|
### Step 3 — Loop(LoopForm)の受理
|
||||||
|
- 目的: 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)とスモーク/テストを更新する。***
|
||||||
37
docs/development/roadmap/phases/phase-25.2b/README.md
Normal file
37
docs/development/roadmap/phases/phase-25.2b/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Phase 25.2b — Lambda / FunctionBox Semantics (Planning)
|
||||||
|
|
||||||
|
Status: planning-only(将来フェーズ用のメモ/実装はまだ行わない)
|
||||||
|
|
||||||
|
## ゴール
|
||||||
|
|
||||||
|
- Phase 25.1b で selfhost builder の Program→MIR パリティが整ったあとに、Hakorune 言語としてのラムダ式(`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` のラムダ構文が記載済み。
|
||||||
|
- Stage‑3 パーサは `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)」完了後に着手する前提であり、本ドキュメントは計画メモのみとする。***
|
||||||
|
|
||||||
@ -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` | Stage‑B で Program(JSON v0) を出力 |
|
| `emit program-json` | Stage‑B で 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)`:
|
||||||
|
- Stage‑B (`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`)は NyRT(nyash_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 が空のまま。Stage‑B 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。
|
||||||
|
- using 解決は Stage0(Rust Runner)と Stage1(Hakorune)の二系統に分離する方針。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」として扱い、実装時は本仕様を先に更新→テスト→コードの順で進める。***
|
||||||
|
|
||||||
|
- Stage‑B using 未解決メモ:
|
||||||
|
- `lang.mir.builder.MirBuilderBox` などの `using` 依存を Stage‑B emit が連結できておらず、`tools/selfhost/build_stage1.sh` では `undefined variable: MirBuilderBox` で停止する。BundleResolver / using resolver を Stage‑B 経路に統合し、依存 Box 定義を Program(JSON) に含めるのが次タスク。
|
||||||
|
|||||||
@ -10,9 +10,10 @@
|
|||||||
// - Recommended: Dev/CI は wrapper(tools/hakorune_emit_mir.sh)経由で Program→MIR を行い、失敗時は Gate‑C に自動委譲する。
|
// - Recommended: Dev/CI は wrapper(tools/hakorune_emit_mir.sh)経由で Program→MIR を行い、失敗時は Gate‑C に自動委譲する。
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|||||||
@ -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 ""
|
||||||
}
|
}
|
||||||
|
|||||||
150
lang/src/compiler/entry/using_resolver_box.hako
Normal file
150
lang/src/compiler/entry/using_resolver_box.hako
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Stage1UsingResolverBox — resolve `using` statements for Stage‑1 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 Stage‑B.
|
||||||
|
//
|
||||||
|
// 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 } }
|
||||||
@ -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"
|
||||||
|
|||||||
@ -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 → Fail‑Fast tag
|
// Provider not wired → Fail‑Fast 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")
|
||||||
|
|||||||
@ -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 {
|
||||||
// Minimal entry
|
method _norm_json_if_needed(m) {
|
||||||
method emit_from_program_json_v0(program_json, opts) {
|
|
||||||
if program_json == null { print("[mirbuilder/min/input:null]"); return null }
|
|
||||||
local s = "" + program_json
|
|
||||||
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 }
|
if m == null { return null }
|
||||||
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
|
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
|
||||||
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(m) }
|
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(m) }
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
// Minimal entry
|
||||||
|
method emit_from_program_json_v0(program_json, opts) {
|
||||||
|
if program_json == null { print("[mirbuilder/min/input:null]"); return null }
|
||||||
|
local s = "" + program_json
|
||||||
|
if !(s.contains("\"version\"")) || !(s.contains("\"kind\"")) { print("[mirbuilder/min/input:invalid]"); return null }
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Responsibility
|
Responsibility
|
||||||
- Convert Stage‑B Program(JSON v0) into MIR(JSON v0) for VM/LLVM lines.
|
- Convert Stage‑B 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 Fail‑Fast; 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 (Fail‑Fast, stable)
|
Tags (Fail‑Fast, 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 selected(Runner/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` — dev‑only: minimal loop MIR を強制生成(テスト用)
|
||||||
|
- `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` — inject_funcs で `"name":"main"` を持たない MIR に defs を追加するのを禁止(既定=0)
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
- Box‑First policy: boundary/contract first, then implementation. Keep tags stable; no silent fallback.
|
- Box‑First 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 string‑based.
|
- Large payloads: implementation is currently string/JsonFrag‑based; later phases may stream or segment JSON, but I/F は維持する。
|
||||||
|
- Phase 25.1b では `FuncLoweringBox` との連携を拡張し、「main + defs」構造を前提とした multi‑function MIR の土台を整える(defs‑only モジュールは Fail‑Fast)。
|
||||||
|
|||||||
344
lang/src/mir/builder/func_body/basic_lower_box.hako
Normal file
344
lang/src/mir/builder/func_body/basic_lower_box.hako
Normal 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 + "]}]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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 == "") {
|
||||||
|
|
||||||
if prog_path != null && prog_path != "" {
|
|
||||||
// Read Program(JSON v0) from file
|
|
||||||
@fb = new FileBox()
|
|
||||||
@ok = fb.open(prog_path, "r")
|
|
||||||
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 {
|
|
||||||
// Expect .hako source path
|
|
||||||
if source_path == null || source_path == "" {
|
|
||||||
print("[hakorune] emit mir-json: require --from-program-json <file> or <source.hako>")
|
print("[hakorune] emit mir-json: require --from-program-json <file> or <source.hako>")
|
||||||
return 92
|
return 92
|
||||||
}
|
}
|
||||||
@fb2 = new FileBox()
|
|
||||||
@ok2 = fb2.open(source_path, "r")
|
local tag = "[hakorune] emit mir-json"
|
||||||
if ok2 != 1 {
|
local prog_json = null
|
||||||
print("[hakorune] emit mir-json: failed to open source " + source_path)
|
|
||||||
return 92
|
if prog_path != null && prog_path != "" {
|
||||||
}
|
local prog_text = me._read_file(tag, prog_path)
|
||||||
@src = fb2.read()
|
if prog_text == null { return 92 }
|
||||||
fb2.close()
|
prog_json = prog_text
|
||||||
if src == null || src == "" {
|
} else {
|
||||||
print("[hakorune] emit mir-json: source is empty")
|
local src = me._read_file(tag, source_path)
|
||||||
return 92
|
if src == null { return 92 }
|
||||||
}
|
local prog = BuildBox.emit_program_json_v0(src, null)
|
||||||
// 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 Stage‑B 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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>,
|
||||||
|
|||||||
@ -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)?;
|
||||||
|
|||||||
@ -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はOFF(Phase 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()))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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} \
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
73
tools/selfhost/run_stage1_cli.sh
Normal file
73
tools/selfhost/run_stage1_cli.sh
Normal 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" "$@"
|
||||||
@ -43,7 +43,12 @@ echo "[link] EXE: $OUT"
|
|||||||
|
|
||||||
if [[ "$DO_RUN" = "1" ]]; then
|
if [[ "$DO_RUN" = "1" ]]; then
|
||||||
set +e
|
set +e
|
||||||
|
_silent="${NYASH_NYRT_SILENT_RESULT:-}"
|
||||||
|
if [[ -n "$_silent" ]]; then
|
||||||
"$OUT"; rc=$?
|
"$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
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# stage1_launcher_program_to_mir_canary_vm.sh
|
||||||
|
# - Canary for Phase 25.1a: ensure Stage‑B + 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
|
||||||
|
|
||||||
Reference in New Issue
Block a user