DEV Community

Lahari Tenneti
Lahari Tenneti

Posted on

Evaluating Statements

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 rules varDecl or statement
  • statement follows two other rules, exprStmt or printStmt to add support for print (which isn't a function in lox) and other kinds of statements.
  • Like Expr, we create a Stmt class using GenerateAst, which has the subclasses Block, Expression, Print, and Var - each with their own visitSubclass() methods to be overridden by the Interpreter class.

2) Flow:

  • run() calls parse(), which repeatedly calls declaration() 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 call statement()
  • 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() and assign() for dealing with variables.
  • When we’re defining/declaring/creating a variable, Stmt’s Var 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’s Assign 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;
Enter fullscreen mode Exit fullscreen mode
  • 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 rule varDecl, and hence a new environment E0 is created for name to be stored in, with its value as "World".
  • In Line 2, the parser sees a block through {, due to which visitBlockStmt() calls executeBlock(), passing the list of statements in the block, while creating a new environment E1, which has E0 as its parent.
  • Again, var in Line 3 (in the block), creates a new environment in E1, with greeting 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 calls evaluate(expr), after which the Environment class' assign() function is called. This checks for the presence of the variable (name), first in its own environment E1, and then recursively in parent environments until the name is found.
  • name is found in E0, following which put() updates its value in that environment: "World" → "Lox"
  • Once the block ends in Line 6 (when } is encountered), its environment E1 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)