Prototypes SparkP39 · JavaScript engine

Inside the engine

What Spark makes of the code: the syntax tree its parser builds, the instructions its compiler writes, the environment records that hold the bindings while it runs, and the queue of jobs that promises and await put work on.

Code

Not stepped yet

Syntax tree

Instructions

    Environment records

    Job queue

      Jobs run so far

        How Spark is built
        1. Spark is a JavaScript engine written in JavaScript for this prototype, with no dependencies. Code typed in these pages runs in Spark: it has its own parser, its own compiler, its own machine that runs the compiled instructions, its own objects and its own job queue. It never hands the guest’s code to the browser’s eval or Function.
        2. The lexer reads tokens on demand, because a slash or a closing brace means different things by context. The parser builds a tree in the ESTree shape for scripts and modules up to ES2023, inserts the semicolons that automatic semicolon insertion requires, and reports the early errors of the specification with their position.
        3. The compiler turns the tree into instructions for a stack machine, one code object per function. It works out what the declaration steps need: the var and lexical names, the functions to create, the Annex B functions in blocks. Break, continue and return through finally blocks become jumps with explicit exits.
        4. The machine runs one instruction at a time. A frame holds a program counter, an operand stack, the current environment record and the handlers of the try blocks it is in. Environment records are the specification’s: declarative, function, object, global and module records, with the TDZ for let, const and class.
        5. Objects have the internal methods of the specification, written out: property descriptors, the prototype chain, arrays with their length, string objects, mapped arguments objects, bound functions and proxies with every invariant check. ToPrimitive, ToNumber, ToString, ToPropertyKey, SameValue and the comparisons are Spark’s own.
        6. A generator is a frame that is suspended at yield and resumed later; Spark does not use the host’s generators for it. Async functions await by suspending their frame and resuming it from a promise job. The job queue is Spark’s, and its order matches the order node records for the same program.
        7. The built-ins are written in Spark’s object model: 437 constructors, functions and accessors in a fresh global object. 40 of them pass their core work to the host: parseInt and parseFloat, number formatting, Math’s transcendental functions, Math.random, the URI functions, case mapping and normalization, and Date.now. Date.parse hands a string to the host only when it is in none of the formats Spark writes. Spark’s local time zone is UTC.
        8. Regular expressions run on Spark’s own engine: a pattern typed here never reaches the browser’s RegExp. A parser reads the ES2023 pattern syntax, with the web syntax of Annex B when the u flag is off, named groups, lookbehind and \p{…}. The pattern compiles to instructions for a backtracking machine that keeps its choices on a stack of its own: a quantifier clears its captures on each turn, lookarounds are atomic and lookbehind matches backwards. The Unicode properties and case folding come from tables that a script wrote once from node’s Unicode 17.0 data. A match that takes more than 10,000,000 steps stops with a RangeError, so a pattern that backtracks without end cannot hang the page. The v flag is read as u: its set notation is not written.
        9. ArrayBuffer, DataView and the twelve typed arrays are Spark’s objects. A typed array reads its numeric keys as elements, a detached buffer leaves it empty, and the conversions between numbers and bytes are written out: the integers by modulo arithmetic, Uint8Clamped by its rounding rule and the floats by their IEEE 754 fields. Float16Array, from ES2025, rounds a number to binary16 once, half to even, straight from the 64-bit value rather than through 32 bits, which would round twice; Math.f16round and DataView’s getFloat16 and setFloat16 use the same conversion. The bytes are stored in a Uint8Array of the host, used as memory, and a buffer holds at most 256 MB.
        10. Spark is judged by test262, the official conformance suite. In the full run in node, 22,763 of 22,763 tests pass in test/language and 15,876 of 15,878 in test/built-ins for the built-ins Spark has; 2,787 tests are skipped by feature. A subset of 3,127 tests runs live in the test262 page and in the unit tests on every pull request.
        11. The playground runs code in slices of steps, so the page stays responsive; a step limit and a time limit stop an endless loop. Step runs one node of the tree at a time and shows the call stack and the scopes with their bindings.
        12. What Spark does not do: resizable array buffers, shared memory and Atomics, the set notation of the v flag, RegExp’s legacy properties such as RegExp.$1, Intl, Temporal, WeakRef and FinalizationRegistry, the proposals after ES2023, proper tail calls and more than one global object at a time. It has no JIT, so it runs about as fast as a simple interpreter: tens of millions of instructions a second, far slower than the browser’s engine. Calls that native built-ins make into guest code, such as the callback of Array.prototype.map, run as one step of the stepper.