feat(repl): Phase 288 P2 - Implicit local binding (VMValue persistence)

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 <noreply@anthropic.com>
This commit is contained in:
2025-12-25 13:38:04 +09:00
parent 8941e3bb03
commit 934a774b50
5 changed files with 123 additions and 4 deletions

View File

@ -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<String, ValueId>,
/// 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)
}
}

View File

@ -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<String, VMValue>,
/// Last expression value (for `_` variable)
pub last_value: Option<VMValue>,
/// 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;
}
}

View File

@ -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(());

View File

@ -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,

View File

@ -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<Option<crate::mir::builder::repl_session::ReplSessionBox>>,
}
/// 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<String, String> {
Ok("(evaluation Phase 288 P2)".to_string())
/// Phase 288 P2: Evaluate REPL line (VMValue persistence)
fn eval_repl_line(&self, line: &str) -> Result<String, String> {
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("<repl>"))
.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())
}
}