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:
@ -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
22
src/runner/repl/mod.rs
Normal 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()
|
||||
}
|
||||
143
src/runner/repl/repl_runner.rs
Normal file
143
src/runner/repl/repl_runner.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
58
src/runner/repl/repl_session.rs
Normal file
58
src/runner/repl/repl_session.rs
Normal file
@ -0,0 +1,58 @@
|
||||
//! 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(super) 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(super) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// REPL set: 変数に実行時の値を保存
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn set(&mut self, name: String, value: VMValue) {
|
||||
self.variables.insert(name, value);
|
||||
}
|
||||
|
||||
/// REPL get: 変数の実行時の値を取得(未定義は None)
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn get(&self, name: &str) -> Option<&VMValue> {
|
||||
self.variables.get(name)
|
||||
}
|
||||
|
||||
/// セッションに変数が存在するか確認
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn has(&self, name: &str) -> bool {
|
||||
self.variables.contains_key(name)
|
||||
}
|
||||
|
||||
pub(super) fn set_last_value(&mut self, value: VMValue) {
|
||||
self.last_value = Some(value.clone());
|
||||
self.variables.insert("_".to_string(), value);
|
||||
}
|
||||
|
||||
pub(super) fn reset(&mut self) {
|
||||
self.variables.clear();
|
||||
self.last_value = None;
|
||||
self.eval_count = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user