refactor(repl): Phase 288 Box化 - REPL Runner モジュール分離

箱理論モジュール化(Box-First):
- src/runner/repl/ 新規モジュール作成
- ReplRunnerBox: REPL実行器の完全隔離
- ReplSessionBox: mir/builder → runner/repl へ移動

File changes:
- src/runner/mod.rs: -118行(REPL コード削除)
- src/runner/repl/mod.rs: +22行(公開API)
- src/runner/repl/repl_runner.rs: +143行(ReplRunnerBox実装)
- src/runner/repl/repl_session.rs: moved from mir/builder/

Benefits:
- runner/mod.rs が綺麗(REPL 関連削除)
- REPL 機能が完全隔離(file mode への影響ゼロ保証)
- テスト容易性向上(Box 単体テスト可能)

Test results:
   REPL 動作確認(print/.reset/.exit)
   File mode regression: 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 14:06:22 +09:00
parent 2b86b658d8
commit 3445ef7a7d
5 changed files with 178 additions and 126 deletions

View File

@ -88,7 +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)
// Phase 288 Box化: repl_session moved to src/runner/repl/repl_session.rs
// Unified member property kinds for computed/once/birth_once
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

View File

@ -34,6 +34,7 @@ pub mod modes;
mod pipe_io;
mod pipeline;
mod plugins;
mod repl; // Phase 288: REPL module
mod selfhost;
mod stage1_bridge;
mod tasks;
@ -51,8 +52,6 @@ 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
@ -72,7 +71,6 @@ impl NyashRunner {
Self {
config,
repl_session: std::cell::RefCell::new(None), // Phase 288 P2
}
}
@ -94,120 +92,6 @@ impl NyashRunner {
build::run_build_mvp_impl(self, cfg_path)
}
/// Phase 288 P1: Run REPL mode
fn run_repl(&self) -> ! {
use std::io::{self, Write};
println!("Nyash REPL v1.0 - Phase 288 MVP");
println!("Type .help for commands, .exit to quit");
let stdin = io::stdin();
let mut line_buf = String::new();
loop {
print!(">>> ");
io::stdout().flush().unwrap();
line_buf.clear();
match stdin.read_line(&mut line_buf) {
Ok(0) => break, // EOF
Ok(_) => {
let line = line_buf.trim();
// REPL commands
match line {
".exit" | ".quit" => break,
".help" => {
println!("Commands:");
println!(" .exit / .quit - Exit REPL");
println!(" .reset - Clear session");
println!(" .help - Show this help");
continue;
}
".reset" => {
// Phase 288 P3: Reset session
let mut session_ref = self.repl_session.borrow_mut();
if let Some(ref mut session) = *session_ref {
session.reset();
println!("Session reset");
} else {
println!("Session reset (no active session)");
}
continue;
}
"" => continue,
_ => {}
}
// Evaluate line
match self.eval_repl_line(line) {
Ok(result) => {
if !result.is_empty() {
println!("{}", result);
}
}
Err(e) => eprintln!("Error: {}", e),
}
}
Err(e) => {
eprintln!("Input error: {}", e);
break;
}
}
}
println!("Goodbye!");
std::process::exit(0);
}
/// 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 - use Main for VM entry point)
let code = format!("static box Main {{ 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 P3: 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;
}
}
// Phase 288 P3: print() output already displayed via ExternCall
// Expression auto-display deferred to Phase 288.1
Ok(String::new())
}
}
#[cfg(not(feature = "jit-direct-only"))]
@ -221,7 +105,7 @@ impl NyashRunner {
}
// Phase 288 P1: REPL mode
if self.config.repl {
self.run_repl(); // never returns
repl::run_repl(self.config.clone()); // never returns
}
let groups = self.config.as_groups();

22
src/runner/repl/mod.rs Normal file
View File

@ -0,0 +1,22 @@
//! REPL Module - Box-First Architecture
//!
//! Phase 288: Box化モジュール化
//! - ReplRunnerBox: REPL実行器の完全隔離
//! - ReplSessionBox: セッション状態の管理
//!
//! 公開API: run_repl() のみ
mod repl_runner;
mod repl_session;
use repl_runner::ReplRunnerBox;
use crate::cli::CliConfig;
/// Phase 288: REPL モード起動公開API
///
/// REPL ループを開始し、プログラムは終了しないnever returns
/// `.exit` コマンドで終了する。
pub(crate) fn run_repl(config: CliConfig) -> ! {
let runner = ReplRunnerBox::new(config);
runner.run()
}

View File

