This repo is my journey going through the book Writing an Intrpreter in Go by Thorsten Ball.
Monkey is a simple programming language that does not have an official implementation. It is purppose made for Thorsten Ball's books. More details can be found about it here: https://monkeylang.org/
Below is what is implemented in this implementation of the language.
I've followed the book diligently from start to finish and did not stray at any point. Once
completed I have created a 1.0 release to denote that everything up to that point was
"as the author intended".
Anything beyond version 1.0 can be recognized as my own ideas and implementations.
I'm assumming if you're even looking at this repository you're researching whether or not the book in question is worth buying. I would say it was a worthwhile read from my perspective.
The author has a very easy to read style of writing that I appreciated and I enjoy the learning style of having your hand held while going on a very verbose tour of a code base. The twist being that instead of just looking at an existing code base you're creating it from scratch.
I would note that part of why I found the verboseness tolerable was because I downloaded a copy of the repo that the author instructs you to download for reference. This allowed me to copy and paste the lengthy test suite in as needed and spend more focus on the actual code that mattered.
I personally hand typed the first few tests to get an idea of the methodology then once I felt I was bored by it I started copying and pasting.
The interpreter is just a "REPL" (Read, Evaluate, Print, Loop) and is meant to be used interactively like so:
$ go run main.go
Hello michohl! This is the Monkey programming language!
Feel free to type in commands
>> let x = {"foo": "bar", "zed": "cool"}
>> x["zed"]
cool
>>These are the building blocks of the language. Everything evaluates down to literals in the end.
Strings are combinations of characters surrounded with double quotes (").
>> let x = "foo"
>> x
fooNothing noteworthy. Just simple integers. They can be positive or negative
>> let x = 2
>> x
2Booleans are "truthy" in Monkey. This means if the value is not explicitly false or NULL
then we will evaluate it as a true value.
>> let x = true
>> x
true
>> let y = 1 > 0
>> y
trueWe can store the result of expressions into variables for reference later.
We accomplish this by using the let keyword followed by a variable name = and an expression.
>> let x = 1
>> x
1Arrays (also called lists) are a simple sequence of preserved order elements.
>> let x = [ 2, 3, 1, 6, 9 ]
>> x
[2, 3, 1, 6, 9]We have some "built in" functions for interacting with arrays that are mentioned later on.
Hashes are often referred to as "maps" or "dictionaries" in other languages. In Monkey we allow for mapping any "hashable" type to any value. Hashable types are Strings, Integers, and Booleans.
>> let x = {true: "foo", "one": 1, 2: "two"}
>> x[true]
foo
>> x["one"]
1
>> x[2]
twoIndexes are also evaluated expressions so we can do more complex indexing like so:
>> x[1 > 0] # 1 > 0 == true
fooFunctions in Monkey are treated the same as all other values which means Monkey supports higher order functions!
>> let square = fn(x) { return x * x }
>> square(8)
64Supporting higher order functions means we can pass functions as arguments to other functions.
>> let square = fn(x) { return x * x }
>> let wrapper = fn(x, y) { return x(y) }
>> wrapper(square, 6)
36"Errors" in Monkey are simply wrappers around strings. We assign them a type so we can "handle" them when we encounter them but they are just strings that we can use to present errors to the caller.
>> x = 5
__,__
.--. .-" "-. .--.
/ .. \/ .-. .-. \/ .. \
| | '| / Y \ |' | |
| \ \ \ 0 | 0 / / / |
\ '- ,\.-"""""""-./, -' /
''-' /_ ^ ^ _\ '-''
| \._ _./ |
\ \ '~' / /
'._ '-=-' _.'
'-----'
Woops! We ran into some monkey business here!
parser errors:
no prefix parse function for = foundNull is not fully leveragable in our implementation of monkey. We don't allow it for regular use in program space but it is leveraged under the hood. So it is not possible to set a variable to null directly like so:
>> let x = null
ERROR: identifier not found: NULLBut if you have an expression that doesn't return anything then it could return null like so:
>> let x = if (1 > 2) { 10 }
>> x
nullExpressions refer to chunks of code that need to be further evaluated to result in literals.
If else statements are run of the mill compared to other languages.
>> let x = if (1 > 0) { "hit" } else { "miss" }
>> let y = if (1 < 0) { "hit" } else { "miss" }
>> x
hit
>> y
missInfix epxressions are operators that come between two other types and takes action with the two of them to produce a new result.
>> let x = 2
>> let y = 3
>> x + y
5>> let x = 3
>> let y = 2
>> x - y
1>> let x = 2
>> let y = 3
>> x * y
6>> let x = 6
>> let y = 3
>> x / y
2Combine two strings
>> let x = "hello, "
>> let y = "world!"
x + y
hello, world!Monkey implements > and < for comparing integers and == and != for all others types.
Caution
The current implementation does not handle comparisons well when you start to try and compare complex literals like arrays. It will compare the pointer value of the objects instead of their values.
>> let x = 1
>> let y = 2
>> x > y
false
>> y > x
true>> let x = 1
>> let y = 2
>> x < y
true
>> y < x
false>> let x = 3
>> let y = 3
>> let z = 6
>> x == y
true
>> x == z
false>> let x = 3
>> let y = 3
>> let z = 6
>> x != y
false
>> x != z
truePrefix expressions are operators that can be put in front of an expression to change it's final meaning.
Tip
Prefix expressions can be "stacked" to do things like "double negatives"
You can negate integers by prefixing them with a -
>> let x = 5
>> -x
-5You can reverse the meaning of a boolean by prefixing it with a !
>> let x = true
>> !x
falseBuilt in functions are the functions we have implemented in the language Monkey is written in (go) and exposed to the user. Some are for convenience and some are necessary for the language to actually be usable.
Note
Any built in functions that seem to "take action" on an array are not mutative. They will simply return a new list rather than implicitly modify the original.
len is a simple "length" function which takes an array or string and returns the length of it.
>> let x = [1,2,3,4,5]
>> len(x)
5puts is our print equivalent. This is how we allow the user to write to the screen.
>> let x = [1,2,3,4,5]
>> puts("one", x, "two")
one
[1, 2, 3, 4, 5]
two
nullfirst is a helper function that takes an array as input and returns the first element.
>> let x = [2,3,4,5]
>> first(x)
2last is the inverse of first and takes an array as input and returns the last element.
>> let x = [2,3,4,5]
>> first(x)
5rest is a helper function that is more useful for functional programming which takes an
array as input and returns everything except for the last element in the array.
>> let x = [1,2,3,4,5]
>> rest(x)
[2, 3, 4, 5]push is a helper function that takes an array and a new value as input and returns a copy of
the original array with the new element appended to the end.
>> let x = [1,2,3,4,5]
>> push(x, 8)
[1, 2, 3, 4, 5, 8]