Assigned: Monday, March 26, 2001.
Due: Monday, April 9, 7pm. Not accepted after 7pm on Thursday, April 12.
Who needs a semantic checker? Programmers who make mistakes! The errors that the semantic analyzer can find include forgetting to declare a variable, or forgetting what the types of your class fields are. Note that all programmers make mistakes. Typically, many mistakes. So, all programmers need a semantic checker. You, too. The semantic checker will find your errors before they show up as nasty bugs in your programs.
The code generator needs the semantic checker, too. It needs it because it does not know how to machine code for badly formed intermediate code. E.g., a code generator typically does not how to generate MIPS instructions that compare a floating-point number with a String.
For this assignment you will do the following:
The two passes of the static-semantic analyzer that you will write are the (1) name analyzer and the (2) type checker. The name analyzer will perform the following tasks (the semantic rules of Simple are in lecture 17, slide 4!):
The type checker will determine the type of every expression represented in the abstract-syntax tree, and will use this information to identify type errors, as described below.
The main program, P4.java, will be similar to P3.java, except that after parsing, if there are no syntax errors, it will call the name analyzer before calling the decompiler. After calling decompiler, if there are no errors so far, it will then call the type checker. (Calling the name analyzer and the type checker means calling the appropriate methods of the ASTnode that is the root of the tree built by the parser.)
The decompiler will be slightly different from the version written for program 3: when decompiling an IdNode, the type of the node will be printed (in parentheses) after the name. This will help you to see whether your name analyzer is working correctly.
You will also write three input files to test your code: nameErrors.sim should contain code with errors detected by the name analyzer, typeErrors.sim should contain code with errors detected by the type checker, and test.sim should contain code with no errors.
You will be graded on the correctness of your code, and on how thoroughly your input files test your code.
In order to simplify the code generation in P5, I decided to make a small change to the front end. We will remove the keyword float and replace it with a keyword boolean. You will have to make a minor change to your scanner, parser, and the AST. My apologies for the late change. The files that you download from the class Web site should have all the necessary changes.
Code for you to use can be found in ~cs536-1/public/PROG4. The files are:
An error message (see below) should be given for each multiply declared identifier, and all but the first declaration should be ignored (i.e., do not update the symbol table when you are processing an identifier that is multiply declared, and do not bother to link that IdNode to any symbol-table entry).
The name analyzer is also responsible for finding all instances of uses of undeclared identifiers. Note that this includes calls to methods that have not yet been declared and uses of fields that have not yet been declared. An error message (see below) should be given for each use of an undeclared identifier. (It is up to you whether you treat a use of the class name as an undeclared identifier during name analysis, or instead treat it as a type error during type checking. If you choose the latter, you will need one more error in addition to those described below: "Equality operator applied to a class".)
In addition, the name analyzer is responsible for checking that the program includes a method named main. If the program does not, an error message (see below) should be given.
In summary, your name analyzer should find all of the errors described in the following table; it must report the specified position of the error, and it must give exactly the specified error message (each message should appear on a single line, rather than how it is formatted in the following table). Error messages should have the same format as in the scanner and parser (i.e., they should be issued using a call to Errors.fatal).
More than one declaration of an identifier in a given scope
Multiply declared identifier
The first character of the ID in the duplicate declaration
Use of an undeclared identifier
Undeclared identifier
The first character of the undeclared identifier
No main method in the program
No main method declared
0:0 Changes to the Errors Class
Your compiler should quit after
decompiling the program if any errors have been detected so far (either by the
scanner, the parser, or the name analyzer). To accomplish this, you can add a
static boolean field to the Errors class that is initialized to
false and is set to true if the fatal method
is ever called (warnings should not change the value of this field). Your
main program can check the value of this field (by calling a new
method of the Errors class that returns the field's value) and only
call the type-checker if it is false.
Type Checking
You must implement your type checker by writing
appropriate member methods for the different subclasses of ASTnode.
Your type checker should find all of the type errors described in the following
table; it must report the specified position of the error, and it must give
exactly the specified error message. (Each message should appear on a
single line, rather than how it is formatted in the following table.) Note: if
your name analyzer does not give an "Undeclared identifier" error message for
uses of the class name, then your type checker should give the appropriate error
message for any use of the class name.
|
|
|
|
| Assigning to a method; e.g., "f = 3;", where f is a method name. | Assignment to a method | The first character of the method name. |
| Assigning from a method; e.g., "x = f;", where f is a method name. | Assignment from a method | The first character of the method name. |
| Writing a method; e.g., "System.out.println(f)", where f is a method name. | Attempt to write a method | The first character of the method name. |
| Equality or inequality operator applied to a method; e.g., "f == exp" or "exp != g", where f and g are method names. | Equality operator applied to a method | The first character of the method name. |
| Calling something other than a method; e.g.: "x();", where x is not a method name. Note: In this case, you should not type-check the actual parameters. | Attempt to call a non-method | The first character of the variable name. |
| Calling a method with the wrong number of arguments. Note: In this case, you should not type-check the actual parameters. | Method call with wrong number of args | The first character of the method name. |
| Calling a method with an argument of the wrong type. Note: you should only check for this error if the number of arguments is correct; if there are several arguments with the wrong type, you must give an error message for each such argument. | Type of actual does not match type of formal | The first character of the first identifier or literal in the actual expression with the wrong type. |
| Applying an arithmetic operator (+, -, *, /) to an operand with type other than int. | Arithmetic operator applied to non-numeric operand | The first character of the first identifier or literal in an operand that is an expression of the wrong type. |
| Applying a relational operator (<, >, <=, >=) to an operand with type other than int. | Relational operator applied to non-numeric operand | The first character of the first identifier or literal in an operand that is an expression of the wrong type. |
| Applying a logical operator (!, &&, ||) to an operand with type other than boolean. | Logical operator applied to non-bool operand | The first character of the first identifier or literal in an operand that is an expression of the wrong type. |
| Using a non-boolean expression as the condition of an if. | Non-bool expression used as an if condition | The first character of the first identifier or literal in the condition. |
| Using a non-boolean expression as the condition of a while. | Non-bool expression used as a while condition | The first character of the first identifier or literal in the condition. |
| Mixing modes: applying an equality operator (==, !=) to operands of two different, non-method types (e.g., "j == true", where j is of type int); assigning a value of one, non-method type to a variable of another, non-method type (e.g., "j = true", where j is of type int). | Type mismatch | The first character of the first identifier or literal in the left-hand expression. |
The Types Class
You will need a way to represent the different types
of the identifiers and expressions found in a program. It is up to you how you
do this, but one possibility is to use the values in the class defined in Types.java.
For example, you would use int as the type of the new field added to
each symbol-table entry, and also as the type returned by the type-check methods
of ExpNodes. You would use: Types.IntType as the value that
represents the int type.
Note that the purpose of Types.ErrorType is to prevent a single type error in an expression or statement from triggering multiple error messages. For example, each of the following expressions should cause only one error message:
(true + 3) * 4 true && (false || 3)The type given to (true + 3) should be ErrorType, and the type-check method for the multiplication node should not report "Arithmetic operator applied to non-numeric operand" for the first operand. But note that the following should cause two error messages (one for each of the non-integer operands of the plus operator):
true + "hello"
To provide some help with this issue, here is an
example input file, along with the corresponding
error messsages. (Note: This is not meant to a complete test of the type
checker; it is provided merely to help you understand some of the messages you
need to report.)
Some Advice
Here are few words of advice about various issues that
come up in the assignment:
However, you will need to declare the name-analysis and type-checking methods to be abstract methods of some of the classes that are lower down in the inheritance hierarchy; for example, you will need to declare an abstract symbol-table-building method for the DeclNode class, because the method for the DeclListNode class will call that method for each node in the list.
Instead, you might want to divide up some of the "incidental tasks" (like modifying the Errors, Sym, and IdNode classes), then work together to get a small part of the name-analysis phase working (e.g., finding multiply declared field names). Then you could split up the ASTnode subclasses, and each implement both the name-analysis and type-checking methods for your subset of those classes (you might want to start by choosing just a few each, until you have a better idea which ones will require the most work).
Don't forget to test your work as you go along, rather than waiting until everything is finished!
~cs536-1/public/P4/<your login name>using your actual login name in place of <your login name>. Please do not create any subdirectories in your handin directory, and do not copy any .class files.
If you are working with a partner only one of you should hand in files. Include a comment at the top of P4.java with the names of both partners.