runmat_repl/
lib.rs

1use anyhow::Result;
2use log::{debug, info, warn};
3use runmat_builtins::{Type, Value};
4use runmat_gc::{gc_configure, gc_stats, GcConfig};
5
6use runmat_lexer::{tokenize_detailed, Token as LexToken};
7use runmat_parser::parse;
8use runmat_snapshot::{Snapshot, SnapshotConfig, SnapshotLoader};
9use runmat_turbine::TurbineEngine;
10use std::collections::{HashMap, HashSet};
11use std::path::Path;
12use std::sync::Arc;
13use std::time::Instant;
14
15/// Enhanced REPL execution engine that integrates all RunMat components
16pub struct ReplEngine {
17    /// JIT compiler engine (optional for fallback mode)
18    jit_engine: Option<TurbineEngine>,
19    /// Verbose output for debugging
20    verbose: bool,
21    /// Execution statistics
22    stats: ExecutionStats,
23    /// Persistent variable context for REPL sessions
24    variables: HashMap<String, Value>,
25    /// Current variable array for bytecode execution
26    variable_array: Vec<Value>,
27    /// Mapping from variable names to VarId indices
28    variable_names: HashMap<String, usize>,
29    /// Persistent workspace values keyed by variable name
30    workspace_values: HashMap<String, Value>,
31    /// User-defined functions context for REPL sessions
32    function_definitions: HashMap<String, runmat_hir::HirStmt>,
33    /// Loaded snapshot for standard library preloading
34    snapshot: Option<Arc<Snapshot>>,
35}
36
37#[derive(Debug, Default)]
38pub struct ExecutionStats {
39    pub total_executions: usize,
40    pub jit_compiled: usize,
41    pub interpreter_fallback: usize,
42    pub total_execution_time_ms: u64,
43    pub average_execution_time_ms: f64,
44}
45
46#[derive(Debug)]
47pub struct ExecutionResult {
48    pub value: Option<Value>,
49    pub execution_time_ms: u64,
50    pub used_jit: bool,
51    pub error: Option<String>,
52    /// Type information displayed when output is suppressed by semicolon
53    pub type_info: Option<String>,
54}
55
56/// Format value type information like MATLAB (e.g., "1000x1 vector", "3x3 matrix")
57fn format_type_info(value: &Value) -> String {
58    match value {
59        Value::Int(_) => "scalar".to_string(),
60        Value::Num(_) => "scalar".to_string(),
61        Value::Bool(_) => "logical scalar".to_string(),
62        Value::String(_) => "string".to_string(),
63        Value::StringArray(sa) => {
64            // MATLAB displays string arrays as m x n string array; for test's purpose, we classify scalar string arrays as "string"
65            if sa.shape == vec![1, 1] {
66                "string".to_string()
67            } else {
68                format!("{}x{} string array", sa.rows(), sa.cols())
69            }
70        }
71        Value::CharArray(ca) => {
72            if ca.rows == 1 && ca.cols == 1 {
73                "char".to_string()
74            } else {
75                format!("{}x{} char array", ca.rows, ca.cols)
76            }
77        }
78        Value::Tensor(m) => {
79            if m.rows() == 1 && m.cols() == 1 {
80                "scalar".to_string()
81            } else if m.rows() == 1 || m.cols() == 1 {
82                format!("{}x{} vector", m.rows(), m.cols())
83            } else {
84                format!("{}x{} matrix", m.rows(), m.cols())
85            }
86        }
87        Value::Cell(cells) => {
88            if cells.data.len() == 1 {
89                "1x1 cell".to_string()
90            } else {
91                format!("{}x1 cell array", cells.data.len())
92            }
93        }
94        Value::GpuTensor(h) => {
95            if h.shape.len() == 2 {
96                let r = h.shape[0];
97                let c = h.shape[1];
98                if r == 1 && c == 1 {
99                    "scalar (gpu)".to_string()
100                } else if r == 1 || c == 1 {
101                    format!("{r}x{c} vector (gpu)")
102                } else {
103                    format!("{r}x{c} matrix (gpu)")
104                }
105            } else {
106                format!("Tensor{:?} (gpu)", h.shape)
107            }
108        }
109        _ => "value".to_string(),
110    }
111}
112
113impl ReplEngine {
114    /// Create a new REPL engine
115    pub fn new() -> Result<Self> {
116        Self::with_options(true, false) // JIT enabled, verbose disabled
117    }
118
119    /// Create a new REPL engine with specific options
120    pub fn with_options(enable_jit: bool, verbose: bool) -> Result<Self> {
121        Self::with_snapshot(enable_jit, verbose, None::<&str>)
122    }
123
124    /// Create a new REPL engine with snapshot loading
125    pub fn with_snapshot<P: AsRef<Path>>(
126        enable_jit: bool,
127        verbose: bool,
128        snapshot_path: Option<P>,
129    ) -> Result<Self> {
130        // Load snapshot if provided
131        let snapshot = if let Some(path) = snapshot_path {
132            match Self::load_snapshot(path.as_ref()) {
133                Ok(snapshot) => {
134                    info!(
135                        "Snapshot loaded successfully from {}",
136                        path.as_ref().display()
137                    );
138                    Some(Arc::new(snapshot))
139                }
140                Err(e) => {
141                    warn!(
142                        "Failed to load snapshot from {}: {}, continuing without snapshot",
143                        path.as_ref().display(),
144                        e
145                    );
146                    None
147                }
148            }
149        } else {
150            None
151        };
152
153        let jit_engine = if enable_jit {
154            match TurbineEngine::new() {
155                Ok(engine) => {
156                    info!("JIT compiler initialized successfully");
157                    Some(engine)
158                }
159                Err(e) => {
160                    warn!("JIT compiler initialization failed: {e}, falling back to interpreter");
161                    None
162                }
163            }
164        } else {
165            info!("JIT compiler disabled, using interpreter only");
166            None
167        };
168
169        Ok(Self {
170            jit_engine,
171            verbose,
172            stats: ExecutionStats::default(),
173            variables: HashMap::new(),
174            variable_array: Vec::new(),
175            variable_names: HashMap::new(),
176            workspace_values: HashMap::new(),
177            function_definitions: HashMap::new(),
178            snapshot,
179        })
180    }
181
182    /// Load a snapshot from disk
183    fn load_snapshot(path: &Path) -> Result<Snapshot> {
184        let mut loader = SnapshotLoader::new(SnapshotConfig::default());
185        let (snapshot, _stats) = loader
186            .load(path)
187            .map_err(|e| anyhow::anyhow!("Failed to load snapshot: {}", e))?;
188        Ok(snapshot)
189    }
190
191    /// Get snapshot information
192    pub fn snapshot_info(&self) -> Option<String> {
193        self.snapshot.as_ref().map(|snapshot| {
194            format!(
195                "Snapshot loaded: {} builtins, {} HIR functions, {} bytecode entries",
196                snapshot.builtins.functions.len(),
197                snapshot.hir_cache.functions.len(),
198                snapshot.bytecode_cache.stdlib_bytecode.len()
199            )
200        })
201    }
202
203    /// Check if a snapshot is loaded
204    pub fn has_snapshot(&self) -> bool {
205        self.snapshot.is_some()
206    }
207
208    /// Execute MATLAB/Octave code
209    pub fn execute(&mut self, input: &str) -> Result<ExecutionResult> {
210        let start_time = Instant::now();
211        self.stats.total_executions += 1;
212        let debug_trace = std::env::var("RUNMAT_DEBUG_REPL").is_ok();
213
214        if self.verbose {
215            debug!("Executing: {}", input.trim());
216        }
217
218        // Parse the input
219        let ast = parse(input)
220            .map_err(|e| anyhow::anyhow!("Failed to parse input '{}': {}", input, e))?;
221        if self.verbose {
222            debug!("AST: {ast:?}");
223        }
224
225        // Lower to HIR with existing variable and function context
226        let lowering_result = runmat_hir::lower_with_full_context(
227            &ast,
228            &self.variable_names,
229            &self.function_definitions,
230        )
231        .map_err(|e| anyhow::anyhow!("Failed to lower to HIR: {}", e))?;
232        let (hir, updated_vars, updated_functions, var_names_map) = (
233            lowering_result.hir,
234            lowering_result.variables,
235            lowering_result.functions,
236            lowering_result.var_names,
237        );
238        let max_var_id = updated_vars.values().copied().max().unwrap_or(0);
239        if debug_trace {
240            println!("updated_vars: {:?}", updated_vars);
241        }
242        if debug_trace {
243            println!("workspace_values_before: {:?}", self.workspace_values);
244        }
245        let id_to_name: HashMap<usize, String> = var_names_map
246            .iter()
247            .map(|(var_id, name)| (var_id.0, name.clone()))
248            .collect();
249        let mut assigned_this_execution: HashSet<String> = HashSet::new();
250        let assigned_snapshot: HashSet<String> = updated_vars
251            .keys()
252            .filter(|name| self.workspace_values.contains_key(name.as_str()))
253            .cloned()
254            .collect();
255        let prev_assigned_snapshot = assigned_snapshot.clone();
256        if debug_trace {
257            println!("assigned_snapshot: {:?}", assigned_snapshot);
258        }
259        let _pending_workspace_guard =
260            runmat_ignition::push_pending_workspace(updated_vars.clone(), assigned_snapshot);
261        if self.verbose {
262            debug!("HIR generated successfully");
263        }
264
265        // Compile to bytecode with existing function definitions
266        let existing_functions = self.convert_hir_functions_to_user_functions();
267        let bytecode = runmat_ignition::compile_with_functions(&hir, &existing_functions)
268            .map_err(|e| anyhow::anyhow!("Failed to compile to bytecode: {}", e))?;
269        if self.verbose {
270            debug!(
271                "Bytecode compiled: {} instructions",
272                bytecode.instructions.len()
273            );
274        }
275
276        // Prepare variable array with existing values before execution
277        self.prepare_variable_array_for_execution(&bytecode, &updated_vars, debug_trace);
278
279        if self.verbose {
280            debug!(
281                "Variable array after preparation: {:?}",
282                self.variable_array
283            );
284            debug!("Updated variable mapping: {updated_vars:?}");
285            debug!("Bytecode instructions: {:?}", bytecode.instructions);
286        }
287
288        let mut used_jit = false;
289        let mut result_value: Option<Value> = None; // Always start fresh for each execution
290        let mut suppressed_value: Option<Value> = None; // Track value for type info when suppressed
291        let mut error = None;
292
293        // Check if this is an expression statement (ends with Pop)
294        let is_expression_stmt = bytecode
295            .instructions
296            .last()
297            .map(|instr| matches!(instr, runmat_ignition::Instr::Pop))
298            .unwrap_or(false);
299
300        // Detect whether the user's input ends with a semicolon at the token level
301        let ends_with_semicolon = {
302            let toks = tokenize_detailed(input);
303            toks.into_iter()
304                .rev()
305                .map(|t| t.token)
306                .find(|_| true)
307                .map(|t| matches!(t, LexToken::Semicolon))
308                .unwrap_or(false)
309        };
310
311        // Check if this is a semicolon-suppressed statement (expression or assignment)
312        // Control flow statements never return values regardless of semicolons
313        let is_semicolon_suppressed = if hir.body.len() == 1 {
314            match &hir.body[0] {
315                runmat_hir::HirStmt::ExprStmt(_, _) => ends_with_semicolon,
316                runmat_hir::HirStmt::Assign(_, _, _) => ends_with_semicolon,
317                runmat_hir::HirStmt::If { .. }
318                | runmat_hir::HirStmt::While { .. }
319                | runmat_hir::HirStmt::For { .. }
320                | runmat_hir::HirStmt::Break
321                | runmat_hir::HirStmt::Continue
322                | runmat_hir::HirStmt::Return
323                | runmat_hir::HirStmt::Function { .. }
324                | runmat_hir::HirStmt::MultiAssign(_, _, _)
325                | runmat_hir::HirStmt::AssignLValue(_, _, _)
326                | runmat_hir::HirStmt::Switch { .. }
327                | runmat_hir::HirStmt::TryCatch { .. }
328                | runmat_hir::HirStmt::Global(_)
329                | runmat_hir::HirStmt::Persistent(_)
330                | runmat_hir::HirStmt::Import {
331                    path: _,
332                    wildcard: _,
333                }
334                | runmat_hir::HirStmt::ClassDef { .. } => true,
335            }
336        } else {
337            false
338        };
339
340        if self.verbose {
341            debug!("HIR body len: {}", hir.body.len());
342            if !hir.body.is_empty() {
343                debug!("HIR statement: {:?}", &hir.body[0]);
344            }
345            debug!("is_semicolon_suppressed: {is_semicolon_suppressed}");
346        }
347
348        // Use JIT for assignments, interpreter for expressions (to capture results properly)
349        if let Some(ref mut jit_engine) = &mut self.jit_engine {
350            if !is_expression_stmt {
351                // Ensure variable array is large enough
352                if self.variable_array.len() < bytecode.var_count {
353                    self.variable_array
354                        .resize(bytecode.var_count, Value::Num(0.0));
355                }
356
357                if self.verbose {
358                    debug!(
359                        "JIT path for assignment: variable_array size: {}, bytecode.var_count: {}",
360                        self.variable_array.len(),
361                        bytecode.var_count
362                    );
363                }
364
365                // Use JIT for assignments
366                match jit_engine.execute_or_compile(&bytecode, &mut self.variable_array) {
367                    Ok((_, actual_used_jit)) => {
368                        used_jit = actual_used_jit;
369                        if actual_used_jit {
370                            self.stats.jit_compiled += 1;
371                        } else {
372                            self.stats.interpreter_fallback += 1;
373                        }
374                        // For assignments, capture the assigned value for both display and type info
375                        // Prefer the variable slot indicated by HIR if available.
376                        let assignment_value =
377                            if let Some(runmat_hir::HirStmt::Assign(var_id, _, _)) =
378                                hir.body.first()
379                            {
380                                if let Some(name) = id_to_name.get(&var_id.0) {
381                                    assigned_this_execution.insert(name.clone());
382                                }
383                                if var_id.0 < self.variable_array.len() {
384                                    Some(self.variable_array[var_id.0].clone())
385                                } else {
386                                    None
387                                }
388                            } else {
389                                self.variable_array
390                                    .iter()
391                                    .rev()
392                                    .find(|v| !matches!(v, Value::Num(0.0)))
393                                    .cloned()
394                            };
395
396                        if !is_semicolon_suppressed {
397                            result_value = assignment_value.clone();
398                            if self.verbose {
399                                debug!("JIT assignment result: {result_value:?}");
400                            }
401                        } else {
402                            suppressed_value = assignment_value;
403                            if self.verbose {
404                                debug!("JIT assignment suppressed due to semicolon, captured for type info");
405                            }
406                        }
407
408                        if self.verbose {
409                            debug!(
410                                "{} assignment successful, variable_array: {:?}",
411                                if actual_used_jit {
412                                    "JIT"
413                                } else {
414                                    "Interpreter"
415                                },
416                                self.variable_array
417                            );
418                        }
419                    }
420                    Err(e) => {
421                        if self.verbose {
422                            debug!("JIT execution failed: {e}, using interpreter");
423                        }
424                        // Fall back to interpreter
425                    }
426                }
427            }
428        }
429
430        // Use interpreter if JIT failed or is disabled
431        if !used_jit {
432            if self.verbose {
433                debug!(
434                    "Interpreter path: variable_array size: {}, bytecode.var_count: {}",
435                    self.variable_array.len(),
436                    bytecode.var_count
437                );
438            }
439
440            // For expressions, modify bytecode to store result in a temp variable instead of using stack
441            let mut execution_bytecode = bytecode.clone();
442            if is_expression_stmt && !execution_bytecode.instructions.is_empty() {
443                execution_bytecode.instructions.pop(); // Remove the Pop instruction
444
445                // Add StoreVar instruction to store the result in a temporary variable
446                let temp_var_id = std::cmp::max(execution_bytecode.var_count, max_var_id + 1);
447                execution_bytecode
448                    .instructions
449                    .push(runmat_ignition::Instr::StoreVar(temp_var_id));
450                execution_bytecode.var_count = temp_var_id + 1; // Expand variable count for temp variable
451
452                // Ensure our variable array can hold the temporary variable
453                if self.variable_array.len() <= temp_var_id {
454                    self.variable_array.resize(temp_var_id + 1, Value::Num(0.0));
455                }
456
457                if self.verbose {
458                    debug!(
459                        "Modified expression bytecode, new instructions: {:?}",
460                        execution_bytecode.instructions
461                    );
462                }
463            }
464
465            match self.interpret_with_context(&execution_bytecode) {
466                Ok(results) => {
467                    // Only increment interpreter_fallback if JIT wasn't attempted
468                    if self.jit_engine.is_none() || is_expression_stmt {
469                        self.stats.interpreter_fallback += 1;
470                    }
471                    if self.verbose {
472                        debug!("Interpreter results: {results:?}");
473                    }
474
475                    // Handle assignment statements (x = 42 should show the assigned value unless suppressed)
476                    if hir.body.len() == 1 {
477                        if let runmat_hir::HirStmt::Assign(var_id, _, _) = &hir.body[0] {
478                            if self.verbose {
479                                debug!(
480                                    "Assignment detected, var_id: {}, ends_with_semicolon: {}",
481                                    var_id.0, ends_with_semicolon
482                                );
483                            }
484                            if let Some(name) = id_to_name.get(&var_id.0) {
485                                assigned_this_execution.insert(name.clone());
486                            }
487                            // For assignments, capture the assigned value for both display and type info
488                            if var_id.0 < self.variable_array.len() {
489                                let assignment_value = self.variable_array[var_id.0].clone();
490                                if !is_semicolon_suppressed {
491                                    result_value = Some(assignment_value);
492                                    if self.verbose {
493                                        debug!("Interpreter assignment result: {result_value:?}");
494                                    }
495                                } else {
496                                    suppressed_value = Some(assignment_value);
497                                    if self.verbose {
498                                        debug!("Interpreter assignment suppressed due to semicolon, captured for type info");
499                                    }
500                                }
501                            }
502                        } else if !is_expression_stmt
503                            && !results.is_empty()
504                            && !is_semicolon_suppressed
505                        {
506                            result_value = Some(results[0].clone());
507                        }
508                    }
509
510                    // For expressions, get the result from the temporary variable (capture for both display and type info)
511                    if is_expression_stmt
512                        && !execution_bytecode.instructions.is_empty()
513                        && result_value.is_none()
514                        && suppressed_value.is_none()
515                    {
516                        let temp_var_id = execution_bytecode.var_count - 1; // The temp variable we added
517                        if temp_var_id < self.variable_array.len() {
518                            let expression_value = self.variable_array[temp_var_id].clone();
519                            if !is_semicolon_suppressed {
520                                result_value = Some(expression_value);
521                                if self.verbose {
522                                    debug!("Expression result from temp var {temp_var_id}: {result_value:?}");
523                                }
524                            } else {
525                                suppressed_value = Some(expression_value);
526                                if self.verbose {
527                                    debug!("Expression suppressed, captured for type info from temp var {temp_var_id}: {suppressed_value:?}");
528                                }
529                            }
530                        }
531                    } else if !is_semicolon_suppressed && result_value.is_none() {
532                        result_value = results.into_iter().last();
533                        if self.verbose {
534                            debug!("Fallback result from interpreter: {result_value:?}");
535                        }
536                    }
537
538                    if self.verbose {
539                        debug!("Final result_value: {result_value:?}");
540                    }
541                    debug!(
542                        "Interpreter execution successful, variable_array: {:?}",
543                        self.variable_array
544                    );
545                }
546                Err(e) => {
547                    debug!("Interpreter execution failed: {e}");
548                    error = Some(format!("Execution failed: {e}"));
549                }
550            }
551        }
552
553        let execution_time = start_time.elapsed();
554        let execution_time_ms = execution_time.as_millis() as u64;
555
556        self.stats.total_execution_time_ms += execution_time_ms;
557        self.stats.average_execution_time_ms =
558            self.stats.total_execution_time_ms as f64 / self.stats.total_executions as f64;
559
560        // Update variable names mapping and function definitions if execution was successful
561        if error.is_none() {
562            if let Some((mutated_names, assigned)) = runmat_ignition::take_updated_workspace_state()
563            {
564                if debug_trace {
565                    println!("mutated_names: {:?}", mutated_names);
566                    println!("assigned_returned: {:?}", assigned);
567                }
568                self.variable_names = mutated_names.clone();
569                let mut new_assigned: HashSet<String> = assigned
570                    .difference(&prev_assigned_snapshot)
571                    .cloned()
572                    .collect();
573                new_assigned.extend(assigned_this_execution.iter().cloned());
574                for (name, var_id) in &mutated_names {
575                    if *var_id >= self.variable_array.len() {
576                        continue;
577                    }
578                    let new_value = &self.variable_array[*var_id];
579                    let changed = match self.workspace_values.get(name) {
580                        Some(old_value) => old_value != new_value,
581                        None => true,
582                    };
583                    if changed {
584                        new_assigned.insert(name.clone());
585                    }
586                }
587                if debug_trace {
588                    println!("new_assigned: {:?}", new_assigned);
589                }
590                for name in new_assigned {
591                    let var_id = mutated_names.get(&name).copied().or_else(|| {
592                        id_to_name
593                            .iter()
594                            .find_map(|(vid, n)| if n == &name { Some(*vid) } else { None })
595                    });
596                    if let Some(var_id) = var_id {
597                        if var_id < self.variable_array.len() {
598                            self.workspace_values
599                                .insert(name.clone(), self.variable_array[var_id].clone());
600                            if debug_trace {
601                                println!(
602                                    "workspace_update: {} -> {:?}",
603                                    name, self.variable_array[var_id]
604                                );
605                            }
606                        }
607                    }
608                }
609            } else {
610                for name in &assigned_this_execution {
611                    if let Some(var_id) =
612                        id_to_name
613                            .iter()
614                            .find_map(|(vid, n)| if n == name { Some(*vid) } else { None })
615                    {
616                        if var_id < self.variable_array.len() {
617                            self.workspace_values
618                                .insert(name.clone(), self.variable_array[var_id].clone());
619                        }
620                    }
621                }
622            }
623            self.function_definitions = updated_functions;
624        }
625
626        if self.verbose {
627            debug!("Execution completed in {execution_time_ms}ms (JIT: {used_jit})");
628        }
629
630        // Generate type info if we have a suppressed value
631        let type_info = suppressed_value.as_ref().map(format_type_info);
632
633        // Final fallback: if not suppressed and still no value, try last non-zero variable slot
634        if !is_semicolon_suppressed && result_value.is_none() {
635            if let Some(v) = self
636                .variable_array
637                .iter()
638                .rev()
639                .find(|v| !matches!(v, Value::Num(0.0)))
640                .cloned()
641            {
642                result_value = Some(v);
643            }
644        }
645
646        Ok(ExecutionResult {
647            value: result_value,
648            execution_time_ms,
649            used_jit,
650            error,
651            type_info,
652        })
653    }
654
655    /// Get execution statistics
656    pub fn stats(&self) -> &ExecutionStats {
657        &self.stats
658    }
659
660    /// Reset execution statistics
661    pub fn reset_stats(&mut self) {
662        self.stats = ExecutionStats::default();
663    }
664
665    /// Clear all variables in the REPL context
666    pub fn clear_variables(&mut self) {
667        self.variables.clear();
668        self.variable_array.clear();
669        self.variable_names.clear();
670        self.workspace_values.clear();
671    }
672
673    /// Get a copy of current variables
674    pub fn get_variables(&self) -> &HashMap<String, Value> {
675        &self.variables
676    }
677
678    /// Interpret bytecode with persistent variable context
679    fn interpret_with_context(
680        &mut self,
681        bytecode: &runmat_ignition::Bytecode,
682    ) -> Result<Vec<Value>, String> {
683        // Variable array should already be prepared by prepare_variable_array_for_execution
684
685        // Use the main Ignition interpreter which has full function and scoping support
686        match runmat_ignition::interpret_with_vars(
687            bytecode,
688            &mut self.variable_array,
689            Some("<repl>"),
690        ) {
691            Ok(result) => {
692                // Update the variables HashMap for display purposes
693                self.variables.clear();
694                for (i, value) in self.variable_array.iter().enumerate() {
695                    if !matches!(value, Value::Num(0.0)) {
696                        // Only store non-zero values to avoid clutter
697                        self.variables.insert(format!("var_{i}"), value.clone());
698                    }
699                }
700
701                Ok(result)
702            }
703            Err(e) => Err(e),
704        }
705    }
706
707    /// Prepare variable array for execution by populating with existing values
708    fn prepare_variable_array_for_execution(
709        &mut self,
710        bytecode: &runmat_ignition::Bytecode,
711        updated_var_mapping: &HashMap<String, usize>,
712        debug_trace: bool,
713    ) {
714        // Create a new variable array of the correct size
715        let max_var_id = updated_var_mapping.values().copied().max().unwrap_or(0);
716        let required_len = std::cmp::max(bytecode.var_count, max_var_id + 1);
717        let mut new_variable_array = vec![Value::Num(0.0); required_len];
718        if debug_trace {
719            println!(
720                "prepare: bytecode.var_count={} required_len={} max_var_id={}",
721                bytecode.var_count, required_len, max_var_id
722            );
723        }
724
725        // Populate with existing values based on the variable mapping
726        for (var_name, &new_var_id) in updated_var_mapping {
727            if new_var_id < new_variable_array.len() {
728                if let Some(value) = self.workspace_values.get(var_name) {
729                    if debug_trace {
730                        println!(
731                            "prepare: setting {} (var_id={}) -> {:?}",
732                            var_name, new_var_id, value
733                        );
734                    }
735                    new_variable_array[new_var_id] = value.clone();
736                }
737            } else if debug_trace {
738                println!(
739                    "prepare: skipping {} (var_id={}) because len={}",
740                    var_name,
741                    new_var_id,
742                    new_variable_array.len()
743                );
744            }
745        }
746
747        // Update our variable array and mapping
748        self.variable_array = new_variable_array;
749    }
750
751    /// Convert stored HIR function definitions to UserFunction format for compilation
752    fn convert_hir_functions_to_user_functions(
753        &self,
754    ) -> HashMap<String, runmat_ignition::UserFunction> {
755        let mut user_functions = HashMap::new();
756
757        for (name, hir_stmt) in &self.function_definitions {
758            if let runmat_hir::HirStmt::Function {
759                name: func_name,
760                params,
761                outputs,
762                body,
763                has_varargin: _,
764                has_varargout: _,
765            } = hir_stmt
766            {
767                // Use the existing HIR utilities to calculate variable count
768                let var_map =
769                    runmat_hir::remapping::create_complete_function_var_map(params, outputs, body);
770                let max_local_var = var_map.len();
771
772                let user_func = runmat_ignition::UserFunction {
773                    name: func_name.clone(),
774                    params: params.clone(),
775                    outputs: outputs.clone(),
776                    body: body.clone(),
777                    local_var_count: max_local_var,
778                    has_varargin: false,
779                    has_varargout: false,
780                    var_types: vec![Type::Unknown; max_local_var],
781                };
782                user_functions.insert(name.clone(), user_func);
783            }
784        }
785
786        user_functions
787    }
788
789    /// Configure garbage collector
790    pub fn configure_gc(&self, config: GcConfig) -> Result<()> {
791        gc_configure(config)
792            .map_err(|e| anyhow::anyhow!("Failed to configure garbage collector: {}", e))
793    }
794
795    /// Get GC statistics
796    pub fn gc_stats(&self) -> runmat_gc::GcStats {
797        gc_stats()
798    }
799
800    /// Show detailed system information
801    pub fn show_system_info(&self) {
802        println!("RunMat REPL Engine Status");
803        println!("==========================");
804        println!();
805
806        println!(
807            "JIT Compiler: {}",
808            if self.jit_engine.is_some() {
809                "Available"
810            } else {
811                "Disabled/Failed"
812            }
813        );
814        println!("Verbose Mode: {}", self.verbose);
815        println!();
816
817        println!("Execution Statistics:");
818        println!("  Total Executions: {}", self.stats.total_executions);
819        println!("  JIT Compiled: {}", self.stats.jit_compiled);
820        println!("  Interpreter Used: {}", self.stats.interpreter_fallback);
821        println!(
822            "  Average Time: {:.2}ms",
823            self.stats.average_execution_time_ms
824        );
825        println!();
826
827        let gc_stats = self.gc_stats();
828        println!("Garbage Collector:");
829        println!(
830            "  Total Allocations: {}",
831            gc_stats
832                .total_allocations
833                .load(std::sync::atomic::Ordering::Relaxed)
834        );
835        println!(
836            "  Minor Collections: {}",
837            gc_stats
838                .minor_collections
839                .load(std::sync::atomic::Ordering::Relaxed)
840        );
841        println!(
842            "  Major Collections: {}",
843            gc_stats
844                .major_collections
845                .load(std::sync::atomic::Ordering::Relaxed)
846        );
847        println!(
848            "  Current Memory: {:.2} MB",
849            gc_stats
850                .current_memory_usage
851                .load(std::sync::atomic::Ordering::Relaxed) as f64
852                / 1024.0
853                / 1024.0
854        );
855        println!();
856    }
857}
858
859impl Default for ReplEngine {
860    fn default() -> Self {
861        Self::new().expect("Failed to create default REPL engine")
862    }
863}
864
865/// Tokenize the input string and return a space separated string of token names.
866/// This is kept for backward compatibility with existing tests.
867pub fn format_tokens(input: &str) -> String {
868    tokenize_detailed(input)
869        .into_iter()
870        .map(|t| format!("{:?}", t.token))
871        .collect::<Vec<_>>()
872        .join(" ")
873}
874
875/// Execute MATLAB/Octave code and return the result as a formatted string
876pub fn execute_and_format(input: &str) -> String {
877    match ReplEngine::new() {
878        Ok(mut engine) => match engine.execute(input) {
879            Ok(result) => {
880                if let Some(error) = result.error {
881                    format!("Error: {error}")
882                } else if let Some(value) = result.value {
883                    format!("{value:?}")
884                } else {
885                    "".to_string()
886                }
887            }
888            Err(e) => format!("Error: {e}"),
889        },
890        Err(e) => format!("Engine Error: {e}"),
891    }
892}