|
10 months ago | |
---|---|---|
ast | 2 years ago | |
builtins | 2 years ago | |
code | 4 years ago | |
compiler | 2 years ago | |
eval | 2 years ago | |
examples | 3 years ago | |
lexer | 2 years ago | |
object | 2 years ago | |
parser | 2 years ago | |
plugins | 2 years ago | |
repl | 2 years ago | |
testdata | 4 years ago | |
token | 4 years ago | |
tools | 4 years ago | |
typing | 2 years ago | |
utils | 4 years ago | |
vim | 4 years ago | |
vm | 2 years ago | |
.gitignore | 4 years ago | |
.goreleaser.yml | 4 years ago | |
Dockerfile | 3 years ago | |
LICENSE | 5 years ago | |
Makefile | 4 years ago | |
README.md | 2 years ago | |
ReadingGuide.md | 2 years ago | |
_config.yml | 4 years ago | |
go.mod | 2 years ago | |
go.sum | 10 months ago | |
main.go | 2 years ago | |
version.go | 4 years ago |
README.md
monkey-lang
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
Status
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
Development
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
Programs
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.
Types
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
12
Conditional Expressions
Monkey supports if
and else
:
>> a := 10
>> b := a * 2
>> c := if (b > a) { 99 } else { 100 }
>> c
99
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)
"1"
>> test(3)
"Fizz"
>> test(5)
"Buzz"
>> test(15)
"FizzBuzz"
While Loops
Monkey supports only one looping construct, the while
loop:
i := 3
while (i > 0) {
print(i)
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)
50
>> fn(x) { x + 10 }(10)
20
>> newAdder := fn(x) { fn(y) { x + y } }
>> addTwo := newAdder(2)
>> addTwo(3)
5
>> sub := fn(a, b) { a - b }
>> applyFunc := fn(a, b, func) { func(a, b) }
>> applyFunc(10, 2, sub)
8
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()
2
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)
9227465
Strings
>> makeGreeter := fn(greeting) { fn(name) { greeting + " " + name + "!" } }
>> hello := makeGreeter("Hello")
>> hello("skatsuta")
Hello skatsuta!
Arrays
>> myArray := ["Thorsten", "Ball", 28, fn(x) { x * x }]
>> myArray[0]
Thorsten
>> myArray[4 - 2]
28
>> myArray[3](2)
4
Hashes
>> myHash := {"name": "Jimmy", "age": 72, true: "yes, a boolean", 99: "correct, an integer"}
>> myHash["name"]
Jimmy
>> myHash["age"]
72
>> 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
print(i)
}
print(i)
mutate()
print(i)
// 1
// 2
// 2
map = {"a": 1}
func mutate() {
map.a = 2
print(map.a)
}
print(map.a)
mutate()
print(map.a)
// 1
// 2
// 2
lst := [0, 1, 2]
lst[1] = "one"
print(lst)
// [0, "one", 2]
map = {"a": 1, "b": 2}
map["a"] = 3
map.c = 4
print(map)
// {"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
orhash
).input([prompt])
Reads a line from standard input optionally printingprompt
.print(value...)
Prints thevalue
(s) to standard output followed by a newline.first(array)
Returns the first element of thearray
.last(array)
Returns the last element of thearray
.rest(array)
Returns a new array with the first element ofarray
removed.push(array, value)
Returns a new array withvalue
pushed onto the end ofarray
.pop(array)
Returns the last value of thearray
ornull
if empty.exit([status])
Exits the program immediately with the optionalstatus
or0
.assert(expr, [msg])
Exits the program immediately with a non-zero status ifexpr
isfalse
optionally displayingmsg
to standard error.bool(value)
Convertsvalue
to abool
. Ifvalue
isbool
returns the value directly. Returnstrue
for non-zeroint
(s),false
otherwise. Returnstrue
for non-emptystr
,array
andhash
values. Returnstrue
for all other values exceptnull
which always returnsfalse
.int(value)
Converts decimalvalue
str
toint
. Ifvalue
is invalid returnsnull. If
valueis an
int` returns its value directly.str(value)
Returns the string representation ofvalue
:null
for null,true
orfalse
forbool
, decimal forint
(eg:1234
), the string itself forstr
(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 astr
denoting the type of value:nil
,bool
,int
,str
,array
,hash
, orfn
.args()
Returns an array of command-line options passed to the program.lower(str)
Returns a lowercased version ofstr
.upper(str)
Returns an uppercased version ofstr
.join(array, sep)
Concatenatesstr
s inarray
to form a singlestr
, with the separatorstr
between each element.split(str[, sep])
Splits thestr
using given separatorsep
, and returns the parts (excluding the separator) as anarray
. Ifsep
is not given ornull
, it splits on whitespace.find(haystack, needle) Returns the index of
needlein
haystack, or the index of
needleelement in
haystack` array. Returns -1 if not found.readfile(filename)
Reads the contents of the filefilename
and returns it as astr
.writefile(filename, data)
Writesdata
to a filefilename
.abs(n)
Returns the absolute value of then
.- pow(x, y)
Returns
xto the power of
yas
int`(s). divmod(a, b)
Returns an array containing the quotient and remainder ofa
andb
asint
(s). Equivilent to[a / b, b % b]
.bin(n
) Returns the binary representation ofn
as astr
.hex(n)
Returns the hexidecimal representation ofn
as astr
.oct(n)
Returns the octal representation ofn
as astr
.ord(c)
Returns the ordincal value of the characterc
as anint
.chr(n)
Returns the character value ofn
as astr
.hash(any)
Returns the hash value ofany
as anint
.id(any)
Returns the identity ofany
as anint
.min(array)
Returns the minimum value of elements inarray
.max(array)
Returns the maximum value of elements inarray
.sorted(array)
Sorts thearray
using a stable sort, and returns a newarray
.. Elements in thearray
must be orderable with<
(int
,str
, orarray
of those).reversed(array)
Reverses the arrayarray
and returns a newarray
.open(filename[, mode])
write(fd, data)
Writesstr
data
to the open file descriptor given byint
fd
.read(fd, [n])
Reads from the file descriptorfd
(int
) optinoally up ton
(int
) bytes and returns the read data as astr
.close(fd)
Closes the open file descriptor given byfd
(int
).seek(fd, offset[, whence])
Seeks the file descriptorfd
(int
) to theoffset
(int
). The optionalwhence
(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)
Objects
>> 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"
Modules
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
5
>> foo.Sum(2, 3)
5
License
This work is licensed under the terms of the MIT License.