So far, we evaluated expressions. Now, we'll be interpreting statements and adding support for variables, assignment, and blocks.
What I built: Commit e04ea1d
What I understood:
1) Summary:
- Without any "memory," an interpreter is merely a glorified calculator. To really work, it should be able to remember variables and their values - basically storing them somewhere.
- As against expressions - which evaluate to something - statements can execute an action, and can change the state of the world outside them.
- We extend our grammar to now begin with
declarationas the top-level rule, which refers to the rulesvarDeclorstatement -
statementfollows two other rules,exprStmtorprintStmtto add support for print (which isn't a function in lox) and other kinds of statements. - Like
Expr, we create aStmtclass usingGenerateAst, which has the subclassesBlock,Expression,Print, andVar- each with their ownvisitSubclass()methods to be overridden by theInterpreterclass.
2) Flow:
-
run()callsparse(), which repeatedly callsdeclaration()to process the Lox script line by line. - The parser starts building the AST as it consumes tokens and first creates
List<Stmt>(list of statements in the program) - It first looks for a
VARtoken, which if not found, causes it to callstatement() - When it encounters a block, it enters and starts parsing the block’s contents as well.
- Similar to the contents within
()being treated as one entity, the entire contents of a block (within{}) are treated as a statement. - It also uses
peek(), when a=is encountered, to check whether it is an expression or assignment. - Also, it checks whether or not the left-side of the
=is an assignable target. -
Ex:
a + b = 5is not an assignment; it is an expression - REPL:
- The Interpreter uses a hash-map called
Environmentto keep track of the declared variables. - There are two methods, namely
define()andassign()for dealing with variables. - When we’re defining/declaring/creating a variable,
Stmt’sVarsubclass operates on the current environment. - Whenever we update a variable,
assign()searches up the enclosing chain recursively, until it finds the mentioned variable; In that scope, it updates the variable.Expr’sAssignsubclass uses this method. - Whenever a local variable is created (i.e., a new environment), it also keeps track of its parent environment.
- For print statements, it performs concatenation if more than one (string) token is involved.
Let's understand this through an example:
var name = "World";
{
var greeting = "Hello, ";
print greeting + name;
name = "Lox";
}
print name;
- The parser creates a list of statements, in which each item is further parsed for tokens.
- In Line 1,
varis encountered, leading to the rulevarDecl, and hence a new environmentE0is created fornameto be stored in, with its value as"World". - In Line 2, the parser sees a block through
{, due to whichvisitBlockStmt()callsexecuteBlock(), passing the list of statements in the block, while creating a new environmentE1, which hasE0as its parent. - Again,
varin Line 3 (in the block), creates a new environment inE1, withgreetingas the name and"Hello, "as its value. - When
printis encountered in Line 4,evaluate(expr)is triggered, which finds that the print statement has a binary expression -greeting + name - While Line 5 is being parsed,
visitAssignExpr()is called, which further callsevaluate(expr), after which theEnvironmentclass'assign()function is called. This checks for the presence of the variable (name), first in its own environmentE1, and then recursively in parent environments until thenameis found. -
nameis found inE0, following whichput()updates its value in that environment:"World" → "Lox" - Once the block ends in Line 6 (when
}is encountered), its environmentE1is destroyed. - Finally, in Line 7, when
print name;is encountered,name's (changed) value"Lox"is printed.
What's next: Support for logical operators, while, and for loops.
Musings:
I felt very, very, very happy when the code started working. Passing a .lox file felt almost magical. Except, I now know what went behind it. I still find myself most fascinated by the Scanner. Strings (and by extension, characters) never seemed very important to me, so I was pleasantly surprised to find out that they’re critical to interpreters/compilers. All it takes is a switch-case! I’m loving how much this project has taught me to appreciate all parts of programming.

Top comments (0)