pub struct Vm { /* private fields */ }Expand description
the bytecode virtual machine.
holds the program being run, the value stack, the call-frame stack, the
heap, the console buffer, the leak log, the persistent globals, and the
original source text (needed to build a line-covering Span for a
runtime error and for the REPL pipeline).
Implementations§
Source§impl Vm
impl Vm
Sourcepub fn new(program: Program, src: String) -> Vm
pub fn new(program: Program, src: String) -> Vm
construct a VM ready to run program from its entry point.
pushes the initial CallFrame for program.main_index. src is the
program’s source text – the VM keeps it to build a line-covering span
for any runtime error and to drive the REPL pipeline.
Sourcepub fn new_repl() -> Vm
pub fn new_repl() -> Vm
construct an empty VM ready to receive REPL calls.
unlike Vm::new, there is no Program and no main frame yet – a
REPL VM starts blank: an empty program, an empty heap, an empty
console, an empty repl_history. the first Vm::repl_eval call
builds the first real Program from the first line and runs it; every
later call rebuilds the program from the whole accumulated source.
the empty Program has no chunks, so this VM must not be run()
directly – repl_eval is the only entry point for a REPL VM. the
persistent console / leak_log accumulate across repl_eval calls.
Sourcepub fn render_value(&self, v: Value) -> (String, String)
pub fn render_value(&self, v: Value) -> (String, String)
render a runtime Value to its (display string, type name) pair.
the public counterpart of the in-crate Vm::value_to_string and
Vm::runtime_type_name helpers, for an external consumer that holds a
Value – specifically the qala CLI’s REPL, which evaluates a line
against this VM and must display the result. the WASM bridge does not use
this method (it is in-crate and reaches the two helpers directly, building
a StateValue); this exists so the separate qala-cli crate has the same
rendering without the two helpers leaving pub(crate).
purely additive: it calls the two existing helpers and changes no v1
behavior. never panics – the helpers are total over every Value.
Sourcepub fn run(&mut self) -> Result<(), QalaError>
pub fn run(&mut self) -> Result<(), QalaError>
run the program from the current state to Halt or the first runtime
error.
loops over Vm::dispatch_one; a Ran outcome continues, a Halted
outcome returns Ok(()). shares every byte of execution logic with
Vm::step – both go through dispatch_one.
Sourcepub fn step(&mut self) -> Result<StepOutcome, QalaError>
pub fn step(&mut self) -> Result<StepOutcome, QalaError>
advance exactly one instruction.
one step() call executes one full instruction including its operands:
ip moves by 1 + operand_bytes() of the executed opcode. the
playground’s step-through calls this in a loop. a thin wrapper over
Vm::dispatch_one – no duplicated dispatch logic.
Sourcepub fn get_state(&self) -> VmState
pub fn get_state(&self) -> VmState
snapshot the VM’s execution state for the playground’s step-through.
the VmState carries the current chunk index and instruction
pointer, the value stack (each slot rendered and type-tagged), the
current frame’s in-scope variables paired with their REAL source
names, the accumulated console output, and the leak log.
the output is deterministic – it iterates Vecs in index order, never
a HashMap, so two get_state calls on the same VM state produce
byte-identical snapshots (the contract Phase 6’s WASM bridge needs).
never panics. when frames is empty (the program has finished and the
last frame returned) it reports a terminal snapshot: the last chunk
index and an ip one past that chunk’s code – not an out-of-bounds
index, not a panic. an out-of-range chunk_idx is handled the same
defensive way.
Sourcepub fn repl_eval(&mut self, source: &str) -> Result<Value, QalaError>
pub fn repl_eval(&mut self, source: &str) -> Result<Value, QalaError>
evaluate one line of REPL source against the persistent VM state.
the accumulating-source REPL: every prior accepted line plus source
is concatenated into one wrapped program, the whole pipeline (lex,
parse, typecheck, codegen) runs on it, and the result executes against
this VM. because the whole accumulated program is rebuilt and re-run
each call, a let binding from an earlier call is simply an earlier
statement in the same body and is in scope for the new line – that is
how state persists.
the wrapping shape: the accepted statements plus the new line live
inside a synthetic fn __repl_main() is io { ... }. when the new line
is an expression its value is captured via a let __repl_result = <expr> binding (the result this method returns); a statement line
yields void. a line that parses as a top-level item (fn / struct
/ enum / interface) is placed OUTSIDE __repl_main as a sibling
item and the result is void.
on a lexer / parser / typechecker / codegen error the error is returned
and source is NOT appended to the history – a line that does not
compile cannot poison later evaluations. the persistent console and
leak_log survive across calls so output accumulates; the heap is
naturally rebuilt each call because the whole program re-runs from
scratch (correct and simplest – there is no stale heap state).