Inheritance in Bison
These notes give you some design hints for your parsing assignment.
I. There should be a class for each nonterminal and a subclass for
each production.
Example: Given the productions P->PC | C, there should be a
class for P and subclasses called P_PC and P_C. Given the
production C -> x there should be a class for C and a subclass
called C_x (although if there is only one production you might
not define the subclass since the class can handle the production.
Example: Given the input xxx, you would have the following parse
tree:
P_PC
|
----------------------
| |
P_PC C
| |
------------- x
| |
P_C C
| |
C x
|
x
II. Printing the Tree
A. Each subclass should have its own version of print_production
Example: P_PC::print_production () {
print name
print '->'
print child1.get_name();
print child2.get_name();
child1.print_production();
child2.print_production();
}
B. print_production should be declared virtual because each subclass
needs to provide its own customized version
C. Each class should have a class variable that defines its name
1. In C++, class variables are declared using the keyword static
Example: class P {
static char *name;
...
};
2. Class variables are shared by all instances
3. A class variable needs to be initialized outside the class
declaration
Example: In the .cc file that defines the methods for class P, we
could include the statement:
static char *P::name = P::initialize_name("P");
4. Methods like initialize_name can be declared as class methods using
the static keyword:
Example: class P {
static char *initialize_name(char *);
...
};
In the .cc file:
char *P::initialize_name(char *value) {
// only allow the name to be initialized once
static initialized = false;
if (initialized == false) {
initialized = true;
name = new char[strlen(value) + 1];
strcpy(name, value);
}
return name;
}
III. Creating the Parse Tree in Bison
A. The nonterminals in a Bison production are labeled $$, $1, $2, ..., $n
where $$ points to the non-terminal on the left side of the production
and $1, $2, ..., $n point to the non-terminals on the right side of
the production.
Example: Given the production P : P C
$$ = the left hand side P
$1 = the right hand side P
$2 = C
1. Each non-terminal is allowed to point to one object. Somewhere
at the beginning of Bison you tell Bison what $$ points to. In
our assignment you would tell Bison that $$ points to a Node
class.
B. To build the parse tree, each production should allocate the subclass
that corresponds to that production and install the right side
nonterminals as children. The right side nonterminals have already
had their parse nodes created so it is a simple matter of passing
them to the new object.
Example: P : P C {
$$ = new P_PC((P *) $1, (C *) $2);
}
1. The downcast is needed because the $'s have been declared
to point to a (Node *) but the constructor expects a (P *) and
a (C *). The downcasts tell the compiler that $1 points to
a (P *) and that $2 points to a (C *).
IV. Flattening the Tree: The productions P -> PC | C are meant to
simulate the production P -> C+. I want your parser
to print out P -> C+ rather than the productions
P -> PC and P -> C. I also want your parse tree to look like:
this: P and not this: P
----------- / \
| | | / \
C C C P C
/ \
/ \
P C
|
|
C
A. This is called flattening the tree
B. To do it, you need to define a class called P_CList that has a
list of C's:
class P_CList {
CList *my_clist;
}
1. The P -> C production creates a P_CList object and inserts the
first C onto the list:
P : C { $$ = new P_CList((C*) $1); }
2. The P -> PC production adds its C object to the list and passes
the P_CList object to its parent:
P : P C { $$ = $1;
((P *)$$)->Append((C*)$2);
}
a. The assignment "$$ = $1" passes the P_CList object from
the right side child to its left side parent.
3. What these productions do is collect the leaves and insert them
onto a list