CS 536 Program 3: The Parser
Due date: Friday, November 5 (by midnight)
Not accepted after midnight on Monday, November 8
Overview |
Requirements |
Announcements | Handin
Overview
For this assignment you will use the parser-generator Java Cup
to write a parser for the C-- language.
The parser will find syntax errors, and for syntactically correct programs,
it will build an abstract-syntax tree (AST) representation of the program.
You will also write methods to unparse the AST built by
your parser, and an input file to test your parser (a main program that
calls the parser and then the unparser has been provided).
You will be graded on the correctness of your parser and your unparse methods,
and on how thoroughly your input file tests the parser.
Requirements
Getting Started
Skeleton files on which you should build are in:
~cs536-1/public/prog3
The files are:
- c.jlex:
A JLex specification for the C-- language (a solution to program 2).
Use this if there were problems with your JLex specification.
- c.cup:
A Java Cup specification for a very small subset of the C--
language (you will need to add to this file).
Note: you will need to change the terminal declarations for
ID and STRINGLITERAL if you use your own scanner specification, and
you used names other than IdTokenVal and
StrLitTokenVal for the corresponding subclasses of TokenVal.
- c.grammar:
A CFG for the C-- language. Use this to guide the enhancements
you make to c.cup.
- ast.java:
Contains class definitions for the AST structure that the parser
will build (you will need to add to this file).
- P3.java:
The main program that calls the parser, then, for a successful
parse, calls the unparser (no changes needed).
- Makefile: A Makefile for
program 3 (no changes needed).
- test.C:
Input for the current version of the parser (you will need to
change this file).
- Errors.java:
Same as for program 2 (no changes needed).
- IO.java:
Same as for program 1; used by P3.java (no changes needed).
Here is a link to the Java Cup
reference manual.Operator Precedences and Associativities
The C-- grammar in the file c.grammar is ambiguous;
it does not uniquely define the precedences and associativities of
the arithmetic, relational, equality, and logical operators.
You will need to add appropriate precedence and associativity declarations
to your Java Cup specification.
- All of the binary operators are left associative, except the relational
and equality operators (<, >, <=, >=, ==, and !=), which are
non-associative (i.e., expressions like a < b < c
are not allowed and should cause a syntax error).
- The unary minus and not (!) operators have the highest
precedence, then multiplication and division,
then addition and subtraction,
then the relational and equality operators, and finally the logical
operators (&& and ||).
Note that the same token (MINUS) is used for both the unary and binary minus
operator, and that they have different precedences;
however, the C-- grammar has been written so that the unary minus operator
has the correct (highest) precedence;
therefore, you can declare MINUS to have
the precedence appropriate for the binary minus operator.
Java Cup will print a message telling you how many conflicts
it found in your grammar.
If the number is not zero, it means that your grammar is still
ambiguous, and the parser is unlikely to work correctly.
Do not ignore this!
Go back and fix your specification so that your grammar is not ambiguous.
Building an Abstract-Syntax Tree
To make your parser build an abstract-syntax tree, you must add new
productions, declarations, and actions to c.cup.
You will need to decide, for each nonterminal that you add, what
type its associated value should have.
Then you must add the appropriate nonterminal
declaration to the specification.
For most nonterminals, the value will either be some kind of tree node
(a subclass of ASTnode), or a LinkedList
of some kind of node
(use the information in ast.java to guide your decision).
You must also add actions to each new grammar production to assign
an appropriate value to RESULT.
Note that the parser will return a Symbol whose value
field contains the value assigned to RESULT in the production
for the root nonterminal (nonterminal program).
Unparsing
To test your parser, you must write the unparse
methods for the subclasses of ASTnode.
When the unparse method of the root node of the program's
abstract-syntax tree is called, it should print a nicely formatted
version of the program (this is called unparsing
the abstract-syntax tree).
The output produced by calling unparse should be the
same as the input to the parser except that:
- There will be no comments in the output.
- The output will be "pretty printed" (newlines and indentation
will be used to make the program readable); and
- Expressions will be fully parenthesized to reflect the order of
evaluation.
For example, if the input program includes:
if (b == -1) { x = 4+3*5-y; while (c) { y = y*2+x; } } else { x = 0; }
the output of unparse should be something like the following:
if ((b == (-1))) {
x = ((4 + (3 * 5)) - y);
while (c) {
y = ((y * 2) + x);
}
}
else {
x = 0;
}
To make grading easier, put open curly braces on the same
line as the preceding code, and put closing curly braces on a line with no
other code (as in the example above).
Put the first statement in the body of an if or while
on the line following the open curly brace.
Whitespace within a line is up to you (as long as it looks reasonable).
Note: Trying to unparse a tree will help you determine whether you have
built the tree correctly in the first place.
Besides looking at the output of your unparser, you should try
using it as the input to your parser;
if it doesn't parse, you've made a mistake either in how you built
your abstract-syntax tree, or in how you've written your unparser.
Another good way to test your code is to try compiling the output
of your unparser using the C++ compiler (g++).
If your input program uses I/O (cin or cout),
you will first need to add: #include <iostream.h>
at the beginning of the file.
It is a good idea to work incrementally:
- Add a few grammar productions to c.cup.
- Write the corresponding unparse operations.
- Write a test program that uses the new language constructs.
- Create a parser and run it on your test program.
Modifying ast.java
We will test your program by using our unparse
methods on your abstract-syntax trees, and by using your unparse methods
on our abstract-syntax trees.
To make this work, you will need to:
- Modify ast.java only by filling in
the bodies of the unparse methods (and you must fill in all of the
method bodies).
- Make sure that no LinkedList field is null (i.e., when you call the
constructor of a class with a LinkedList argument, that argument should
never be null).
Note that it is OK to make the ExpNode field of a ReturnStmtNode null
(when no value is returned).
- Follow the convention that the mySize field of a
VarDeclNode has the value VarDeclNode.NOT_ARRAY if the type
of the declared variable
is a non-array type (i.e., if the declaration did not include
square brackets).
Testing
Part of your task will be to write an input file that thoroughly
tests your parser and your unparser.
Note that since you are to provide only one input file,
it should contain no syntax errors (you should also test your
parser on some bad inputs, but don't hand those in).
Name your test file test.C.
Use comments in the file to explain what aspects of the parser are
being tested.
Working in Pairs
This assignment involves three main tasks:
- Writing the parser specification (c.cup).
- Writing the unparse methods for the AST nodes (in ast.java).
- Writing an input file to test your implementation.
To ensure that both partners understand all aspects of the assignment,
it is a good idea to share responsibility for all tasks.
I suggest that you proceed as follows, testing your parser
after each change:
- Working together, start by making a very small change
to c.cup.
For example, add the rules and actions for:
type ::= BOOL
type ::= VOID
Also update the appropriate unparse method in
ast.java.
Make sure that you can create and run the parser after making
this small change.
- Next, add the rules needed to allow array declarations.
- Next, add the rules needed to allow programs to include methods
with no arguments and with empty statement lists only, and update the
corresponding unparse methods.
- Still working together, add the rules (and unparse methods)
for the simplest kind of expressions -- just plain identifiers.
- Now divide up the statement nonterminals into two parts, one part for
each person.
- Each person should extend their own copy of c.cup
by adding rules for their half of the statements, and should extend
their own copy of ast.java to define the unparse methods
needed for those statements.
- Write test inputs for your statements.
- After each person makes sure that their parser and unparser
work on their own statements, combine the two by cutting and
pasting one person's grammar rules into the other person's
c.cup (and similarly for ast.java).
- Now divide up the expression nonterminals into two parts, and
implement those using a similar approach.
Note that you will also need to give the operators the right
precedences and associativities during this step (see
above).
- Divide up any remaining productions that need to be added,
and add them.
- Talk about what needs to be tested, and decide together what your
final version of test.C should include.
- When working on your own, do not try to implement all of
your nonterminals at once. Instead, add one new rule at a time
to the Java Cup specification, make the corresponding changes
to the unparse methods in ast.java, and test
your work by writing a C--
program that includes the new construct you added, and make sure
that it is parsed and unparsed correctly.
If you worked alone on the previous program and are now working with
a partner, see programming assignment 2 for more suggestions on how to work in pairs.
Announcements
Includes: Additions, Revisions, and FAQs
(Frequently Asked Questions).
Please check here frequently.
10/15/2004 |
Program released. |
Handin
What to turn in
See the assignments page for information about how to submit your code.
The late policy is also found on the assignments page.
Electronically submit all of the files that are needed to
create and run P3.class as well as your Makefile and
your test.C.
Do not copy any ".class" files, and do not create any subdirectories
in your handin directory.
If you are working with a partner only one of you should hand in files.
Include a comment at the top of P3.java with the names of both
partners.
General information on program grading criteria can be found on the Grading Criteria
for Programs page.