Chapter 1
How Bloom Works
A journey from text to pixels on screen
When you write Bloom code and run it, a lot happens behind the scenes. This book explains the entire journey your code takes, from the moment you type it to the moment shapes appear on your canvas. It is written to be read front to back: each chapter builds on the one before, the way a compiler's stages hand work to the next. But every chapter also stands on its own, so you can jump straight to the part you're curious about and follow the cross-references from there.
The first three stages — lexer, parser, and the resulting AST — are shared. After that, Bloom has two different ways to actually run the tree: a tree-walking interpreter and a bytecode virtual machine. The rest of this guide explains both.
- 2. The Lexer — Breaking code into tokens
- 3. The Parser — Building the syntax tree
- 4. The Interpreter — Running your code
- 5. The Bytecode VM — High-performance execution
- 6. Closures — Functions that remember where they were born
- 7. Reading the Disassembly — Bytecode as assembly you can read
- 8. The Error System — Turning crashes into friendly suggestions
- 9. The WASM Compiler — Pure-numeric functions to WebAssembly
- 10. The Runtime — Backend selection and the draw loop
- 11. The Rendering Pipeline — Drawing 100k+ shapes per frame
- 12. Performance — How Bloom Got Fast — Charted optimization history
The Big Picture
Programming languages are like translators. They take something humans can read and turn it into something a computer can execute. Bloom does this in four main stages:
Lexical Analysis
The Lexer reads your code character by character and groups them into meaningful chunks called tokens. It's like breaking a sentence into words.
circle(100, 50, 30)
becomes
IDENTIFIER "circle" LPAREN NUMBER 100 COMMA NUMBER 50 COMMA NUMBER 30 RPAREN
Parsing
The Parser takes those tokens and figures out how they relate to each other, building a tree structure called an Abstract Syntax Tree (AST).
Interpretation
The Interpreter walks through the AST and executes each node. When it sees a function call to circle, it draws a circle on the canvas.
Why This Matters
Understanding how a language works helps you:
- Debug better — Know where errors come from
- Write cleaner code — Understand what the language expects
- Build your own tools — Syntax highlighters, linters, etc.
- Learn other languages faster — The concepts transfer
Two Execution Backends
Bloom can run the very same AST in two completely different ways:
How Bloom chooses
You don't pick a backend by hand. Bloom defaults to the bytecode VM because it is much faster, and the VM now runs almost everything the language offers — including closures, which it captures natively with a boxed-cell upvalue model (Chapter 6 tells that whole story). The one feature the VM still can't handle is modules / imports: it has no module system. When the compiler sees an import, it throws a clear error, and Bloom transparently re-runs the program on the tree-walking interpreter, which links modules.
The result: you get VM speed for the overwhelming majority of sketches — automatically, with no flag to set — and the interpreter steps in only for the few programs that pull in modules.
nil, false, and 0 are falsy; empty strings and arrays are truthy; the same arithmetic and vector rules; the same beginner-friendly error text). That shared front-end is why falling back from one to the other is safe.