Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lessons from Building

The Compiler Is the Easy Part

Parsing s-expressions and emitting ESTree is straightforward. The hard part is deciding what to emit: what type checks to generate, when to use IIFE wrapping, how to handle await in expression position, what error messages to show. Design decisions outnumber implementation decisions ten to one.

Error Messages Are a Feature

A compiler’s quality is measured by its errors as much as its output. “Expected type keyword at position 3” is bad. “Field ‘name’ missing type annotation (use :any to opt out)” is good. Every error should suggest a fix. The error message is the compiler’s user interface.

Tests Are the Specification

With two independent compilers (JS and Rust), the test fixtures are the language’s ground truth. If both compilers produce the same output for a test case, the behaviour is specified. Cross-compiler tests are worth more than documentation.

Design Documents Prevent Re-Litigation

Every settled decision has a DD. When a question arises during implementation, the DD answers it. When a new feature interacts with an old decision, the DD records the reasoning. Without DDs, the same argument happens three times.

Thin Skin Means Inheriting Problems

JavaScript’s floating-point-only arithmetic, typeof null === "object", and Array.isArray vs typeof for arrays are all inherited by Lykn. The surface compiler can warn about them but can’t fix them. Some hazards are below the skin.