feat(mir): Phase 21.7 Step 1-2 - NamingBox decode & Hotfix 7修正
## 実装内容 ### Step 1: NamingBox decode関数追加 (naming.rs) - ✅ `decode_static_method(func_name) -> Option<(box, method, arity)>` - ✅ `is_static_method_name(func_name) -> bool` - 対称性: encode ⇔ decode のペア実装で一貫性確保 ### Step 2: unified_emitter Hotfix 7修正 (Lines 267-304) - ✅ StaticCompiler box kind判定追加 - ✅ static box method は receiver 追加をスキップ - ✅ instance method(RuntimeData/UserDefined)のみ receiver 追加 - ✅ トレース: NYASH_STATIC_METHOD_TRACE=1 でログ出力 ## 判定ロジック ```rust if box_kind == CalleeBoxKind::StaticCompiler { // "BoxName.method/arity" 形式か確認 let func_name = format!("{}.{}/{}", box_name, method, args.len()); if is_static_method_name(&func_name) { // static box method → receiver 追加しない } } ``` ## 検証 ✅ Stage-1 テスト: RC=0 (apps/tests/stage1_skip_ws_repro.hako) ✅ ビルド成功(0 error) ## 次のステップ - Step 3: methodization実装 (HAKO_MIR_BUILDER_METHODIZE=1) Co-Authored-By: ChatGPT5 <chatgpt@openai.com>
This commit is contained in:
7
apps/tests/stage1_run_min.hako
Normal file
7
apps/tests/stage1_run_min.hako
Normal file
@ -0,0 +1,7 @@
|
||||
// Minimal run path fixture for Stage‑1 CLI.
|
||||
// Produces a trivial Program(JSON v0) with Main.main returning 0.
|
||||
static box Main {
|
||||
static method main() {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@ -41,5 +41,19 @@ Rollback
|
||||
- Disable HAKO_MIR_BUILDER_METHODIZE. Revert to Global("Box.method") resolution path (current 21.6 behavior).
|
||||
|
||||
Current notes (Phase 25.x bring-up)
|
||||
- static box 内のローカル呼び出し(例: `Main.main` → `me._nop()`)が Global 呼び出しのまま落ちるケースを確認。NamingBox(`src/mir/naming.rs`)で `main`→`Main` 正規化は済み。
|
||||
- 次のステップ: methodization 時に `ensure_static_box_instance` 経由で receiver を付与し、Global 呼び出しを Method 呼び出しに寄せる(本フェーズの実装タスクとして残置)。
|
||||
- NamingBox / static 名 SSOT
|
||||
- `src/mir/naming.rs` に NamingBox を実装済み。`Main.main` / `main._nop` などの揺れを `"Main.main/0"` 形式に正規化する経路は Rust VM/LLVM/JSON bridge から既に利用中。
|
||||
- VM 側は `normalize_static_global_name` を通して static box 名を一元化するよう更新済み。
|
||||
|
||||
- 既知のギャップ
|
||||
- static box 内のローカル呼び出し(例: `Main.main` → `me._nop()`)が Global 呼び出しのまま落ちるケースを確認済み。NamingBox で `main`→`Main` 正規化は済んでいるが、「Method 化+receiver 付与」が未完了。
|
||||
- MeCallPolicy / method_call_handlers 周りで、static box method に対しても一律で receiver(`me`)を先頭に追加してしまうパスがあり、arity 不一致や miss-call の温床になり得る。
|
||||
|
||||
- このフェーズでやること(具体タスク)
|
||||
1. `src/mir/builder/method_call_handlers.rs` / MeCallPolicy で「static box method かどうか」を判定し、static のときは receiver を追加しない(args はそのまま Global 互換で流す)ガードを入れる。
|
||||
2. methodization ロジック(HAKO_MIR_BUILDER_METHODIZE=1 有効時)で、static box 呼びを `ensure_static_box_instance(Box)` 経由の Method 呼び出しに寄せる:
|
||||
- Global("Main._nop/0") → Method{ receiver = ensure_static_box_instance("Main"), box="Main", method="_nop", arity=0 }。
|
||||
3. 最小テストを追加:
|
||||
- `.hako` 側 minimal: `static box Main { static method _nop() { return 0 } }` を呼ぶケース。
|
||||
- Rust 側: `mir_stage1_static_main_nop_resolves_and_execs`(仮)で、MIR verify + VM 実行が methodization ON/OFF の両方で安定することを確認。
|
||||
4. docs: 本ファイルに「static box 正規化の完了条件」と「NamingBox / ensure_static_box_instance / MeCallPolicy の責務分担」を 1 ページにまとめ、Phase 21.7 の完了ラインを明文化する。
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
- Stage‑1 UsingResolver:
|
||||
- Program(JSON) を入力に using/extern を解決(今は apply_usings=0 でバイパス多め)。defs/body の構造はそのまま Rust LoopForm v2 に渡る前提。
|
||||
- region+next_i 形ループで JSON スキャン・modules_map を決定、prefix 結合するだけのテキスト担当箱。
|
||||
- using 解決の意味論そのものは `UsingResolveSSOTBox` に委譲する(Stage‑1 は「SSOT に渡す JSON/front を整える箱」)。
|
||||
- Rust bridge (Stage0):
|
||||
- Program(JSON v0) → json_v0_bridge lowering → LoopForm v2 → MIR → VM/LLVM
|
||||
- Loop/PHI/SSA の SSOT は Rust 側。Stage‑1/.hako は「正しい形の Program(JSON) を渡す」責務に徹する。
|
||||
@ -121,6 +122,22 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
|
||||
- modules_list 分割ループ(start/next_start)をそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。
|
||||
- resolve_for_source 相当で entries ループと modules_map 参照(MapBox.has/get)を同時に行うケースを `mir_stage1_using_resolver_resolve_with_modules_map_verifies` で固定。
|
||||
- 残り: なし(現状の 3 ループは all Region 形)
|
||||
|
||||
### UsingResolveSSOTBox 境界メモ
|
||||
|
||||
- ファイル: `lang/src/using/resolve_ssot_box.hako`。
|
||||
- 責務(SSOT 箱):
|
||||
- IO 禁止。modules_json / using_entries_json / ctx(map) だけを入力に取り、解決結果(path/prefix/modules_json_resolved)を返す純粋関数群。
|
||||
- 主な I/F:
|
||||
- `resolve(name, ctx)`
|
||||
- ctx.modules / ctx.using_paths / ctx.cwd から純粋に path を合成(MVP では modules の厳密一致+相対ヒントのみ)。
|
||||
- `resolve_modules(modules_json, using_entries_json, ctx)`
|
||||
- nyash.toml 相当の modules_json と UsingCollector 出力を突き合わせ、競合解決済み modules_json を返す予定(MVP では echo-back)。
|
||||
- `resolve_prefix(using_entries_json, modules_json, ctx)`
|
||||
- using_entries_json と modules_json から prefix 文字列を構成(MVP では空文字、Stage‑1 entry 側で prefix 結合だけを担当)。
|
||||
- Stage‑1 UsingResolverBox との分担:
|
||||
- Stage‑1 UsingResolverBox は「JSON フロントから using_entries / modules_map を Region+next_i で収集・整形」する箱。
|
||||
- UsingResolveSSOTBox は「その結果をもとに最終的な modules/prefix を決める唯一の箱(SSOT)」として、IO なしで名前解決の意味論を持つ。
|
||||
- pipeline_v2 UsingResolver(lang/src/compiler/pipeline_v2/using_resolver_box.hako)
|
||||
- 役割: modules_json 上で alias を解決する stateful helper(テキスト収集は entry 側)。
|
||||
- ループ: `loop(true)` で RegexFlow.find_from を使い key/tail マッチを走査する単一路。continue/backedge の多経路は無く、Region+next_i へのリライトは不要と判断。
|
||||
@ -132,6 +149,13 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
|
||||
- 「pos/next_pos が forward するだけの region」で PHI が揺れないこと。
|
||||
- `using` 収集で early-exit しても merge 後の `pos` が決定的になること。
|
||||
- いずれも LoopForm v2 経路(JSON front→Rust)で MirVerifier 緑を確認するスモークとして追加。
|
||||
- 追加済みテスト(Rust `src/tests/mir_stage1_using_resolver_verify.rs`)でカバーする LoopForm パターン:
|
||||
- `mir_stage1_using_resolver_modules_map_early_exit_verifies`: Region+next_i + break(name=="" で早期終了)
|
||||
- `mir_stage1_using_resolver_modules_map_continue_and_break_verifies`: Region+next_i + continue/break 混在("" を skip, "STOP" で break)
|
||||
- `mir_stage1_using_resolver_resolve_with_modules_map_verifies`: entries ループ+modules_map 参照を同時に行う本線形
|
||||
- `mir_stage1_using_resolver_collect_entries_early_exit_verifies`: JSON スキャン+early-exit sentinel で next_pos を決める Region+next_pos 形
|
||||
- `mir_stage1_using_resolver_module_map_regionized_verifies`: modules_list 分割(start/next_start)で MapBox set を行う Region 形
|
||||
- `mir_stage1_using_resolver_modules_map_continue_break_with_lookup_verifies`: entries ループ+modules_map.has/get+continue/break が同居する本線寄りパターン
|
||||
- Rust 側テストの取り扱い:
|
||||
- `src/tests/mir_stage1_using_resolver_verify.rs` に追加した構造テストは cargo test 経路で維持する。v2 quick スモークへの昇格は実行時間とノイズを見つつ後続フェーズで再検討(今回の設計タスクでは据え置き)。
|
||||
- 観測ログ: MIR dump を残す場合は dev オンリー(`NYASH_LOOPFORM_DEBUG` / `HAKO_LOOP_PHI_TRACE`)に限定し、ログ経路は docs にも記載しておく。
|
||||
|
||||
@ -51,13 +51,13 @@ hakorune <command> [<subcommand>] [options] [-- script_args...]
|
||||
|-----------------------------------|-------------------------------------------|----------------------|
|
||||
| `run` | .hako をコンパイルして実行(既定 VM) | プレースホルダ(`[hakorune] run: not implemented yet`) |
|
||||
| `build exe` | .hako からネイティブ EXE を AOT ビルド | 実装済み(env.codegen経由で `.o` → EXE を生成) |
|
||||
| `emit program-json` | Stage‑B で Program(JSON v0) を出力 | 実装済み(`BuildBox.emit_program_json_v0`) |
|
||||
| `emit mir-json` | Program(JSON) → MIR(JSON) を出力 | 実装済み(`MirBuilderBox.emit_from_program_json_v0`) |
|
||||
| `emit program-json` | Stage‑B で Program(JSON v0) を出力 | 実装済み(Stage0 ブリッジ + `.hako` Stage1Cli 完了) |
|
||||
| `emit mir-json` | Program(JSON) → MIR(JSON) を出力 | 実装済み(Stage0 ブリッジ + `.hako` Stage1Cli 完了) |
|
||||
| `check` | 将来の構文/型/using チェック(予約) | プレースホルダ(`[hakorune] check: not implemented yet`) |
|
||||
|
||||
Phase 25.1a では、**`emit program-json` / `emit mir-json` / `build exe` の 3 系列のみが実働コード** であり、`run` / `check` はメッセージを返して終了するプレースホルダのまま運用する。CLI の出口コード(90〜93)やログ形式は docs と実装を同期済み。
|
||||
|
||||
### 実装ステータス(Phase 25.1a)
|
||||
### 実装ステータス(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 を生成。
|
||||
@ -267,6 +267,18 @@ hakorune emit mir-json [-o <out>] [--quiet] <source.hako>
|
||||
- `STAGE1_CLI_DEBUG`:
|
||||
- `1` のとき Stage‑1 CLI 側の debug ログ(`[stage1-cli/debug] ...`)と `__mir__.log` を有効化。
|
||||
|
||||
- 新形式のエイリアス(Phase 25.1 で導入済み):
|
||||
- `NYASH_STAGE1_MODE` / `NYASH_STAGE1_INPUT` / `NYASH_STAGE1_BACKEND`
|
||||
- Stage1 stub が最初に参照する統一 env。未設定なら上記 legacy `STAGE1_*` からブリッジ(`stage1_bridge/env.rs`)が補完する。
|
||||
- 許容値: `emit-program` / `emit-program-json` / `emit-mir` / `emit-mir-json` / `run`。
|
||||
|
||||
| 目的 | 新 env | 既存/フォールバック |
|
||||
|-----------------|---------------------------|------------------------------|
|
||||
| モード選択 | `NYASH_STAGE1_MODE` | `STAGE1_EMIT_*` / `NYASH_USE_STAGE1_CLI` |
|
||||
| 入力パス | `NYASH_STAGE1_INPUT` | `STAGE1_SOURCE` |
|
||||
| backend 指定 | `NYASH_STAGE1_BACKEND` | `STAGE1_BACKEND` |
|
||||
| Program(JSON) パス | `NYASH_STAGE1_PROGRAM_JSON` | `STAGE1_PROGRAM_JSON` |
|
||||
|
||||
env-only 仕様の原則:
|
||||
- 入口 `Stage1Cli.stage1_main(args)` は `cli_args_raw` を一切参照せず、上記 ENV だけを見てモード/入力ソース/backend を決定する。
|
||||
- `.hako` 側で Program(JSON v0) / MIR(JSON) を emit したうえで、実行や AOT は常に Stage0/Rust に委譲する(Stage1 は CLI オーケストレーション専任)。
|
||||
@ -285,7 +297,7 @@ hakorune check [options] <entry.hako>
|
||||
- System Hakorune subset 制約
|
||||
などを検証するためのエントリポイント。
|
||||
|
||||
- 実装は `.hako` 側で `tools/hako-check` 相当のロジックを呼び出す想定。
|
||||
- 実装は `.hako` 側で `tools/hako-check` 相当のロジックを呼び出す想定。
|
||||
- Phase 25.1 では「名前予約」と「インターフェース定義」のみを行い、実装は Phase 26 以降。
|
||||
|
||||
## Stage0 / Stage1 の責務分離(CLI 視点)
|
||||
|
||||
@ -157,23 +157,40 @@ static box Stage1Cli {
|
||||
// env から直接読み取り、Stage1CliConfigBox は将来の構造化用として温存する。
|
||||
|
||||
// mode 判定(env-only)
|
||||
local mode = "disabled"
|
||||
if env.get("STAGE1_EMIT_PROGRAM_JSON") == "1" {
|
||||
mode = "emit_program_json"
|
||||
} else if env.get("STAGE1_EMIT_MIR_JSON") == "1" {
|
||||
mode = "emit_mir_json"
|
||||
} else if env.get("NYASH_USE_STAGE1_CLI") == "1" {
|
||||
mode = "run"
|
||||
// 新形式(NYASH_STAGE1_MODE)があればそちらを優先し、旧 STAGE1_* はフォールバックとして扱う。
|
||||
local mode = env.get("NYASH_STAGE1_MODE")
|
||||
if mode != null { mode = "" + mode }
|
||||
if mode == null || mode == "" {
|
||||
if env.get("STAGE1_EMIT_PROGRAM_JSON") == "1" {
|
||||
mode = "emit-program"
|
||||
} else if env.get("STAGE1_EMIT_MIR_JSON") == "1" {
|
||||
mode = "emit-mir"
|
||||
} else if env.get("NYASH_USE_STAGE1_CLI") == "1" {
|
||||
mode = "run"
|
||||
} else {
|
||||
mode = "disabled"
|
||||
}
|
||||
}
|
||||
|
||||
// backend と入力まわり
|
||||
local backend = env.get("STAGE1_BACKEND")
|
||||
local backend = env.get("NYASH_STAGE1_BACKEND")
|
||||
if backend == null || backend == "" {
|
||||
backend = env.get("STAGE1_BACKEND")
|
||||
}
|
||||
if backend == null { backend = "vm" }
|
||||
backend = "" + backend
|
||||
|
||||
local source = env.get("STAGE1_SOURCE")
|
||||
local source = env.get("NYASH_STAGE1_INPUT")
|
||||
if source == null || source == "" {
|
||||
source = env.get("STAGE1_SOURCE")
|
||||
}
|
||||
local source_text = env.get("STAGE1_SOURCE_TEXT")
|
||||
local prog_path = env.get("STAGE1_PROGRAM_JSON")
|
||||
local prog_path = env.get("NYASH_STAGE1_PROGRAM_JSON")
|
||||
if prog_path == null || prog_path == "" {
|
||||
prog_path = env.get("STAGE1_PROGRAM_JSON")
|
||||
}
|
||||
|
||||
// デバッグは当面 STAGE1_CLI_DEBUG を優先し、将来 NYASH_DEBUG に統合する。
|
||||
local debug = env.get("STAGE1_CLI_DEBUG")
|
||||
|
||||
// Log entry point for debugging
|
||||
@ -196,7 +213,7 @@ static box Stage1Cli {
|
||||
}
|
||||
|
||||
// Dispatch by mode
|
||||
if mode == "emit_program_json" {
|
||||
if mode == "emit-program" || mode == "emit_program_json" {
|
||||
if source == null || source == "" {
|
||||
if source_text != null && source_text != "" {
|
||||
source = source_text // Fallback to direct text for testing
|
||||
@ -211,7 +228,7 @@ static box Stage1Cli {
|
||||
return 0
|
||||
}
|
||||
|
||||
if mode == "emit_mir_json" {
|
||||
if mode == "emit-mir" || mode == "emit_mir_json" {
|
||||
local prog_json = null
|
||||
if prog_path != null && prog_path != "" {
|
||||
prog_json = me._read_file("[stage1-cli] emit mir-json", prog_path)
|
||||
|
||||
@ -264,16 +264,43 @@ impl UnifiedCallEmitterBox {
|
||||
&mut args_local,
|
||||
);
|
||||
|
||||
// 📦 Hotfix 7: Include receiver in args for Callee::Method
|
||||
// 📦 Hotfix 7 (Phase 21.7 fixed): Include receiver in args for instance methods ONLY
|
||||
// VM's exec_function_inner expects receiver as the first parameter (ValueId(0))
|
||||
// but finalize_call_operands keeps receiver in Callee, not in args.
|
||||
// We must add it to args_local here so VM can bind it correctly.
|
||||
//
|
||||
// 🎯 Phase 21.7: static box method の receiver 追加を防止
|
||||
// - StaticCompiler box kind: コンパイル時 static box(ParserBox, StageBArgsBox等)
|
||||
// - これらは lowered function として定義され、receiver を期待しない
|
||||
// - instance method(RuntimeData/UserDefined)のみ receiver を追加
|
||||
if let Callee::Method {
|
||||
receiver: Some(recv),
|
||||
box_kind,
|
||||
box_name,
|
||||
method,
|
||||
..
|
||||
} = &callee
|
||||
{
|
||||
args_local.insert(0, *recv);
|
||||
use crate::mir::definitions::call_unified::CalleeBoxKind;
|
||||
|
||||
// 暫定判定: decode_static_method で static box method か確認
|
||||
let is_static_box_method = if *box_kind == CalleeBoxKind::StaticCompiler {
|
||||
// StaticCompiler の場合、関数名が "BoxName.method/arity" 形式か確認
|
||||
let func_name = format!("{}.{}/{}", box_name, method, args_local.len());
|
||||
crate::mir::naming::is_static_method_name(&func_name)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// instance method のみ receiver を追加(static box method は追加しない)
|
||||
if !is_static_box_method {
|
||||
args_local.insert(0, *recv);
|
||||
} else if std::env::var("NYASH_STATIC_METHOD_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"[hotfix7] skipped receiver for static box method: {}.{}",
|
||||
box_name, method
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create MirCall instruction using the new module (pure data composition)
|
||||
|
||||
@ -44,3 +44,42 @@ pub fn normalize_static_global_name(func_name: &str) -> String {
|
||||
func_name.to_string()
|
||||
}
|
||||
|
||||
/// Decode a MIR function name into (box_name, method, arity).
|
||||
///
|
||||
/// Format: "BoxName.method/arity"
|
||||
///
|
||||
/// Returns:
|
||||
/// - Some((box_name, method, arity)) if successfully parsed
|
||||
/// - None if not in expected format
|
||||
///
|
||||
/// Examples:
|
||||
/// - "Main.main/0" → Some(("Main", "main", 0))
|
||||
/// - "Calculator.add/2" → Some(("Calculator", "add", 2))
|
||||
/// - "print" → None (no dot)
|
||||
/// - "Main.main" → None (no arity)
|
||||
pub fn decode_static_method(func_name: &str) -> Option<(&str, &str, usize)> {
|
||||
// Split by '.' to extract box_name and "method/arity"
|
||||
let (box_name, method_arity) = func_name.split_once('.')?;
|
||||
|
||||
// Split by '/' to extract method and arity
|
||||
let (method, arity_str) = method_arity.split_once('/')?;
|
||||
|
||||
// Parse arity as usize
|
||||
let arity = arity_str.parse::<usize>().ok()?;
|
||||
|
||||
Some((box_name, method, arity))
|
||||
}
|
||||
|
||||
/// Check if a function name is a static box method.
|
||||
///
|
||||
/// Returns true if the name matches "BoxName.method/arity" format.
|
||||
///
|
||||
/// Examples:
|
||||
/// - "Main.main/0" → true
|
||||
/// - "Calculator.add/2" → true
|
||||
/// - "print" → false
|
||||
/// - "instance.method" → false (no arity)
|
||||
pub fn is_static_method_name(func_name: &str) -> bool {
|
||||
decode_static_method(func_name).is_some()
|
||||
}
|
||||
|
||||
|
||||
@ -24,19 +24,31 @@ pub(super) struct Stage1Args {
|
||||
/// - emit_mir: emit mir-json (<source.hako> or STAGE1_PROGRAM_JSON)
|
||||
/// - run: run --backend <backend> <source.hako>
|
||||
pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
let source = groups
|
||||
.input
|
||||
.file
|
||||
.as_ref()
|
||||
.cloned()
|
||||
// Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility.
|
||||
let source = std::env::var("NYASH_STAGE1_INPUT")
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
groups
|
||||
.input
|
||||
.file
|
||||
.as_ref()
|
||||
.cloned()
|
||||
})
|
||||
.or_else(|| std::env::var("STAGE1_SOURCE").ok())
|
||||
.or_else(|| std::env::var("STAGE1_INPUT").ok());
|
||||
|
||||
let emit_program = std::env::var("STAGE1_EMIT_PROGRAM_JSON")
|
||||
let mode_env = std::env::var("NYASH_STAGE1_MODE")
|
||||
.ok()
|
||||
.map(|m| m.to_ascii_lowercase().replace('_', "-"));
|
||||
let emit_program = matches!(
|
||||
mode_env.as_deref(),
|
||||
Some("emit-program") | Some("emit-program-json")
|
||||
) || std::env::var("STAGE1_EMIT_PROGRAM_JSON")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1");
|
||||
let emit_mir = std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1");
|
||||
let emit_mir = matches!(mode_env.as_deref(), Some("emit-mir") | Some("emit-mir-json"))
|
||||
|| std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1");
|
||||
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
let mut source_env: Option<String> = None;
|
||||
@ -52,7 +64,9 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
args.push(src);
|
||||
source_env = args.last().cloned();
|
||||
} else if emit_mir {
|
||||
if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") {
|
||||
if let Ok(pjson) = std::env::var("NYASH_STAGE1_PROGRAM_JSON")
|
||||
.or_else(|_| std::env::var("STAGE1_PROGRAM_JSON"))
|
||||
{
|
||||
args.push("emit".into());
|
||||
args.push("mir-json".into());
|
||||
args.push("--from-program-json".into());
|
||||
@ -74,8 +88,9 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
process::exit(97);
|
||||
});
|
||||
args.push("run".into());
|
||||
let backend = std::env::var("STAGE1_BACKEND")
|
||||
let backend = std::env::var("NYASH_STAGE1_BACKEND")
|
||||
.ok()
|
||||
.or_else(|| std::env::var("STAGE1_BACKEND").ok())
|
||||
.unwrap_or_else(|| groups.backend.backend.clone());
|
||||
args.push("--backend".into());
|
||||
args.push(backend);
|
||||
|
||||
@ -21,6 +21,29 @@ pub(super) fn configure_stage1_env(
|
||||
// Child recursion guard
|
||||
cmd.env("NYASH_STAGE1_CLI_CHILD", "1");
|
||||
|
||||
// Unified Stage-1 env (NYASH_STAGE1_*) — derive from legacy if unset to keep compatibility.
|
||||
if std::env::var("NYASH_STAGE1_MODE").is_err() {
|
||||
if std::env::var("STAGE1_EMIT_PROGRAM_JSON")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
cmd.env("NYASH_STAGE1_MODE", "emit-program");
|
||||
} else if std::env::var("STAGE1_EMIT_MIR_JSON")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
cmd.env("NYASH_STAGE1_MODE", "emit-mir");
|
||||
} else if std::env::var("NYASH_USE_STAGE1_CLI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
cmd.env("NYASH_STAGE1_MODE", "run");
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime defaults
|
||||
if std::env::var("NYASH_NYRT_SILENT_RESULT").is_err() {
|
||||
cmd.env("NYASH_NYRT_SILENT_RESULT", "1");
|
||||
@ -35,6 +58,23 @@ pub(super) fn configure_stage1_env(
|
||||
cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first");
|
||||
}
|
||||
|
||||
// Stage-1 unified input/backend (fallback to legacy)
|
||||
if std::env::var("NYASH_STAGE1_INPUT").is_err() {
|
||||
if let Ok(src) = std::env::var("STAGE1_SOURCE") {
|
||||
cmd.env("NYASH_STAGE1_INPUT", src);
|
||||
}
|
||||
}
|
||||
if std::env::var("NYASH_STAGE1_BACKEND").is_err() {
|
||||
if let Ok(be) = std::env::var("STAGE1_BACKEND") {
|
||||
cmd.env("NYASH_STAGE1_BACKEND", be);
|
||||
}
|
||||
}
|
||||
if std::env::var("NYASH_STAGE1_PROGRAM_JSON").is_err() {
|
||||
if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") {
|
||||
cmd.env("NYASH_STAGE1_PROGRAM_JSON", pjson);
|
||||
}
|
||||
}
|
||||
|
||||
// Stage-1 CLI 経路では既定で using 適用を無効化し、
|
||||
// prefix は空(HAKO_STAGEB_APPLY_USINGS=0)とする。
|
||||
// UsingResolver/UsingCollector の検証は専用テストで行い、
|
||||
|
||||
@ -233,6 +233,115 @@ static box Stage1UsingResolverRegionLoop {
|
||||
}
|
||||
}
|
||||
|
||||
/// Region+next_i で modules_map を構築し、name=="" で early-exit するパターンを固定する。
|
||||
#[test]
|
||||
fn mir_stage1_using_resolver_modules_map_early_exit_verifies() {
|
||||
ensure_stage3_env();
|
||||
let src = r#"
|
||||
static box Stage1UsingResolverModulesMapEarlyExit {
|
||||
build_modules(entries) {
|
||||
if entries == null { return new MapBox() }
|
||||
local modules = new MapBox()
|
||||
local i = 0
|
||||
local n = entries.length()
|
||||
loop(i < n) {
|
||||
local entry = entries.get(i)
|
||||
if entry == null { break }
|
||||
local name = "" + entry.get("name")
|
||||
if name == "" { break } // early-exit path
|
||||
local path = "" + entry.get("path")
|
||||
modules.set(name, path)
|
||||
i = i + 1
|
||||
}
|
||||
return modules
|
||||
}
|
||||
|
||||
main() {
|
||||
// valid → empty name → break(最後の無効要素は無視される)
|
||||
local entries = new ArrayBox()
|
||||
local e1 = new MapBox()
|
||||
e1.set("name", "Foo")
|
||||
e1.set("path", "foo.hako")
|
||||
entries.push(e1)
|
||||
local e2 = new MapBox()
|
||||
e2.set("name", "")
|
||||
entries.push(e2)
|
||||
local e3 = new MapBox()
|
||||
e3.set("name", "Bar")
|
||||
e3.set("path", "bar.hako")
|
||||
entries.push(e3)
|
||||
|
||||
local modules = me.build_modules(entries)
|
||||
return modules.size()
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let cr = mc.compile(ast).expect("compile");
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[rust-mir-verify] {}", e);
|
||||
}
|
||||
panic!("MIR verification failed for Stage1UsingResolverModulesMapEarlyExit");
|
||||
}
|
||||
}
|
||||
|
||||
/// Region+next_i で continue/break が混在する modules_map 構築を固定する。
|
||||
#[test]
|
||||
fn mir_stage1_using_resolver_modules_map_continue_and_break_verifies() {
|
||||
ensure_stage3_env();
|
||||
let src = r#"
|
||||
static box Stage1UsingResolverModulesMapContinueBreak {
|
||||
build_modules(entries) {
|
||||
if entries == null { return new MapBox() }
|
||||
local modules = new MapBox()
|
||||
local i = 0
|
||||
local n = entries.length()
|
||||
loop(i < n) {
|
||||
local next_i = i + 1
|
||||
local entry = entries.get(i)
|
||||
if entry == null { break }
|
||||
local name = "" + entry.get("name")
|
||||
if name == "" { i = next_i; continue } // skip empty → continue
|
||||
if name == "STOP" { break } // sentinel → break
|
||||
local path = "" + entry.get("path")
|
||||
modules.set(name, path)
|
||||
i = next_i
|
||||
}
|
||||
return modules
|
||||
}
|
||||
|
||||
main() {
|
||||
// A(keep) → ""(continue) → STOP(break) → after break (ignored)
|
||||
local entries = new ArrayBox()
|
||||
local e1 = new MapBox(); e1.set("name", "A"); e1.set("path", "a.hako"); entries.push(e1)
|
||||
local e2 = new MapBox(); e2.set("name", ""); entries.push(e2)
|
||||
local e3 = new MapBox(); e3.set("name", "STOP"); e3.set("path", "z.hako"); entries.push(e3)
|
||||
local e4 = new MapBox(); e4.set("name", "B"); e4.set("path", "b.hako"); entries.push(e4)
|
||||
|
||||
local modules = me.build_modules(entries)
|
||||
return modules.size()
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let cr = mc.compile(ast).expect("compile");
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[rust-mir-verify] {}", e);
|
||||
}
|
||||
panic!("MIR verification failed for Stage1UsingResolverModulesMapContinueBreak");
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON スキャン + early-exit パターンでも PHI/SSA が崩れないことを確認する。
|
||||
#[test]
|
||||
fn mir_stage1_using_resolver_collect_entries_early_exit_verifies() {
|
||||
@ -539,3 +648,95 @@ static box Stage1UsingResolverResolveWithMap {
|
||||
panic!("MIR verification failed for Stage1UsingResolverResolveWithMap");
|
||||
}
|
||||
}
|
||||
|
||||
/// entries ループと modules_map 参照に加え、continue/break が混在する本線寄りパターン。
|
||||
/// Region+next_i ループで MapBox.has/get と sentinel(\"STOP\")break/空 name continue が同居しても SSA が崩れないことを確認する。
|
||||
#[test]
|
||||
fn mir_stage1_using_resolver_modules_map_continue_break_with_lookup_verifies() {
|
||||
ensure_stage3_env();
|
||||
let src = r#"
|
||||
static box Stage1UsingResolverModulesMapContinueBreakLookup {
|
||||
_build_module_map(raw) {
|
||||
// Simple key=val||| 形式を MapBox に積む Region+next_start 形。
|
||||
local map = new MapBox()
|
||||
if raw == null { return map }
|
||||
local delim = "|||"
|
||||
local start = 0
|
||||
local cont = 1
|
||||
loop(cont == 1) {
|
||||
local next_start = raw.length()
|
||||
local next = raw.indexOf(delim, start)
|
||||
local seg = ""
|
||||
if next >= 0 {
|
||||
seg = raw.substring(start, next)
|
||||
next_start = next + delim.length()
|
||||
} else {
|
||||
seg = raw.substring(start, raw.length())
|
||||
cont = 0
|
||||
}
|
||||
if seg.length() > 0 {
|
||||
local eq_idx = seg.indexOf("=", 0)
|
||||
if eq_idx >= 0 {
|
||||
local key = seg.substring(0, eq_idx)
|
||||
local val = seg.substring(eq_idx + 1, seg.length())
|
||||
if key != "" && val != "" {
|
||||
map.set(key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
start = next_start
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
resolve_with_modules(entries, raw) {
|
||||
if entries == null { return 0 }
|
||||
local modules_map = me._build_module_map(raw)
|
||||
local prefix = ""
|
||||
local i = 0
|
||||
local n = entries.length()
|
||||
loop(i < n) {
|
||||
local next_i = i + 1
|
||||
local entry = entries.get(i)
|
||||
if entry == null { break }
|
||||
local name = "" + entry.get("name")
|
||||
if name == "" { i = next_i; continue } // 空 name は skip
|
||||
if name == "STOP" { break } // sentinel で break
|
||||
prefix = prefix + name
|
||||
if modules_map.has(name) {
|
||||
prefix = prefix + modules_map.get(name)
|
||||
}
|
||||
i = next_i
|
||||
}
|
||||
return prefix.length()
|
||||
}
|
||||
|
||||
main() {
|
||||
// entries: A(keep) → ""(continue) → STOP(break) → C(無視)
|
||||
// modules_map: A→/a, C→/c
|
||||
local entries = new ArrayBox()
|
||||
local e1 = new MapBox(); e1.set("name", "A"); entries.push(e1)
|
||||
local e2 = new MapBox(); e2.set("name", ""); entries.push(e2)
|
||||
local e3 = new MapBox(); e3.set("name", "STOP"); entries.push(e3)
|
||||
local e4 = new MapBox(); e4.set("name", "C"); entries.push(e4)
|
||||
|
||||
local raw = "A=/a|||C=/c|||"
|
||||
return me.resolve_with_modules(entries, raw)
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let cr = mc.compile(ast).expect("compile");
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
if let Err(errors) = verifier.verify_module(&cr.module) {
|
||||
for e in &errors {
|
||||
eprintln!("[rust-mir-verify] {}", e);
|
||||
}
|
||||
panic!(
|
||||
"MIR verification failed for Stage1UsingResolverModulesMapContinueBreakLookup"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
80
tools/stage1_smoke.sh
Normal file
80
tools/stage1_smoke.sh
Normal file
@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# stage1_smoke.sh — Stage‑1 CLI quick smoke
|
||||
#
|
||||
# 役割:
|
||||
# - rust Stage0 ブリッジ経由で Stage1Cli.stub を叩き、
|
||||
# - emit program-json
|
||||
# - emit mir-json
|
||||
# の 2 経路が正常に JSON を出力することを確認する軽量スモーク。
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BIN="${ROOT_DIR}/target/release/hakorune"
|
||||
|
||||
set +e
|
||||
if [[ ! -x "${BIN}" ]]; then
|
||||
echo "[stage1-smoke] missing binary: ${BIN}" >&2
|
||||
exit 97
|
||||
fi
|
||||
set -e
|
||||
|
||||
case "${1:-}" in
|
||||
""|"help"|-h|--help)
|
||||
cat <<EOF
|
||||
Usage: tools/stage1_smoke.sh [program-json|mir-json|all]
|
||||
|
||||
program-json : apps/tests/stage1_using_minimal.hako で Program(JSON v0) を確認
|
||||
mir-json : apps/tests/stage1_using_minimal.hako で MIR(JSON) を確認
|
||||
run : apps/tests/stage1_run_min.hako で run(vm) 経路(現状は MIR 出力のみ)を確認
|
||||
all : 3 経路すべて実行(既定)
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
MODE="${1:-all}"
|
||||
|
||||
SRC_MIN="apps/tests/stage1_using_minimal.hako"
|
||||
SRC_RUN="apps/tests/stage1_run_min.hako"
|
||||
|
||||
run_prog_json() {
|
||||
echo "[stage1-smoke] emit program-json: ${SRC_MIN}" >&2
|
||||
NYASH_USE_STAGE1_CLI=1 \
|
||||
STAGE1_EMIT_PROGRAM_JSON=1 \
|
||||
STAGE1_SOURCE="${SRC_MIN}" \
|
||||
"${BIN}" -- stage1_stub_test > /tmp/stage1_smoke_program.json
|
||||
echo "[stage1-smoke] program-json length=$(wc -c < /tmp/stage1_smoke_program.json)" >&2
|
||||
}
|
||||
|
||||
run_mir_json() {
|
||||
echo "[stage1-smoke] emit mir-json: ${SRC_MIN}" >&2
|
||||
NYASH_USE_STAGE1_CLI=1 \
|
||||
STAGE1_EMIT_MIR_JSON=1 \
|
||||
STAGE1_SOURCE="${SRC_MIN}" \
|
||||
"${BIN}" -- stage1_stub_test > /tmp/stage1_smoke_mir.json
|
||||
echo "[stage1-smoke] mir-json length=$(wc -c < /tmp/stage1_smoke_mir.json)" >&2
|
||||
}
|
||||
|
||||
run_run_vm() {
|
||||
echo "[stage1-smoke] run (vm backend): ${SRC_RUN}" >&2
|
||||
NYASH_STAGE1_MODE="run" \
|
||||
NYASH_STAGE1_INPUT="${SRC_RUN}" \
|
||||
NYASH_STAGE1_BACKEND="vm" \
|
||||
"${BIN}" -- stage1_stub_test > /tmp/stage1_smoke_run_vm.out
|
||||
echo "[stage1-smoke] run(vm) output length=$(wc -c < /tmp/stage1_smoke_run_vm.out)" >&2
|
||||
}
|
||||
|
||||
if [[ "${MODE}" == "program-json" ]]; then
|
||||
run_prog_json
|
||||
elif [[ "${MODE}" == "mir-json" ]]; then
|
||||
run_mir_json
|
||||
elif [[ "${MODE}" == "run" ]]; then
|
||||
run_run_vm
|
||||
else
|
||||
run_prog_json
|
||||
run_mir_json
|
||||
run_run_vm
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user