Article

PeruneJS, or How I Built My Own JavaScript Engine

· 2 min read

Over the past few months I've been spending my evenings on a proof of concept of a JavaScript engine. The goal I set myself was to write, from scratch, an interpreter capable of running JS code conforming to the ES5.1 standard (so, the somewhat easier version). The main point was really education and the chance to learn something new, rather than building a full production engine that could be used anywhere else.

The interpreter itself has a fairly simple structure. It consists of:

  • a lexer, which handles UTF-8 source code and turns it into tokens,
  • a parser, fairly primitive, based on recursive descent, supporting 39 node types in total,
  • an evaluator, the actual AST interpreter.

Beyond ES5.1 support, I added a few extra things, e.g. globalThis, very primitive support for ES6 Symbols, a microtask queue (including Promises), and simple data structures: Map, Set, WeakMap and WeakSet. The overall goal was to swap the engine in React Native and try to render the UI with my own interpreter instead of the built-in Hermes (but more on that later).

Garbage Collection

The memory reclamation mechanism is definitely worth mentioning. At first I planned to do something similar to the reference counting mechanism in QuickJS, but after thinking it over I took the path of least resistance and implemented a mark & sweep algorithm. Collection runs only on allocation, once the number of cells exceeds twice the number of live ones after the previous GC.

Standard Library and Regex Support

This is where I definitely relied on AI. While I tried to write the lexer, parser and evaluator code myself, the sheer size of the built-in functions and objects really surprised me. Claude helped me a lot here, so I don't have as tight a grip on that part of the code.

Summary

The engine passes 95% of the test262 cases for the ES5.1 standard. That's a satisfying result for me, and it took a lot of acrobatics to get there :) Performance leaves a lot to be desired (after all, it's just a plain interpreter, not an AOT or JIT compiler, and there's no compilation to bytecode with optimizations along the way). Still, it let me learn a lot of techniques and methods, and I got to see with my own eyes how idiotic and nonsensical the JavaScript language standards are :)

Here's the repository: https://github.com/lukaszkurantdev/perunejs