With the integration of semantic routines into the project as it exists so far, the project will be complete, and you should have a working generator for an Intermediate Code representation of any valid program written in the subset of Pascal described by the grammar.
To implement the semantic actions, you will use the augmented version of the original grammar that includes semantic actions in the right hand sides of most productions. The parser must be modified to recognize the presence of a semantic action on the stack top and call the appropriate routine when it does.
In addition to writing the routines, you should provide for error handling in the following ways:
Errors should be handled by issuing as explicit and meaningful a message as possible (don’t issue messages that are meaningless to the user, such as messages which refer to the contents of the semantic stack, etc.) and by giving an indication of where in the source (i.e., print out the line containing the error) the error occurred. This can be easily done by modifying your lexical analyzer to provide a source listing, so that when an error occurs the point of difficulty will be apparent (but you should provide a message as well, and perhaps an arrow pointing to the token that caused a problem). Adapt your lexical error parser error classes to do the same.
Semantic errors are detected by the semantic action routines, and include such things as undeclared and multiply declared variables, mismatches in number and type of actual and formal parameters in a procedure call, any of the various kinds-of-objects used in the wrong context, array without subscripts (or simple variable with subscripts), etc. Each of these should generate an appropriate error message, but the parse should continue thereafter.
When undeclared variables are detected, avoid printing a message every time the undeclared variable is subsequently encountered in the source. Print the message only the first time the variable is encountered. The variable should then be entered into the symbol table and a flag (in the symbol table entry for that variable) set to indicate that the variable was entered into the table as the result of an error condition. Similarly, when a simple variable is referenced as if it were an array, set a flag to indicate this in the symbol table entry for that name so that subsequent illegal references do not generate the same error over and over again.
The semantic actions are the biggest part of the compiler, in part because this is where all the pieces you have developed finally start working together. Therefore, this part of the project is itself divided into four phases, each due on a given date between now and the time the entire project is due. The due dates are listed on the main course page.
In this phase of the project, you will implement actions 1, 2, 3, 4, 6, 7, 9, 13. These actions and approriate intializations are summarized here.
To lay the groundwork for this, do the following, in this order:
Set up the semantic action class, including the declarations for the Execute method and the semantic stack. For the time being you do not need to set up the Quadruple class–leave it to a later phase.
- Add the following private data members to the class and initialize them as indicated. All of these are boolean variables:
INSERT/SEARCH = INSERT ( flags insertion/search mode in symbol table )
GLOBAL/LOCAL = GLOBAL ( flags global vs. local environment )
ARRAY/SIMPLE = SIMPLE ( flags array vs. simple variable )
Do not concern yourself with any of the other variables and methods described in the semantic action routines for now.
Implement and test the Execute method, which simply consults the action number passed to it and calls the appropriate semantic action routine.
- Implement and test the semantic actions listed below, in this order:
Action 3 is the most involved. Before you start on action 3, make sure the others work, your semantic stack behaves as it should etc.
Actions 1, 2, and 6
Actions 4, 7, and 13 (these involve pushing an item on the semantic stack)
Implement a "semantic stack dump" routine that prints the semantic stack contents.
In this phase of the project, you will implement actions 55, 56, 30, 31, 40, 41, 42, 43, 44, 45, 46, and 48. These actions and appropriate intializations are summarized on the phase 2 semantic actions page.
The next part of the semantic actions implementation involves generating intermediate code.
This is the general outline of tasks:
Set up the quadruple class (see the sample code), in order to store the Intermediate Code as it is generated.
Implement a GEN (generate) function as decribed in the phase 2 semantic actions page.
implement actions 55 and 56.
The next set of actions involve simple expressions, which can appear on the right side of an assignment operator. You will need to implement two more functions:
CREATE, which creates new variables (typically, temporary variables used in expression evaluation); and
TYPECHECK, which determines the types of operands in an expression.
Once these routines are set up, implement actions 30, 31, 40, 41, 42, 43, 44, 45, 46, and 48.
Test your code on these files, using only the ones labeled "(no-subscripts)". Each file includes the expected output (in a comment at the end of the file). Note that the address numbers might be slightly different in your code, depending on the order in which you generate them.
Enhancements (not required): Provide for each user-defined variable to be initialized automatically to 0. Or, implement means to flag and issue an error for use of an undefined variable anywhere but on the left of an assignment operator.
Implement actions 53, 54, 32, 33, 34, which handle subscripted array variables on the left and right hand sides of an assignment operator. You will also have to update action 48 to handle array references.
The next set of actions involve relational and boolean operators. You need to implement the following functions:
makelist, which creates a new list of integers that represent the targets of quadruples on E.true and E.false lists;
merge, which merges two of these lists;
backpatch, which fills in the target field of a given quadruple with values from an E.true or E.false list.
These routines are described on the semantic actions page.
Next, implement actions handling relational expressions (actions 38 and 39) and the parts of actions 42, 43, 44, 45 that deal with boolean operators (AND, OR), as well as action 47, which deals with NOT.
Once these actions are implemented, implement actions handling flow of control: actions 22, 27, 28, 29, 24, 25, 26.
Test your code on these files. Each file includes the expected output (in a comment at the end of the file).
In the final phase, you will implement semantic actions that handle procedure and function declarations, setting up the function result variable, formal parameter declarations, and calling procedures and functions. This will involve checking that the number and types of parameters are correct, allocating local storage within a procedure or function, and freeing it at the end.
Actions 5 and 11 (11 generates code that ends a procedure or function by freeing storage);
Actions 15, 16, and 17, which insert procedure and function names in the symbol table and handle the function result variable;
Actions 19, 20, and 21, which handle formal parameters;
Actions 35, 51, 54, 36, and 37, which handle procedure calls;
Actions 49, 50, and 52, which handle function calls;
Actions 51WRITE and 51READ, which handle calls to the built-in procedures READ and WRITE.
The output of your compiler should be two-fold:
the printed output should consist of the source listing and messages issued by your compiler. Provide understandable and useful messages for the user—
try to develop a compiler that is as user-friendly as possible.
Second, the Intermediate Code representation of the program should be put in a file named out.tvi. The ".tvi" extension indicates that this is a file for input into The Vassar Interpreter (TVI), which will execute the code.