feat(repl): Phase 288.1 session persistence + auto-display

実装内容:
- AST Rewriter (~430行): 未宣言変数を __repl.get/set に変換
- ExternCall Bridge: VM で __repl.get/set ハンドラー実装
- Rc<RefCell<>> セッション共有: VM と REPL runner 間で永続化
- 式自動表示: pure expression の結果を自動出力
- _ 変数: 最後の表示値を保存(Void は除外)
- .reset 実装: セッション変数の完全クリア
- Fail-Fast: 未定義変数読み取りで明示的エラー + ヒント

変更ファイル (8ファイル, +592行):
- src/runner/repl/ast_rewriter.rs (NEW, +430行)
- src/runner/repl/repl_runner.rs (+84/-35行)
- src/backend/mir_interpreter/handlers/externals.rs (+54行)
- src/mir/builder/calls/build.rs (+41行)
- src/backend/mir_interpreter/mod.rs (+12行)
- src/runner/repl/repl_session.rs (+11/-9行)
- src/runner/repl/mod.rs (+2行)
- src/runner/mod.rs (+2/-1行)

REPL専用設計(src/mir/builder/calls/build.rs の特別扱い理由):
- __repl.get/set は REPL mode 専用の橋渡し機能
- try_build_repl_method_call() で早期検出・ExternCall 変換
- file mode では決して使用されない(VM で "outside REPL mode" エラー)
- 将来的にも file mode への影響ゼロを保証

検証済み:
- 変数永続化: x = 42; print(x) → 42 
- 式自動表示: 1 + 1 → 2 
- _ 変数: 10 * 2 → 20; _ → 20 
- Fail-Fast: 未定義エラー + ヒント 
- 回帰テスト: 154/154 PASS 

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-25 23:04:59 +09:00
parent e986e279b4
commit 46fbe12ce6
8 changed files with 597 additions and 35 deletions

View File

@ -264,6 +264,60 @@ impl MirInterpreter {
}
return Err(self.err_invalid("hostbridge.extern_invoke unsupported [externals]"));
}
// Phase 288.1: REPL session variable bridge
("__repl", "get") => {
// args: [name: String]
if args.len() != 1 {
return Err(self.err_invalid(format!(
"__repl.get expects 1 argument, got {}",
args.len()
)));
}
let name = self.reg_load(args[0])?.to_string();
// REPL session から取得
if let Some(session) = &self.repl_session {
// Clone the value before the borrow ends to avoid borrowing conflicts
let value_opt = session.borrow().get(&name).cloned();
match value_opt {
Some(value) => {
self.write_result(dst, value);
Ok(())
}
None => Err(self.err_invalid(format!(
"Undefined variable: '{}'\nHint: Variable not defined. Assign a value first.",
name
))),
}
} else {
Err(self.err_invalid("__repl.get called outside REPL mode".to_string()))
}
}
("__repl", "set") => {
// args: [name: String, value: Any]
if args.len() != 2 {
return Err(self.err_invalid(format!(
"__repl.set expects 2 arguments, got {}",
args.len()
)));
}
let name = self.reg_load(args[0])?.to_string();
let value = self.reg_load(args[1])?.clone();
// REPL session に保存
if let Some(session) = &self.repl_session {
session.borrow_mut().set(name, value);
self.write_void(dst);
Ok(())
} else {
Err(self.err_invalid("__repl.set called outside REPL mode".to_string()))
}
}
_ => Err(self.err_invalid(format!("ExternCall {}.{} not supported", iface, method))),
}
}

View File

@ -6,7 +6,9 @@
* Print/Debug (best-effort), Barrier/Safepoint (no-op).
*/
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
use crate::box_trait::NyashBox;
@ -46,6 +48,9 @@ pub struct MirInterpreter {
/// Call stack depth (exec_function_inner nesting). Used as a safety valve
/// to prevent Rust stack overflow on accidental infinite recursion in MIR.
pub(super) call_depth: usize,
/// Phase 288.1: REPL session reference (REPL mode only)
/// Enables variable persistence across REPL lines via __repl.get/set bridge
pub(super) repl_session: Option<Rc<RefCell<crate::runner::repl::ReplSessionBox>>>,
}
impl MirInterpreter {
@ -64,6 +69,7 @@ impl MirInterpreter {
branch_count: 0,
compare_count: 0,
call_depth: 0,
repl_session: None,
}
}
@ -95,6 +101,12 @@ impl MirInterpreter {
.detect_from_mir_functions(self.functions.keys());
}
/// Phase 288.1: Set REPL session for variable persistence
/// Enables __repl.get/set ExternCall handlers to access session state
pub fn set_repl_session(&mut self, session: Rc<RefCell<crate::runner::repl::ReplSessionBox>>) {
self.repl_session = Some(session);
}
/// Execute a BoxCall with VM's complete semantics (Phase 27-shortterm S-5.2-improved)
///
/// This wrapper allows external modules (e.g., JoinIR Runner) to invoke BoxCall