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
15pub struct ReplEngine {
17 jit_engine: Option<TurbineEngine>,
19 verbose: bool,
21 stats: ExecutionStats,
23 variables: HashMap<String, Value>,
25 variable_array: Vec<Value>,
27 variable_names: HashMap<String, usize>,
29 workspace_values: HashMap<String, Value>,
31 function_definitions: HashMap<String, runmat_hir::HirStmt>,
33 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 pub type_info: Option<String>,
54}
55
56fn 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 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 pub fn new() -> Result<Self> {
116 Self::with_options(true, false) }
118
119 pub fn with_options(enable_jit: bool, verbose: bool) -> Result<Self> {
121 Self::with_snapshot(enable_jit, verbose, None::<&str>)
122 }
123
124 pub fn with_snapshot<P: AsRef<Path>>(
126 enable_jit: bool,
127 verbose: bool,
128 snapshot_path: Option<P>,
129 ) -> Result<Self> {
130 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 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 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 pub fn has_snapshot(&self) -> bool {
205 self.snapshot.is_some()
206 }
207
208 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 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 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 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 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; let mut suppressed_value: Option<Value> = None; let mut error = None;
292
293 let is_expression_stmt = bytecode
295 .instructions
296 .last()
297 .map(|instr| matches!(instr, runmat_ignition::Instr::Pop))
298 .unwrap_or(false);
299
300 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 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 if let Some(ref mut jit_engine) = &mut self.jit_engine {
350 if !is_expression_stmt {
351 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 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 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 }
426 }
427 }
428 }
429
430 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 let mut execution_bytecode = bytecode.clone();
442 if is_expression_stmt && !execution_bytecode.instructions.is_empty() {
443 execution_bytecode.instructions.pop(); 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; 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 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 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 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 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; 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 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 let type_info = suppressed_value.as_ref().map(format_type_info);
632
633 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 pub fn stats(&self) -> &ExecutionStats {
657 &self.stats
658 }
659
660 pub fn reset_stats(&mut self) {
662 self.stats = ExecutionStats::default();
663 }
664
665 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 pub fn get_variables(&self) -> &HashMap<String, Value> {
675 &self.variables
676 }
677
678 fn interpret_with_context(
680 &mut self,
681 bytecode: &runmat_ignition::Bytecode,
682 ) -> Result<Vec<Value>, String> {
683 match runmat_ignition::interpret_with_vars(
687 bytecode,
688 &mut self.variable_array,
689 Some("<repl>"),
690 ) {
691 Ok(result) => {
692 self.variables.clear();
694 for (i, value) in self.variable_array.iter().enumerate() {
695 if !matches!(value, Value::Num(0.0)) {
696 self.variables.insert(format!("var_{i}"), value.clone());
698 }
699 }
700
701 Ok(result)
702 }
703 Err(e) => Err(e),
704 }
705 }
706
707 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 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 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 self.variable_array = new_variable_array;
749 }
750
751 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 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 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 pub fn gc_stats(&self) -> runmat_gc::GcStats {
797 gc_stats()
798 }
799
800 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
865pub 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
875pub 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}