Game Programming
Language
Phase 5: Expressions
This is the
hardest
phase of the project. Students who finish this phase on time
usually finish the entire project. Start right away.
Usually this
assignment is due before spring break. However, this year spring
break is early so it is due at the end of the week after spring
break. I strongly suggest that you start right away and finish
before spring break.
It is
unlikely that you will be able to finish this assignment if you don't
start until after spring break. I will be out of town for
spring break and will not be able to help.
Overview:
Implement a class to hold expressions. Specifically,
you need to implement a class that describes a node in an expression
tree. Since a pointer to the root of an expression tree (which is
a node in the tree) is a pointer to an entire expression tree, an
expression tree can be stored with a single pointer to the root.
In my implementation and in this document, I named this class
Expression. You may name your class whatever you want.
After you have implemented the expression class, add actions to your
gpl.y that build an expression tree when an expression is parsed.
Specifically, you need to implement actions for all rules that have the
following non-terminals as their left-hand-side (it will help you
design the expression class if you understand the code you will write
for these actions):
expression
primary_expression
optional_initializer
math_operator
geometric_operator
variable // except for the rules that have a T_PERIOD on the right-hand-side, will do them later
Once your parser is building expression trees, add actions to
initialize integer, double, and string variables to the value of the
given expression. This is done in the rules that have optional_initializer
as their left-hand-side. When you are done, you will be able to
initialize int/double/string variables to any legal expression:
int i = 42;
int j = i * 42;
double x = i * j * 1.42 / (55 + 2 * i); // integers are automatically cast to doubles
string = "hello" + " " + "world";
string = "x = " + x; // doubles and integers are automatically cast to string
For assignment p4 you changed the rule for declaring arrays:
simple_type T_ID T_LBRACKET expression T_RBRACKET
Was changed to:
simple_type T_ID T_LBRACKET T_INT_CONSTANT T_RBRACKET
For p5, you need to change
it back (replace T_INT_CONSTANT to
expression) and update your action for creating an array so that the
expression is evaluated to get the size of the array. My code
looks like this:
simple_type T_ID T_LBRACKET expression T_RBRACKET
{
...
// check that $4 is of type int
int array_size = $4->eval_int(); // my Expression class has a function eval_int()
...
}
The bulk of this assignment is the creation of the data structure to
hold an expression tree. In many gpl statements expressions
are
evaluated at run-time, and thus we need a data structure to hold an
expression so that we can evaluate it at run time. In this phase
of the assignments you will use expression only in one place: variable
initialization. Since variables are initialize only once it would
be possible to avoid building an expression tree and simply calculate
the value of the expression like you did in p1. HOWEVER, since we
will need
the expression tree in the next phase YOU MUST IMPLEMENT IT NOW and use
it for variable declarations.
A small but important part of this assignment is to develop a class
that
can represent a variable. I call this class Variable (you can use
whatever name you want). There are three forms of variables in
gpl,
simple variables (such as the variable i), member variables (such as
the member variable x of a rectangle variable: my_rectangle.x),
and array variables (such as i[x*y] and my_rectangle[42].x).
There are two types of operations that are
performed on variables, set and get. Class Variable provides a
single interface
to the three types of variables (simple, member, and array) and to both
types of
operations (get and set). A simple variable contains a
pointer to a symbol. If the variable is a member variable, it
contains the name of the
field plus a pointer to a symbol. If the variable is an array, it
contains a pointer to the
expression used to calculate the index and after the index is
calculated at run time the symbol can be looked up in the symbol table.
Requirements:
In addition to the requirements from p4, your interpreter must be
able
to initializes variable (integers, doubles, strings) using an
expression and your parser must handle an expression as the size of and
array.
In p4 all variables (integer, double, string) were initialized to the
constants 42, 3.145, "Hello world". In this assignment if an
optional initializer is not specified, use the values 0, 0.0, "".
Since gpl arrays can never have initial values, they should always be
initialized to these default values.
Hints:
WARNING: In order to
initialize variables you do not need an expression tree. You
could simply evaluate the expression while parsing it. DO NOT DO
THIS. In order to implement statements you need to be able to
represent an expression (using an expression tree). If you do not
build an expression tree in this assignment you will not be able to
complete the rest of the project (and you won't have enough
time to implement the expression tree when you are implementing
statements).
The variable class is needed for future
assignments, make sure you implement the variable class as I describe
it. If you don't understand the variable class, talk to me until
you do understand it.
If you do not implement an expression class and a variable class you
will not be able to complete future assignments.
Expressions in gpl are strongly typed. The types are integer,
double,
string, and animation_block (since you don't need them for this phase, it is easier if you add animation_block to
your expressions in a later phase). Since expressions are
strongly typed, you must keep type information in the expression
class. If you don't keep type information you will not be able to
perform the required error checking. Consider the following
example:
int i = 42 + "hello world";
This input will match the
following rule:
simple_type T_ID optional_initializer
The <int> will match
simple_type, the <i> will match T_ID,
and the <42 + "hello world"> will match expression
(optional_initializer derives expression). Since integers are
automatically converted to strings when in a string expression, the
expression <42 + "hello world"> is a valid string expression (it has the value
"42hello"). In other words, this legal expression is of type string. In gpl it is a
semantic error to assign a string to an integer and thus the above
input should generate an error.
The check for correct type can be made as follows (assume that
optional_initializer returns a pointer to an expression) and your expression class has a get_type() function:
simple_type T_ID optional_initializer
{
if ($1 == INT) // the type of my simple_type token is Gpl_type
{
int initial_value = 0; // 0 is the default value for integers
// if an initializer was specified
if ($3 != NULL)
{
if ($3->get_type() != INT)
error -- the initializer is not of the correct type
else initial_value = $3->eval_int();
}
// now a new symbol can be created using initial_value
}
// do other cases here (e.g. $1 == DOUBLE)
There are two primary functions of class Expression.
(1) you need to construct the tree, and (2) you need to be able to
evaluate it.
The construction is straight forward. For each expression and
primary_expression rule in
the grammar, you create a new node in the expression tree. Since
the parser will match the expression in a bottom up fashion, you just
build the tree as you parse the expression. Consider the example
of the addition rule:
expression:
expression T_PLUS expression
{
// check that the type of $1 and $3 are compatible
$$ = new Expression(PLUS, $1, $3);
}
All you have to do is create constructors for Expression that can
handle all the different configurations of an expression tree and call
them in the appropriate actions. The constructor shown in the
above example is for binary expressions. It takes an operator (I
used an enumerated type Operator_type from gpl_type.h) and pointers to two
other expressions. Expression_grammar
shows all the
rules used to parse expressions.
Note: for this assignment you will
not be adding an action for
the following rule:
expression -> variable geometric_operator variable
The Expression evaluation function recursively evaluates each node
in the tree. The algorithm performs a depth-first traversal of
the tree. When a node's evaluation function is called, if the
node has children, it calls evaluate on the left child, then the right
child, then applies its operator. If the node holds a constant
(integer, double, string), it returns the value.
If it holds a variable, it evaluates the variable (e.g.
my_variable->get_int_value() ) and returns the value.
You will need to add a pointer to your expression class to the
union in gpl.y.
This means that you will have to add an include for the expression's .h
file in parser.h.
Variable class:
The expression node class has to handle several different basic forms:
| Expression Node Forms |
Node Contents |
| Binary operator |
operator, pointer to left child, pointer to right child |
| Unary operator |
operator, pointer to child |
| Integer constant |
value of integer constant |
| Double constant |
value of double constant |
| String constant |
value of string constant |
These forms handle all expressions that do not contain any variables:
3 + 4
7 * -3
6 * sin(42)
In order to handle expressions that contain variables, the expression
node will have to handle all the forms of variables. Consider the
following expressions
a
my_rectangle.x
nums[a + b]
rectangles[i + 2].y
We could implement several expression nodes to handle all these possible
| Expression Node Forms |
Node contents |
| a |
pointer to a symbol that holds a integer, double, or string variable
|
| my_rectangle.x |
pointer to a symbol that holds a game object, AND a string to describe the field
|
| nums[a + b] |
a symbol for an array AND an
expression for the index of the array
|
| rectangles[i + 2].y |
a symbol for an array AND an
expression for the index of the array AND a string for the field
|
Or we could implement a class to encapsulate all the
possible variables:
| Expression Node Forms |
Node contents |
| a |
pointer to a Variable object |
| my_rectangle.x |
pointer to a Variable object |
| nums[a + b] |
pointer to a Variable object |
| rectangles[i + 2].y |
pointer to a Variable object |
The variable class simply
encapsulate these possible forms of a
variable into a single interface.
Note: for this assignment you will not be implementing variables that
have fields (i.e. no game object variables).
An added advantage of the Variable class is that when you implement the
assignment statement you will be able to use this class to represent
variables on the left-hand-side of the assignment operator.
Turning in and Testing: