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
declaration
as the top-level rule, which refers to the rulesvarDecl
orstatement
-
statement
follows two other rules,exprStmt
orprintStmt
to add support for print (which isn't a function in lox) and other kinds of statements. - Like
Expr
, we create aStmt
class usingGenerateAst
, which has the subclassesBlock
,Expression
,Print
, andVar
- each with their ownvisitSubclass()
methods to be overridden by theInterpreter
class.
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
VAR
token, 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 = 5
is not an assignment; it is an expression - REPL:
- The Interpreter uses a hash-map called
Environment
to 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
’sVar
subclass 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
’sAssign
subclass 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,
var
is encountered, leading to the rulevarDecl
, and hence a new environmentE0
is created forname
to 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 hasE0
as its parent. - Again,
var
in Line 3 (in the block), creates a new environment inE1
, withgreeting
as the name and"Hello, "
as its value. - When
print
is 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 theEnvironment
class'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 thename
is found. -
name
is found inE0
, following whichput()
updates its value in that environment:"World" → "Lox"
- Once the block ends in Line 6 (when
}
is encountered), its environmentE1
is 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 Parser. 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)