A C++ Implementation of Lox language
For more details about Lox, please check Crafting Interpreters Book Website.
Also, there is a repository of the book mentioned above, you can visit here.
$ cmake .
$ make
$ ./loxpp samples/fib.lox
I will start with describing the syntax of the language, you can also find the abstract grammar at the end.
Classic introduction to the language; here is your very first program:
print "Hello World!";
There are two Boolean values, obviously, and a literal for each one:
true;
false;
Lox only has one kind of number: double-precision floating point. For simplicity, it only supports integers and decimal numbers.
1234;
12.34;
Like most languages, strings are enclosed in double quotes:
"I am a string";
""; // The empty string.
"123"; // This is a string, not a number
The representation of "no value", NULL, like many other languages have.
Lox supports basic arithmetic operations as they common in all languages;
add + me;
subtract - me;
multiply * me;
divide / me;
-negateMe;
All operations are applicable on numeric values, except addition. You can concatanate two strings with addition operation.
You can compare different types of values, not restricted to numbers.
less < than;
lessThan <= orEqual;
greater > than;
greaterThan >= orEqual;
equal == than;
not != equal;
You can compare strings;
"cat" != "dog"; // true.
Or even a string with a number;
123 == "123"; // false.
!true; // false.
!false; // true.
true and false; // false.
true and true; // true.
false or false; // false.
true or false; // true.
You can group stuff to ensure correct precedence that you want to see:
var average = (min + max) / 2;
You have seen some statements already:
print "Hello World!";
Lox also have a special type of statement called expression statement. It is basically an expression followed by a semicolon.
"Hello World!";
You can group statements in a block, but this will effect the scope of variables which will be discussed later.
{
print "Hello World! 1";
print "Hello World! 2";
}
You declare variables using var
statements. If you omit the initializer, the variable’s value defaults to nil
:
var imAVariable = "here is my value";
var iAmNil;
You can use variables in function calls and statements after defining:
var breakfast = "bagels";
print breakfast; // "bagels".
breakfast = "beignets";
print breakfast; // "beignets"
An if statement executes one of two statements based on some condition:
if (condition) {
print "yes";
} else {
print "no";
}
A while loop executes the body repeatedly as long as the condition expression evaluates to true:
var a = 1;
while (a < 10) {
print a;
a = a + 1;
}
Finally, we have for loops:
for (var a = 1; a < 10; a = a + 1) {
print a;
}
A function call expression looks the same as it does in C:
makeBreakfast(bacon, eggs, toast);
You can also call a function without passing anything to it:
makeBreakfast();
In Lox, you declare your own functions with fun:
fun printSum(a, b) {
print a + b;
}
The body of a function is always a block. Inside it, you can return a value using a return statement:
fun returnSum(a, b) {
return a + b;
}
If execution reaches the end of the block without hitting a return,
it implicitly returns nil
.
Functions are first class in Lox, which just means they are real values that you can get a reference to, store in variables, pass around, etc. This works:
fun addPair(a, b) {
return a + b;
}
fun identity(a) {
return a;
}
print identity(addPair)(1, 2); // Prints "3".
Since function declarations are statements, you can declare local functions inside another function:
fun outerFunction() {
fun localFunction() {
print "I'm local!";
}
localFunction();
}
If you combine local functions, first-class functions, and block scope, you run into this interesting situation:
fun returnFunction() {
var outside = "outside";
fun inner() {
print outside;
}
return inner;
}
var fn = returnFunction();
fn();
class Breakfast {
cook() {
print "Eggs a-fryin'!";
}
serve(who) {
print "Enjoy your breakfast, " + who + ".";
}
}
// Store it in variables.
var someVariable = Breakfast;
// Pass it to functions.
someFunction(Breakfast);
var breakfast = Breakfast();
print breakfast; // "Breakfast instance".
breakfast.meat = "sausage";
breakfast.bread = "sourdough";
Assigning to a field creates it if it doesn’t already exist.
If you want to access a field or method on the current object from within a method, you use good old this:
class Breakfast {
serve(who) {
print "Enjoy your " + this.meat + " and " +
this.bread + ", " + who + ".";
}
// ...
}
Part of encapsulating data within an object is ensuring the object is in a valid state when it’s created. To do that, you can define an initializer. If your class has a method named init(), it is called automatically when the object is constructed. Any parameters passed to the class are forwarded to its initializer:
class Breakfast {
init(meat, bread) {
this.meat = meat;
this.bread = bread;
}
// ...
}
var baconAndToast = Breakfast("bacon", "toast");
baconAndToast.serve("Dear Reader");
// "Enjoy your bacon and toast, Dear Reader."
class Brunch < Breakfast {
drink() {
print "How about a Bloody Mary?";
}
}
Here, Brunch is the derived class or subclass, and Breakfast is the base class or superclass. Every method defined in the superclass is also available to its subclasses:
var benedict = Brunch("ham", "English muffin");
benedict.serve("Noble Reader");
As in Java, you can use super:
class Brunch < Breakfast {
init(meat, bread, drink) {
super.init(meat, bread);
this.drink = drink;
}
}
program → declaration* EOF ;
declaration → classDecl
| funDecl
| varDecl
| statement ;
classDecl → "class" IDENTIFIER ( "<" IDENTIFIER )?
"{" function* "}" ;
funDecl → "fun" function ;
varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
statement → exprStmt
| forStmt
| ifStmt
| printStmt
| returnStmt
| whileStmt
| block ;
exprStmt → expression ";" ;
forStmt → "for" "(" ( varDecl | exprStmt | ";" )
expression? ";"
expression? ")" statement ;
ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
printStmt → "print" expression ";" ;
returnStmt → "return" expression? ";" ;
whileStmt → "while" "(" expression ")" statement ;
block → "{" declaration* "}" ;
expression → assignment ;
assignment → ( call "." )? IDENTIFIER "=" assignment
| logic_or ;
logic_or → logic_and ( "or" logic_and )* ;
logic_and → equality ( "and" equality )* ;
equality → comparison ( ( "!=" | "==" ) comparison )* ;
comparison → addition ( ( ">" | ">=" | "<" | "<=" ) addition )* ;
addition → multiplication ( ( "-" | "+" ) multiplication )* ;
multiplication → unary ( ( "/" | "*" ) unary )* ;
unary → ( "!" | "-" ) unary | call ;
call → primary ( "(" arguments? ")" | "." IDENTIFIER )* ;
primary → "true" | "false" | "nil" | "this"
| NUMBER | STRING | IDENTIFIER | "(" expression ")"
| "super" "." IDENTIFIER ;
function → IDENTIFIER "(" parameters? ")" block ;
parameters → IDENTIFIER ( "," IDENTIFIER )* ;
arguments → expression ( "," expression )* ;
NUMBER → DIGIT+ ( "." DIGIT+ )? ;
STRING → "\"" <any char except "\"">* "\"" ;
IDENTIFIER → ALPHA ( ALPHA | DIGIT )* ;
ALPHA → "a" ... "z" | "A" ... "Z" | "_" ;
DIGIT → "0" ... "9" ;