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

@ -117,6 +117,11 @@ impl MirBuilder {
return Ok(result);
}
// Phase 288.1: REPL session variable bridge: __repl.get/set → ExternCall
if let Some(result) = self.try_build_repl_method_call(&object, &method, &arguments)? {
return Ok(result);
}
// 1. Static box method call: BoxName.method(args)
if let Some(result) = self.try_build_static_receiver_method_call(&object, &method, &arguments)? {
return Ok(result);
@ -181,6 +186,42 @@ impl MirBuilder {
self.try_build_mir_debug_call(method, arguments)
}
/// Phase 288.1: REPL session variable bridge
/// Transform __repl.get/set → ExternCall("__repl", "get/set", args)
fn try_build_repl_method_call(
&mut self,
object: &ASTNode,
method: &str,
arguments: &[ASTNode],
) -> Result<Option<ValueId>, String> {
let ASTNode::Variable { name: obj_name, .. } = object else {
return Ok(None);
};
if obj_name != "__repl" {
return Ok(None);
}
// Only handle get/set methods
if method != "get" && method != "set" {
return Err(format!("__repl.{} is not supported. Only __repl.get and __repl.set are allowed.", method));
}
// Build argument values
let arg_values = self.build_call_args(arguments)?;
// Emit ExternCall instruction
let dst = self.next_value_id();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(dst),
iface_name: "__repl".to_string(),
method_name: method.to_string(),
args: arg_values,
effects: EffectMask::PURE, // get/set are pure from MIR perspective
})?;
Ok(Some(dst))
}
fn try_build_static_receiver_method_call(
&mut self,
object: &ASTNode,