From 934a774b506cc384f4ccea479ce6d29562177e87 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Thu, 25 Dec 2025 13:38:04 +0900 Subject: [PATCH] feat(repl): Phase 288 P2 - Implicit local binding (VMValue persistence) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Box-First architecture: - ReplSessionBox: VMValue-based session state (not ValueId) - repl_mode flag: File/REPL mode isolation - MirCompiler.set_repl_mode(): Public API for REPL Files modified: - src/mir/builder/repl_session.rs: NEW (+55 lines) - Session state - src/mir/builder.rs: repl_mode field (+4 lines) - src/mir/mod.rs: set_repl_mode() method (+4 lines) - src/mir/builder/vars/assignment_resolver.rs: REPL skip (+3 lines) - src/runner/mod.rs: repl_session + eval implementation (+45 lines) REPL behavior: - x = 1: Implicit local creation (暗黙 local) - Compilation + execution pipeline complete - Variable persistence: TODO for P3 (as planned) Test results: ✅ x = 1 compiles and executes ✅ File mode regression: 154/154 tests pass File mode unchanged (assignment_resolver.rs: minimal 3-line check). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/mir/builder.rs | 7 +++ src/mir/builder/repl_session.rs | 55 +++++++++++++++++++++ src/mir/builder/vars/assignment_resolver.rs | 5 ++ src/mir/mod.rs | 5 ++ src/runner/mod.rs | 55 +++++++++++++++++++-- 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/mir/builder/repl_session.rs diff --git a/src/mir/builder.rs b/src/mir/builder.rs index b1f077dc..88afd982 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -88,6 +88,7 @@ pub(crate) mod type_registry; mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段) mod utils; mod vars; // variables/scope helpers // small loop helpers (header/exit context) // TypeRegistryBox(型情報管理の一元化) +pub(crate) mod repl_session; // Phase 288 P2: REPL session state (VMValue-based persistence) // Unified member property kinds for computed/once/birth_once #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -207,6 +208,11 @@ pub struct MirBuilder { /// Used when HAKO_MIR_BUILDER_METHODIZE=1 to convert Global("BoxName.method/arity") /// to Method{receiver=singleton} calls pub(super) static_box_singletons: HashMap, + + /// Phase 288 P2: REPL mode flag - enables implicit local declarations + /// File mode: false (explicit local required) + /// REPL mode: true (暗黙 local 許可) + pub(crate) repl_mode: bool, } impl MirBuilder { @@ -256,6 +262,7 @@ impl MirBuilder { recursion_depth: 0, root_is_app_mode: None, static_box_singletons: HashMap::new(), // Phase 21.7: methodization support + repl_mode: false, // Phase 288 P2: REPL mode (default: file mode) } } diff --git a/src/mir/builder/repl_session.rs b/src/mir/builder/repl_session.rs new file mode 100644 index 00000000..8ae13381 --- /dev/null +++ b/src/mir/builder/repl_session.rs @@ -0,0 +1,55 @@ +//! ReplSessionBox - Session state for REPL mode +//! +//! Box-First Design: Encapsulates REPL-specific state +//! Phase 288 P2 +//! +//! **重要**: ValueId は MIR コンパイル単位でのみ有効なので、 +//! 実行時の値(VMValue)を保持する。 + +use crate::backend::VMValue; +use std::collections::BTreeMap; + +/// REPL session context - isolated from file mode +#[derive(Debug, Default)] +pub struct ReplSessionBox { + /// Session-level variables (runtime values, persists across evaluations) + pub variables: BTreeMap, + + /// Last expression value (for `_` variable) + pub last_value: Option, + + /// Evaluation counter + pub eval_count: usize, +} + +impl ReplSessionBox { + pub fn new() -> Self { + Self::default() + } + + /// REPL set: 変数に実行時の値を保存 + pub fn set(&mut self, name: String, value: VMValue) { + self.variables.insert(name, value); + } + + /// REPL get: 変数の実行時の値を取得(未定義は None) + pub fn get(&self, name: &str) -> Option<&VMValue> { + self.variables.get(name) + } + + /// セッションに変数が存在するか確認 + pub fn has(&self, name: &str) -> bool { + self.variables.contains_key(name) + } + + pub fn set_last_value(&mut self, value: VMValue) { + self.last_value = Some(value.clone()); + self.variables.insert("_".to_string(), value); + } + + pub fn reset(&mut self) { + self.variables.clear(); + self.last_value = None; + self.eval_count = 0; + } +} diff --git a/src/mir/builder/vars/assignment_resolver.rs b/src/mir/builder/vars/assignment_resolver.rs index c5c95832..40117ff7 100644 --- a/src/mir/builder/vars/assignment_resolver.rs +++ b/src/mir/builder/vars/assignment_resolver.rs @@ -12,6 +12,11 @@ impl AssignmentResolverBox { builder: &MirBuilder, var_name: &str, ) -> Result<(), String> { + // Phase 288 P2: REPL mode allows implicit local declarations + if builder.repl_mode { + return Ok(()); + } + // Compiler-generated temporaries are not part of the user variable namespace. if var_name.starts_with("__pin$") { return Ok(()); diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 59058af0..02325e6b 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -126,6 +126,11 @@ impl MirCompiler { } } + /// Phase 288 P2: Set REPL mode flag + pub fn set_repl_mode(&mut self, repl_mode: bool) { + self.builder.repl_mode = repl_mode; + } + /// Compile AST to MIR module with verification pub fn compile_with_source( &mut self, diff --git a/src/runner/mod.rs b/src/runner/mod.rs index b2c79a9e..57ddc345 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -51,6 +51,8 @@ use nyash_rust::runtime; /// Main execution coordinator pub struct NyashRunner { config: CliConfig, + /// Phase 288 P2: REPL session - stores runtime values across evaluations + repl_session: std::cell::RefCell>, } /// Minimal task runner: read hako.toml (preferred) or nyash.toml [env]/[tasks], run the named task via shell @@ -68,7 +70,10 @@ impl NyashRunner { std::sync::Arc::new(runtime::ring0::Ring0Registry::build(profile)) }); - Self { config } + Self { + config, + repl_session: std::cell::RefCell::new(None), // Phase 288 P2 + } } /// Run Nyash based on the configuration @@ -148,9 +153,51 @@ impl NyashRunner { std::process::exit(0); } - /// Phase 288 P1: Evaluate REPL line (stub) - fn eval_repl_line(&self, _line: &str) -> Result { - Ok("(evaluation Phase 288 P2)".to_string()) + /// Phase 288 P2: Evaluate REPL line (VMValue persistence) + fn eval_repl_line(&self, line: &str) -> Result { + use crate::parser::NyashParser; + use crate::mir::MirCompiler; + use crate::backend::mir_interpreter::MirInterpreter; + use crate::mir::builder::repl_session::ReplSessionBox; + + // Initialize session on first use + { + let mut session_ref = self.repl_session.borrow_mut(); + if session_ref.is_none() { + *session_ref = Some(ReplSessionBox::new()); + } + } + + // Parse (minimal wrapper for REPL context) + let code = format!("static box __Repl {{ main() {{ {} }} }}", line); + let ast = NyashParser::parse_from_string(&code) + .map_err(|e| format!("Parse error: {}", e))?; + + // Compile with REPL mode flag (暗黙 local 許可) + let mut compiler = MirCompiler::new(); + compiler.set_repl_mode(true); + + let mir_result = compiler.compile_with_source(ast, Some("")) + .map_err(|e| format!("Compile error: {}", e))?; + + // Execute + let mut vm = MirInterpreter::new(); + let result_box = vm.execute_module(&mir_result.module) + .map_err(|e| format!("Runtime error: {}", e))?; + + // Phase 288 P2: Convert to VMValue and store in session + use crate::backend::VMValue; + let vm_value = VMValue::from_nyash_box(result_box); + + { + let mut session_ref = self.repl_session.borrow_mut(); + if let Some(ref mut session) = *session_ref { + session.set_last_value(vm_value); + session.eval_count += 1; + } + } + + Ok("(execution Phase 288 P3)".to_string()) } }