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.