use std::mem;
use ahash::AHashMap;
use monty_types::{ExcType, MontyException, MontyObject, OsFunctionCall, PrintWriter, ResourceTracker};
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::{InterpolatedStringErrorType, LexicalErrorType, ParseErrorType, parse_module};
use crate::{
args::{ArgValues, KwargsValues},
asyncio::CallId,
bytecode::{VM, VMSnapshot},
defer_drop,
exception_private::{ExcTypeExt, RunError},
heap::{DropWithContext, Heap, HeapData, HeapReader},
intern::{InternerBuilder, Interns},
name_map::NameMap,
object_bridge::MontyObjectExt,
run::{CompileOptions, Executor},
run_progress::{ConvertedExit, ExtFunctionResult, ExtFunctionResultExt, NameLookupResult, convert_frame_exit},
value::Value,
};
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct MontyRepl {
script_name: String,
next_input_id: u64,
global_names: NameMap,
interns: Interns,
#[serde(default)]
sources: AHashMap<String, String>,
#[serde(default)]
options: CompileOptions,
heap: Heap,
globals: Vec<Value>,
}
impl MontyRepl {
#[must_use]
pub fn new(script_name: &str, resource_tracker: ResourceTracker, options: CompileOptions) -> Self {
let heap = Heap::new(0, resource_tracker);
Self {
script_name: script_name.to_owned(),
next_input_id: 0,
global_names: NameMap::new(),
interns: Interns::new(InternerBuilder::default(), Vec::new()),
sources: AHashMap::new(),
options,
heap,
globals: Vec::new(),
}
}
pub fn tracker(&self) -> &ResourceTracker {
self.heap.tracker()
}
pub fn tracker_mut(&mut self) -> &mut ResourceTracker {
self.heap.tracker_mut()
}
pub fn feed_start(
self,
code: &str,
inputs: Vec<(String, MontyObject)>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>> {
let mut this = self;
if code.is_empty() {
return Ok(ReplProgress::Complete {
repl: this,
value: MontyObject::None,
});
}
let (input_names, input_values): (Vec<_>, Vec<_>) = inputs.into_iter().unzip();
let input_script_name = this.next_input_script_name();
this.sources.insert(input_script_name.clone(), code.to_owned());
let executor = match Executor::new_repl_snippet(
code.to_owned(),
&input_script_name,
this.global_names.clone(),
&this.interns,
&input_names,
this.options,
) {
Ok(exec) => exec,
Err(error) => return Err(Box::new(ReplStartError { repl: this, error })),
};
this.ensure_globals_size(executor.namespace_size());
match HeapReader::with(&mut this.heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::new(
mem::take(&mut this.globals),
reader,
&executor.interns,
print.reborrow(),
executor.assert_repr_max_bytes,
);
if let Err(error) = inject_inputs_into_vm(executor, input_values, &mut vm) {
this.globals = vm.take_globals();
return Err(error);
}
let vm_result = vm.run_module(&executor.module_code);
let converted = convert_frame_exit(vm_result, &mut vm);
let vm_state = if converted.needs_snapshot() {
Some(vm.snapshot())
} else {
this.globals = vm.take_globals();
None
};
Ok((converted, vm_state))
}) {
Ok((converted, vm_state)) => build_repl_progress(converted, vm_state, executor, this),
Err(error) => Err(Box::new(ReplStartError { repl: this, error })),
}
}
pub fn feed_run(
&mut self,
code: &str,
inputs: Vec<(String, MontyObject)>,
print: PrintWriter<'_>,
) -> Result<MontyObject, MontyException> {
if code.is_empty() {
return Ok(MontyObject::None);
}
let (input_names, input_values): (Vec<_>, Vec<_>) = inputs.into_iter().unzip();
let input_script_name = self.next_input_script_name();
self.sources.insert(input_script_name.clone(), code.to_owned());
let executor = Executor::new_repl_snippet(
code.to_owned(),
&input_script_name,
self.global_names.clone(),
&self.interns,
&input_names,
self.options,
)?;
self.ensure_globals_size(executor.namespace_size());
let result = HeapReader::with(&mut self.heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::new(
mem::take(&mut self.globals),
reader,
&executor.interns,
print.reborrow(),
executor.assert_repr_max_bytes,
);
if let Err(e) = inject_inputs_into_vm(executor, input_values, &mut vm) {
self.globals = vm.take_globals();
return Err(e);
}
let result = executor.run_to_completion(&mut vm);
self.globals = vm.take_globals();
Ok(result)
})?;
let Executor {
globals: snippet_globals,
interns,
..
} = executor;
self.global_names = snippet_globals;
self.interns = interns;
result.map_err(|e| e.into_python_exception(&self.interns, |fname| self.sources.get(fname).map(String::as_str)))
}
pub fn call_function(
&mut self,
name: &str,
args: Vec<MontyObject>,
print: PrintWriter<'_>,
) -> Result<MontyObject, MontyException> {
let slot_idx = self
.interns
.get_string_id_by_name(name)
.and_then(|name_id| self.global_names.get(name_id));
let Some(slot_idx) = slot_idx else {
return Err(RunError::from(ExcType::name_error(name))
.into_python_exception(&self.interns, |fname| self.sources.get(fname).map(String::as_str)));
};
let assert_repr_max_bytes = self.options.assert_message_annotations.max_bytes();
HeapReader::with(
&mut self.heap,
&mut (&self.interns, print),
|reader, (interns, print)| {
let vm = &mut VM::new(
mem::take(&mut self.globals),
reader,
interns,
print.reborrow(),
assert_repr_max_bytes,
);
let callable = vm.globals[slot_idx.index()].clone_with_heap(vm);
defer_drop!(callable, vm);
let arg_values = match convert_args(args, vm) {
Ok(av) => av,
Err(e) => {
self.globals = vm.take_globals();
return Err(e);
}
};
vm.heap.tracker().on_execution_start();
let eval_result = vm.evaluate_function("MontyRepl::call_function", callable, arg_values);
vm.heap.tracker().on_execution_stop();
let result = match eval_result {
Ok(value) => Ok(MontyObject::new(value, vm)),
Err(e) => {
Err(e.into_python_exception(&self.interns, |fname| self.sources.get(fname).map(String::as_str)))
}
};
self.globals = vm.take_globals();
result
},
)
}
#[must_use]
pub fn function_names(&self) -> Vec<&str> {
self.global_names
.iter()
.filter_map(|(ns_id, name_id)| {
let idx = ns_id.index();
if idx < self.globals.len() && is_callable(&self.globals[idx], &self.heap) {
Some(self.interns.get_str(name_id))
} else {
None
}
})
.collect()
}
#[must_use]
pub fn has_function(&self, name: &str) -> bool {
let Some(name_id) = self.interns.get_string_id_by_name(name) else {
return false;
};
self.global_names.get(name_id).is_some_and(|ns_id| {
let idx = ns_id.index();
idx < self.globals.len() && is_callable(&self.globals[idx], &self.heap)
})
}
fn ensure_globals_size(&mut self, size: usize) {
if self.globals.len() < size {
self.globals.resize_with(size, || Value::Undefined);
}
}
fn next_input_script_name(&mut self) -> String {
let input_id = self.next_input_id;
self.next_input_id += 1;
format!("<python-input-{input_id}>")
}
}
impl Drop for MontyRepl {
fn drop(&mut self) {
self.globals.drain(..).drop_with(&mut self.heap);
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum ReplProgress {
FunctionCall(ReplFunctionCall),
OsCall(ReplOsCall),
ResolveFutures(ReplResolveFutures),
NameLookup(ReplNameLookup),
Complete {
repl: MontyRepl,
value: MontyObject,
},
}
#[derive(Debug)]
pub struct ReplStartError {
pub repl: MontyRepl,
pub error: MontyException,
}
impl ReplProgress {
#[must_use]
pub fn into_function_call(self) -> Option<ReplFunctionCall> {
match self {
Self::FunctionCall(call) => Some(call),
_ => None,
}
}
#[must_use]
pub fn into_resolve_futures(self) -> Option<ReplResolveFutures> {
match self {
Self::ResolveFutures(state) => Some(state),
_ => None,
}
}
#[must_use]
pub fn into_name_lookup(self) -> Option<ReplNameLookup> {
match self {
Self::NameLookup(lookup) => Some(lookup),
_ => None,
}
}
#[must_use]
pub fn into_complete(self) -> Option<(MontyRepl, MontyObject)> {
match self {
Self::Complete { repl, value } => Some((repl, value)),
_ => None,
}
}
#[must_use]
pub fn into_repl(self) -> MontyRepl {
match self {
Self::FunctionCall(call) => call.into_repl(),
Self::OsCall(call) => call.into_repl(),
Self::ResolveFutures(state) => state.into_repl(),
Self::NameLookup(lookup) => lookup.into_repl(),
Self::Complete { repl, .. } => repl,
}
}
pub fn tracker(&self) -> &ResourceTracker {
match self {
Self::FunctionCall(call) => call.snapshot.repl.tracker(),
Self::OsCall(call) => call.snapshot.repl.tracker(),
Self::ResolveFutures(state) => state.repl.tracker(),
Self::NameLookup(lookup) => lookup.snapshot.repl.tracker(),
Self::Complete { repl, .. } => repl.tracker(),
}
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ReplFunctionCall {
pub function_name: String,
pub args: Vec<MontyObject>,
pub kwargs: Vec<(MontyObject, MontyObject)>,
pub call_id: u32,
pub method_call: bool,
snapshot: ReplSnapshot,
}
impl ReplFunctionCall {
#[must_use]
pub fn into_repl(self) -> MontyRepl {
self.snapshot.into_repl()
}
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>> {
self.snapshot.run(result, print)
}
pub fn resume_pending(self, print: PrintWriter<'_>) -> Result<ReplProgress, Box<ReplStartError>> {
self.snapshot.run(ExtFunctionResult::Future(self.call_id), print)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ReplOsCall {
pub function_call: OsFunctionCall,
pub call_id: u32,
snapshot: ReplSnapshot,
}
impl ReplOsCall {
#[must_use]
pub fn into_repl(self) -> MontyRepl {
self.snapshot.into_repl()
}
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>> {
self.snapshot.run(result.into(), print)
}
pub fn resume_with(
self,
print: PrintWriter<'_>,
handler: impl FnOnce(OsFunctionCall) -> ExtFunctionResult,
) -> Result<ReplProgress, Box<ReplStartError>> {
let result = handler(self.function_call);
self.snapshot.run(result, print)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ReplNameLookup {
pub name: String,
namespace_slot: u16,
is_global: bool,
snapshot: ReplSnapshot,
}
impl ReplNameLookup {
#[must_use]
pub fn into_repl(self) -> MontyRepl {
self.snapshot.into_repl()
}
pub fn resume(self, result: NameLookupResult, print: PrintWriter<'_>) -> Result<ReplProgress, Box<ReplStartError>> {
let Self {
name,
namespace_slot,
is_global,
snapshot,
} = self;
let ReplSnapshot {
mut repl,
executor,
vm_state,
} = snapshot;
match HeapReader::with(&mut repl.heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::restore(
vm_state,
&executor.module_code,
reader,
&executor.interns,
print.reborrow(),
executor.assert_repr_max_bytes,
);
let vm_result = match result {
NameLookupResult::Value(obj) => {
let value = match obj.to_value(&mut vm) {
Ok(v) => v,
Err(e) => {
repl.globals = vm.take_globals();
return Err(MontyException::runtime_error(format!(
"invalid name lookup result: {e}"
)));
}
};
let slot_idx = namespace_slot as usize;
let cloned = value.clone_with_heap(&vm);
let slot = if is_global {
&mut vm.globals[slot_idx]
} else {
let stack_base = vm.current_stack_base();
&mut vm.stack[stack_base + slot_idx]
};
let old = mem::replace(slot, cloned);
old.drop_with(&mut vm);
vm.push(value);
vm.run_external()
}
NameLookupResult::Undefined => {
let err: RunError = ExcType::name_error(&name).into();
vm.resume_with_exception(err)
}
};
let converted = convert_frame_exit(vm_result, &mut vm);
let vm_state = if converted.needs_snapshot() {
Some(vm.snapshot())
} else {
repl.globals = vm.take_globals();
None
};
Ok((converted, vm_state))
}) {
Ok((converted, vm_state)) => build_repl_progress(converted, vm_state, executor, repl),
Err(error) => Err(Box::new(ReplStartError { repl, error })),
}
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ReplResolveFutures {
repl: MontyRepl,
executor: Executor,
vm_state: VMSnapshot,
pending_call_ids: Vec<u32>,
}
impl ReplResolveFutures {
#[must_use]
pub fn into_repl(self) -> MontyRepl {
let Self { mut repl, vm_state, .. } = self;
repl.globals = vm_state.globals;
repl
}
#[must_use]
pub fn pending_call_ids(&self) -> &[u32] {
&self.pending_call_ids
}
pub fn resume(
self,
results: Vec<(u32, ExtFunctionResult)>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>> {
let Self {
mut repl,
executor,
vm_state,
pending_call_ids,
} = self;
let invalid_call_id = results
.iter()
.find(|(call_id, _)| !pending_call_ids.contains(call_id))
.map(|(call_id, _)| *call_id);
match HeapReader::with(&mut repl.heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::restore(
vm_state,
&executor.module_code,
reader,
&executor.interns,
print.reborrow(),
executor.assert_repr_max_bytes,
);
if let Some(call_id) = invalid_call_id {
repl.globals = vm.take_globals();
return Err(MontyException::runtime_error(format!(
"unknown call_id {call_id}, expected one of: {pending_call_ids:?}"
)));
}
let vm_result = vm.resume_with_resolved_futures(results);
let converted = convert_frame_exit(vm_result, &mut vm);
let vm_state = if converted.needs_snapshot() {
Some(vm.snapshot())
} else {
repl.globals = vm.take_globals();
None
};
Ok((converted, vm_state))
}) {
Ok((converted, vm_state)) => build_repl_progress(converted, vm_state, executor, repl),
Err(error) => Err(Box::new(ReplStartError { repl, error })),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReplContinuationMode {
Complete,
IncompleteImplicit,
IncompleteBlock,
}
#[must_use]
pub fn detect_repl_continuation_mode(source: &str) -> ReplContinuationMode {
let Err(error) = parse_module(source) else {
return ReplContinuationMode::Complete;
};
match error.error {
ParseErrorType::OtherError(msg) => {
if msg.starts_with("Expected an indented block after ") {
ReplContinuationMode::IncompleteBlock
} else {
ReplContinuationMode::Complete
}
}
ParseErrorType::Lexical(LexicalErrorType::Eof)
| ParseErrorType::ExpectedToken {
found: TokenKind::EndOfFile,
..
}
| ParseErrorType::FStringError(InterpolatedStringErrorType::UnterminatedTripleQuotedString)
| ParseErrorType::TStringError(InterpolatedStringErrorType::UnterminatedTripleQuotedString) => {
ReplContinuationMode::IncompleteImplicit
}
_ => ReplContinuationMode::Complete,
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct ReplSnapshot {
repl: MontyRepl,
executor: Executor,
vm_state: VMSnapshot,
}
impl ReplSnapshot {
fn into_repl(self) -> MontyRepl {
let Self { mut repl, vm_state, .. } = self;
repl.globals = vm_state.globals;
repl
}
fn run(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>> {
let Self {
mut repl,
executor,
vm_state,
} = self;
let ext_result = result.into();
let (converted, vm_state) =
HeapReader::with(&mut repl.heap, &mut (&executor, print), |reader, (executor, print)| {
let mut vm = VM::restore(
vm_state,
&executor.module_code,
reader,
&executor.interns,
print.reborrow(),
executor.assert_repr_max_bytes,
);
let vm_result = match ext_result {
ExtFunctionResult::Return(obj) => vm.resume(obj),
ExtFunctionResult::Error(exc) => vm.resume_with_exception(exc.into()),
ExtFunctionResult::Future(raw_call_id) => {
let call_id = CallId::new(raw_call_id);
vm.add_pending_call(call_id);
vm.run_external()
}
ExtFunctionResult::NotFound(function_name) => {
vm.resume_with_exception(ExtFunctionResult::not_found_exc(&function_name))
}
};
let converted = convert_frame_exit(vm_result, &mut vm);
let vm_state = if converted.needs_snapshot() {
Some(vm.snapshot())
} else {
repl.globals = vm.take_globals();
None
};
(converted, vm_state)
});
build_repl_progress(converted, vm_state, executor, repl)
}
}
fn inject_inputs_into_vm(
executor: &Executor,
input_values: Vec<MontyObject>,
vm: &mut VM<'_>,
) -> Result<(), MontyException> {
for (&slot, obj) in executor.input_slots.iter().zip(input_values) {
let value = obj
.to_value(vm)
.map_err(|e| MontyException::runtime_error(format!("invalid input type: {e}")))?;
let old = mem::replace(&mut vm.globals[slot.index()], value);
old.drop_with(vm);
}
Ok(())
}
fn build_repl_progress(
converted: ConvertedExit,
vm_state: Option<VMSnapshot>,
executor: Executor,
mut repl: MontyRepl,
) -> Result<ReplProgress, Box<ReplStartError>> {
macro_rules! new_repl_snapshot {
() => {
ReplSnapshot {
repl,
executor,
vm_state: vm_state.expect("snapshot should exist"),
}
};
}
match converted {
ConvertedExit::Complete(obj) => {
let Executor {
globals: snippet_globals,
interns,
..
} = executor;
repl.global_names = snippet_globals;
repl.interns = interns;
Ok(ReplProgress::Complete { repl, value: obj })
}
ConvertedExit::FunctionCall {
function_name,
args,
kwargs,
call_id,
method_call,
} => Ok(ReplProgress::FunctionCall(ReplFunctionCall {
function_name,
args,
kwargs,
call_id,
method_call,
snapshot: new_repl_snapshot!(),
})),
ConvertedExit::OsCall { function_call, call_id } => Ok(ReplProgress::OsCall(ReplOsCall {
function_call,
call_id,
snapshot: new_repl_snapshot!(),
})),
ConvertedExit::ResolveFutures(pending_call_ids) => Ok(ReplProgress::ResolveFutures(ReplResolveFutures {
repl,
executor,
vm_state: vm_state.expect("snapshot should exist for ResolveFutures"),
pending_call_ids,
})),
ConvertedExit::NameLookup {
name,
namespace_slot,
is_global,
} => Ok(ReplProgress::NameLookup(ReplNameLookup {
name,
namespace_slot,
is_global,
snapshot: new_repl_snapshot!(),
})),
ConvertedExit::Error(err) => {
let error =
err.into_python_exception(&executor.interns, |fname| repl.sources.get(fname).map(String::as_str));
let Executor {
globals: snippet_globals,
interns,
..
} = executor;
repl.global_names = snippet_globals;
repl.interns = interns;
Err(Box::new(ReplStartError { repl, error }))
}
}
}
fn convert_args(args: Vec<MontyObject>, vm: &mut VM<'_>) -> Result<ArgValues, MontyException> {
match args.len() {
0 => Ok(ArgValues::Empty),
1 => {
let value = args
.into_iter()
.next()
.expect("checked len")
.to_value(vm)
.map_err(|e| MontyException::runtime_error(format!("invalid argument type: {e}")))?;
Ok(ArgValues::One(value))
}
2 => {
let mut iter = args.into_iter();
let a = iter
.next()
.expect("checked len")
.to_value(vm)
.map_err(|e| MontyException::runtime_error(format!("invalid argument type: {e}")))?;
match iter.next().expect("checked len").to_value(vm) {
Ok(b) => Ok(ArgValues::Two(a, b)),
Err(e) => {
a.drop_with(&mut *vm);
Err(MontyException::runtime_error(format!("invalid argument type: {e}")))
}
}
}
_ => {
let mut values = Vec::with_capacity(args.len());
for arg in args {
match arg.to_value(vm) {
Ok(value) => values.push(value),
Err(e) => {
values.drain(..).drop_with(&mut *vm);
return Err(MontyException::runtime_error(format!("invalid argument type: {e}")));
}
}
}
Ok(ArgValues::ArgsKargs {
args: values,
kwargs: KwargsValues::Empty,
})
}
}
}
fn is_callable(value: &Value, heap: &Heap) -> bool {
match value {
Value::Builtin(_) | Value::ModuleFunction(_) | Value::DefFunction(_) => true,
Value::Ref(id) => matches!(
heap.get(*id),
HeapData::Closure(_) | HeapData::FunctionDefaults(_) | HeapData::ExtFunction(_)
),
_ => false,
}
}