In this part of the series,
we add support for comparison operators,
if
statements, loops, and return
, break
and continue
statements.
The grammar is in the EasyScript.g4
file.
The changes compared to the
grammar from part 7
are the last eight productions for statements,
and two new for expressions
(EqNotEqExpr2
and ComparisonExpr3
).
Our parsing class again needs major changes.
We need to expand the state we're tracking as part of parsing. We will need four fields instead of just two:
- Whether we're parsing the top-level scope, a nested scope of the top-level, or a function.
- The
FrameDescriptor.Builder
-- in this part, the top-level scope can include local variables too (they are not exclusive to function definitions). - A Stack of Maps that contain the local members (function arguments, and local variables) in each scope. When we enter a new scope, we push a new Map onto this Stack; when we leave a scope, we pop a Map off the Stack.
- A counter that we keep incrementing after every usage that generates unique frame slot
names by combining it with the name of the variable
(because of nested scopes, variable names are no longer guaranteed to be unique in a given
FrameDescriptor
).
When parsing a new scope
(which happens when entering a block of statements,
a function declaration, or a for
loop),
we save the previous state, change it appropriately for the element we are parsing,
perform the parsing, and then set it back to the saved state.
We use the BlockStmtNode
class
to represent a block of statements, which is unchanged from the
last chapter.
In order to support comparison operators,
we need to add booleans to our language.
This means adding boolean.class
to the
TypeSystem
hierarchy,
and adding a new executeBool()
method to the
superclass of all expression Nodes.
Note that unlike other execute*()
methods that return primitives,
executeBool()
doesn't throw UnexpectedResultException
,
because in JavaScript, any value can be interpreted as a boolean
(only false
, 0
and undefined
are considered false
,
all other values are true
).
We will also have to accommodate booleans by adding specializations for them in local variable assignment and reference.
We implement (strict)
equality (===
)
and inequality (!==
),
and also lesser (<
),
lesser or equal (<=
),
greater (>
) and
greater or equal (>=
).
We don't worry about edge cases with comparisons,
such as whether true
is greater than 0
,
or if undefined
is lesser or equal than undefined
--
we simply return false
in all of these cases.
The implementation of if
is
very simple --
we check the condition, and, if it’s satisfied, we execute the “then” part;
if it’s not, and an “else” part was provided, we execute that.
The interesting part of this Node is using the ConditionProfile
Truffle class that is used for profiling the condition.
Graal might use this information when doing JIT compilation --
for example, if it sees a given condition was never true
,
it might generate different code for it.
Control flow statements like return
, break
and continue
are implemented in Truffle interpreters with exceptions.
We need a separate type of exception for each type of statement;
each
of
them
needs to extend Truffle's ControlFlowException
.
Then, the actual statements Nodes
are
extremely
simple --
they basically just throw the appropriate exception
(return
first evaluating its expression).
The exceptions will be caught by other Nodes --
the ones for loops in case of BreakException
and ContinueException
(see below),
or by the UserFuncBodyStmtNode
class
that is used for the body of a user-defined function.
Loops are implemented using a dedicated Truffle helper, LoopNode
.
With it, we simply implement a different interface, RepeatingNode
,
and its executeRepeating()
method.
In that method, we execute the body of the loop, once,
and then return a boolean from it indicating whether we should continue with the next iteration of the loop.
If we return true
, LoopNode
will call executeRepating()
again,
if we return false
, the loop will terminate.
We have to make sure to catch BreakException
and ContinueException
to return false
from executeRepeating()
or continue with the loop, respectively.
We implement three types of loops:
while
,
do-while
and for
.
There is a unit test exercising a few common scenarios with control flow statements.