Welcome to the ECE 467 labs homepage for 2020 Fall. See here for the latest version of the course.
expression := name
so that variable names can be used in expressions (see lab 2 page)type
with the token TOK_TYPE
(see lab 2 page)
In this lab, you will be modifying your parser to produce an
abstract syntax tree (AST).
The base class for your tree nodes is provided in src/node.<hpp|cpp>
.
While an input program may conform to a language's grammar,
it is not always a valid program.
You will implement semantic analysis to verify the correctness of the input program.
Finally, you will implement a simple form of constant propagation and dead code elimination.
Your task in this lab will be to:
parser.y
to create your ASTstd::unique_ptr
)
Location tracking is a simple addition with Bison's built in location tracking.
A helper function make_node
has been provided in parser.y
.
You use it as such: make_node<LeafNode>(@$, constructor args...)
,
which returns a std::unique_ptr<LeafNode>
.
The @$
represents the location of the production target in Bison.
This helper function is so that all your leaf nodes do not need to take a location in their constructor
(simplifies the code in node.<hpp|cpp>
).
Again, you can read more about location tracking in Bison in sections 3.5 and 10.1.5 of the manual.
Note: due to corner cases in how Bison propagates location information, we will only be verifying the location of local variable declarations, ints, floats, and bools. You should not have to modify anything to get these to work.
Here, you will be verifying semantic properties of your AST in
verify_ast
in src/compiler.cpp
to ensure that the input is a valid program.
It is recommended that you implement a
visitor pattern to traverse your tree.
You should verify the following properties:
Error | Description |
---|---|
type_decl |
types must be one of bool, int, float .
Functions may have return type void
|
type_mismatch | operands of a binary or relational operator have the same type |
type_arg | type of a function call argument/parameter does not match function declaration |
type_bool | type of an if-statement, for-statement, while-statement, or ternary expression predicate must be bool
|
type_return | type of return expression matches function declaration |
main_function | a main function exists, and has return type int |
return_statement | void functions do not return expressions. Non-void functions have a return statement (with an expression) on control flow paths (excess return statements are ok) |
duplicate_decl | duplicate function or variable declaration in the same scope |
In the optimize
pass in compiler.cpp
,
you should simplify binary, unary, and relational expressions whose operands are strictly constants
(if you have int x = 3; int y = x * 4;
you do not need to simplify this;
but you should simplify int x = 3 * 4;
).
You should also eliminate dead code of the following form:
false
predicate (eliminate the loop)optimize
function should return a new tree.
Again, it is recommended that you iterate your tree with a visitor.
Note that constant propagation may give rise to additional opportunities for dead code elimination,
which you must handle.
In general, dead code elimination may also create opportunities for constant propagation,
but requires more advanced analysis,
which you do not need to handle.
If semantic analysis detects an error,
you should print a line [output] <error name>: <location>
,
where the error name is given in the table above.
On success, optimize
and print_ast
from compiler.cpp
will be called.
It is recommended but not necessary to implement these using a visitor pattern.
This will be verified manually, so choose a suitable format.
Include the location of each node (node->location.begin.line
and node->location.begin.column
which are int
s).
For example:
binary expression (1, 1) { op: + // The "+" was a token in the parser/lexer, // but isn't a node in the AST, // so it doesn't have a location. left: binary expression (1, 1) { op: * left: int 3 (1, 1) right: int 4 (1, 5) } right: float 5.5 (1, 9) }Additionally, Your compiler will be run through Valgrind to catch memory bugs; i.e. run
valgrind ./src/ece467c 3 <input-file.c>
.
You should avoid global variables. While this will not be marked, generally speaking there should be no need for global variables in this code.
The following test cases are provided for you.
Same as Lab 1.