heyq02/moonjs/src/vm does not have a README file

    Engine

    Top-level engine. Holds:

    • globals: the global object (Step 9 populates builtin constructors and prototypes via the Builtins object; M1 also seeds undefined / NaN / Infinity / globalThis).
    • chunk_registry: maps chunk_id → Chunk. Every callable JS function references its underlying chunk through this table; assignment happens in register_chunk, called from run_chunk and from the OP_NEW_CLOSURE arm when it walks into a nested chunk that has not been registered yet. Chunks are physically-identified in the registry (physical_equal), which means re-registering the same Chunk twice returns the existing id.
    • builtins: the Step 9 builtin registry. Kept as Option[Builtins] so downstream milestones can construct an Engine with a stripped-down builtin set (e.g. for isolated unit tests) if needed; M1 always sets it to Some(...) inside Engine::new.

    Engine::eval_script

    fn Engine::eval_script(self : Engine, source : String, filename : String) -> Result[
    JSValue
    ,
    JSException
    ]

    Top-level entry point: parse, compile, and run a JS source string. This is the primary M1 public API — Engine::new().eval_script(src, filename) takes JS source text and returns the top-level completion value (or an unhandled JSException).

    Error handling: parse errors and compile errors are surfaced as JSException values wrapping a SyntaxError-shaped plain object (with name: "SyntaxError", a descriptive message, and a single-frame stack pointing at the offending source location). Runtime exceptions come from execute_frame unchanged.

    The reason parse / compile errors are shaped as SyntaxError rather than being returned via a separate error channel is that from the JS user's perspective they are indistinguishable — both come out of eval_script as thrown Error values. eval() in JS behaves the same way, and this keeps the M1 API surface minimal.

    Engine::new

    fn Engine::new() -> Engine

    Construct a fresh engine with a minimal global object seeded via Builtins::install_into. This wires up Object / Error / the four Error subclasses / undefined / NaN / Infinity / globalThis.

    Engine::run_chunk

    Compile-independent entry point: given a fully-compiled chunk, run it in a fresh top-level frame. Returns the value left on the operand stack when the chunk terminates (via fallthrough, OP_RETURN_UNDEF, etc.), or the unwound JSException on an uncaught throw.

    The value returned on success is the operand-stack top at halt time, if any. If the stack is empty (which is the common case for a script that ended with OP_RETURN_UNDEF), we return Undefined. This differs from run_chunk in a hypothetical future release which might return the script's implicit completion value; M1 keeps it simple.

    Frame

    One activation record. Corresponds to design.md §8.1's Frame with the M1 simplifications documented in this file's header:

    • pc is the instruction word index into chunk.code.
    • locals is preallocated to chunk.local_count. Every slot is a @value.Upvalue heap cell (uniform for M1 — see design deviations in src/value/function.mbt). OP_GET_LOCAL / OP_SET_LOCAL read/write the cell contents; local-slot capture across a closure boundary reuses the cell directly.
    • upvalues holds shared cells captured from the enclosing scope. One entry per chunk.upvalue_slots decl.
    • operand_stack is a growable value stack.
    • try_stack is reserved for 8b2 (handler installation). 8b1 never writes to it — declared here so we don't reshape Frame for 8b2.
    • this_val is stored per design.md §8.1 outside of the locals table.
    • caller links to the calling frame for stack-trace capture.

    Frame::new

    Construct a fresh top-level frame. Parameters:

    • chunk: the compiled unit to run. Its local_count determines how many slots the frame preallocates.
    • this_val: the initial this for the frame. Scripts get Undefined.

    TryHandler

    pub struct TryHandler {
    catch_pc : Int
    finally_pc : Int
    operand_depth : Int
    }

    Try-handler entry. Reserved for 8b2. operand_depth remembers the operand-stack depth at the time the handler was installed so throw can unwind the stack back to that point before landing in the catch handler.

    TryHandler::new

    fn TryHandler::new(catch_pc : Int, finally_pc : Int, operand_depth : Int) -> TryHandler

    8b2 hook: construct a try handler. Not used by 8b1; declared so the type checker doesn't warn about an unconstructed struct.