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        let mut ans_update: Option<(usize, Value)> = None;
293
294        // Check if this is an expression statement (ends with Pop)
295        let is_expression_stmt = bytecode
296            .instructions
297            .last()
298            .map(|instr| matches!(instr, runmat_ignition::Instr::Pop))
299            .unwrap_or(false);
300
301        // Detect whether the user's input ends with a semicolon at the token level
302        let ends_with_semicolon = {
303            let toks = tokenize_detailed(input);
304            toks.into_iter()
305                .rev()
306                .map(|t| t.token)
307                .find(|_| true)
308                .map(|t| matches!(t, LexToken::Semicolon))
309                .unwrap_or(false)
310        };
311
312        // Check if this is a semicolon-suppressed statement (expression or assignment)
313        // Control flow statements never return values regardless of semicolons
314        let is_semicolon_suppressed = if hir.body.len() == 1 {
315            match &hir.body[0] {
316                runmat_hir::HirStmt::ExprStmt(_, _) => ends_with_semicolon,
317                runmat_hir::HirStmt::Assign(_, _, _) => ends_with_semicolon,
318                runmat_hir::HirStmt::If { .. }
319                | runmat_hir::HirStmt::While { .. }
320                | runmat_hir::HirStmt::For { .. }
321                | runmat_hir::HirStmt::Break
322                | runmat_hir::HirStmt::Continue
323                | runmat_hir::HirStmt::Return
324                | runmat_hir::HirStmt::Function { .. }
325                | runmat_hir::HirStmt::MultiAssign(_, _, _)
326                | runmat_hir::HirStmt::AssignLValue(_, _, _)
327                | runmat_hir::HirStmt::Switch { .. }
328                | runmat_hir::HirStmt::TryCatch { .. }
329                | runmat_hir::HirStmt::Global(_)
330                | runmat_hir::HirStmt::Persistent(_)
331                | runmat_hir::HirStmt::Import {
332                    path: _,
333                    wildcard: _,
334                }
335                | runmat_hir::HirStmt::ClassDef { .. } => true,
336            }
337        } else {
338            false
339        };
340
341        if self.verbose {
342            debug!("HIR body len: {}", hir.body.len());
343            if !hir.body.is_empty() {
344                debug!("HIR statement: {:?}", &hir.body[0]);
345            }
346            debug!("is_semicolon_suppressed: {is_semicolon_suppressed}");
347        }
348
349        // Use JIT for assignments, interpreter for expressions (to capture results properly)
350        if let Some(ref mut jit_engine) = &mut self.jit_engine {
351            if !is_expression_stmt {
352                // Ensure variable array is large enough
353                if self.variable_array.len() < bytecode.var_count {
354                    self.variable_array
355                        .resize(bytecode.var_count, Value::Num(0.0));
356                }
357
358                if self.verbose {
359                    debug!(
360                        "JIT path for assignment: variable_array size: {}, bytecode.var_count: {}",
361                        self.variable_array.len(),
362                        bytecode.var_count
363                    );
364                }
365
366                // Use JIT for assignments
367                match jit_engine.execute_or_compile(&bytecode, &mut self.variable_array) {
368                    Ok((_, actual_used_jit)) => {
369                        used_jit = actual_used_jit;
370                        if actual_used_jit {
371                            self.stats.jit_compiled += 1;
372                        } else {
373                            self.stats.interpreter_fallback += 1;
374                        }
375                        // For assignments, capture the assigned value for both display and type info
376                        // Prefer the variable slot indicated by HIR if available.
377                        let assignment_value =
378                            if let Some(runmat_hir::HirStmt::Assign(var_id, _, _)) =
379                                hir.body.first()
380                            {
381                                if let Some(name) = id_to_name.get(&var_id.0) {
382                                    assigned_this_execution.insert(name.clone());
383                                }
384                                if var_id.0 < self.variable_array.len() {
385                                    Some(self.variable_array[var_id.0].clone())
386                                } else {
387                                    None
388                                }
389                            } else {
390                                self.variable_array
391                                    .iter()
392                                    .rev()
393                                    .find(|v| !matches!(v, Value::Num(0.0)))
394                                    .cloned()
395                            };
396
397                        if !is_semicolon_suppressed {
398                            result_value = assignment_value.clone();
399                            if self.verbose {
400                                debug!("JIT assignment result: {result_value:?}");
401                            }
402                        } else {
403                            suppressed_value = assignment_value;
404                            if self.verbose {
405                                debug!("JIT assignment suppressed due to semicolon, captured for type info");
406                            }
407                        }
408
409                        if self.verbose {
410                            debug!(
411                                "{} assignment successful, variable_array: {:?}",
412                                if actual_used_jit {
413                                    "JIT"
414                                } else {
415                                    "Interpreter"
416                                },
417                                self.variable_array
418                            );
419                        }
420                    }
421                    Err(e) => {
422                        if self.verbose {
423                            debug!("JIT execution failed: {e}, using interpreter");
424                        }
425                        // Fall back to interpreter
426                    }
427                }
428            }
429        }
430
431        // Use interpreter if JIT failed or is disabled
432        if !used_jit {
433            if self.verbose {
434                debug!(
435                    "Interpreter path: variable_array size: {}, bytecode.var_count: {}",
436                    self.variable_array.len(),
437                    bytecode.var_count
438                );
439            }
440
441            // For expressions, modify bytecode to store result in a temp variable instead of using stack
442            let mut execution_bytecode = bytecode.clone();
443            if is_expression_stmt && !execution_bytecode.instructions.is_empty() {
444                execution_bytecode.instructions.pop(); // Remove the Pop instruction
445
446                // Add StoreVar instruction to store the result in a temporary variable
447                let temp_var_id = std::cmp::max(execution_bytecode.var_count, max_var_id + 1);
448                execution_bytecode
449                    .instructions
450                    .push(runmat_ignition::Instr::StoreVar(temp_var_id));
451                execution_bytecode.var_count = temp_var_id + 1; // Expand variable count for temp variable
452
453                // Ensure our variable array can hold the temporary variable
454                if self.variable_array.len() <= temp_var_id {
455                    self.variable_array.resize(temp_var_id + 1, Value::Num(0.0));
456                }
457
458                if self.verbose {
459                    debug!(
460                        "Modified expression bytecode, new instructions: {:?}",
461                        execution_bytecode.instructions
462                    );
463                }
464            }
465
466            match self.interpret_with_context(&execution_bytecode) {
467                Ok(results) => {
468                    // Only increment interpreter_fallback if JIT wasn't attempted
469                    if self.jit_engine.is_none() || is_expression_stmt {
470                        self.stats.interpreter_fallback += 1;
471                    }
472                    if self.verbose {
473                        debug!("Interpreter results: {results:?}");
474                    }
475
476                    // Handle assignment statements (x = 42 should show the assigned value unless suppressed)
477                    if hir.body.len() == 1 {
478                        if let runmat_hir::HirStmt::Assign(var_id, _, _) = &hir.body[0] {
479                            if self.verbose {
480                                debug!(
481                                    "Assignment detected, var_id: {}, ends_with_semicolon: {}",
482                                    var_id.0, ends_with_semicolon
483                                );
484                            }
485                            if let Some(name) = id_to_name.get(&var_id.0) {
486                                assigned_this_execution.insert(name.clone());
487                            }
488                            // For assignments, capture the assigned value for both display and type info
489                            if var_id.0 < self.variable_array.len() {
490                                let assignment_value = self.variable_array[var_id.0].clone();
491                                if !is_semicolon_suppressed {
492                                    result_value = Some(assignment_value);
493                                    if self.verbose {
494                                        debug!("Interpreter assignment result: {result_value:?}");
495                                    }
496                                } else {
497                                    suppressed_value = Some(assignment_value);
498                                    if self.verbose {
499                                        debug!("Interpreter assignment suppressed due to semicolon, captured for type info");
500                                    }
501                                }
502                            }
503                        } else if !is_expression_stmt
504                            && !results.is_empty()
505                            && !is_semicolon_suppressed
506                        {
507                            result_value = Some(results[0].clone());
508                        }
509                    }
510
511                    // For expressions, get the result from the temporary variable (capture for both display and type info)
512                    if is_expression_stmt
513                        && !execution_bytecode.instructions.is_empty()
514                        && result_value.is_none()
515                        && suppressed_value.is_none()
516                    {
517                        let temp_var_id = execution_bytecode.var_count - 1; // The temp variable we added
518                        if temp_var_id < self.variable_array.len() {
519                            let expression_value = self.variable_array[temp_var_id].clone();
520                            // Capture for 'ans' update
521                            ans_update = Some((temp_var_id, expression_value.clone()));
522
523                            if !is_semicolon_suppressed {
524                                result_value = Some(expression_value);
525                                if self.verbose {
526                                    debug!("Expression result from temp var {temp_var_id}: {result_value:?}");
527                                }
528                            } else {
529                                suppressed_value = Some(expression_value);
530                                if self.verbose {
531                                    debug!("Expression suppressed, captured for type info from temp var {temp_var_id}: {suppressed_value:?}");
532                                }
533                            }
534                        }
535                    } else if !is_semicolon_suppressed && result_value.is_none() {
536                        result_value = results.into_iter().last();
537                        if self.verbose {
538                            debug!("Fallback result from interpreter: {result_value:?}");
539                        }
540                    }
541
542                    if self.verbose {
543                        debug!("Final result_value: {result_value:?}");
544                    }
545                    debug!(
546                        "Interpreter execution successful, variable_array: {:?}",
547                        self.variable_array
548                    );
549                }
550                Err(e) => {
551                    debug!("Interpreter execution failed: {e}");
552                    error = Some(format!("Execution failed: {e}"));
553                }
554            }
555        }
556
557        let execution_time = start_time.elapsed();
558        let execution_time_ms = execution_time.as_millis() as u64;
559
560        self.stats.total_execution_time_ms += execution_time_ms;
561        self.stats.average_execution_time_ms =
562            self.stats.total_execution_time_ms as f64 / self.stats.total_executions as f64;
563
564        // Update variable names mapping and function definitions if execution was successful
565        if error.is_none() {
566            if let Some((mutated_names, assigned)) = runmat_ignition::take_updated_workspace_state()
567            {
568                if debug_trace {
569                    println!("mutated_names: {:?}", mutated_names);
570                    println!("assigned_returned: {:?}", assigned);
571                }
572                self.variable_names = mutated_names.clone();
573                let mut new_assigned: HashSet<String> = assigned
574                    .difference(&prev_assigned_snapshot)
575                    .cloned()
576                    .collect();
577                new_assigned.extend(assigned_this_execution.iter().cloned());
578                for (name, var_id) in &mutated_names {
579                    if *var_id >= self.variable_array.len() {
580                        continue;
581                    }
582                    let new_value = &self.variable_array[*var_id];
583                    let changed = match self.workspace_values.get(name) {
584                        Some(old_value) => old_value != new_value,
585                        None => true,
586                    };
587                    if changed {
588                        new_assigned.insert(name.clone());
589                    }
590                }
591                if debug_trace {
592                    println!("new_assigned: {:?}", new_assigned);
593                }
594                for name in new_assigned {
595                    let var_id = mutated_names.get(&name).copied().or_else(|| {
596                        id_to_name
597                            .iter()
598                            .find_map(|(vid, n)| if n == &name { Some(*vid) } else { None })
599                    });
600                    if let Some(var_id) = var_id {
601                        if var_id < self.variable_array.len() {
602                            self.workspace_values
603                                .insert(name.clone(), self.variable_array[var_id].clone());
604                            if debug_trace {
605                                println!(
606                                    "workspace_update: {} -> {:?}",
607                                    name, self.variable_array[var_id]
608                                );
609                            }
610                        }
611                    }
612                }
613            } else {
614                for name in &assigned_this_execution {
615                    if let Some(var_id) =
616                        id_to_name
617                            .iter()
618                            .find_map(|(vid, n)| if n == name { Some(*vid) } else { None })
619                    {
620                        if var_id < self.variable_array.len() {
621                            self.workspace_values
622                                .insert(name.clone(), self.variable_array[var_id].clone());
623                        }
624                    }
625                }
626            }
627            self.function_definitions = updated_functions;
628
629            // Apply 'ans' update if applicable (persisting expression result)
630            if let Some((var_id, value)) = ans_update {
631                self.variable_names.insert("ans".to_string(), var_id);
632                self.workspace_values.insert("ans".to_string(), value);
633                if debug_trace {
634                    println!("Updated 'ans' to var_id {}", var_id);
635                }
636            }
637        }
638
639        if self.verbose {
640            debug!("Execution completed in {execution_time_ms}ms (JIT: {used_jit})");
641        }
642
643        // Generate type info if we have a suppressed value
644        let type_info = suppressed_value.as_ref().map(format_type_info);
645
646        // Final fallback: if not suppressed and still no value, try last non-zero variable slot
647        if !is_semicolon_suppressed && result_value.is_none() {
648            if let Some(v) = self
649                .variable_array
650                .iter()
651                .rev()
652                .find(|v| !matches!(v, Value::Num(0.0)))
653                .cloned()
654            {
655                result_value = Some(v);
656            }
657        }
658
659        Ok(ExecutionResult {
660            value: result_value,
661            execution_time_ms,
662            used_jit,
663            error,
664            type_info,
665        })
666    }
667
668    /// Get execution statistics
669    pub fn stats(&self) -> &ExecutionStats {
670        &self.stats
671    }
672
673    /// Reset execution statistics
674    pub fn reset_stats(&mut self) {
675        self.stats = ExecutionStats::default();
676    }
677
678    /// Clear all variables in the REPL context
679    pub fn clear_variables(&mut self) {
680        self.variables.clear();
681        self.variable_array.clear();
682        self.variable_names.clear();
683        self.workspace_values.clear();
684    }
685
686    /// Get a copy of current variables
687    pub fn get_variables(&self) -> &HashMap<String, Value> {
688        &self.variables
689    }
690
691    /// Interpret bytecode with persistent variable context
692    fn interpret_with_context(
693        &mut self,
694        bytecode: &runmat_ignition::Bytecode,
695    ) -> Result<Vec<Value>, String> {
696        // Variable array should already be prepared by prepare_variable_array_for_execution
697
698        // Use the main Ignition interpreter which has full function and scoping support
699        match runmat_ignition::interpret_with_vars(
700            bytecode,
701            &mut self.variable_array,
702            Some("<repl>"),
703        ) {
704            Ok(result) => {
705                // Update the variables HashMap for display purposes
706                self.variables.clear();
707                for (i, value) in self.variable_array.iter().enumerate() {
708                    if !matches!(value, Value::Num(0.0)) {
709                        // Only store non-zero values to avoid clutter
710                        self.variables.insert(format!("var_{i}"), value.clone());
711                    }
712                }
713
714                Ok(result)
715            }
716            Err(e) => Err(e),
717        }
718    }
719
720    /// Prepare variable array for execution by populating with existing values
721    fn prepare_variable_array_for_execution(
722        &mut self,
723        bytecode: &runmat_ignition::Bytecode,
724        updated_var_mapping: &HashMap<String, usize>,
725        debug_trace: bool,
726    ) {
727        // Create a new variable array of the correct size
728        let max_var_id = updated_var_mapping.values().copied().max().unwrap_or(0);
729        let required_len = std::cmp::max(bytecode.var_count, max_var_id + 1);
730        let mut new_variable_array = vec![Value::Num(0.0); required_len];
731        if debug_trace {
732            println!(
733                "prepare: bytecode.var_count={} required_len={} max_var_id={}",
734                bytecode.var_count, required_len, max_var_id
735            );
736        }
737
738        // Populate with existing values based on the variable mapping
739        for (var_name, &new_var_id) in updated_var_mapping {
740            if new_var_id < new_variable_array.len() {
741                if let Some(value) = self.workspace_values.get(var_name) {
742                    if debug_trace {
743                        println!(
744                            "prepare: setting {} (var_id={}) -> {:?}",
745                            var_name, new_var_id, value
746                        );
747                    }
748                    new_variable_array[new_var_id] = value.clone();
749                }
750            } else if debug_trace {
751                println!(
752                    "prepare: skipping {} (var_id={}) because len={}",
753                    var_name,
754                    new_var_id,
755                    new_variable_array.len()
756                );
757            }
758        }
759
760        // Update our variable array and mapping
761        self.variable_array = new_variable_array;
762    }
763
764    /// Convert stored HIR function definitions to UserFunction format for compilation
765    fn convert_hir_functions_to_user_functions(
766        &self,
767    ) -> HashMap<String, runmat_ignition::UserFunction> {
768        let mut user_functions = HashMap::new();
769
770        for (name, hir_stmt) in &self.function_definitions {
771            if let runmat_hir::HirStmt::Function {
772                name: func_name,
773                params,
774                outputs,
775                body,
776                has_varargin: _,
777                has_varargout: _,
778            } = hir_stmt
779            {
780                // Use the existing HIR utilities to calculate variable count
781                let var_map =
782                    runmat_hir::remapping::create_complete_function_var_map(params, outputs, body);
783                let max_local_var = var_map.len();
784
785                let user_func = runmat_ignition::UserFunction {
786                    name: func_name.clone(),
787                    params: params.clone(),
788                    outputs: outputs.clone(),
789                    body: body.clone(),
790                    local_var_count: max_local_var,
791                    has_varargin: false,
792                    has_varargout: false,
793                    var_types: vec![Type::Unknown; max_local_var],
794                };
795                user_functions.insert(name.clone(), user_func);
796            }
797        }
798
799        user_functions
800    }
801
802    /// Configure garbage collector
803    pub fn configure_gc(&self, config: GcConfig) -> Result<()> {
804        gc_configure(config)
805            .map_err(|e| anyhow::anyhow!("Failed to configure garbage collector: {}", e))
806    }
807
808    /// Get GC statistics
809    pub fn gc_stats(&self) -> runmat_gc::GcStats {
810        gc_stats()
811    }
812
813    /// Show detailed system information
814    pub fn show_system_info(&self) {
815        println!("RunMat REPL Engine Status");
816        println!("==========================");
817        println!();
818
819        println!(
820            "JIT Compiler: {}",
821            if self.jit_engine.is_some() {
822                "Available"
823            } else {
824                "Disabled/Failed"
825            }
826        );
827        println!("Verbose Mode: {}", self.verbose);
828        println!();
829
830        println!("Execution Statistics:");
831        println!("  Total Executions: {}", self.stats.total_executions);
832        println!("  JIT Compiled: {}", self.stats.jit_compiled);
833        println!("  Interpreter Used: {}", self.stats.interpreter_fallback);
834        println!(
835            "  Average Time: {:.2}ms",
836            self.stats.average_execution_time_ms
837        );
838        println!();
839
840        let gc_stats = self.gc_stats();
841        println!("Garbage Collector:");
842        println!(
843            "  Total Allocations: {}",
844            gc_stats
845                .total_allocations
846                .load(std::sync::atomic::Ordering::Relaxed)
847        );
848        println!(
849            "  Minor Collections: {}",
850            gc_stats
851                .minor_collections
852                .load(std::sync::atomic::Ordering::Relaxed)
853        );
854        println!(
855            "  Major Collections: {}",
856            gc_stats
857                .major_collections
858                .load(std::sync::atomic::Ordering::Relaxed)
859        );
860        println!(
861            "  Current Memory: {:.2} MB",
862            gc_stats
863                .current_memory_usage
864                .load(std::sync::atomic::Ordering::Relaxed) as f64
865                / 1024.0
866                / 1024.0
867        );
868        println!();
869    }
870}
871
872impl Default for ReplEngine {
873    fn default() -> Self {
874        Self::new().expect("Failed to create default REPL engine")
875    }
876}
877
878/// Tokenize the input string and return a space separated string of token names.
879/// This is kept for backward compatibility with existing tests.
880pub fn format_tokens(input: &str) -> String {
881    tokenize_detailed(input)
882        .into_iter()
883        .map(|t| format!("{:?}", t.token))
884        .collect::<Vec<_>>()
885        .join(" ")
886}
887
888/// Execute MATLAB/Octave code and return the result as a formatted string
889pub fn execute_and_format(input: &str) -> String {
890    match ReplEngine::new() {
891        Ok(mut engine) => match engine.execute(input) {
892            Ok(result) => {
893                if let Some(error) = result.error {
894                    format!("Error: {error}")
895                } else if let Some(value) = result.value {
896                    format!("{value:?}")
897                } else {
898                    "".to_string()
899                }
900            }
901            Err(e) => format!("Error: {e}"),
902        },
903        Err(e) => format!("Engine Error: {e}"),
904    }
905}