🐵 Monkey programming language interpreter designed in Writing An Interpreter In Go and Writing a Compiler in Go. A step-by-step walk-through where each commit is a fully working part. Read the books…
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Go to file
James Mills c734ae7f3c
go mod tidy
10 months ago
ast Fix import paths 2 years ago
builtins Fix import paths 2 years ago
code Added module and import support (#6) 4 years ago
compiler Fix import paths 2 years ago
eval Fix import paths 2 years ago
examples Added DependaBot config 3 years ago
lexer Fix import paths 2 years ago
object Fix import paths 2 years ago
parser Fix import paths 2 years ago
plugins Fix import paths 2 years ago
repl Fix import paths 2 years ago
testdata Added module and import support (#6) 4 years ago
token Added module and import support (#6) 4 years ago
tools Fixed typo in release script 4 years ago
typing Fix import paths 2 years ago
utils Added module and import support (#6) 4 years ago
vim Updated vim syntax file 4 years ago
vm Fix import paths 2 years ago
.gitignore Ignore vim backup files 4 years ago
.goreleaser.yml Add goreleaser config 4 years ago
Dockerfile Added Dockerfile (#15) 3 years ago
LICENSE Move project to Github 5 years ago
Makefile Fix Drone CI pipeline 4 years ago
README.md Fix import paths 2 years ago
ReadingGuide.md Fix import paths 2 years ago
_config.yml Set theme jekyll-theme-hacker 4 years ago
go.mod Fix import paths 2 years ago
go.sum go mod tidy 10 months ago
main.go Fix import paths 2 years ago
version.go Improved build and added version support. Added support for executing files and some cli options 4 years ago



CodeCov Go Report Card GoDoc Sourcegraph

Monkey programming language interpreter designed in Writing An Interpreter In Go. A step-by-step walk-through where each commit is a fully working part. Read the book and follow along with the commit history.

Table of Contents


Still working on a self-hosted Monkey lang (Monkey written in Monkey).

Read and Follow

Read the books and follow along with the following commit history. (This also happens to be the elapsed days I took to read both books!)

See: Reading Guide

Please note that whilst reading the awesome books I slightly modified this version of Monkey-lang in some places. For example I opted to have a single RETURN Opcode.

Quick start

$ go get git.mills.io/prologic/monkey-lang
$ monkey-lang


To build run make.

$ git clone https://git.mills.io/prologic/monkey-lang
$ monkey-lang
$ make
This is the Monkey programming language!
Feel free to type in commands

To run the tests run make test

You can also execute program files by invoking monkey-lang <filename> There are also some command-line options:

$ ./monkey-lang -h
Usage: monkey-lang [options] [<filename>]
  -c	compile input to bytecode
  -d	enable debug mode
  -e string
    	engine to use (eval or vm) (default "vm")
  -i	enable interactive mode
  -v	display version information

Monkey Language

See also: examples


A Monkey program is simply zero or more statements. Statements don't actually have to be separated by newlines, only by white space. The following is a valid program (but you'd probably use newlines in theif block in real life):

s := "world"
print("Hello, " + s)
if (s != "") { t := "The end" print(t) }
// Hello, world
// The end

Between tokens, white space and comments (lines starting with // or # through to the end of a line) are ignored.


Monkey has the following data types: null, bool, int, str, array, hash, and fn. The int type is a signed 64-bit integer, strings are immutable arrays of bytes, arrays are grow-able arrays (use the append() builtin), and hashes are unordered hash maps. Trailing commas are NOT allowed after the last element in an array or hash:

Type Syntax Comments
null null
bool true false
int 0 42 1234 -5 -5 is actually 5 with unary -
str "" "foo" "\"quotes\" and a\nline break" Escapes: \" \\ \t \r \n \t \xXX
array [] [1, 2] [1, 2, 3]
hash {} {"a": 1} {"a": 1, "b": 2}

Variable Bindings

>> a := 10

Arithmetic Expressions

>> a := 10
>> b := a * 2
>> (a + b) / 2 - 3

Conditional Expressions

Monkey supports if and else:

>> a := 10
>> b := a * 2
>> c := if (b > a) { 99 } else { 100 }
>> c

Monkey also supports else if:

>> test := fn(n) { if (n % 15 == 0) { return "FizzBuzz" } else if (n % 5 == 0) { return "Buzz" } else if (n % 3 == 0) { return "Fizz" } else { return str(n) } }
>> test(1)
>> test(3)
>> test(5)
>> test(15)

While Loops

Monkey supports only one looping construct, the while loop:

i := 3
while (i > 0) {
    i = i - 1
// 3
// 2
// 1

Monkey does not have break or continue, but you can return <value> as one way of breaking out of a loop early inside a function.

Functions and Closures

You can define named or anonymous functions, including functions inside functions that reference outer variables (closures).

>> multiply := fn(x, y) { x * y }
>> multiply(50 / 2, 1 * 2)
>> fn(x) { x + 10 }(10)
>> newAdder := fn(x) { fn(y) { x + y } }
>> addTwo := newAdder(2)
>> addTwo(3)
>> sub := fn(a, b) { a - b }
>> applyFunc := fn(a, b, func) { func(a, b) }
>> applyFunc(10, 2, sub)

NOTE: You cannot have a "bare return" -- it requires a return value. So if you don't want to return anything (functions always return at least null anyway), just say return null.

Recursive Functions

Monkey also supports recursive functions including recursive functions defined in the scope of another function (self-recursion).

>> wrapper := fn() { inner := fn(x) { if (x == 0) { return 2 } else { return inner(x - 1) } } return inner(1) }
>> wrapper()

Monkey also does tail call optimization and turns recursive tail-calls into iteration.

>> fib := fn(n, a, b) { if (n == 0) { return a } if (n == 1) { return b } return fib(n - 1, b, a + b) }
>> fib(35, 0, 1)


>> makeGreeter := fn(greeting) { fn(name) { greeting + " " + name + "!" } }
>> hello := makeGreeter("Hello")
>> hello("skatsuta")
Hello skatsuta!


>> myArray := ["Thorsten", "Ball", 28, fn(x) { x * x }]
>> myArray[0]
>> myArray[4 - 2]
>> myArray[3](2)


>> myHash := {"name": "Jimmy", "age": 72, true: "yes, a boolean", 99: "correct, an integer"}
>> myHash["name"]
>> myHash["age"]
>> myHash[true]
yes, a boolean
>> myHash[99]
correct, an integer

Assignment Expressions

Assignment can assign to a name, an array element by index, or a hash value by key. When assigning to a name (variable), it always assigns to the scope the variable was defined .

To help with object-oriented programming, obj.foo = bar is syntactic sugar for obj["foo"] = bar. They're exactly equivalent.

i := 1
func mutate() {
    i = 2
// 1
// 2
// 2

map = {"a": 1}
func mutate() {
    map.a = 2
// 1
// 2
// 2

lst := [0, 1, 2]
lst[1] = "one"
// [0, "one", 2]

map = {"a": 1, "b": 2}
map["a"] = 3
map.c = 4
// {"a": 3, "b": 2, "c": 4}

Binary and unary operators

Monkey supports pretty standard binary and unary operators. Here they are with their precedence, from highest to lowest (operators of the same precedence evaluate left to right):

Operators Description
[] obj.keu Subscript
- Unary minus
* / % Multiplication, Division, Modulo
+ - Addition, Subtraction
< <= > >= in Comparison
== != Equality
<< >> Bit Shift
~ Bitwise not
& Bitwise and
| Bitwise or
|| Logical or (short-circuit)
&& Logical and (short-circuit)
! Logical not

Several of the operators are overloaded. Here are the types they can operate on:

Operator Types Action
[] str[int] fetch nth byte of str (0-based)
[] array[int] fetch nth element of array (0-based)
[] hash[str] fetch hash value by key str
- int negate int
* int * int multiply ints
* str * int repeat str n times
* int * str repeat str n times
* array * int repeat array n times, give new array
* int * array repeat array n times, give new array
/ int / int divide ints, truncated
% int % int divide ints, give remainder
+ int + int add ints
+ str + str concatenate strs, give new string
+ array + array concatenate arrays, give new array
+ hash + hash merge hashes into new hash, keys in right hash win
- int - int subtract ints
< int < int true iff left < right
< str < str true iff left < right (lexicographical)
< array < array true iff left < right (lexicographical, recursive)
<= > >= same as < similar to <
<< int << int Shift left by n bits
>> int >> int Shift right by n bits
== any == any deep equality (always false if different type)
!= any != any same as not ==
| int | int Bitwise or
& int & int Bitwise and
~ ~int Bitwise not (1's complement)
|| bool || bool true iff either true, right not evaluated if left true
&& bool && bool true iff both true, right not evaluated if left false
! !bool inverse of bool

Builtin functions

  • len(iterable) Returns the length of the iterable (str, array or hash).
  • input([prompt]) Reads a line from standard input optionally printing prompt.
  • print(value...) Prints the value(s) to standard output followed by a newline.
  • first(array) Returns the first element of the array.
  • last(array) Returns the last element of the array.
  • rest(array) Returns a new array with the first element of array removed.
  • push(array, value) Returns a new array with value pushed onto the end of array.
  • pop(array) Returns the last value of the array or null if empty.
  • exit([status]) Exits the program immediately with the optional status or 0.
  • assert(expr, [msg]) Exits the program immediately with a non-zero status if expr is false optionally displaying msg to standard error.
  • bool(value) Converts value to a bool. If value is bool returns the value directly. Returns true for non-zero int(s), false otherwise. Returns true for non-empty str, array and hash values. Returns true for all other values except null which always returns false.
  • int(value) Converts decimal value str to int. If value is invalid returns null. If valueis anint` returns its value directly.
  • str(value) Returns the string representation of value: null for null, true or false for bool, decimal for int (eg: 1234), the string itself for str (not quoted), the Monkey representation for array and hash (eg: [1, 2] and {"a": 1} with keys sorted), and something like <fn name(...) at 0x...> for functions..
  • type(value) Returns a str denoting the type of value: nil, bool, int, str, array, hash, or fn.
  • args() Returns an array of command-line options passed to the program.
  • lower(str) Returns a lowercased version of str.
  • upper(str) Returns an uppercased version of str.
  • join(array, sep) Concatenates strs in array to form a single str, with the separator str between each element.
  • split(str[, sep]) Splits the str using given separator sep, and returns the parts (excluding the separator) as an array. If sep is not given or null, it splits on whitespace.
  • find(haystack, needle) Returns the index of needle strinhaystack str, or the index of needleelement inhaystack` array. Returns -1 if not found.
  • readfile(filename) Reads the contents of the file filename and returns it as a str.
  • writefile(filename, data) Writes data to a file filename.
  • abs(n) Returns the absolute value of the n.
  • pow(x, y)Returnsxto the power ofyasint`(s).
  • divmod(a, b) Returns an array containing the quotient and remainder of a and b as int(s). Equivilent to [a / b, b % b].
  • bin(n) Returns the binary representation of n as a str.
  • hex(n) Returns the hexidecimal representation of n as a str.
  • oct(n) Returns the octal representation of n as a str.
  • ord(c) Returns the ordincal value of the character c as an int.
  • chr(n) Returns the character value of n as a str.
  • hash(any) Returns the hash value of any as an int.
  • id(any) Returns the identity of any as an int.
  • min(array) Returns the minimum value of elements in array.
  • max(array) Returns the maximum value of elements in array.
  • sorted(array) Sorts the array using a stable sort, and returns a new array.. Elements in the array must be orderable with < (int, str, or array of those).
  • reversed(array) Reverses the array array and returns a new array.
  • open(filename[, mode])
  • write(fd, data) Writes str data to the open file descriptor given by int fd.
  • read(fd, [n]) Reads from the file descriptor fd (int) optinoally up to n (int) bytes and returns the read data as a str.
  • close(fd) Closes the open file descriptor given by fd (int).
  • seek(fd, offset[, whence]) Seeks the file descriptor fd (int) to the offset (int). The optional whence (int) determins whether to seek from the beginning of the file (0), relativie to the current offset (1) or the end of the file (2).
  • socket(type)
  • bind(fd, address)
  • listen(fd, backlog)
  • accept(fd)
  • connect(fd, address)


>> Person := fn(name, age) { self := {} self.name = name self.age = age self.str = fn() { return self.name + ", aged " + str(self.age) } return self }
>> p := Person("John", 35)
>> p.str()
"John, aged 35"


Monkey supports modules. Modules are just like other Monkey source files with the extension .monkey. Modules are searched for by SearchPaths which can be controlled by the environment MONKEYPATH. By default this is always the current directory.

To import a module:

>> foo := import("foo")
>> foo.A
>> foo.Sum(2, 3)


This work is licensed under the terms of the MIT License.