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:
@ -88,7 +88,7 @@ pub(crate) mod type_registry;
|
|||||||
mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段)
|
mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段)
|
||||||
mod utils;
|
mod utils;
|
||||||
mod vars; // variables/scope helpers // small loop helpers (header/exit context) // TypeRegistryBox(型情報管理の一元化)
|
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
|
// Unified member property kinds for computed/once/birth_once
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
|||||||
@ -34,6 +34,7 @@ pub mod modes;
|
|||||||
mod pipe_io;
|
mod pipe_io;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod plugins;
|
mod plugins;
|
||||||
|
mod repl; // Phase 288: REPL module
|
||||||
mod selfhost;
|
mod selfhost;
|
||||||
mod stage1_bridge;
|
mod stage1_bridge;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
@ -51,8 +52,6 @@ use nyash_rust::runtime;
|
|||||||
/// Main execution coordinator
|
/// Main execution coordinator
|
||||||
pub struct NyashRunner {
|
pub struct NyashRunner {
|
||||||
config: CliConfig,
|
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
|
/// 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 {
|
Self {
|
||||||
config,
|
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)
|
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"))]
|
#[cfg(not(feature = "jit-direct-only"))]
|
||||||
@ -221,7 +105,7 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
// Phase 288 P1: REPL mode
|
// Phase 288 P1: REPL mode
|
||||||
if self.config.repl {
|
if self.config.repl {
|
||||||
self.run_repl(); // never returns
|
repl::run_repl(self.config.clone()); // never returns
|
||||||
}
|
}
|
||||||
let groups = self.config.as_groups();
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
/// REPL session context - isolated from file mode
|
/// REPL session context - isolated from file mode
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ReplSessionBox {
|
pub(super) struct ReplSessionBox {
|
||||||
/// Session-level variables (runtime values, persists across evaluations)
|
/// Session-level variables (runtime values, persists across evaluations)
|
||||||
pub variables: BTreeMap<String, VMValue>,
|
pub variables: BTreeMap<String, VMValue>,
|
||||||
|
|
||||||
@ -23,31 +23,34 @@ pub struct ReplSessionBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ReplSessionBox {
|
impl ReplSessionBox {
|
||||||
pub fn new() -> Self {
|
pub(super) fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// REPL set: 変数に実行時の値を保存
|
/// 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);
|
self.variables.insert(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// REPL get: 変数の実行時の値を取得(未定義は None)
|
/// 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)
|
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)
|
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.last_value = Some(value.clone());
|
||||||
self.variables.insert("_".to_string(), value);
|
self.variables.insert("_".to_string(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub(super) fn reset(&mut self) {
|
||||||
self.variables.clear();
|
self.variables.clear();
|
||||||
self.last_value = None;
|
self.last_value = None;
|
||||||
self.eval_count = 0;
|
self.eval_count = 0;
|
||||||
Reference in New Issue
Block a user