↓ Skip to main content

Monkey Interpreter In Zig

Tested On
Zig 0.16.0
Last Updated
September 2026

This book is heavily inspired by the Interpreter and Compiler books written by Thorsten Ball.

My most significant learning of general programming concepts came from these two books. They’ve been invaluable to me in my career. I’ve personally purchased several copies of both books (for myself and friends) and implore you to do the same if you are programming in Go. This book is a work in progress and I’ll continue updating it as I learn more and as the Zig programming language changes.

In this Zig book we’ll be borrowing Thorsten’s Monkey language. The goal is to learn how interpreters work, not to learn Zig. I’m assuming you can already read Zig comfortably, so the explanations are about how the interpreter works rather than how Zig works. As you read through the book please try to write the code blocks instead of copy/pasting, it’ll help build muscle memory for the Zig language but also it’ll force you to think about each line as you write it and understand what the code is doing.

How to read this book
#

Each file is shown twice. First in pieces, with the explanation of each piece next to it, and then as a “Complete” listing at the end of the section. The complete listing is the one to trust if they ever seem to disagree. Every chapter ends with a test run, and no output from zig build test means everything passed.

If you find bugs or have issues following along please feel free to reach out on my socials and I’ll do the best I can to improve. I’m making this book free because the concept and the structure are not entirely mine, it’s borrowing from Thorsten’s books and I felt the Zig community could use a version of those books.


What We Build
#

A fully working Monkey interpreter including a lexer, parser, evaluator and REPL. Self-contained and testable all on its own. Source code goes in one end, values come out the other, and each chapter builds one stage of the pipeline.

Chapter Component What It Does
1 Lexer Turns Monkey source code into tokens.
2 Parser Pratt parser that builds an Abstract Syntax Tree. Covers precedence, memory strategy.
3 Evaluator Tree-walking interpreter. Object system, environments, closures, return values, errors.
4 Built-ins and REPL The six built-in functions and the interactive prompt, plus the capstone programs.
5 Where to go from here Exercises to expand your knowledge on what we learned.

Project Setup
#

Create the project:

mkdir monkey && cd monkey
zig init
rm src/main.zig src/root.zig

zig init creates boilerplate src/main.zig and src/root.zig files. Delete both, we won’t need those where we’re going. We build every file from scratch.

build.zig
#

Replace the boilerplate build.zig with this code block below. We’ll update this file as we make progress through the book.

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "monkey",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    const run_step = b.step("run", "Run the Monkey REPL");
    run_step.dependOn(&run_cmd.step);

    // Uncomment entries as you create files in later chapters.
    const test_files = [_][]const u8{
        "src/token.zig",
        "src/lexer.zig",
        // "src/parser_test.zig", // Chapter 2.
        // "src/evaluator_test.zig", // Chapter 3.
    };

    const test_step = b.step("test", "Run all tests");
    for (test_files) |file| {
        const t = b.addTest(.{
            .root_module = b.createModule(.{
                .root_source_file = b.path(file),
                .target = target,
                .optimize = optimize,
            }),
        });
        const run_t = b.addRunArtifact(t);
        test_step.dependOn(&run_t.step);
    }
}

The Monkey Language
#

// Variable bindings.
let name = "Monkey";
let age = 1;

// Integer arithmetic.
let result = (5 + 10 * 2 + 15 / 3) * 2 + -10;  // => 50

// Boolean expressions.
let isTrue = 1 < 2;       // => true
let isFalse = 1 > 2;      // => false
let isEqual = 10 == 10;    // => true
let isNotEqual = 10 != 9;  // => true

// String concatenation.
let greeting = "Hello" + " " + "World!";

// If/else expressions.
let max = fn(a, b) {
  if (a > b) { a } else { b }
};
max(5, 10);  // => 10

// Functions are first-class.
let add = fn(x, y) { x + y; };
add(1, 2);  // => 3

// Closures.
let newAdder = fn(x) {
  fn(y) { x + y; };
};
let addTwo = newAdder(2);
addTwo(3);  // => 5

// Implicit return.
let fibonacci = fn(x) {
  if (x == 0) {
    0
  } else {
    if (x == 1) {
      return 1;
    } else {
      fibonacci(x - 1) + fibonacci(x - 2);
    }
  }
};
fibonacci(10);  // => 55

// No loops.
let map = fn(arr, f) {
  let iter = fn(arr, accumulated) {
    if (len(arr) == 0) {
      accumulated
    } else {
      iter(rest(arr), push(accumulated, f(first(arr))));
    }
  };
  iter(arr, []);
};

// Arrays.
let numbers = [1, 2, 3, 4, 5];
let doubled = map(numbers, fn(x) { x * 2; });
// => [2, 4, 6, 8, 10]

// Hashes.
let people = {"name": "Monkey", "age": 1};
people["name"];  // => "Monkey"

// Built-in functions.
len("hello");        // => 5
len([1, 2, 3]);      // => 3
first([1, 2, 3]);    // => 1
last([1, 2, 3]);     // => 3
rest([1, 2, 3]);     // => [2, 3]
push([1, 2], 3);     // => [1, 2, 3]
puts("hello world"); // prints it