Skip to main content

Monkey Interpreter In Zig

Tested On
Zig 0.15.2
Last Updated
March 2026

This book is heavily inspired by the Intepreter 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. 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.

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
#

The Interpreter
#

A fully working Monkey interpreter including a Lexer, parser, evaluator and REPL. Self-contained and testable all on its own.

Chapter Component What It Does
1 Lexer Tokenizes Monkey source code into tokens.
2 Parser Pratt parser that builds an Abstract Syntax Tree.
3 Evaluator Tree-walking interpreter with object system and environments.
4 Extension Strings, arrays, hashes, built-in functions, REPL.

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 to stdout