@ -0,0 +1,143 @@
//! ReplRunnerBox - REPL execution engine
//!
//! Box-First Design: Complete isolation of REPL execution logic
//! Phase 288 P1-P3
//!
//! Responsibilities:
//! - REPL loop management (.exit, .help, .reset commands)
//! - Line evaluation (parse → compile → execute)
//! - Session state management via ReplSessionBox
use super::repl_session::ReplSessionBox;
use crate::cli::CliConfig;
use std::cell::RefCell;
/// Phase 288: REPL実行器箱理論モジュール化
pub(super) struct ReplRunnerBox {
#[allow(dead_code)]
config: CliConfig,
session: RefCell<Option<ReplSessionBox>>,
}
impl ReplRunnerBox {
pub(super) fn new(config: CliConfig) -> Self {
Self {
config,
session: RefCell::new(None),
}
}
/// REPL ループ(メインエントリーポイント)
pub(super) fn run(&self) -> ! {
use std::io::{self, Write};
println!("Nyash REPL v1.0 - Phase 288 MVP");
println!("Type .help for commands, .exit to quit");
let stdin = io::stdin();
let mut line_buf = String::new();
loop {
print!(">>> ");
io::stdout().flush().unwrap();
line_buf.clear();
match stdin.read_line(&mut line_buf) {
Ok(0) => break, // EOF
Ok(_) => {
let line = line_buf.trim();
// REPL commands
match line {
".exit" | ".quit" => break,
".help" => {
println!("Commands:");
println!(" .exit / .quit - Exit REPL");
println!(" .reset - Clear session");
println!(" .help - Show this help");
continue;
}
".reset" => {
// Phase 288 P3: Reset session
let mut session_ref = self.session.borrow_mut();
if let Some(ref mut session) = *session_ref {
session.reset();
println!("Session reset");
} else {
println!("Session reset (no active session)");
}
continue;
}
"" => continue,
_ => {}
}
// Evaluate line
match self.eval_line(line) {
Ok(result) => {
if !result.is_empty() {
println!("{}", result);
}
}
Err(e) => eprintln!("Error: {}", e),
}
}
Err(e) => {
eprintln!("Input error: {}", e);
break;
}
}
}
println!("Goodbye!");
std::process::exit(0);
}
/// 1行評価内部メソッド
fn eval_line(&self, line: &str) -> Result<String, String> {
use crate::parser::NyashParser;
use crate::mir::MirCompiler;
use crate::backend::mir_interpreter::MirInterpreter;
// Initialize session on first use
{
let mut session_ref = self.session.borrow_mut();
if session_ref.is_none() {
*session_ref = Some(ReplSessionBox::new());
}
}
// Parse (minimal wrapper for REPL context - use Main for VM entry point)
let code = format!("static box Main {{ 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 P3: 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.session.borrow_mut();
if let Some(ref mut session) = *session_ref {
session.set_last_value(vm_value);
session.eval_count += 1;
}
}
// Phase 288 P3: print() output already displayed via ExternCall
// Expression auto-display deferred to Phase 288.1
Ok(String::new())
}
}

View File

@ -11,7 +11,7 @@ use std::collections::BTreeMap;
/// REPL session context - isolated from file mode
#[derive(Debug, Default)]
pub struct ReplSessionBox {
pub(super) struct ReplSessionBox {
/// Session-level variables (runtime values, persists across evaluations)
pub variables: BTreeMap<String, VMValue>,
@ -23,31 +23,34 @@ pub struct ReplSessionBox {
}
impl ReplSessionBox {
pub fn new() -> Self {
pub(super) fn new() -> Self {
Self::default()
}
/// REPL set: 変数に実行時の値を保存
pub fn set(&mut self, name: String, value: VMValue) {
#[allow(dead_code)]
pub(super) fn set(&mut self, name: String, value: VMValue) {
self.variables.insert(name, value);
}
/// REPL get: 変数の実行時の値を取得(未定義は None
pub fn get(&self, name: &str) -> Option<&VMValue> {
#[allow(dead_code)]
pub(super) fn get(&self, name: &str) -> Option<&VMValue> {
self.variables.get(name)
}
/// セッションに変数が存在するか確認
pub fn has(&self, name: &str) -> bool {
#[allow(dead_code)]
pub(super) fn has(&self, name: &str) -> bool {
self.variables.contains_key(name)
}
pub fn set_last_value(&mut self, value: VMValue) {
pub(super) 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) {
pub(super) fn reset(&mut self) {
self.variables.clear();
self.last_value = None;
self.eval_count = 0;