Prototypes Prototype docsP11 · Docs and assistant
JavaScript engine (P39)
DocsPage 39 of 39

JavaScript engine (P39)

Spark is a JavaScript engine written for this prototype in JavaScript, with no dependencies. Code typed in its pages runs in Spark instead of the browser's own engine. Spark has its own parser, compiler, machine, objects and job queue. It never hands the code to the browser's eval or Function.

Parsing

The lexer reads one token at a time when the parser asks for it. A slash can start a regular expression or divide, and a closing brace can end a block or continue a template. The parser asks the lexer for the kind of token its grammar expects.

The parser builds a syntax tree in the ESTree shape for scripts and modules up to ES2023: classes with fields, private names and static blocks, generators, async functions and async generators, destructuring, optional chaining, BigInt and the rest. It inserts the semicolons that the rules of automatic semicolon insertion require. It reports the early errors of the specification, such as a let declared twice or a break outside a loop, with the place in the source.

Compiling and running

The compiler turns the tree into instructions for a stack machine, one code object per function. It works out what the declaration steps of the specification need: the var names, the lexical names, the functions to create, and the functions in blocks that also get a var binding in sloppy code. A break, continue or return that leaves a finally block becomes a jump with explicit exits.

The machine runs one instruction at a time. A frame holds its program counter, an operand stack, the current environment record and the handlers of the try blocks it is in. The environment records are the specification's: declarative, function, object, global and module records, with the temporal dead zone for let, const and class.

A generator is a frame that stops at yield and goes on later; Spark does not use the host's generators for it. An async function stops its frame at await and goes on from a promise job. The job queue is Spark's own, and its order matches the order node records for the same program.

Objects and built-ins

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.

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. Spark's local time zone is UTC.

ArrayBuffer, DataView and the twelve typed arrays are Spark's objects. A typed array reads its numeric keys as elements, and a detached buffer leaves it empty. 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.

Regular expressions

Regular expressions run on Spark's own engine, and a pattern typed in the pages never reaches the browser's RegExp. A parser reads the ES2023 pattern syntax: the strict syntax of the u flag, the web syntax of Annex B without it, 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.

How it is judged

Spark is held against test262, the official conformance suite of JavaScript. It ran all of test/language and the directories of test/built-ins for the built-ins it has, in node, in strict and sloppy mode as each test asks. 22,763 of 22,763 tests pass in test/language and 15,876 of 15,878 in test/built-ins. 2,787 tests are skipped because they need a feature Spark does not attempt, and the page lists each feature with its count.

A subset of 3,127 tests from 75 directories, chosen by a seed, runs live in the page in a Web Worker and in the unit tests on every pull request. The unit tests also parse a corpus, print it and parse it again, and require the same tree for 1,432 programs.

The pages

The playground runs a sample or your own code. Run goes in slices of steps, so the page stays responsive, and a step limit and a time limit stop an endless loop. Step walks the code one node at a time and shows the call stack and the scopes with their bindings. An error marks its place in the code.

Inside the engine shows the syntax tree of the code as a tree you can open, the instructions of the frame that runs, the environment records out to the global record, and the job queue during a promise sample.

The limits

Spark has no resizable array buffers, shared memory, Atomics, set notation for the v flag, legacy RegExp properties such as RegExp.$1, Intl, Temporal, WeakRef or FinalizationRegistry, none of the proposals after ES2023, no proper tail calls and one global object per run. It has no JIT, so it is far slower than the browser's engine. When a built-in such as Array.prototype.map calls back into your code, the callback runs as one step of the stepper.

Previous: SQL database (P38).

How Prototype docs is built

The pages are written in Markdown and built into these pages and an index of 221 sections. The assistant answers from the index, and every answer names the sections it was taken from.