| CIT 594 Assignment 4: Parser Addendum Spring 2004, David Matuszek |
Yet another fix to the grammar:
Add this BNF rule:
<program> ::= <command> { <command> } { <procedure> }
That is, a "program" consists of one or more commands, followed by zero or more procedures.
The parse tree for a <program> is as follows: The root node
holds a Token whose type is Token.KEYWORD and whose
value is the string "program". The left child is a list
(type Token.KEYWORD, value "list") of commands;
the right child is a similar list of procedures.
<program> ::= <command> { <command>
} |
penup \n |
![]() |
The root node is a "program" node, and each child is a "list" node. Note that an empty list is represented by a "list" node with no children. |
Token values:
Three new "keyword" types are added to the grammar. The Token class
should now have a public static final int called Token.KEYWORD,
distinct from the other types provided by this class.
The Tokenizer needs to recognize keywords such as if, blue,
end, etc., and the newly-added and return these with type program,
Token.KEYWORD. The Tokenizer should
not recognize "list" or "header"
or "program" as keywords; if it encounters these, they
should be returned with type Token.NAME.
However, the Token(String value, int type) constructor
must be able to create Tokens with these value "list"
or "header" and with type Token.KEYWORD.
Such tokens will be created by the Parser, not by the Tokenizer.
Building the parse tree:
The interplay between the Stack and the various BinaryTrees is central to this program.
The assignment is very specific about what kind of parse tree is built for each nonterminal type, but it isn't clear about is in the parse tree, or in the stack. Here are the two simple rules you should follow:
In order to force everything into binary trees (where each node has at most
two children), we need to define some intermediate nodes. One of these is the
general-purpose list node. The value of this BinaryTree
node is a Token, with value = "list" and type
= KEYWORD. (Don't let yourself get confused by the fact that both BinaryTrees
and Tokens have a value field.)
Examples:
Note: In these examples, I assume you are using my name(),
name(String), number(), and symbol() methods, each
of which leaves, if successful, one BinaryTree node on the stack. That BinaryTree
node contains the desired Token. I further assume that you have added your own
convenience routine, keyword(String).
You don't have to use these methods if you don't like them. You are free to
do anything you like about private methods. It's only the public API that must
meet the specifications of the assignment. This includes the public instance
variable stack (because otherwise testing is much more difficult).
To parse the command "color red \n":
According to the grammar,
<command> ::= ... | "color"
<colorname> <eol> | ...
command() method calls keyword("color").keyword("color") succeeds and leaves a BinaryTree
on the stack. The value of this BinaryTree is the appropriate keyword token.command() now calls colorname() which finds, as
a Token, the keyword "red".colorname() constructs a BinaryTree with this Token as its
value, and no children. It pushes the BinaryTree onto the stack.command() now calls eol() which finds an EOL Token.eol() for you, but it should leave a BinaryTree
on the stack containing an EOL Token.command() has now found everything it needs to make a command.command() constructs a new BinaryTree with the "color"
node as its value and the "red" node as its left child. The eol
node is discarded (ignored).command() pushes this new BinaryTree onto the stack, and returns
true.Here is what is important to note:
To parse the factor "(3 * n + 1)":
According to the grammar,
<factor> ::= <name> | <number>
| "(" <expression> ")"
factor() method calls name(), which
fails (returns false).factor() method then calls number(), which
also fails.factor() method then calls symbol("("),
which succeeds.factor() method next calls expression(), which
succeeds."(". (There
may be other things deeper in the stack, but these are the only ones
factor() cares about.)factor() method next calls symbol(")"),
which succeeds.factor() has now found everything it needs to make a <factor>.<expression>
back onto the stack.Here is my code for doing this. It is slightly different from the above description: Since I know I won't be using the parentheses in the BinaryTree, my code just discards (pops) them as it finds them.
public boolean factor() {
if (name())
return true;
if (number())
return true;
if (symbol("(")) {
stack.pop();
if (!expression())
error("Error in parenthesized expression");
if (!symbol(")"))
error("Unclosed parenthetical expression");
stack.pop();
return true;
}
return false;
}
|