diff --git a/src/backend/mir_interpreter/handlers/externals.rs b/src/backend/mir_interpreter/handlers/externals.rs index 3ceba8a4..2f115086 100644 --- a/src/backend/mir_interpreter/handlers/externals.rs +++ b/src/backend/mir_interpreter/handlers/externals.rs @@ -264,6 +264,60 @@ impl MirInterpreter { } return Err(self.err_invalid("hostbridge.extern_invoke unsupported [externals]")); } + + // Phase 288.1: REPL session variable bridge + ("__repl", "get") => { + // args: [name: String] + if args.len() != 1 { + return Err(self.err_invalid(format!( + "__repl.get expects 1 argument, got {}", + args.len() + ))); + } + + let name = self.reg_load(args[0])?.to_string(); + + // REPL session から取得 + if let Some(session) = &self.repl_session { + // Clone the value before the borrow ends to avoid borrowing conflicts + let value_opt = session.borrow().get(&name).cloned(); + match value_opt { + Some(value) => { + self.write_result(dst, value); + Ok(()) + } + None => Err(self.err_invalid(format!( + "Undefined variable: '{}'\nHint: Variable not defined. Assign a value first.", + name + ))), + } + } else { + Err(self.err_invalid("__repl.get called outside REPL mode".to_string())) + } + } + + ("__repl", "set") => { + // args: [name: String, value: Any] + if args.len() != 2 { + return Err(self.err_invalid(format!( + "__repl.set expects 2 arguments, got {}", + args.len() + ))); + } + + let name = self.reg_load(args[0])?.to_string(); + let value = self.reg_load(args[1])?.clone(); + + // REPL session に保存 + if let Some(session) = &self.repl_session { + session.borrow_mut().set(name, value); + self.write_void(dst); + Ok(()) + } else { + Err(self.err_invalid("__repl.set called outside REPL mode".to_string())) + } + } + _ => Err(self.err_invalid(format!("ExternCall {}.{} not supported", iface, method))), } } diff --git a/src/backend/mir_interpreter/mod.rs b/src/backend/mir_interpreter/mod.rs index ec631a2d..510f65a0 100644 --- a/src/backend/mir_interpreter/mod.rs +++ b/src/backend/mir_interpreter/mod.rs @@ -6,7 +6,9 @@ * Print/Debug (best-effort), Barrier/Safepoint (no-op). */ +use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; +use std::rc::Rc; use crate::box_trait::NyashBox; @@ -46,6 +48,9 @@ pub struct MirInterpreter { /// Call stack depth (exec_function_inner nesting). Used as a safety valve /// to prevent Rust stack overflow on accidental infinite recursion in MIR. pub(super) call_depth: usize, + /// Phase 288.1: REPL session reference (REPL mode only) + /// Enables variable persistence across REPL lines via __repl.get/set bridge + pub(super) repl_session: Option>>, } impl MirInterpreter { @@ -64,6 +69,7 @@ impl MirInterpreter { branch_count: 0, compare_count: 0, call_depth: 0, + repl_session: None, } } @@ -95,6 +101,12 @@ impl MirInterpreter { .detect_from_mir_functions(self.functions.keys()); } + /// Phase 288.1: Set REPL session for variable persistence + /// Enables __repl.get/set ExternCall handlers to access session state + pub fn set_repl_session(&mut self, session: Rc>) { + self.repl_session = Some(session); + } + /// Execute a BoxCall with VM's complete semantics (Phase 27-shortterm S-5.2-improved) /// /// This wrapper allows external modules (e.g., JoinIR Runner) to invoke BoxCall diff --git a/src/mir/builder/calls/build.rs b/src/mir/builder/calls/build.rs index 19b486f8..746d97f1 100644 --- a/src/mir/builder/calls/build.rs +++ b/src/mir/builder/calls/build.rs @@ -117,6 +117,11 @@ impl MirBuilder { return Ok(result); } + // Phase 288.1: REPL session variable bridge: __repl.get/set → ExternCall + if let Some(result) = self.try_build_repl_method_call(&object, &method, &arguments)? { + return Ok(result); + } + // 1. Static box method call: BoxName.method(args) if let Some(result) = self.try_build_static_receiver_method_call(&object, &method, &arguments)? { return Ok(result); @@ -181,6 +186,42 @@ impl MirBuilder { self.try_build_mir_debug_call(method, arguments) } + /// Phase 288.1: REPL session variable bridge + /// Transform __repl.get/set → ExternCall("__repl", "get/set", args) + fn try_build_repl_method_call( + &mut self, + object: &ASTNode, + method: &str, + arguments: &[ASTNode], + ) -> Result, String> { + let ASTNode::Variable { name: obj_name, .. } = object else { + return Ok(None); + }; + if obj_name != "__repl" { + return Ok(None); + } + + // Only handle get/set methods + if method != "get" && method != "set" { + return Err(format!("__repl.{} is not supported. Only __repl.get and __repl.set are allowed.", method)); + } + + // Build argument values + let arg_values = self.build_call_args(arguments)?; + + // Emit ExternCall instruction + let dst = self.next_value_id(); + self.emit_instruction(MirInstruction::ExternCall { + dst: Some(dst), + iface_name: "__repl".to_string(), + method_name: method.to_string(), + args: arg_values, + effects: EffectMask::PURE, // get/set are pure from MIR perspective + })?; + + Ok(Some(dst)) + } + fn try_build_static_receiver_method_call( &mut self, object: &ASTNode, diff --git a/src/runner/mod.rs b/src/runner/mod.rs index db5c4a49..d3ad80b2 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -34,7 +34,7 @@ pub mod modes; mod pipe_io; mod pipeline; mod plugins; -mod repl; // Phase 288: REPL module +pub(crate) mod repl; // Phase 288.1: Made pub(crate) for ExternCall bridge access // Phase 288: REPL module mod selfhost; mod stage1_bridge; mod tasks; diff --git a/src/runner/repl/ast_rewriter.rs b/src/runner/repl/ast_rewriter.rs new file mode 100644 index 00000000..276587d9 --- /dev/null +++ b/src/runner/repl/ast_rewriter.rs @@ -0,0 +1,426 @@ +//! REPL AST Rewriter - Phase 288.1 +//! +//! Transforms AST to bridge session variables via __repl.get/set +//! +//! Box-First Design: REPL-only logic, completely isolated from file mode +//! +//! Key responsibilities: +//! - Collect declared names (local, function params, loop vars) +//! - Rewrite undeclared Variable → __repl.get("name") +//! - Rewrite undeclared Assignment → __repl.set("name", value) +//! - Skip nested scopes (function/method bodies) +//! - Exclude reserved words (me, true, false, null, _) + +use crate::ast::{ASTNode, Span}; +use std::collections::HashSet; + +/// Reserved names that should NOT be rewritten +/// Phase 288.1: "_" is NOT reserved - it should be rewritten like any other session variable +const RESERVED_NAMES: &[&str] = &["me", "true", "false", "null"]; + +/// REPL AST Rewriter +pub struct ReplAstRewriter { + /// Names declared in the current input (local, params, loop vars) + declared_names: HashSet, + /// Are we inside a nested scope (function/method body)? + in_nested_scope: bool, +} + +impl ReplAstRewriter { + /// Create new rewriter + fn new() -> Self { + Self { + declared_names: HashSet::new(), + in_nested_scope: false, + } + } + + /// Main entry point: rewrite AST for REPL session variable bridge + pub fn rewrite(ast: ASTNode) -> ASTNode { + let mut rewriter = Self::new(); + rewriter.collect_declared_names(&ast); + rewriter.rewrite_node(ast) + } + + /// Phase 1: Collect declared names in current input + fn collect_declared_names(&mut self, ast: &ASTNode) { + match ast { + ASTNode::Program { statements, .. } => { + for stmt in statements { + self.collect_from_node(stmt); + } + } + _ => self.collect_from_node(ast), + } + } + + fn collect_from_node(&mut self, node: &ASTNode) { + match node { + // Local declarations (variables: Vec) + ASTNode::Local { variables, .. } => { + for var in variables { + self.declared_names.insert(var.clone()); + } + } + + // Function declarations (params are declared) + ASTNode::FunctionDeclaration { params, .. } => { + for param in params { + self.declared_names.insert(param.clone()); + } + } + + // Box declarations (methods: HashMap) + ASTNode::BoxDeclaration { methods, static_init, .. } => { + for (_method_name, method_node) in methods { + self.collect_from_node(method_node); + } + if let Some(init) = static_init { + for stmt in init { + self.collect_from_node(stmt); + } + } + } + + // Recurse into compound nodes + ASTNode::If { then_body, else_body, .. } => { + for stmt in then_body { + self.collect_from_node(stmt); + } + if let Some(else_stmts) = else_body { + for stmt in else_stmts { + self.collect_from_node(stmt); + } + } + } + + ASTNode::Loop { body, .. } => { + for stmt in body { + self.collect_from_node(stmt); + } + } + + _ => {} // Other nodes don't declare names + } + } + + /// Phase 2: Rewrite AST nodes + fn rewrite_node(&mut self, node: ASTNode) -> ASTNode { + match node { + // Program: recurse into statements + ASTNode::Program { statements, span } => { + let rewritten = statements.into_iter().map(|s| self.rewrite_node(s)).collect(); + ASTNode::Program { statements: rewritten, span } + } + + // Variable read: rewrite if undeclared and not in nested scope + ASTNode::Variable { name, span } => { + if self.should_rewrite_variable(&name) { + self.make_repl_get(name, span) + } else { + ASTNode::Variable { name, span } + } + } + + // Assignment: rewrite target if undeclared Variable + ASTNode::Assignment { target, value, span } => { + let rewritten_value = Box::new(self.rewrite_node(*value)); + + // Check if target is a simple Variable + if let ASTNode::Variable { name, span: var_span } = *target { + if self.should_rewrite_variable(&name) { + // Rewrite to __repl.set("name", value) + return self.make_repl_set(name, *rewritten_value, span); + } else { + // Keep as Assignment + return ASTNode::Assignment { + target: Box::new(ASTNode::Variable { name, span: var_span }), + value: rewritten_value, + span, + }; + } + } else { + // Complex assignment target (e.g., field access) + ASTNode::Assignment { + target: Box::new(self.rewrite_node(*target)), + value: rewritten_value, + span, + } + } + } + + // Function/Method declarations: enter nested scope + // Phase 288.1: Special case - don't enter nested scope for "main" function (REPL wrapper) + ASTNode::FunctionDeclaration { name, params, body, is_static, is_override, span } => { + let prev_nested = self.in_nested_scope; + // Don't enter nested scope for "main" function (REPL wrapper) + let is_repl_main = name == "main"; + if !is_repl_main { + self.in_nested_scope = true; + } + + let rewritten_body = body.into_iter().map(|s| self.rewrite_node(s)).collect(); + + self.in_nested_scope = prev_nested; + + ASTNode::FunctionDeclaration { + name, + params, + body: rewritten_body, + is_static, + is_override, + span, + } + } + + // Box declarations: enter nested scope for methods (methods: HashMap) + // Phase 288.1: Special case - don't enter nested scope for static box "Main" (REPL wrapper) + ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, span } => { + let prev_nested = self.in_nested_scope; + // Don't enter nested scope for static box "Main" (REPL wrapper) + let is_repl_wrapper = is_static && name == "Main"; + if !is_repl_wrapper { + self.in_nested_scope = true; + } + + let rewritten_methods = methods.into_iter().map(|(k, v)| (k, self.rewrite_node(v))).collect(); + let rewritten_constructors = constructors.into_iter().map(|(k, v)| (k, self.rewrite_node(v))).collect(); + let rewritten_static_init = static_init.map(|stmts| { + stmts.into_iter().map(|s| self.rewrite_node(s)).collect() + }); + + self.in_nested_scope = prev_nested; + + ASTNode::BoxDeclaration { + name, + fields, + public_fields, + private_fields, + methods: rewritten_methods, + constructors: rewritten_constructors, + init_fields, + weak_fields, + is_interface, + extends, + implements, + type_parameters, + is_static, + static_init: rewritten_static_init, + span, + } + } + + // If: recurse into branches + ASTNode::If { condition, then_body, else_body, span } => { + let rewritten_cond = Box::new(self.rewrite_node(*condition)); + let rewritten_then = then_body.into_iter().map(|s| self.rewrite_node(s)).collect(); + let rewritten_else = else_body.map(|stmts| { + stmts.into_iter().map(|s| self.rewrite_node(s)).collect() + }); + + ASTNode::If { + condition: rewritten_cond, + then_body: rewritten_then, + else_body: rewritten_else, + span, + } + } + + // Loop: recurse into body + ASTNode::Loop { condition, body, span } => { + let rewritten_cond = Box::new(self.rewrite_node(*condition)); + let rewritten_body = body.into_iter().map(|s| self.rewrite_node(s)).collect(); + + ASTNode::Loop { + condition: rewritten_cond, + body: rewritten_body, + span, + } + } + + // Binary operation: recurse into operands + ASTNode::BinaryOp { operator, left, right, span } => { + ASTNode::BinaryOp { + operator, + left: Box::new(self.rewrite_node(*left)), + right: Box::new(self.rewrite_node(*right)), + span, + } + } + + // Unary operation: recurse into operand + ASTNode::UnaryOp { operator, operand, span } => { + ASTNode::UnaryOp { + operator, + operand: Box::new(self.rewrite_node(*operand)), + span, + } + } + + // Method call: recurse into object and arguments + ASTNode::MethodCall { object, method, arguments, span } => { + ASTNode::MethodCall { + object: Box::new(self.rewrite_node(*object)), + method, + arguments: arguments.into_iter().map(|a| self.rewrite_node(a)).collect(), + span, + } + } + + // Function call: recurse into arguments + ASTNode::FunctionCall { name, arguments, span } => { + ASTNode::FunctionCall { + name, + arguments: arguments.into_iter().map(|a| self.rewrite_node(a)).collect(), + span, + } + } + + // Field access: recurse into object + ASTNode::FieldAccess { object, field, span } => { + ASTNode::FieldAccess { + object: Box::new(self.rewrite_node(*object)), + field, + span, + } + } + + // Return: recurse into value + ASTNode::Return { value, span } => { + ASTNode::Return { + value: value.map(|v| Box::new(self.rewrite_node(*v))), + span, + } + } + + // Print: recurse into expression + ASTNode::Print { expression, span } => { + ASTNode::Print { + expression: Box::new(self.rewrite_node(*expression)), + span, + } + } + + // New: recurse into arguments + ASTNode::New { class, arguments, type_arguments, span } => { + ASTNode::New { + class, + arguments: arguments.into_iter().map(|a| self.rewrite_node(a)).collect(), + type_arguments, + span, + } + } + + // Match: recurse into scrutinee and arms + ASTNode::MatchExpr { scrutinee, arms, else_expr, span } => { + ASTNode::MatchExpr { + scrutinee: Box::new(self.rewrite_node(*scrutinee)), + arms: arms.into_iter().map(|(pattern, body)| { + (pattern, self.rewrite_node(body)) + }).collect(), + else_expr: Box::new(self.rewrite_node(*else_expr)), + span, + } + } + + // All other nodes pass through unchanged + other => other, + } + } + + /// Should this variable be rewritten? + fn should_rewrite_variable(&self, name: &str) -> bool { + !self.declared_names.contains(name) + && !self.in_nested_scope + && !RESERVED_NAMES.contains(&name) + } + + /// Create __repl.get("name") call + fn make_repl_get(&self, name: String, span: Span) -> ASTNode { + ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "__repl".to_string(), + span: span.clone(), + }), + method: "get".to_string(), + arguments: vec![ASTNode::Literal { + value: crate::ast::LiteralValue::String(name), + span: span.clone(), + }], + span, + } + } + + /// Create __repl.set("name", value) call + fn make_repl_set(&self, name: String, value: ASTNode, span: Span) -> ASTNode { + ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "__repl".to_string(), + span: span.clone(), + }), + method: "set".to_string(), + arguments: vec![ + ASTNode::Literal { + value: crate::ast::LiteralValue::String(name), + span: span.clone(), + }, + value, + ], + span, + } + } + + /// Check if wrapper AST represents a pure expression (for auto-display) + pub fn is_pure_expression(ast: &ASTNode) -> bool { + match ast { + ASTNode::Program { statements, .. } => { + // Look for static box Main { ... } + for stmt in statements { + if let ASTNode::BoxDeclaration { name, methods, .. } = stmt { + if name == "Main" { + // Check if main() method has a single expression in its body + if let Some(main_fn) = methods.get("main") { + return Self::check_main_function_is_expression(main_fn); + } + } + } + } + false + } + _ => false, + } + } + + fn check_main_function_is_expression(func: &ASTNode) -> bool { + // Check if main() function body has exactly 1 expression node + if let ASTNode::FunctionDeclaration { body, .. } = func { + return body.len() == 1 && Self::is_expression_node(&body[0]); + } + false + } + + fn is_expression_node(node: &ASTNode) -> bool { + match node { + // Expressions: display target + ASTNode::Literal { .. } | + ASTNode::Variable { .. } | + ASTNode::BinaryOp { .. } | + ASTNode::UnaryOp { .. } | + ASTNode::FieldAccess { .. } | + ASTNode::MethodCall { .. } | + ASTNode::FunctionCall { .. } | + ASTNode::New { .. } | + ASTNode::MatchExpr { .. } => true, + + // Statements: don't display + ASTNode::Assignment { .. } | + ASTNode::Local { .. } | + ASTNode::Return { .. } | + ASTNode::Print { .. } | + ASTNode::If { .. } | + ASTNode::Loop { .. } => false, + + _ => false, + } + } +} diff --git a/src/runner/repl/mod.rs b/src/runner/repl/mod.rs index 49da8276..5e2bf718 100644 --- a/src/runner/repl/mod.rs +++ b/src/runner/repl/mod.rs @@ -8,8 +8,10 @@ mod repl_runner; mod repl_session; +mod ast_rewriter; // Phase 288.1: AST rewriting for session variable bridge use repl_runner::ReplRunnerBox; +pub use repl_session::ReplSessionBox; // Phase 288.1: Export for ExternCall bridge use crate::cli::CliConfig; /// Phase 288: REPL モード起動(公開API) diff --git a/src/runner/repl/repl_runner.rs b/src/runner/repl/repl_runner.rs index b7ad2a1a..b14fb494 100644 --- a/src/runner/repl/repl_runner.rs +++ b/src/runner/repl/repl_runner.rs @@ -9,14 +9,18 @@ //! - Session state management via ReplSessionBox use super::repl_session::ReplSessionBox; +use crate::backend::VMValue; // Phase 288.1: For auto-display use crate::cli::CliConfig; use std::cell::RefCell; +use std::rc::Rc; /// Phase 288: REPL実行器(箱理論モジュール化) +/// Phase 288.1: session を Rc> で保持(永続化のため) pub(super) struct ReplRunnerBox { #[allow(dead_code)] config: CliConfig, - session: RefCell>, + /// Phase 288.1: Rc> で保持(clone は Rc のみ、中身は永続化) + session: Rc>, /// REPL mode での内部ログ抑制フラグ /// verbose が false の場合に true(REPL 専用) quiet_internal_logs: bool, @@ -29,7 +33,8 @@ impl ReplRunnerBox { Self { config, - session: RefCell::new(None), + // Phase 288.1: Rc> で初期化(即座に生成) + session: Rc::new(RefCell::new(ReplSessionBox::new())), quiet_internal_logs, } } @@ -65,14 +70,9 @@ impl ReplRunnerBox { 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)"); - } + // Phase 288.1: Reset session + self.session.borrow_mut().reset(); + println!("Session reset"); continue; } "" => continue, @@ -109,19 +109,20 @@ impl ReplRunnerBox { // REPL mode では内部デバッグログを自動抑制 // (quiet_internal_logs フラグで制御、環境変数操作不要) - // Initialize session on first use - { - let mut session_ref = self.session.borrow_mut(); - if session_ref.is_none() { - *session_ref = Some(ReplSessionBox::new()); - } - } + // Phase 288.1: No lazy initialization needed (session already created in 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))?; + // Phase 288.1: Check if wrapper AST is a pure expression (for auto-display) + use super::ast_rewriter::ReplAstRewriter; + let is_expression = ReplAstRewriter::is_pure_expression(&ast); + + // Phase 288.1: REPL AST rewrite (session variable bridge) + let rewritten_ast = ReplAstRewriter::rewrite(ast); + // Compile with REPL mode flag (暗黙 local 許可) let mut compiler = MirCompiler::new(); compiler.set_repl_mode(true); @@ -129,29 +130,54 @@ impl ReplRunnerBox { // MirCompiler に quiet フラグを渡す compiler.set_quiet_internal_logs(self.quiet_internal_logs); - let mir_result = compiler.compile_with_source(ast, Some("")) + // Phase 288.1: Use rewritten AST for compilation + let mir_result = compiler.compile_with_source(rewritten_ast, Some("")) .map_err(|e| format!("Compile error: {}", e))?; - // Execute + // Phase 288.1: Set REPL session in VM (Rc clone, not inner session clone) let mut vm = MirInterpreter::new(); + vm.set_repl_session(self.session.clone()); + + // Execute 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; + // Phase 288.1: Convert to VMValue and store in session 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.1: Auto-display logic + let display_output = if is_expression { + match &vm_value { + VMValue::Void => String::new(), // Void は表示しない + value => { + // `_` 変数に保存 + self.session.borrow_mut().set("_".to_string(), value.clone()); + Self::format_vm_value(value) + } } + } else { + String::new() + }; + + // Phase 288.1: Update session metadata + { + let mut session = self.session.borrow_mut(); + 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(display_output) + } - Ok(String::new()) + /// Phase 288.1: Format VMValue for auto-display + fn format_vm_value(value: &VMValue) -> String { + match value { + VMValue::Integer(i) => i.to_string(), + VMValue::Float(f) => f.to_string(), + VMValue::Bool(b) => b.to_string(), + VMValue::String(s) => s.clone(), + VMValue::Void => String::new(), + _ => format!("{:?}", value), + } } } diff --git a/src/runner/repl/repl_session.rs b/src/runner/repl/repl_session.rs index 40f007b2..457bab8f 100644 --- a/src/runner/repl/repl_session.rs +++ b/src/runner/repl/repl_session.rs @@ -10,8 +10,9 @@ use crate::backend::VMValue; use std::collections::BTreeMap; /// REPL session context - isolated from file mode +/// Phase 288.1: Made public for ExternCall bridge access #[derive(Debug, Default)] -pub(super) struct ReplSessionBox { +pub struct ReplSessionBox { /// Session-level variables (runtime values, persists across evaluations) pub variables: BTreeMap, @@ -28,14 +29,14 @@ impl ReplSessionBox { } /// REPL set: 変数に実行時の値を保存 - #[allow(dead_code)] - pub(super) fn set(&mut self, name: String, value: VMValue) { + /// Phase 288.1: Made public for ExternCall bridge (__repl.set) + pub 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> { + /// Phase 288.1: Made public for ExternCall bridge (__repl.get) + pub fn get(&self, name: &str) -> Option<&VMValue> { self.variables.get(name) }