A Golang implementation of the Lox language from the book Crafting Interpreters with my own features
Supports running code from a file:
lox code.lox
or from stdin as a REPL:
lox
or as a string from the terminal:
lox -c <code>
First, install Go if it's not installed already. Then run the following commands to build this interpreter:
git clone https://github.com/AlanLuu/lox-go.git
cd lox-go
go build
This will create an executable binary called lox
on Linux/macOS and lox.exe
on Windows that can be run directly.
- Concatenating a string with another data type will convert that type into a string and concatenate them together
- The following additional operations are supported in this implementation of Lox:
a % b
, which returns the remainder of two numbersa
andb
a ** b
, which returnsa
raised to the power ofb
, wherea
andb
are numbers- The exponent operator has higher precedence than any unary operators on the left, so
-a ** b
is equivalent to-(a ** b)
.
- The exponent operator has higher precedence than any unary operators on the left, so
a << b
anda >> b
, which returns a number representing the numbera
shifted byb
bits to the left and right respectively.- If
a
orb
are floats, they are converted into integers before the shift operation
- If
~a
, which returns the bitwise NOT of the numbera
a & b
,a | b
, anda ^ b
, which returns the bitwise AND, OR, and XOR of two numbersa
andb
respectively.- If
a
orb
are floats, they are converted into integers before the bitwise operation - Unlike in C, the precedence of the bitwise operators is higher than the precedence of the comparison operators, so
a & b == value
is equivalent to(a & b) == value
- If
- Division by 0 results in
Infinity
, which uses Golang'smath.Inf()
under the hood - Performing a binary operation that isn't supported between two types results in
NaN
, which stands for "not-a-number", using Golang'smath.NaN()
under the hood - Booleans and
nil
are treated as integers when performing arithmetic operations on them, withtrue
andfalse
being treated as1
and0
respectively, andnil
being treated as0
- Besides
false
andnil
, the values0
,0.0
,NaN
,""
,[]
, and{}
are also falsy values - Binary, hexadecimal, and octal integer literals are supported in this implementation of Lox
- Binary literals start with the prefix
0b
- Hexadecimal literals start with the prefix
0x
- Octal literals start with the prefixes
0o
or0
- All letters in prefixes are case-insensitive
- Binary literals start with the prefix
- Number literals support the following features:
- An underscore character can be used to group digits, such as
1_000_000
, which is equivalent to1000000
- Underscore characters are also allowed in binary, hexadecimal, and octal literals, except for octal literals starting with a
0
- Underscore characters are also allowed in binary, hexadecimal, and octal literals, except for octal literals starting with a
- An underscore character can be used to group digits, such as
break
andcontinue
statements are supported in this implementation of Lox- For loops are implemented with their own AST node instead of being desugared into while loop nodes
- This makes it easier to implement the
continue
statement inside for loops
- This makes it easier to implement the
- Variables declared in the initialization part of a for loop are locally scoped to that loop and do not become global variables
- Do while loops are supported in this implementation of Lox
var i = 1; do { print i; i = i + 1; } while (i <= 10);
- Anonymous function expressions are supported in this implementation of Lox. There are two forms supported:
fun(param1, paramN) {<statements>}
, which is a traditional anonymous function expression that contains a block with statementsfun(param1, paramN) => <expression>
, which is an arrow function expression that implicitly returns the given expression when called- The parser will attempt to parse anonymous function expressions that appear on their own line as function declarations, throwing a parser error as a result. This is expected behavior; to force the parser to parse them as expressions, wrap the function expression inside parentheses, like
(fun() {})()
. In this case, this creates an anonymous function expression that is called immediately
- Static class fields and methods are supported in this implementation of Lox
- Classes also support initializing instance fields to an initial value directly in the class body without the need for a constructor
class A { static x = 10; static y() { return 20; } z = 30; } print A.x; //Prints "10" print A.y(); //Prints "20" var a = A(); print a.z; //Prints "30"
- Various mathematical functions and constants are defined under a built-in class called
Math
, which is documented here - Strings have some additional features associated with them:
- Strings can be represented using single quotes as well
- Strings can be indexed by an integer, which will return a new string with only the character at the specified index:
string[index]
- Get a new string with all characters from indexes
start
toend
exclusive, wherestart
andend
are integers andstart < end
:string[start:end]
- If
start >= end
, a new empty string is returned start
orend
can be omitted, in which case the starting index will have a value of0
ifstart
is omitted and the ending index will have a value oflen(string)
ifend
is omitted
- If
- Negative integers are supported for string indexes, where a negative index
i
is equivalent to the indexi + len(string)
. For example,string[-1]
refers to the last character in the string - It is a runtime error to use an index value that is less than 0 or greater than or equal to the length of the string to index into that string
- Get a new string that is the original string repeated
n
times, wheren
is an integer:string * n
- Escape characters in strings are supported:
\'
: single quote\"
: double quote\\
: backslash\a
: bell\n
: newline\r
: carriage return\t
: horizontal tab\b
: backspace\f
: form feed\v
: vertical tab
- Besides these features, strings also have some methods associated with them:
string.compare(string2)
, which lexicographically comparesstring
andstring2
and returns0
ifstring == string2
,-1
ifstring < string2
, and1
ifstring > string2
string.contains(substr)
, which returnstrue
ifsubstr
is contained withinstring
andfalse
otherwisestring.endsWith(suffix)
, which returnstrue
ifstring
ends withsuffix
andfalse
otherwisestring.index(string2)
, which returns an integer representing the index value of the location ofstring2
instring
, or-1
ifstring2
is not instring
string.lastIndex(string2)
, which returns an integer representing the index value of the last occurrence ofstring2
instring
, or-1
ifstring2
is not instring
string.lower()
, which returns a new string with all lowercase lettersstring.padEnd(length, padStr)
, which pads the contents ofpadStr
to the end ofstring
until the new string is of lengthlength
string.padStart(length, padStr)
, which pads the contents ofpadStr
to the beginning ofstring
until the new string is of lengthlength
string.replace(oldStr, newStr)
, which returns a new string where all occurrences ofoldStr
in the original string are replaced withnewStr
string.split(delimiter)
, which returns a list containing all substrings that are separated bydelimiter
string.startsWith(prefix)
, which returnstrue
ifstring
begins withprefix
andfalse
otherwisestring.strip([chars])
, which returns a new string with all leading and trailing characters fromchars
removed. Ifchars
is omitted, this method returns a new string with all leading and trailing whitespace removedstring.toNum([base])
, which attempts to convertstring
into an integer or float and returns that value if successful andNaN
otherwise. Ifbase
is specified, then this method will attempt to convertstring
that is represented as the specified base into an integer or float and returns that value if the conversion was successful andNaN
otherwisestring.upper()
, which returns a new string with all uppercase lettersstring.zfill(length)
, which returns a new string where the character'0'
is padded to the left until the new string is of lengthlength
. If a leading'+'
or'-'
sign is part of the original string, the'0'
padding is inserted after the leading sign instead of before
- Lists are supported in this implementation of Lox
- Create a list and assign it to a variable:
var list = [1, 2, 3];
- Get an element from a list by index, where
index
is an integer:list[index]
- Get a new list with all elements from indexes
start
toend
exclusive, wherestart
andend
are integers andstart < end
:list[start:end]
- If
start >= end
, a new empty list is returned start
orend
can be omitted, in which case the starting index will have a value of0
ifstart
is omitted and the ending index will have a value oflen(list)
ifend
is omitted
- If
- Set an element:
list[index] = value;
- Negative integers are supported for list indexes, where a negative index
i
is equivalent to the indexi + len(list)
. For example,list[-1]
refers to the last element in the list- Negative indexes are also supported in list methods that accept integer values for list indexes as a parameter
- It is a runtime error to use an index value that is less than 0 or greater than or equal to the length of the list to get or set
- Concatenate two lists together into a new list:
list + list2
- Get a new list with all elements from the original list repeated
n
times, wheren
is an integer:list * n
- Besides these operations, lists also have some methods associated with them:
list.all(callback)
, which returnstrue
if the callback function returnstrue
for all elements in the list andfalse
otherwiselist.any(callback)
which returnstrue
if the callback function returnstrue
for any element in the list andfalse
otherwiselist.append(element)
, which appends an element to the end of the listlist.clear()
, which removes all elements from the listlist.contains(element)
, which returnstrue
ifelement
is contained in the list andfalse
otherwiselist.count(element)
, which returns the number of timeselement
appears in the listlist.extend(list2)
, which appends every element fromlist2
to the end of the listlist.filter(callback)
, which returns a new list containing only the elements from the original list where the callback function returns a truthy value for themlist.find(callback)
, which returns the first element in the list where the callback function returnstrue
, ornil
if the callback returnsfalse
for every element in the listlist.findIndex(callback)
, which returns the index of the first element in the list where the callback function returnstrue
, or-1
if the callback returnsfalse
for every element in the listlist.flatten()
, which returns a new list where all elements contained within nested lists are flattened into a list without any nested listslist.forEach(callback)
, which executes the callback function for each element in the listlist.index(element)
, which returns the index value of the element's position in the list, or-1
if the element is not in the listlist.lastIndex(element)
, which returns the index value of the last occurrence of the element in the list, or-1
if the element is not in the listlist.insert(index, element)
, which inserts an element into the list at the specified indexlist.join(separator)
, which concatenates all elements in the list into a string where each element is separated by a separator stringlist.map(callback)
, which returns a new list with the results of calling a callback function on each element of the original listlist.pop([index])
, which removes and returns the element at the specified index from the list. Ifindex
is omitted, this method removes and returns the last element from the listlist.reduce(callback, [initialValue])
, which applies a reducer callback function on every element in the list from left to right and returns a single valuelist.remove(element)
, which removes the first occurrence ofelement
from the list. Returnstrue
if the list containedelement
andfalse
otherwiselist.shuffle()
, which shuffles all elements in the list in placelist.with(index, element)
, which returns a new list that is a copy of the original list with the original element at the specified index replaced with the new element
- Two lists are compared based on whether they are the same length and for every index
i
, the element from the first list at indexi
is equal to the element from the second list at indexi
- Attempting to use an index value larger than the length of the list will cause a runtime error
- Create a list and assign it to a variable:
- Dictionaries are supported in this implementation of Lox
- Create a dictionary and assign it to a variable:
var dict = {"key": "value"};
- Get an element from a dictionary by key:
dict[key]
- It is a runtime error to attempt to get an element using a key that is not in the dictionary
- Set an element:
dict[key] = value;
- Merge two dictionaries together:
dict | dict2
- If a key exists in both
dict
anddict2
, the key in the merged dictionary becomes associated with the value fromdict2
- If a key exists in both
- The following cannot be used as dictionary keys: dictionary, list
- Besides these operations, dictionaries also have some methods associated with them:
dictionary.clear()
, which removes all keys from the dictionarydictionary.containsKey(key)
, which returnstrue
if the specified key exists in the dictionary andfalse
otherwisedictionary.copy()
, which returns a shallow copy of the original dictionary as a new dictionarydictionary.get(key, [defaultValue])
, which returns the value associated with the specified key from the dictionary, ordefaultValue
if the key doesn't exist in the dictionary anddefaultValue
is provided, ornil
otherwisedictionary.keys()
, which returns a list of all the keys in the dictionary in no particular orderdictionary.removeKey(key)
, which removes the specified key from the dictionary and returns the value originally associated with the key ornil
if the key doesn't exist in the dictionary. Note that a return value ofnil
can also mean that the specified key had a value ofnil
dictionary.values()
, which returns a list of all the values in the dictionary in no particular order
- Create a dictionary and assign it to a variable:
- Enums are supported in this implementation of Lox
enum Token { ADD, SUBTRACT, MULTIPLY, DIVIDE } var a = Token.ADD; var b = Token.ADD; var c = Token.SUBTRACT; print a; //Prints "Token.ADD" print type(a); //Prints "Token" print a == b; //Prints "true" print a == c; //Prints "false"
- The ability to import other Lox files is supported in this implementation of Lox
- Syntax:
import "file-name"; import "file-name" as alias;
- The specified import file is executed and all variable, function, and class declarations declared globally in the imported file are brought into the global environment of the current file
import
is an expression that returnstrue
if the specified import file exists and was executed successfully,false
if the file doesn't exist, and throws an error if the file exists but an error occurred while it was being executedimport
expressions can also have an optional alias specified, in which case only the alias name is brought into the global environment of the current file and all global variable, function, and class declarations from the imported file become properties of the alias and can be accessed using the following notation:alias.property
- Syntax:
- A few other native functions are defined:
chr(i)
, which returns a string with a single character that is the Unicode character value of the code pointi
, wherei
is an integerinput([prompt])
, which writes the value ofprompt
to standard output if it is provided and reads a line from standard input as a string without a trailing newline and returns that stringlen(element)
, which returns the length of a dictionary, list, or string- Dictionaries: the length is the number of keys in the dictionary
- Lists: the length is the number of elements in the list
- Strings: the length is the number of characters in the string
List(length)
, which returns a new list of the specified length, where each initial element isnil
ord(c)
, which returns an integer that represents the Unicode code point of the characterc
, wherec
is a string that contains a single Unicode charactersleep(duration)
, which pauses the program for the specified duration in secondstype(element)
, which returns a string representing the type of the element
- This Lox REPL supports typing in block statements with multiple lines
- Expressions such as
1 + 1
that are typed into the REPL are evaluated and their results are displayed, with no need for semicolons at the end- Assignment expressions still require semicolons when typed into the REPL as standalone expressions, like
x = 0;
,object.property = value;
, andlist[index] = value;
- Assignment expressions still require semicolons when typed into the REPL as standalone expressions, like
- Chapter 4 - Scanning (Complete)
- Chapter 5 - Representing Code (Complete)
- Chapter 6 - Parsing Expressions (Complete)
- Chapter 7 - Evaluating Expressions (Complete)
- Chapter 8 - Statements and State (Complete)
- Chapter 9 - Control Flow (Complete)
- Chapter 10 - Functions (Complete)
- Chapter 11 - Resolving and Binding (Complete)
- Chapter 12 - Classes (Complete)
- Chapter 13 - Inheritance (Complete)
This implementation of Lox is distributed under the terms of the MIT License.