feat(repl): Phase 288.1 session persistence + auto-display
実装内容: - AST Rewriter (~430行): 未宣言変数を __repl.get/set に変換 - ExternCall Bridge: VM で __repl.get/set ハンドラー実装 - Rc<RefCell<>> セッション共有: VM と REPL runner 間で永続化 - 式自動表示: pure expression の結果を自動出力 - _ 変数: 最後の表示値を保存(Void は除外) - .reset 実装: セッション変数の完全クリア - Fail-Fast: 未定義変数読み取りで明示的エラー + ヒント 変更ファイル (8ファイル, +592行): - src/runner/repl/ast_rewriter.rs (NEW, +430行) - src/runner/repl/repl_runner.rs (+84/-35行) - src/backend/mir_interpreter/handlers/externals.rs (+54行) - src/mir/builder/calls/build.rs (+41行) - src/backend/mir_interpreter/mod.rs (+12行) - src/runner/repl/repl_session.rs (+11/-9行) - src/runner/repl/mod.rs (+2行) - src/runner/mod.rs (+2/-1行) REPL専用設計(src/mir/builder/calls/build.rs の特別扱い理由): - __repl.get/set は REPL mode 専用の橋渡し機能 - try_build_repl_method_call() で早期検出・ExternCall 変換 - file mode では決して使用されない(VM で "outside REPL mode" エラー) - 将来的にも file mode への影響ゼロを保証 検証済み: - 変数永続化: x = 42; print(x) → 42 ✅ - 式自動表示: 1 + 1 → 2 ✅ - _ 変数: 10 * 2 → 20; _ → 20 ✅ - Fail-Fast: 未定義エラー + ヒント ✅ - 回帰テスト: 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,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;
|
||||
|
||||
426
src/runner/repl/ast_rewriter.rs
Normal file
426
src/runner/repl/ast_rewriter.rs
Normal file
@ -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<String>,
|
||||
/// 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<String>)
|
||||
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<String, ASTNode>)
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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<RefCell<>> で保持(永続化のため)
|
||||
pub(super) struct ReplRunnerBox {
|
||||
#[allow(dead_code)]
|
||||
config: CliConfig,
|
||||
session: RefCell<Option<ReplSessionBox>>,
|
||||
/// Phase 288.1: Rc<RefCell<>> で保持(clone は Rc のみ、中身は永続化)
|
||||
session: Rc<RefCell<ReplSessionBox>>,
|
||||
/// 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<RefCell<>> で初期化(即座に生成)
|
||||
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("<repl>"))
|
||||
// Phase 288.1: Use rewritten AST for compilation
|
||||
let mir_result = compiler.compile_with_source(rewritten_ast, Some("<repl>"))
|
||||
.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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String, VMValue>,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user