Skip to content

Commit a5e2266

Browse files
authored
deduplicate Executor and ReplExecutor types (#303)
1 parent 11b38c0 commit a5e2266

File tree

3 files changed

+130
-206
lines changed

3 files changed

+130
-206
lines changed

crates/monty/src/repl.rs

Lines changed: 13 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,16 @@ use serde::de::DeserializeOwned;
1414
use crate::{
1515
ExcType, MontyException,
1616
asyncio::CallId,
17-
bytecode::{Code, Compiler, FrameExit, VM, VMSnapshot},
18-
exception_private::{RunError, RunResult},
17+
bytecode::{VM, VMSnapshot},
18+
exception_private::RunError,
1919
heap::{DropWithHeap, Heap, HeapReader},
2020
intern::{InternerBuilder, Interns},
2121
io::PrintWriter,
2222
namespace::NamespaceId,
2323
object::MontyObject,
2424
os::OsFunction,
25-
parse::parse_with_interner,
26-
prepare::prepare_with_existing_names,
2725
resource::ResourceTracker,
26+
run::Executor,
2827
run_progress::{ConvertedExit, ExtFunctionResult, NameLookupResult, convert_frame_exit},
2928
value::Value,
3029
};
@@ -127,7 +126,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
127126
let (input_names, input_values): (Vec<_>, Vec<_>) = inputs.into_iter().unzip();
128127

129128
let input_script_name = this.next_input_script_name();
130-
let executor = match ReplExecutor::new_repl_snippet(
129+
let executor = match Executor::new_repl_snippet(
131130
code.to_owned(),
132131
&input_script_name,
133132
this.global_name_map.clone(),
@@ -191,7 +190,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
191190
let (input_names, input_values): (Vec<_>, Vec<_>) = inputs.into_iter().unzip();
192191

193192
let input_script_name = self.next_input_script_name();
194-
let executor = ReplExecutor::new_repl_snippet(
193+
let executor = Executor::new_repl_snippet(
195194
code.to_owned(),
196195
&input_script_name,
197196
self.global_name_map.clone(),
@@ -210,19 +209,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
210209
return Err(e);
211210
}
212211

213-
let mut frame_exit_result = vm.run_module(&executor.module_code);
214-
215-
// Handle NameLookup exits by raising NameError through the VM so that
216-
// traceback information is properly captured. In the non-iterative REPL path,
217-
// there's no host to resolve names, so all NameLookup exits become NameErrors.
218-
while let Ok(FrameExit::NameLookup { name_id, .. }) = &frame_exit_result {
219-
let name = executor.interns.get_str(*name_id);
220-
let err = ExcType::name_error(name);
221-
frame_exit_result = vm.resume_with_exception(err.into());
222-
}
223-
224-
// Convert output while VM alive
225-
let result = frame_exit_to_object(frame_exit_result, &mut vm);
212+
let result = executor.run_to_completion(&mut vm);
226213

227214
// Reclaim globals before cleanup.
228215
self.globals = vm.take_globals();
@@ -233,7 +220,7 @@ impl<T: ResourceTracker> MontyRepl<T> {
233220
// Commit compiler metadata even on runtime errors.
234221
// Snippets can mutate globals before raising, and those values may contain
235222
// FunctionId/StringId values that must be interpreted with the updated tables.
236-
let ReplExecutor {
223+
let Executor {
237224
name_map,
238225
interns,
239226
code,
@@ -637,7 +624,7 @@ pub struct ReplResolveFutures<T: ResourceTracker> {
637624
/// Persistent REPL session state while this snippet is suspended.
638625
repl: MontyRepl<T>,
639626
/// Compiled snippet and intern/function tables for this execution.
640-
executor: ReplExecutor,
627+
executor: Executor,
641628
/// VM stack/frame state at suspension.
642629
vm_state: VMSnapshot,
643630
/// Pending call IDs expected by this snapshot.
@@ -825,88 +812,6 @@ pub fn detect_repl_continuation_mode(source: &str) -> ReplContinuationMode {
825812
}
826813
}
827814

828-
// ---------------------------------------------------------------------------
829-
// ReplExecutor — internal compilation helper
830-
// ---------------------------------------------------------------------------
831-
832-
/// Compiled snippet representation used only by REPL execution.
833-
///
834-
/// This intentionally mirrors the data shape needed by VM execution in
835-
/// `run.rs` but lives in the REPL module so REPL evolution does not require
836-
/// changing `run.rs`.
837-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
838-
struct ReplExecutor {
839-
/// Number of slots needed in the global namespace.
840-
namespace_size: usize,
841-
/// Maps variable names to their indices in the namespace.
842-
///
843-
/// Stable slot assignment is required across snippets so previously created
844-
/// objects continue to resolve names correctly.
845-
name_map: AHashMap<String, NamespaceId>,
846-
/// Compiled bytecode for the snippet.
847-
module_code: Code,
848-
/// Interned strings and compiled functions for this snippet.
849-
interns: Interns,
850-
/// Source code used for traceback/error rendering.
851-
code: String,
852-
/// Input variable names that were injected for this snippet.
853-
///
854-
/// Stored so that `inject_inputs` can look up their namespace slots
855-
/// after compilation assigns them.
856-
input_names: Vec<String>,
857-
}
858-
859-
impl ReplExecutor {
860-
/// Compiles one REPL snippet against existing session metadata.
861-
///
862-
/// This differs from normal compilation in three ways required for true
863-
/// no-replay execution:
864-
/// - Seeds parsing from `existing_interns` so old `StringId` values stay stable.
865-
/// - Seeds compilation with existing functions so old `FunctionId` values remain valid.
866-
/// - Reuses `existing_name_map` and appends new global names only.
867-
///
868-
/// `input_names` are pre-registered in the name map before preparation so they
869-
/// receive stable namespace slots that `inject_inputs` can use to store values.
870-
fn new_repl_snippet(
871-
code: String,
872-
script_name: &str,
873-
mut existing_name_map: AHashMap<String, NamespaceId>,
874-
existing_interns: &Interns,
875-
input_names: Vec<String>,
876-
) -> Result<Self, MontyException> {
877-
// Pre-register input names so they get stable slots before preparation.
878-
for name in &input_names {
879-
let next_slot = existing_name_map.len();
880-
existing_name_map
881-
.entry(name.clone())
882-
.or_insert_with(|| NamespaceId::new(next_slot));
883-
}
884-
885-
let seeded_interner = InternerBuilder::from_interns(existing_interns, &code);
886-
let parse_result = parse_with_interner(&code, script_name, seeded_interner)
887-
.map_err(|e| e.into_python_exc(script_name, &code))?;
888-
let prepared = prepare_with_existing_names(parse_result, existing_name_map)
889-
.map_err(|e| e.into_python_exc(script_name, &code))?;
890-
891-
let existing_functions = existing_interns.functions_clone();
892-
let mut interns = Interns::new(prepared.interner, Vec::new());
893-
let namespace_size_u16 = u16::try_from(prepared.namespace_size).expect("module namespace size exceeds u16");
894-
let compile_result =
895-
Compiler::compile_module_with_functions(&prepared.nodes, &interns, namespace_size_u16, existing_functions)
896-
.map_err(|e| e.into_python_exc(script_name, &code))?;
897-
interns.set_functions(compile_result.functions);
898-
899-
Ok(Self {
900-
namespace_size: prepared.namespace_size,
901-
name_map: prepared.name_map,
902-
module_code: compile_result.code,
903-
interns,
904-
code,
905-
input_names,
906-
})
907-
}
908-
}
909-
910815
// ---------------------------------------------------------------------------
911816
// ReplSnapshot — internal execution state for suspend/resume
912817
// ---------------------------------------------------------------------------
@@ -921,7 +826,7 @@ pub(crate) struct ReplSnapshot<T: ResourceTracker> {
921826
/// Persistent REPL session state while this snippet is suspended.
922827
repl: MontyRepl<T>,
923828
/// Compiled snippet and intern/function tables for this execution.
924-
executor: ReplExecutor,
829+
executor: Executor,
925830
/// VM stack/frame state at suspension.
926831
vm_state: VMSnapshot,
927832
}
@@ -999,7 +904,7 @@ impl<T: ResourceTracker> ReplSnapshot<T> {
999904
/// Converts each `MontyObject` to a `Value` while the VM is alive, then stores
1000905
/// it in the global slot that the compiler assigned for the corresponding input name.
1001906
fn inject_inputs_into_vm(
1002-
executor: &ReplExecutor,
907+
executor: &Executor,
1003908
input_values: Vec<MontyObject>,
1004909
vm: &mut VM<'_, '_, impl ResourceTracker>,
1005910
) -> Result<(), MontyException> {
@@ -1018,51 +923,6 @@ fn inject_inputs_into_vm(
1018923
Ok(())
1019924
}
1020925

1021-
/// Converts module/frame exit results into plain `MontyObject` outputs.
1022-
///
1023-
/// Used by the non-iterative `feed_run` path where suspendable outcomes (external calls,
1024-
/// name lookups) are not supported and should produce errors.
1025-
fn frame_exit_to_object(
1026-
frame_exit_result: RunResult<FrameExit>,
1027-
vm: &mut VM<'_, '_, impl ResourceTracker>,
1028-
) -> RunResult<MontyObject> {
1029-
match frame_exit_result? {
1030-
FrameExit::Return(return_value) => Ok(MontyObject::new(return_value, vm)),
1031-
FrameExit::ExternalCall {
1032-
function_name, args, ..
1033-
} => {
1034-
args.drop_with_heap(vm);
1035-
let function_name = function_name.as_str(vm.interns);
1036-
Err(ExcType::not_implemented(format!(
1037-
"External function '{function_name}' not implemented with standard execution"
1038-
))
1039-
.into())
1040-
}
1041-
FrameExit::OsCall { function, args, .. } => {
1042-
args.drop_with_heap(vm);
1043-
Err(ExcType::not_implemented(format!(
1044-
"OS function '{function}' not implemented with standard execution"
1045-
))
1046-
.into())
1047-
}
1048-
FrameExit::MethodCall { method_name, args, .. } => {
1049-
args.drop_with_heap(vm);
1050-
let name = method_name.as_str(vm.interns);
1051-
Err(
1052-
ExcType::not_implemented(format!("Method call '{name}' not implemented with standard execution"))
1053-
.into(),
1054-
)
1055-
}
1056-
FrameExit::ResolveFutures(_) => {
1057-
Err(ExcType::not_implemented("async futures not supported by standard execution.").into())
1058-
}
1059-
FrameExit::NameLookup { name_id, .. } => {
1060-
let name = vm.interns.get_str(name_id);
1061-
Err(ExcType::name_error(name).into())
1062-
}
1063-
}
1064-
}
1065-
1066926
/// Assembles a `ReplProgress` from already-converted data.
1067927
///
1068928
/// This is the REPL equivalent of `build_run_progress`. On completion/error,
@@ -1071,7 +931,7 @@ fn frame_exit_to_object(
1071931
fn build_repl_progress<T: ResourceTracker>(
1072932
converted: ConvertedExit,
1073933
vm_state: Option<VMSnapshot>,
1074-
executor: ReplExecutor,
934+
executor: Executor,
1075935
mut repl: MontyRepl<T>,
1076936
) -> Result<ReplProgress<T>, Box<ReplStartError<T>>> {
1077937
macro_rules! new_repl_snapshot {
@@ -1086,7 +946,7 @@ fn build_repl_progress<T: ResourceTracker>(
1086946

1087947
match converted {
1088948
ConvertedExit::Complete(obj) => {
1089-
let ReplExecutor { name_map, interns, .. } = executor;
949+
let Executor { name_map, interns, .. } = executor;
1090950
repl.global_name_map = name_map;
1091951
repl.interns = interns;
1092952
Ok(ReplProgress::Complete { repl, value: obj })
@@ -1138,7 +998,7 @@ fn build_repl_progress<T: ResourceTracker>(
1138998
// Commit compiler metadata even on runtime errors, matching feed() behavior.
1139999
// Snippets can create new variables or functions before raising, and those
11401000
// values may reference FunctionId/StringId values from the new tables.
1141-
let ReplExecutor { name_map, interns, .. } = executor;
1001+
let Executor { name_map, interns, .. } = executor;
11421002
repl.global_name_map = name_map;
11431003
repl.interns = interns;
11441004
Err(Box::new(ReplStartError { repl, error }))

0 commit comments

Comments
 (0)