monty
The in-process interpreter: compile, run, suspend and resume sandboxed Python inside your own process. Most hosts should use monty-pool instead, which keeps a sandbox crash from taking the host process down.
pub struct MontyRun { /* private fields */ }
Primary interface for running Monty code.
MontyRun supports two execution modes:
- Simple execution: Use
runorrun_no_limitsto run code to completion - Iterative execution: Use
startto start execution which will pause at external function calls and can be resumed later
use monty::MontyRun;
use monty_types::{CompileOptions, MontyObject};
let runner = MontyRun::new(
"x + 1".to_owned(),
"test.py",
vec!["x".to_owned()],
CompileOptions::default(),
)
.unwrap();
let result = runner.run_no_limits(vec![MontyObject::Int(41)]).unwrap();
assert_eq!(result, MontyObject::Int(42));
pub fn new(
code: String,
script_name: &str,
input_names: Vec<String>,
options: CompileOptions,
) -> Result<Self, MontyException>
Creates a new run snapshot by parsing the given code.
This only parses and prepares the code - no heap or namespaces are created yet.
Call run or start with inputs to execute it.
code- The Python code to executescript_name- The script name for error messages; its final path component is what__file__places under the working directory (/main.pyformain.pyorsrc/main.pyat the root)input_names- Names of input variablesoptions-CompileOptionscontrolling CPython divergences; usuallyCompileOptions::default()
Returns MontyException if the code cannot be parsed.
pub fn code(&self) -> &str
Returns the code that was parsed to create this snapshot.
pub fn with_host_clock(self, clock: HostClock) -> Self
Chooses what date.today() and datetime.now() read, replacing the
System clock a runner starts with.
Only run and run_no_limits
consult it: they have no host to suspend to. Under
start the host answers both calls itself, so a clock
set here is ignored.
Denied takes the clock away; Fixed
freezes an instant, for runs that have to be reproducible. Reading the
wall clock is a weak but real capability — see docs/security.md.
use monty::MontyRun;
use monty_types::{CompileOptions, HostClock, MontyObject};
let code = "from datetime import date\ndate.today().year".to_owned();
let clock = HostClock::Fixed { unix_seconds: 1_700_000_000, microsecond: 0, local_offset_seconds: 0 };
let runner = MontyRun::new(code, "today.py", vec![], CompileOptions::default()).unwrap().with_host_clock(clock);
assert_eq!(runner.run_no_limits(vec![]).unwrap(), MontyObject::Int(2023));
pub fn set_cwd(&mut self, cwd: &str)
Sets the sandbox working directory the run starts in (default /).
cwd is an absolute POSIX virtual path, passed through
normalize_virtual_path so
os.getcwd() reports a canonical directory: it is what relative paths
in open() / os / pathlib calls resolve against before reaching
the host. Hosts typically pass the first mount’s virtual path.
pub fn run(
&self,
inputs: Vec<MontyObject>,
resource_tracker: ResourceTracker,
print: PrintWriter<'_>,
) -> Result<MontyObject, MontyException>
Executes the code to completion assuming not external functions or snapshotting.
This is marginally faster than running with snapshotting enabled since we don’t need to track the position in code, but does not allow calling of external functions.
inputs- Values to fill the first N slots of the namespaceresource_tracker- Custom resource tracker implementationprint- print output writer
pub fn run_no_limits(
&self,
inputs: Vec<MontyObject>,
) -> Result<MontyObject, MontyException>
Executes the code to completion with no resource limits specified (will use the default), printing to stdout/stderr.
pub fn start(
self,
inputs: Vec<MontyObject>,
resource_tracker: ResourceTracker,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Starts execution with the given inputs and resource tracker, consuming self.
Creates the heap and namespaces, then begins execution.
For iterative execution, start consumes self and returns a RunProgress:
RunProgress::FunctionCall- external function call, callFunctionCall::resumeto resumeRunProgress::Complete- execution finished
This enables snapshotting execution state and returning control to the host application during long-running computations.
inputs- Initial input values (must match length ofinput_namesfromnew)resource_tracker- Resource tracker for the executionprint- Writer for print output
Returns MontyException if:
- The number of inputs doesn’t match the expected count
- An input value is invalid (e.g.,
MontyObject::Repr) - A runtime error occurs during execution
This method should not panic under normal operation. Internal assertions may panic if the VM reaches an inconsistent state (indicating a bug).
Implements: Clone, Debug, Deserialize<'de>, Serialize.
pub enum RunProgress {
/// Execution paused at an external function call, or a method call on a
/// host object (`object_id` set).
FunctionCall(FunctionCall),
/// Execution paused for an OS-level operation (filesystem, network, etc.).
OsCall(OsCall),
/// All async tasks are blocked waiting for external futures to resolve.
ResolveFutures(ResolveFutures),
/// Execution paused for an unresolved name lookup.
NameLookup(NameLookup),
/// Execution completed with a final result.
Complete(monty_types::MontyObject),
}
Result of a single step of iterative execution.
Each variant wraps a dedicated struct that owns the execution state and exposes only the resume methods relevant to that suspension reason.
pub fn into_function_call(self) -> Option<FunctionCall>
Consumes the progress and returns the FunctionCall struct if this is a function call.
pub fn into_os_call(self) -> Option<OsCall>
Consumes the progress and returns the OsCall struct if this is an OS call.
pub fn into_complete(self) -> Option<MontyObject>
Consumes the progress and returns the final value if execution completed.
pub fn into_resolve_futures(self) -> Option<ResolveFutures>
Consumes the progress and returns the ResolveFutures struct.
pub fn into_name_lookup(self) -> Option<NameLookup>
Consumes the progress and returns the NameLookup struct.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct FunctionCall {
/// The name of the function or method being called.
pub function_name: String,
/// The positional arguments passed to the function.
pub args: Vec<monty_types::MontyObject>,
/// The keyword arguments passed to the function (key, value pairs).
pub kwargs: Vec<(monty_types::MontyObject, monty_types::MontyObject)>,
/// Unique identifier for this call (used for async correlation).
pub call_id: u32,
/// Uuid of the routed receiver — an instance, or a class type (a
/// classmethod call, or construction spelled `__call__`); `None` for
/// plain external function calls.
pub object_id: Option<monty_types::MontyUuid>,
/// The host may await a coroutine and answer with `Self::resume_eager`.
pub allow_eager_await: bool,
/* private fields */
}
Execution paused at an external function call or dataclass method call.
The host can choose how to handle this:
- Sync resolution: Call
resumeto push the result and continue. - Async resolution: Call
resume_pendingto push anExternalFutureand continue.
When using async resolution, the code continues and may await the future later.
If the future isn’t resolved when awaited, execution yields with ResolveFutures.
When object_id is set, this represents a method call on a host-backed
object (construction of a host class is a __call__ method call): route
it to the host object with that uuid — the receiver is NOT in args.
pub fn tracker_mut(&mut self) -> &mut ResourceTracker
Returns a mutable reference to the resource tracker.
This allows modifying resource limits between execution phases, e.g. setting a time limit before resuming after an external function call.
pub fn tracker(&self) -> &ResourceTracker
Returns the resource tracker while execution is suspended.
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Resumes execution with the return value or exception from the external function.
Consumes self and returns the next execution progress.
result— The return value, exception, or pending future marker.print— Writer forprint()output.
pub fn resume_pending(self, print: PrintWriter<'_>) -> Result<RunProgress, MontyException>
Resumes execution by pushing an ExternalFuture instead of a concrete value.
This is the async resolution pattern: the host continues execution with a
pending future. The code can then await this future later. If the code
awaits the future before it’s resolved, execution will yield with
RunProgress::ResolveFutures.
Uses self.call_id internally — no need to pass it again.
print— Writer for print output.
pub fn resume_eager(
self,
result: Result<MontyObject, MontyException>,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Resumes with a settled coroutine, preserving its awaitable value and exception timing.
Only use when Self::allow_eager_await is true; synchronous returns use Self::resume.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Aborts the feed with an uncatchable exception; see OsCall::abort.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct NameLookup {
/// The name being looked up.
pub name: String,
/* private fields */
}
Execution paused for an unresolved name lookup, or — when
object_id is set — a lazy attribute lookup on a
host-backed object.
The host should check if the name corresponds to a known external function,
value, or instance attribute. Call resume with
NameLookupResult::Value to continue, NameLookupResult::Undefined
to raise NameError (plain lookups) / AttributeError (instance lookups),
or NameLookupResult::Error to raise a host exception in the sandbox.
The namespace slot and scope are managed internally — the host only needs to provide the name resolution result.
pub fn object_id(&self) -> Option<MontyUuid>
Host identity of the receiver for a lazy attribute lookup; None for
a plain global/local name lookup.
pub fn tracker(&self) -> &ResourceTracker
Returns the resource tracker while execution is suspended.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Aborts the feed with an uncatchable exception; see OsCall::abort.
pub fn resume(
self,
result: impl Into<NameLookupResult>,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Resumes execution after name resolution.
For a plain lookup, caches the resolved value in the appropriate slot
(globals or stack) before pushing it, and Undefined raises
NameError. For an instance attribute lookup, the value is pushed as
the attribute expression’s result (never cached), and Undefined
raises AttributeError. Error raises the host’s exception in the
sandbox, bypassing any hasattr() / getattr() default.
result— The resolved value,Undefined, or a host exception.print— Writer for print output.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct OsCall {
/// Typed OS-call dispatch value (variant + args).
pub function_call: monty_types::OsFunctionCall,
/// Unique identifier for this call (used for async correlation).
pub call_id: u32,
/* private fields */
}
Execution paused for an OS-level operation.
The host should execute the OS operation (filesystem, network, etc.) and
call resume(return_value, print) to provide the result and continue.
This enables sandboxed execution where the interpreter never directly performs I/O.
function_call is a tagged OsFunctionCall whose variants carry the
typed args directly. Host bindings that need a generic
(positional, keyword) MontyObject view can call OsFunctionCall::to_args
(the public projection method).
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Resumes execution with the OS call result.
result— The return value or exception from the OS operation.print— Writer forprint()output.
pub fn resume_with(
self,
print: PrintWriter<'_>,
handler: impl FnOnce(OsFunctionCall) -> ExtFunctionResult,
) -> Result<RunProgress, MontyException>
Dispatches the call to handler and resumes execution with its result.
handler receives the OsFunctionCall by value, so large
WriteText / WriteBytes payloads move into the host without
cloning. Prefer this over reading Self::function_call and calling
Self::resume separately when the handler consumes the call.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Ends the feed by raising exc uncatchably at the suspended call.
The exception builds a traceback but bypasses sandbox handlers. Pending
file effects roll back. Always returns Err.
pub fn tracker(&self) -> &ResourceTracker
Returns the resource tracker while execution is suspended.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct ResolveFutures { /* private fields */ }
Execution state paused while waiting for external future results.
Supports incremental resolution — you can provide partial results and Monty will continue running until all tasks are blocked again.
Use pending_call_ids to see which calls are pending, then call
resume with some or all of the results.
pub fn pending_call_ids(&self) -> &[u32]
Returns unresolved call IDs for this suspended state.
pub fn tracker(&self) -> &ResourceTracker
Returns the resource tracker while execution is suspended.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Aborts with an uncatchable exception and abandons pending futures.
pub fn resume(
self,
results: Vec<(u32, ExtFunctionResult)>,
print: PrintWriter<'_>,
) -> Result<RunProgress, MontyException>
Resumes execution with results for some or all pending futures.
Incremental resolution: You don’t need to provide all results at once. If you provide a partial list, Monty will:
- Mark those futures as resolved
- Unblock any tasks waiting on those futures
- Continue running until all tasks are blocked again
- Return
ResolveFutureswith the remaining pending calls
results— List of(call_id, result)pairs. Can be a subset of pending calls.print— Writer for print output.
Returns MontyException if any call_id in results is not in the pending set.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct MontyRepl { /* private fields */ }
Stateful REPL session that executes snippets incrementally without replay.
MontyRepl preserves heap and global variable state between snippets.
Each feed_run or feed_start call compiles and executes only the new snippet against the current
state, avoiding the cost and semantic risks of replaying prior code.
pub fn new(
script_name: &str,
resource_tracker: ResourceTracker,
options: CompileOptions,
) -> Self
Creates an empty REPL session with no code parsed or executed.
All code execution is driven through feed_run or feed_start. This separates
construction from execution, matching the pattern used by MontyRun::new.
The CompileOptions apply to every snippet fed to the session.
pub fn with_host_clock(self, clock: HostClock) -> Self
Chooses what date.today() and datetime.now() read, replacing the
System clock a session starts with.
Only the non-suspending feed_run and
call_function consult it. Under
feed_start the host answers both calls itself, so
a clock set here is ignored.
pub fn set_cwd(&mut self, cwd: &str)
Switches the sandbox working directory (initially /).
cwd is an absolute POSIX virtual path, normalized here like
MontyRun::set_cwd: os.getcwd() reports
it and relative paths in open() / os / pathlib calls resolve
against it before reaching the host. The directory then persists across
snippets, including a snippet’s os.chdir, until the host switches it
again.
pub fn tracker(&self) -> &ResourceTracker
Returns the resource tracker that will be used for the next snippet.
This is primarily intended for host integrations that need to attach per-execution state, such as cancellation markers, to an existing REPL.
pub fn tracker_mut(&mut self) -> &mut ResourceTracker
Returns mutable access to the resource tracker for the next snippet.
REPL hosts use this to install ephemeral execution controls, such as
async cancellation flags, before calling feed_start.
pub fn feed_start(
self,
code: &str,
inputs: Vec<(String, MontyObject)>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Starts executing a new snippet and returns suspendable REPL progress.
This is the REPL equivalent of MontyRun::start: execution may complete,
suspend at external calls / OS calls / unresolved futures, or raise a Python
exception. Resume with the returned state object and eventually recover the
updated REPL from ReplProgress::into_complete.
Unlike MontyRepl::feed_run, this method consumes self so runtime state can be
safely moved into snapshot objects for serialization and cross-process resume.
On a Python-level runtime exception the REPL is not destroyed: it is
returned inside ReplStartError so the caller can continue feeding
subsequent snippets against the same heap and namespace state.
Returns a boxed ReplStartError for syntax, compile-time, or runtime
failures — the REPL session is always preserved inside the error.
pub fn feed_run(
&mut self,
code: &str,
inputs: Vec<(String, MontyObject)>,
print: PrintWriter<'_>,
) -> Result<MontyObject, MontyException>
Feeds and executes a new snippet against the current REPL state to completion.
This compiles only code using the existing global slot map, extends the
global namespace if new names are introduced, and executes the snippet once.
Previously executed snippets are never replayed. If execution raises after
partially mutating globals, those mutations remain visible in later feeds,
matching Python REPL semantics.
Returns MontyException for syntax/compile/runtime failures.
pub fn call_function(
&mut self,
name: &str,
args: Vec<MontyObject>,
print: PrintWriter<'_>,
) -> Result<MontyObject, MontyException>
Calls a Python function defined in the session by name.
Looks up the function, then executes a synthetic <python-input-N>
call expression so failures include a visible host call site.
Returns MontyException if the function is not found, not callable,
raises an exception, or encounters an external function call.
pub fn function_names(&self) -> Vec<&str>
Returns a list of all callable function names defined in the session.
Includes functions, closures, and functions with default arguments. Does not include builtins or external functions.
pub fn has_function(&self, name: &str) -> bool
Returns whether a function with the given name exists in the session.
Implements: Debug, Deserialize<'de>, Drop, Serialize.
pub enum ReplProgress {
/// Execution paused at an external function call or dataclass method call.
FunctionCall(ReplFunctionCall),
/// Execution paused for an OS-level operation.
OsCall(ReplOsCall),
/// All async tasks are blocked waiting for external futures to resolve.
ResolveFutures(ReplResolveFutures),
/// Execution paused for an unresolved name lookup.
NameLookup(ReplNameLookup),
/// Snippet execution completed with the updated REPL and result value.
Complete { repl: MontyRepl, value: monty_types::MontyObject },
}
Result of a single suspendable REPL snippet execution.
This mirrors RunProgress but returns the updated MontyRepl on completion
so callers can continue feeding additional snippets without replaying prior code.
Each variant (except Complete) wraps a dedicated struct with only the relevant
resume methods.
pub fn into_function_call(self) -> Option<ReplFunctionCall>
Consumes the progress and returns the ReplFunctionCall struct.
pub fn into_resolve_futures(self) -> Option<ReplResolveFutures>
Consumes the progress and returns the ReplResolveFutures struct.
pub fn into_name_lookup(self) -> Option<ReplNameLookup>
Consumes the progress and returns the ReplNameLookup struct.
pub fn into_complete(self) -> Option<(MontyRepl, MontyObject)>
Consumes the progress and returns the completed REPL and value.
pub fn into_repl(self) -> MontyRepl
Extracts the REPL session from any progress variant, discarding the in-flight execution state.
Use this to recover the REPL when you need to abandon the current
snippet (e.g. because feed_run doesn’t support async futures).
The REPL state reflects any mutations that occurred before the
snapshot was taken.
pub fn tracker(&self) -> &ResourceTracker
Returns the session’s resource tracker, whatever the progress state.
Lets hosts read resource accounting — e.g. cumulative execution time
for max_duration budgeting — at any suspension point without
consuming the progress.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct ReplFunctionCall {
/// The name of the function or method being called.
pub function_name: String,
/// The positional arguments passed to the function.
pub args: Vec<monty_types::MontyObject>,
/// The keyword arguments passed to the function (key, value pairs).
pub kwargs: Vec<(monty_types::MontyObject, monty_types::MontyObject)>,
/// Unique identifier for this call (used for async correlation).
pub call_id: u32,
/// Uuid of the routed receiver — an instance, or a class type (a
/// classmethod call, or construction spelled `__call__`); `None` for
/// plain external function calls. The receiver is NOT included in `args`.
pub object_id: Option<monty_types::MontyUuid>,
/// The host may await a coroutine and answer with `Self::resume_eager`.
pub allow_eager_await: bool,
/* private fields */
}
REPL execution paused at an external function call or host-class method call.
Resume with resume to provide the return value and continue,
or resume_pending to push an ExternalFuture for async resolution.
pub fn into_repl(self) -> MontyRepl
Extracts the REPL session, discarding the in-flight execution state.
Restores globals from the VM snapshot so the REPL remains usable.
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Resumes snippet execution with an external result.
pub fn resume_pending(
self,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Resumes execution by pushing an ExternalFuture for async resolution.
Uses self.call_id internally — no need to pass it again.
pub fn resume_eager(
self,
result: Result<MontyObject, MontyException>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Resumes with a settled coroutine, preserving its awaitable value and exception timing.
Only use when Self::allow_eager_await is true; synchronous returns use Self::resume.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Aborts the snippet with an uncatchable exception; see ReplOsCall::abort.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct ReplNameLookup {
/// The name being looked up.
pub name: String,
/* private fields */
}
REPL execution paused for an unresolved name lookup, or — when
object_id is set — a lazy attribute lookup on a
host-backed object (a class instance or class type).
The host should check if the name corresponds to a known external function,
value, or instance attribute. Call resume with the
appropriate NameLookupResult. The namespace slot and scope are managed
internally.
pub fn into_repl(self) -> MontyRepl
Extracts the REPL session, discarding the in-flight execution state.
Restores globals from the VM snapshot so the REPL remains usable.
pub fn object_id(&self) -> Option<MontyUuid>
Identity of the receiver for a lazy attribute lookup; None for a
plain global/local name lookup.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Aborts the snippet with an uncatchable exception; see ReplOsCall::abort.
pub fn resume(
self,
result: NameLookupResult,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Resumes execution after name resolution.
For a plain lookup, caches the resolved value in the namespace slot and
Undefined raises NameError; for a host attribute lookup (instance
or class type) the value is pushed uncached and Undefined raises
AttributeError. Error raises the host’s exception in the sandbox,
bypassing any hasattr() / getattr() default.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct ReplOsCall {
/// Typed OS-call dispatch value (variant + args).
pub function_call: monty_types::OsFunctionCall,
/// Unique identifier for this call (used for async correlation).
pub call_id: u32,
/* private fields */
}
REPL execution paused for an OS-level operation.
Resume with resume(result, print) to provide the OS call result and continue.
pub fn into_repl(self) -> MontyRepl
Extracts the REPL session, discarding the in-flight execution state.
Restores globals from the VM snapshot so the REPL remains usable.
pub fn resume(
self,
result: impl Into<ExtFunctionResult>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Resumes snippet execution with the OS call result.
pub fn resume_with(
self,
print: PrintWriter<'_>,
handler: impl FnOnce(OsFunctionCall) -> ExtFunctionResult,
) -> Result<ReplProgress, Box<ReplStartError>>
REPL mirror of OsCall::resume_with — dispatches the call
to handler (which receives the OsFunctionCall by value, so
write payloads move without cloning) and resumes with its result.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Raises exc uncatchably at the suspended call.
Always returns Err with a reusable session; see OsCall::abort.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct ReplResolveFutures { /* private fields */ }
REPL execution state blocked on unresolved external futures.
This is the REPL-aware counterpart to ResolveFutures.
pub fn into_repl(self) -> MontyRepl
Extracts the REPL session, restoring globals from the suspended VM state.
As with the other REPL snapshot types, globals live inside the VM snapshot while execution is suspended. Recovering the REPL for a cancelled or abandoned async snippet must put those globals back so previously defined REPL bindings remain available, and releases the suspended tasks and stack so nothing leaks into the session heap. The compiler tables are committed too: the abandoned snippet may have rebound a global to a function or literal only they can resolve.
pub fn pending_call_ids(&self) -> &[u32]
Returns unresolved call IDs for this suspended state.
pub fn abort(
self,
exc: MontyException,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Aborts with an uncatchable exception and abandons pending futures.
pub fn resume(
self,
results: Vec<(u32, ExtFunctionResult)>,
print: PrintWriter<'_>,
) -> Result<ReplProgress, Box<ReplStartError>>
Resumes snippet execution with zero or more resolved futures.
Supports incremental resolution: callers can provide only a subset of pending call IDs and continue resolving over multiple resumes.
All errors — including API misuse (unknown call_id) and Python-level
runtime failures — are returned as a boxed ReplStartError so the REPL
session is always preserved.
Implements: Debug, Deserialize<'de>, Serialize.
pub struct ReplStartError {
/// REPL session state after the failed snippet — ready for further use.
pub repl: MontyRepl,
/// The Python exception that was raised.
pub error: monty_types::MontyException,
}
Error returned when a REPL snippet raises a Python exception during feed_start or a resume().
Unlike syntax/compile errors which consume the REPL, runtime errors preserve
the full session state so the caller can inspect the error and continue feeding
subsequent snippets. Any global mutations that occurred before the exception
remain visible in the returned repl.
Implements: Debug.
pub enum ReplContinuationMode {
/// The current snippet is syntactically complete and can run now.
Complete,
/// The snippet is incomplete and needs more continuation lines.
IncompleteImplicit,
/// The snippet opened an indented block and should wait for a trailing blank
/// line before execution, matching CPython interactive behavior.
IncompleteBlock,
}
Parse-derived continuation state for interactive REPL input collection.
monty-runtime uses this to decide whether to execute the buffered snippet
immediately, keep collecting continuation lines, or require a terminating
blank line for block statements (if:, def:, etc.).
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq.
pub fn detect_repl_continuation_mode(source: &str) -> ReplContinuationMode;
Detects whether REPL source is complete or needs more input.
This mirrors CPython’s broad interactive behavior:
- Incomplete bracketed / parenthesized / triple-quoted constructs continue.
- Decorators continue until their class or function definition arrives.
- Clause headers (
if:,def:, etc.) require an indented body and then a terminating blank line before execution. - All other parse outcomes are treated as complete (either valid code or a syntax error that should be shown immediately).
pub enum Session {
/// Between feeds, ready for the next snippet.
Idle(Box<MontyRepl>),
/// Mid-feed, waiting on a resume.
Suspended(Box<ReplProgress>),
/// A one-shot `MontyRun` execution paused at a suspension. Not a
/// repl, so it cannot be fed further — only resumed to completion.
Running(Box<RunProgress>),
}
Where a dumped session was paused. The variant order is mirrored by
SessionRef and encoded as a postcard discriminant — keep them in step.
Both arms are boxed because they differ by hundreds of bytes inline; a
Box<T> serializes exactly as T, so this does not change the wire form.
Implements: Debug, Deserialize<'de>.
pub enum SessionRef<'a> {
/// Between feeds, ready for the next snippet.
Idle(&'a MontyRepl),
/// Mid-feed, waiting on a resume.
Suspended(&'a ReplProgress),
/// A paused one-shot `MontyRun` execution.
Running(&'a RunProgress),
}
Borrowed counterpart of Session used when dumping, so a live session can
be serialized without moving the repl out of the host’s own state.
Implements: Debug, Serialize.
pub fn dump(
script_name: &str,
type_check: Option<&monty_types::TypeCheckState>,
state: SessionRef<'_>,
) -> Result<Vec<u8>, postcard::Error>;
Serializes a live session and its metadata into a versioned dump, readable
by Dump::load.
Takes the state by reference because dumping is read-only: the caller keeps its session and can carry on feeding it.
Returns an error if serialization fails.
pub struct Dump {
/// Script name used for tracebacks and type-check diagnostics.
pub script_name: String,
/// `Some` when the session was created with type checking enabled.
pub type_check: Option<monty_types::TypeCheckState>,
/// The interpreter state, and where it was paused.
pub state: Session,
}
A complete REPL session snapshot: the interpreter state plus the session-scoped context that lives outside it.
The metadata travels with the state because a restored session is otherwise
silently downgraded — losing script_name corrupts tracebacks, and losing
type_check disables enforcement the parent asked for.
pub fn load(bytes: &[u8]) -> Result<Self, DumpError>
Restores a session dumped by dump.
Returns DumpError for a dump this build cannot read — most usefully
DumpError::VersionMismatch, which names both versions so a host can
tell a stale snapshot from a corrupt one.
Implements: Debug, Deserialize<'de>.
pub enum DumpError {
/// Too short to hold a header, or missing the magic prefix.
NotADump,
/// Written by a build using a different dump format version.
VersionMismatch { found: u16, expected: u16 },
/// Header was valid but the postcard payload did not decode.
Payload(postcard::Error),
}
Why a dump could not be restored.
Distinguishes the three failures a host cares about, because they need different responses: an old snapshot should be discarded and rebuilt, while a payload error on a current-version dump means corruption.
Implements: Debug, Display, Eq, Error, PartialEq, StructuralPartialEq.
pub const DUMP_VERSION: u16 = 10;
Version of the dump’s postcard schema.
Bump this whenever a serialized discriminant can shift, so older dumps are
rejected instead of decoding as their neighbour. That covers the
interpreter’s own types and everything reachable from Dump — notably
TypeCheckingConfig in monty-types.
macro_rules! defer_drop { /* macro body */ }
The preferred way to ensure a DropWithContext value is cleaned up on every code path.
Creates a DropGuard and immediately rebinds $value as &V and $ctx as
&mut C via DropGuard::as_parts. The original owned value is moved into the
guard, which will call DropWithContext::drop_with when scope exits — whether
that’s normal completion, early return via ?, continue, or any other branch.
Beyond safety, this is often much more concise than inserting drop_with calls
in every branch of complex control flow. For mutable access to the value, use
defer_drop_mut!.
The macro rebinds $ctx as a new let binding, so it cannot be used when $ctx
is self. In &mut self methods, first assign let this = self; and pass this.
macro_rules! defer_drop_mut { /* macro body */ }
Like defer_drop!, but rebinds $value as &mut V via DropGuard::as_parts_mut.
Use this when the value needs to be mutated in place — for example, advancing an
iterator with for_next(), or swapping values during a min/max comparison.