1use super::*;
2
3impl RunMatSession {
4 pub async fn execute(&mut self, input: &str) -> std::result::Result<ExecutionResult, RunError> {
6 self.run(input).await
7 }
8
9 pub async fn run(&mut self, input: &str) -> std::result::Result<ExecutionResult, RunError> {
11 let _active = ActiveExecutionGuard::new(self).map_err(|err| {
12 RunError::Runtime(
13 build_runtime_error(err.to_string())
14 .with_identifier("RunMat:ExecutionAlreadyActive")
15 .build(),
16 )
17 })?;
18 runmat_vm::set_call_stack_limit(self.callstack_limit);
19 runmat_vm::set_error_namespace(&self.error_namespace);
20 runmat_hir::set_error_namespace(&self.error_namespace);
21 let exec_span = info_span!(
22 "runtime.execute",
23 input_len = input.len(),
24 verbose = self.verbose
25 );
26 let _exec_guard = exec_span.enter();
27 runmat_runtime::console::reset_thread_buffer();
28 runmat_runtime::plotting_hooks::reset_recent_figures();
29 runmat_runtime::warning_store::reset();
30 runmat_builtins::set_display_format(self.format_mode);
31 reset_provider_telemetry();
32 self.interrupt_flag.store(false, Ordering::Relaxed);
33 let _interrupt_guard =
34 runmat_runtime::interrupt::replace_interrupt(Some(self.interrupt_flag.clone()));
35 let start_time = Instant::now();
36 self.stats.total_executions += 1;
37 let debug_trace = std::env::var("RUNMAT_DEBUG_REPL").is_ok();
38 let stdin_events: Arc<Mutex<Vec<StdinEvent>>> = Arc::new(Mutex::new(Vec::new()));
39 let host_async_handler = self.async_input_handler.clone();
40 let stdin_events_async = Arc::clone(&stdin_events);
41 let runtime_async_handler: Arc<runmat_runtime::interaction::AsyncInteractionHandler> =
42 Arc::new(
43 move |prompt: runmat_runtime::interaction::InteractionPromptOwned| {
44 let request_kind = match prompt.kind {
45 runmat_runtime::interaction::InteractionKind::Line { echo } => {
46 InputRequestKind::Line { echo }
47 }
48 runmat_runtime::interaction::InteractionKind::KeyPress => {
49 InputRequestKind::KeyPress
50 }
51 };
52 let request = InputRequest {
53 prompt: prompt.prompt,
54 kind: request_kind,
55 };
56 let (event_kind, echo_flag) = match &request.kind {
57 InputRequestKind::Line { echo } => (StdinEventKind::Line, *echo),
58 InputRequestKind::KeyPress => (StdinEventKind::KeyPress, false),
59 };
60 let mut event = StdinEvent {
61 prompt: request.prompt.clone(),
62 kind: event_kind,
63 echo: echo_flag,
64 value: None,
65 error: None,
66 };
67
68 let stdin_events_async = Arc::clone(&stdin_events_async);
69 let host_async_handler = host_async_handler.clone();
70 Box::pin(async move {
71 let resp: Result<InputResponse, String> =
72 if let Some(handler) = host_async_handler {
73 handler(request).await
74 } else {
75 match &request.kind {
76 InputRequestKind::Line { echo } => {
77 runmat_runtime::interaction::default_read_line(
78 &request.prompt,
79 *echo,
80 )
81 .map(InputResponse::Line)
82 }
83 InputRequestKind::KeyPress => {
84 runmat_runtime::interaction::default_wait_for_key(
85 &request.prompt,
86 )
87 .map(|_| InputResponse::KeyPress)
88 }
89 }
90 };
91
92 let resp = resp.inspect_err(|err| {
93 event.error = Some(err.clone());
94 if let Ok(mut guard) = stdin_events_async.lock() {
95 guard.push(event.clone());
96 }
97 })?;
98
99 let interaction_resp = match resp {
100 InputResponse::Line(value) => {
101 event.value = Some(value.clone());
102 if let Ok(mut guard) = stdin_events_async.lock() {
103 guard.push(event);
104 }
105 runmat_runtime::interaction::InteractionResponse::Line(value)
106 }
107 InputResponse::KeyPress => {
108 if let Ok(mut guard) = stdin_events_async.lock() {
109 guard.push(event);
110 }
111 runmat_runtime::interaction::InteractionResponse::KeyPress
112 }
113 };
114 Ok(interaction_resp)
115 })
116 },
117 );
118 let _async_input_guard =
119 runmat_runtime::interaction::replace_async_handler(Some(runtime_async_handler));
120
121 let compat = self.compat_mode;
136 let _eval_hook_guard =
137 runmat_runtime::interaction::replace_eval_hook(Some(std::sync::Arc::new(
138 move |expr: String| -> runmat_runtime::interaction::EvalHookFuture {
139 async fn eval_expr(
142 expr: String,
143 compat: runmat_parser::CompatMode,
144 ) -> Result<Value, RuntimeError> {
145 let wrapped = format!("__runmat_input_result__ = ({expr});");
146 let ast = parse_with_options(&wrapped, ParserOptions::new(compat))
147 .map_err(|e| {
148 build_runtime_error(format!("input: parse error: {e}"))
149 .with_identifier("RunMat:input:ParseError")
150 .build()
151 })?;
152 let lowering = runmat_hir::lower(
153 &ast,
154 &LoweringContext::new(&HashMap::new(), &HashMap::new()),
155 )
156 .map_err(|e| {
157 build_runtime_error(format!("input: lowering error: {e}"))
158 .with_identifier("RunMat:input:LowerError")
159 .build()
160 })?;
161 let result_idx = lowering.variables.get("__runmat_input_result__").copied();
162 let bc = runmat_vm::compile(&lowering.hir, &HashMap::new())
163 .map_err(RuntimeError::from)?;
164 let vars = runmat_vm::interpret(&bc).await?;
165 result_idx
166 .and_then(|idx| vars.get(idx).cloned())
167 .ok_or_else(|| {
168 build_runtime_error("input: expression produced no value")
169 .with_identifier("RunMat:input:NoValue")
170 .build()
171 })
172 }
173
174 #[cfg(target_arch = "wasm32")]
175 {
176 Box::pin(eval_expr(expr, compat))
180 }
181
182 #[cfg(not(target_arch = "wasm32"))]
183 {
184 let (tx, rx) = tokio::sync::oneshot::channel();
190 let spawn_result = std::thread::Builder::new()
191 .stack_size(16 * 1024 * 1024)
192 .spawn(move || {
193 let result = futures::executor::block_on(eval_expr(expr, compat));
194 let _ = tx.send(result);
195 });
196 Box::pin(async move {
197 spawn_result.map_err(|err| {
198 build_runtime_error(format!(
199 "input: failed to spawn eval thread: {err}"
200 ))
201 .with_identifier("RunMat:input:EvalThreadSpawnFailed")
202 .build()
203 })?;
204 rx.await.unwrap_or_else(|_| {
205 Err(build_runtime_error("input: eval thread panicked")
206 .with_identifier("RunMat:input:EvalThreadPanic")
207 .build())
208 })
209 })
210 }
211 },
212 )));
213
214 if self.verbose {
215 debug!("Executing: {}", input.trim());
216 }
217
218 let _source_guard = runmat_runtime::source_context::replace_current_source(Some(input));
219
220 let PreparedExecution {
221 ast,
222 lowering,
223 mut bytecode,
224 } = self.compile_input(input)?;
225 if self.verbose {
226 debug!("AST: {ast:?}");
227 }
228 let (hir, updated_vars, updated_functions, var_names_map) = (
229 lowering.hir,
230 lowering.variables,
231 lowering.functions,
232 lowering.var_names,
233 );
234 let max_var_id = updated_vars.values().copied().max().unwrap_or(0);
235 if debug_trace {
236 debug!(?updated_vars, "[repl] updated_vars");
237 }
238 if debug_trace {
239 debug!(workspace_values_before = ?self.workspace_values, "[repl] workspace snapshot before execution");
240 }
241 let id_to_name: HashMap<usize, String> = var_names_map
242 .iter()
243 .map(|(var_id, name)| (var_id.0, name.clone()))
244 .collect();
245 let mut assigned_this_execution: HashSet<String> = HashSet::new();
246 let mut removed_this_execution: HashSet<String> = HashSet::new();
247 let assigned_snapshot: HashSet<String> = updated_vars
248 .keys()
249 .filter(|name| self.workspace_values.contains_key(name.as_str()))
250 .cloned()
251 .collect();
252 let prev_assigned_snapshot = assigned_snapshot.clone();
253 if debug_trace {
254 debug!(?assigned_snapshot, "[repl] assigned snapshot");
255 }
256 let _pending_workspace_guard =
257 runmat_vm::push_pending_workspace(updated_vars.clone(), assigned_snapshot.clone());
258 if self.verbose {
259 debug!("HIR generated successfully");
260 }
261
262 let (single_assign_var, single_stmt_non_assign) = if hir.body.len() == 1 {
263 match &hir.body[0] {
264 runmat_hir::HirStmt::Assign(var_id, _, _, _) => (Some(var_id.0), false),
265 _ => (None, true),
266 }
267 } else {
268 (None, false)
269 };
270
271 bytecode.var_names = id_to_name.clone();
272 if self.verbose {
273 debug!(
274 "Bytecode compiled: {} instructions",
275 bytecode.instructions.len()
276 );
277 }
278
279 #[cfg(not(target_arch = "wasm32"))]
280 let fusion_snapshot = if self.emit_fusion_plan {
281 build_fusion_snapshot(bytecode.accel_graph.as_ref(), &bytecode.fusion_groups)
282 } else {
283 None
284 };
285 #[cfg(target_arch = "wasm32")]
286 let fusion_snapshot: Option<FusionPlanSnapshot> = None;
287
288 self.prepare_variable_array_for_execution(&bytecode, &updated_vars, debug_trace);
290
291 if self.verbose {
292 debug!(
293 "Variable array after preparation: {:?}",
294 self.variable_array
295 );
296 debug!("Updated variable mapping: {updated_vars:?}");
297 debug!("Bytecode instructions: {:?}", bytecode.instructions);
298 }
299
300 #[cfg(feature = "jit")]
301 let mut used_jit = false;
302 #[cfg(not(feature = "jit"))]
303 let used_jit = false;
304 #[cfg(feature = "jit")]
305 let mut execution_completed = false;
306 #[cfg(not(feature = "jit"))]
307 let execution_completed = false;
308 let mut result_value: Option<Value> = None; let mut suppressed_value: Option<Value> = None; let mut error = None;
311 let mut workspace_updates: Vec<WorkspaceEntry> = Vec::new();
312 let mut workspace_snapshot_force_full = false;
313 let mut ans_update: Option<(usize, Value)> = None;
314
315 let is_expression_stmt = bytecode
317 .instructions
318 .last()
319 .map(|instr| matches!(instr, runmat_vm::Instr::Pop))
320 .unwrap_or(false);
321
322 let is_semicolon_suppressed = {
324 let toks = tokenize_detailed(input);
325 toks.into_iter()
326 .rev()
327 .map(|t| t.token)
328 .find(|token| {
329 !matches!(
330 token,
331 LexToken::Newline
332 | LexToken::LineComment
333 | LexToken::BlockComment
334 | LexToken::Section
335 )
336 })
337 .map(|t| matches!(t, LexToken::Semicolon))
338 .unwrap_or(false)
339 };
340 let final_stmt_emit = last_displayable_statement_emit_disposition(&hir.body);
341
342 if self.verbose {
343 debug!("HIR body len: {}", hir.body.len());
344 if !hir.body.is_empty() {
345 debug!("HIR statement: {:?}", &hir.body[0]);
346 }
347 debug!("is_semicolon_suppressed: {is_semicolon_suppressed}");
348 }
349
350 #[cfg(feature = "jit")]
352 {
353 if let Some(ref mut jit_engine) = &mut self.jit_engine {
354 if !is_expression_stmt {
355 if self.variable_array.len() < bytecode.var_count {
357 self.variable_array
358 .resize(bytecode.var_count, Value::Num(0.0));
359 }
360
361 if self.verbose {
362 debug!(
363 "JIT path for assignment: variable_array size: {}, bytecode.var_count: {}",
364 self.variable_array.len(),
365 bytecode.var_count
366 );
367 }
368
369 match jit_engine
371 .execute_or_compile_with_workspace(&bytecode, &mut self.variable_array)
372 {
373 Ok((_, actual_used_jit)) => {
374 used_jit = actual_used_jit;
375 execution_completed = true;
376 if actual_used_jit {
377 self.stats.jit_compiled += 1;
378 } else {
379 self.stats.interpreter_fallback += 1;
380 }
381 if let Some(var_id) = single_assign_var {
382 if var_id < self.variable_array.len() {
383 let assignment_value = self.variable_array[var_id].clone();
384 if !is_semicolon_suppressed {
385 result_value = Some(assignment_value);
386 if self.verbose {
387 debug!("JIT assignment result: {result_value:?}");
388 }
389 } else {
390 suppressed_value = Some(assignment_value);
391 if self.verbose {
392 debug!("JIT assignment suppressed due to semicolon, captured for type info");
393 }
394 }
395 }
396 }
397
398 if self.verbose {
399 debug!(
400 "{} assignment successful, variable_array: {:?}",
401 if actual_used_jit {
402 "JIT"
403 } else {
404 "Interpreter"
405 },
406 self.variable_array
407 );
408 }
409 }
410 Err(e) => {
411 if self.verbose {
412 debug!("JIT execution failed: {e}, using interpreter");
413 }
414 }
416 }
417 }
418 }
419 }
420
421 if !execution_completed {
423 if self.verbose {
424 debug!(
425 "Interpreter path: variable_array size: {}, bytecode.var_count: {}",
426 self.variable_array.len(),
427 bytecode.var_count
428 );
429 }
430
431 let mut execution_bytecode = bytecode.clone();
433 if is_expression_stmt
434 && matches!(final_stmt_emit, FinalStmtEmitDisposition::Inline)
435 && !execution_bytecode.instructions.is_empty()
436 {
437 execution_bytecode.instructions.pop(); let temp_var_id = std::cmp::max(execution_bytecode.var_count, max_var_id + 1);
441 execution_bytecode
442 .instructions
443 .push(runmat_vm::Instr::StoreVar(temp_var_id));
444 execution_bytecode.var_count = temp_var_id + 1; if self.variable_array.len() <= temp_var_id {
448 self.variable_array.resize(temp_var_id + 1, Value::Num(0.0));
449 }
450
451 if self.verbose {
452 debug!(
453 "Modified expression bytecode, new instructions: {:?}",
454 execution_bytecode.instructions
455 );
456 }
457 }
458
459 match self.interpret_with_context(&execution_bytecode).await {
460 Ok(runmat_vm::InterpreterOutcome::Completed(results)) => {
461 if !self.has_jit() || is_expression_stmt {
463 self.stats.interpreter_fallback += 1;
464 }
465 if self.verbose {
466 debug!("Interpreter results: {results:?}");
467 }
468
469 if hir.body.len() == 1 {
471 if let runmat_hir::HirStmt::Assign(var_id, _, _, _) = &hir.body[0] {
472 if var_id.0 < self.variable_array.len() {
474 let assignment_value = self.variable_array[var_id.0].clone();
475 if !is_semicolon_suppressed {
476 result_value = Some(assignment_value);
477 if self.verbose {
478 debug!("Interpreter assignment result: {result_value:?}");
479 }
480 } else {
481 suppressed_value = Some(assignment_value);
482 if self.verbose {
483 debug!("Interpreter assignment suppressed due to semicolon, captured for type info");
484 }
485 }
486 }
487 } else if !is_expression_stmt
488 && !results.is_empty()
489 && !is_semicolon_suppressed
490 && matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
491 {
492 result_value = Some(results[0].clone());
493 }
494 }
495
496 if is_expression_stmt
498 && matches!(final_stmt_emit, FinalStmtEmitDisposition::Inline)
499 && !execution_bytecode.instructions.is_empty()
500 && result_value.is_none()
501 && suppressed_value.is_none()
502 {
503 let temp_var_id = execution_bytecode.var_count - 1; if temp_var_id < self.variable_array.len() {
505 let expression_value = self.variable_array[temp_var_id].clone();
506 if !is_semicolon_suppressed {
507 ans_update = Some((temp_var_id, expression_value.clone()));
509 result_value = Some(expression_value);
510 if self.verbose {
511 debug!("Expression result from temp var {temp_var_id}: {result_value:?}");
512 }
513 } else {
514 suppressed_value = Some(expression_value);
515 if self.verbose {
516 debug!("Expression suppressed, captured for type info from temp var {temp_var_id}: {suppressed_value:?}");
517 }
518 }
519 }
520 } else if !is_semicolon_suppressed
521 && matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
522 && result_value.is_none()
523 {
524 result_value = results.into_iter().last();
525 if self.verbose {
526 debug!("Fallback result from interpreter: {result_value:?}");
527 }
528 }
529
530 if self.verbose {
531 debug!("Final result_value: {result_value:?}");
532 }
533 debug!("Interpreter execution successful");
534 }
535
536 Err(e) => {
537 debug!("Interpreter execution failed: {e}");
538 error = Some(e);
539 }
540 }
541 }
542
543 let last_assign_var = last_unsuppressed_assign_var(&hir.body);
544 let last_expr_emits = last_expr_emits_value(&hir.body);
545 if !is_semicolon_suppressed && result_value.is_none() {
546 if last_assign_var.is_some() || last_expr_emits {
547 if let Some(value) = runmat_runtime::console::take_last_value_output() {
548 result_value = Some(value);
549 }
550 }
551 if result_value.is_none() {
552 if last_assign_var.is_some() {
553 if let Some(var_id) = last_emit_var_index(&bytecode) {
554 if var_id < self.variable_array.len() {
555 result_value = Some(self.variable_array[var_id].clone());
556 }
557 }
558 }
559 if result_value.is_none() {
560 if let Some(var_id) = last_assign_var {
561 if var_id < self.variable_array.len() {
562 result_value = Some(self.variable_array[var_id].clone());
563 }
564 }
565 }
566 }
567 }
568
569 let execution_time = start_time.elapsed();
570 let execution_time_ms = execution_time.as_millis() as u64;
571
572 self.stats.total_execution_time_ms += execution_time_ms;
573 self.stats.average_execution_time_ms =
574 self.stats.total_execution_time_ms as f64 / self.stats.total_executions as f64;
575
576 if error.is_none() {
578 if let Some((mutated_names, assigned)) = runmat_vm::take_updated_workspace_state() {
579 if let Some(assigned_report) = runmat_vm::take_updated_workspace_assigned_report() {
580 assigned_this_execution.extend(
581 assigned_report
582 .ids
583 .iter()
584 .filter_map(|var_id| id_to_name.get(var_id).cloned()),
585 );
586 assigned_this_execution.extend(assigned_report.names);
587 removed_this_execution.extend(
588 assigned_report
589 .removed_ids
590 .iter()
591 .filter_map(|var_id| id_to_name.get(var_id).cloned()),
592 );
593 removed_this_execution.extend(assigned_report.removed_names);
594 }
595 if debug_trace {
596 debug!(
597 ?mutated_names,
598 ?assigned,
599 ?assigned_this_execution,
600 "[repl] mutated names and assigned return values"
601 );
602 }
603 self.variable_names = mutated_names.clone();
604 let previous_workspace = self.workspace_values.clone();
605 let current_names: HashSet<String> = assigned
606 .iter()
607 .filter(|name| {
608 mutated_names
609 .get(*name)
610 .map(|var_id| *var_id < self.variable_array.len())
611 .unwrap_or(false)
612 })
613 .cloned()
614 .collect();
615 let mut removed_names: HashSet<String> = previous_workspace
616 .keys()
617 .filter(|name| !current_names.contains(*name))
618 .cloned()
619 .collect();
620 removed_names.extend(
621 removed_this_execution
622 .into_iter()
623 .filter(|name| !current_names.contains(name)),
624 );
625 let mut rebuilt_workspace = HashMap::new();
626 let mut changed_names: HashSet<String> = assigned
627 .difference(&prev_assigned_snapshot)
628 .cloned()
629 .collect();
630 changed_names.extend(assigned_this_execution.iter().cloned());
631
632 for name in ¤t_names {
633 let Some(var_id) = mutated_names.get(name).copied() else {
634 continue;
635 };
636 if var_id >= self.variable_array.len() {
637 continue;
638 }
639 let value_clone = self.variable_array[var_id].clone();
640 if previous_workspace.get(name) != Some(&value_clone) {
641 changed_names.insert(name.clone());
642 }
643 rebuilt_workspace.insert(name.clone(), value_clone);
644 }
645
646 if debug_trace {
647 debug!(?changed_names, ?removed_names, "[repl] workspace changes");
648 }
649
650 self.workspace_values = rebuilt_workspace;
651 if !removed_names.is_empty() {
652 workspace_snapshot_force_full = true;
653 } else {
654 for name in changed_names {
655 if let Some(value_clone) = self.workspace_values.get(&name).cloned() {
656 workspace_updates.push(workspace_entry(&name, &value_clone));
657 if debug_trace {
658 debug!(name, ?value_clone, "[repl] workspace update");
659 }
660 }
661 }
662 }
663 } else {
664 for name in &assigned_this_execution {
665 if let Some(var_id) =
666 id_to_name
667 .iter()
668 .find_map(|(vid, n)| if n == name { Some(*vid) } else { None })
669 {
670 if var_id < self.variable_array.len() {
671 let value_clone = self.variable_array[var_id].clone();
672 self.workspace_values
673 .insert(name.clone(), value_clone.clone());
674 workspace_updates.push(workspace_entry(name, &value_clone));
675 }
676 }
677 }
678 }
679 let mut repl_source_id: Option<SourceId> = None;
680 for (name, stmt) in &updated_functions {
681 if matches!(stmt, runmat_hir::HirStmt::Function { .. }) {
682 let source_id = *repl_source_id
683 .get_or_insert_with(|| self.source_pool.intern("<repl>", input));
684 self.function_source_ids.insert(name.clone(), source_id);
685 }
686 }
687 self.function_definitions = updated_functions;
688 if let Some((var_id, value)) = ans_update {
690 self.variable_names.insert("ans".to_string(), var_id);
691 self.workspace_values.insert("ans".to_string(), value);
692 if debug_trace {
693 println!("Updated 'ans' to var_id {}", var_id);
694 }
695 }
696 }
697
698 if self.verbose {
699 debug!("Execution completed in {execution_time_ms}ms (JIT: {used_jit})");
700 }
701
702 if !is_expression_stmt
703 && !is_semicolon_suppressed
704 && matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
705 && result_value.is_none()
706 {
707 if let Some(v) = self
708 .variable_array
709 .iter()
710 .rev()
711 .find(|v| !matches!(v, Value::Num(0.0)))
712 .cloned()
713 {
714 result_value = Some(v);
715 }
716 }
717
718 if !is_semicolon_suppressed
719 && matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
720 {
721 if let Some(value) = result_value.as_ref() {
722 let label = determine_display_label_from_context(
723 single_assign_var,
724 &id_to_name,
725 is_expression_stmt,
726 single_stmt_non_assign,
727 );
728 runmat_runtime::console::record_value_output(label.as_deref(), value);
729 }
730 }
731
732 let type_info = suppressed_value.as_ref().map(format_type_info);
734
735 let streams = runmat_runtime::console::take_thread_buffer()
736 .into_iter()
737 .map(|entry| ExecutionStreamEntry {
738 stream: match entry.stream {
739 runmat_runtime::console::ConsoleStream::Stdout => ExecutionStreamKind::Stdout,
740 runmat_runtime::console::ConsoleStream::Stderr => ExecutionStreamKind::Stderr,
741 runmat_runtime::console::ConsoleStream::ClearScreen => {
742 ExecutionStreamKind::ClearScreen
743 }
744 },
745 text: entry.text,
746 timestamp_ms: entry.timestamp_ms,
747 })
748 .collect();
749 let (workspace_entries, snapshot_full) = if workspace_snapshot_force_full {
750 let mut entries: Vec<WorkspaceEntry> = self
751 .workspace_values
752 .iter()
753 .map(|(name, value)| workspace_entry(name, value))
754 .collect();
755 entries.sort_by(|a, b| a.name.cmp(&b.name));
756 (entries, true)
757 } else if workspace_updates.is_empty() {
758 let source_map = if self.workspace_values.is_empty() {
759 &self.variables
760 } else {
761 &self.workspace_values
762 };
763 if source_map.is_empty() {
764 (workspace_updates, false)
765 } else {
766 let mut entries: Vec<WorkspaceEntry> = source_map
767 .iter()
768 .map(|(name, value)| workspace_entry(name, value))
769 .collect();
770 entries.sort_by(|a, b| a.name.cmp(&b.name));
771 (entries, true)
772 }
773 } else {
774 (workspace_updates, false)
775 };
776 let workspace_snapshot = self.build_workspace_snapshot(workspace_entries, snapshot_full);
777 let figures_touched = runmat_runtime::plotting_hooks::take_recent_figures();
778 let stdin_events = stdin_events
779 .lock()
780 .map(|guard| guard.clone())
781 .unwrap_or_default();
782
783 let warnings = runmat_runtime::warning_store::take_all();
784
785 if let Some(runtime_error) = &mut error {
786 self.normalize_error_namespace(runtime_error);
787 self.populate_callstack(runtime_error);
788 }
789
790 let suppress_public_value =
791 is_expression_stmt && matches!(final_stmt_emit, FinalStmtEmitDisposition::Suppressed);
792 let public_value = if is_semicolon_suppressed || suppress_public_value {
793 None
794 } else {
795 result_value
796 };
797
798 self.format_mode = runmat_builtins::get_display_format();
799 Ok(ExecutionResult {
800 value: public_value,
801 execution_time_ms,
802 used_jit,
803 error,
804 type_info,
805 streams,
806 workspace: workspace_snapshot,
807 figures_touched,
808 warnings,
809 profiling: gather_profiling(execution_time_ms),
810 fusion_plan: fusion_snapshot,
811 stdin_events,
812 })
813 }
814
815 async fn interpret_with_context(
817 &mut self,
818 bytecode: &runmat_vm::Bytecode,
819 ) -> Result<runmat_vm::InterpreterOutcome, RuntimeError> {
820 let source_name = self.current_source_name().to_string();
821 runmat_vm::interpret_with_vars(
822 bytecode,
823 &mut self.variable_array,
824 Some(source_name.as_str()),
825 )
826 .await
827 }
828}