Block Structure & Symbol Table Organization (cont.)
Block Structure & Symbol Table Organization (cont.)
Last time we discussed how to organize a symbol table that would handle the
scoping rules associated with block-structured programming languages.
We distinguished between four types of entries/structures that are used to
manage the symbol table:
Identifier descriptors
-
One per identifier.
Created by scanner, used by later phases.
Accessed through
hash table managed by the scanner.
Abstract syntax tree produced by the parser
will use pointers to identifier descriptors to represent identifiers referenced in the
program text.
Declaration descriptors
-
One per declaration.
Created during semantic processing, used in later phases.
Accessed through stacks of bindings
associated with identifier descriptors and through pointers
inserted in the abstract syntax tree during semantic processing.
After semantic processing the abstract syntax tree
will use pointers to declaration descriptors to represent identifiers referenced in the
program text.
Contain information about declared items like type, number of parameters, etc.
Binding descriptors
One per activation of declaration (multiple activations can be created
through inheritance).
Created during semantic processing, irrelevant to later phases
Contain pointers to the associated declarations
Contain pointers to lists of other bindings in scope
Contain pointers to stacks of hidden bindings of ids
Scope descriptor
One per scope (classes and methods in Woolite) per pass...
Only used during semantic processing
Hold pointers to surrounding scopes and list of bindings in each scope
We sketched out the process followed to create symbol table entries and associate them with
the appropriate nodes of the abstract syntax tree.
The scanner creates a new identifier descriptor each time it sees an identifier it has not
previously seen. It uses a hash table to keep track of the descriptors it has already created.
The semantic processor creates a new scope descriptor each time it begins
processing the subtree of a class or method in the abstract syntax tree. It keeps
these descriptors in a stack.
The semantic processor creates a declaration descriptor each time it encounters
a declaration.
When the semantic processor wants to make an identifier accessible within a scope,
it creates a binding for that declaration and:
pushes it onto the stack of bindings for that identifier,
adds it to the list of bindings for the current scope.
When the semantic processor encounters a use of an identifier in the tree,
it will place a pointer to the current declaration descriptor for that identifier
(i.e. the one pointed to by the binding on the top of the identifier's stack
of bindings) in that tree node.
When the semantic processor completes the processing of a scope, it pops
the binding stack for each identifier for which there is a binding in the scope,
then it pops the scope descriptor itself off a stack of scopes.