Module 4:
A Semantic Checker for Klein
Stage 4 of the Klein compiler project
Out: Friday, October 17
Due: Friday, October 31, at 11:59 PM
STATUS CHECK DUE: Friday, October 24
Tasks
This stage consists of the third component of your Klein compiler, and three auxiliary programs related to it.
1. A Semantic Analyzer
Your primary task is:
Write a semantic analyzer that ensures a parsed Klein program satisfies the entire language specification.
The semantic analyzer is a function or object that takes as input the abstract syntax tree of a candidate Klein program and produces as output:
- an abstract syntax tree verified as legal, annotated with type information at every expression node, and
- a symbol table that records all of the identifiers defined and used in the program.
Implement your semantic analyzer by walking the nodes of the abstract syntax tree. To assign type information, you will want to do a pre-order traversal of the tree.
Be sure that your semantic analyzer reports all semantic errors that it finds. Your semantic analyzer should produce an error listing that includes enough detail that the programmer can match the error with the offending code.
See below for implementation notes on the code that verifies semantic correctness and produces the symbol table.
2. A Program to Display the Symbol Table
Once your analyzer is able to detect all semantic errors and produce a symbol table:
Write a program that produces a formatted listing of the symbol table.
This listing is like a dictionary of all the symbols used in the Klein program. Each entry in the symbol table is a Klein function. For each, list its type, its parameters, the name of every function it calls, and the name of every function that calls it. If the function is recursive, then both lists of functions will include the function itself.
This listing will give the programmer more information about the structure of the program and maybe even about error messages printed by the analyzer. Entries for functions that are never called will help the programmer find "dead code" in the program.
Extra Credit
The code that walks the AST to produce this listing can find functions that are never called. Use similar code in your semantic analyzer to print warning messages about unused functions and unused parameters along with the required error messages.
3. An Omnibus Test Program
In order to test your analyzer,
Create one new Klein program, named
semantic-errors.kln, that contains all of the
semantics errors found by your analyzer.
Include in the doc/ folder of your repository
a text file that shows the output from running of your
analyzer against this program, with all of the error
messages it produces.
Also include a copy of the program with all of the bugs fixed, so that it is legal Klein and passes through the semantic analyzer with no errors or warnings.
If you do
the extra credit described above,
be sure that semantic-errors.kln contains code
that triggers those warnings, too.
You should, of course, produce as many tests as necessary in order to ensure that your checker works correctly. You are also encouraged to use the test programs available on the project home page.
4. The kleinv Command
Finally, in order to use your client program as a tool,
Create an executable or a Unix command-line script named
kleinv that takes the name of a Klein source
file as an argument and runs your "dictionary" program on the
resulting symbol table.
This program will have to scan, parse, and semantically check the Klein program first, of course.
This is the fourth in a series of tools that makes up the command-line suite of your Klein compiler.
Deliverables
As before, submit only one copy of each assignment per team. The team captain or a designated team member can be responsible for the submission.
Status Check
You will need to create concrete examples to test each
element of your semantic analyzer early on. Create the
semantic-errors.kln program for Task 3 above
as a part of this planning.
Submit:
semantic-errors.kln
Make this file a part of the ongoing documentation of your compiler.
Final Deliverable
By the due time and date, use
the course submission system
to submit
your project directory
electronically as a zip file named project04.zip
or project04.tar.gz.
Implementation Notes
Here are some thoughts on implementing the two components of your semantic analyzer.
Verifying Semantic Correctness
The first part of verifying a program's semantic correctness is to show that all expressions have valid data types. This includes:
-
ensuring that primitive operators and
ifexpressions are applied only to expressions of the correct type - ensuring that each function call passes the correct number of arguments, and that the arguments are of the correct types
- ensuring that each function call returns a value of the correct type
The second part of verifying a program's semantic correctness is to show that the program satisfies all other non-grammatical requirements of the language spec, including at least:
- each function name is defined exactly once
-
there is a user-defined function named
main -
there is not a user-defined function named
print - each parameter name is defined exactly once in a given function
- each function refers only to its formal parameters and other functions
Semantic Warnings
Extra credit is available for warning the programmer about parts of the program that, while not semantically incorrect, are unnecessary or may be undesirable. These warnings must include:
- identifying functions that are defined but never used
- identifying formal parameters that are defined but never used
You might also generate other warnings of the sort discussed in Session 16, including style advice.
Producing the Symbol Table
Your symbol table should contain an entry for every function defined in the program. With each function, record:
- its data type
- a list containing an entry for each of its formal parameters
- the name of every function that it calls, including recursive calls
- the name of every function that calls the function, including recursive calls
- any other information you think useful
Be sure to put an entry for the print() function
into the symbol table. It is
a primitive function.
When you build your compiler's run-time system in future stages of stages of the project, you will likely want to extend, modify, or even replace the implementation of your symbol table. For example, you may want to add entries for the number of temporary objects needed at run-time or its line number in the generated TM code.
So: devote some thought to designing a reasonable interface to the table, and implement it in a way that is open to extension.
The Structure of the Semantic Analyzer
You may choose to implement your checker in at least two different ways:
- a single module to verify the program's correctness and produce the symbol table, or
- separate modules for the two tasks.
This design decision may affect the top-level structure of the compiler you produce at the end of the semester, as well as the structure of other programs that use the semantic analyzer and symbol table.
Either approach will work fine.