Here is a secret that feels like magic until you see it up close: a language is just a
program that runs other programs. When you type 2 * (3 + 4) into a calculator,
or let x = 6 in x*x + 1 into a scripting REPL, some ordinary code reads your text, works
out what it means, and hands back an answer. That code is an interpreter — and the
simplest kind, a tree-walking interpreter, is small enough to build in about
thirty lines.
This page is the capstone that ties four ideas together:
Running a program is a short assembly line. Your source code starts as one long string of characters and ends as a value. In between are three classic stages:
"3 + 4" becomes [3] [+] [4]. Whitespace disappears.2 * (3 + 4) puts * at the root with +
nested inside.The parser's job (text → tree) and the evaluator's job (tree → value) are genuinely separate steps, and keeping them apart is what makes each one simple. Once the parser has done its work, the evaluator never sees a single character of source text again — it only ever sees the tidy tree.
An interpreter walks the tree and produces the answer directly, right now. A compiler walks the same tree but instead emits new code — machine instructions, or bytecode — which some other machine runs later. Same front end (tokenise, parse), different back end. Python and Ruby lean on tree-walking interpreters; C and Rust compile. Many modern languages do both: interpret first for a quick start, then compile the hot paths. Everything on this page is the interpreter side.
Because the AST is a 6, or a variable name
like x. Internal nodes carry operations — a + node has a
left child and a right child; a let node binds a name, then evaluates a body.
Here is the AST for the body of our sample program, x*x + 1. To find its value we visit
the nodes in post-order: evaluate a node's children first, then combine.
The numbered badges show the order the evaluator touches each node when x = 6.
Now the payoff. We model an expression as a union type — a value that is exactly one
of our node shapes — and write evaluate, a function that
let evaluates its bound value, extends the environment, and evaluates
its body. Press Run:
That is the entire idea. evaluate calls itself on each child, so the shape of the
recursion mirrors the shape of the tree exactly — a post-order
let hands its children a
richer environment with one more name bound.
if
Adding a feature to a tree-walker is delightfully local: invent a new node shape, then add one
case to evaluate. Here we drop the environment for brevity and add a
comparison (<, yielding 1 or 0) plus an
if-expression that evaluates its condition and then walks only the chosen
branch. Press Run:
Notice the if case does not evaluate both branches — it picks one. That is a
real semantic choice: control flow is exactly "which subtree do I walk?" You have now built a
language with arithmetic, comparison, variables, scope, and branching, and it is still tiny.
let) extends it for its body.Because that is literally all it does: it takes a walk through the tree. There are faster designs — compile the tree to bytecode and run that on a virtual machine, or JIT-compile hot code to native instructions — and production language runtimes use them. But every one of those still begins by tokenising and parsing into a tree, and the tree-walker is the clearest way to see what a program means. It is the interpreter you write first, and the one you reach for when you embed a little scripting language inside a bigger app.
op node you must
evaluate both subtrees first, then apply the operator — that is what "post-order" means.
Trying to combine before the children are computed is the classic tree-walking bug.
let must evaluate
its body in an environment extended with the new binding. If you forget to pass the
richer environment down, a later var look-up fails — the variable "doesn't exist",
even though you just bound it.
if should not evaluate both branches. Evaluate the condition,
then walk only the taken branch — otherwise side effects (or infinite recursion) in the untaken
branch would fire when they shouldn't.