You should create a SymbolTable class which manages the symbol table. It should manage a stack of Symbol entries (which consist of a scope number, name, and attributes), and keep track of the current scope level (initially 0). It should provide the following interface:
Symbol push(String name,Declaration decl);
Pushes a new symbol entry onto the stack with the given name and decl information, and the current scope level. It should return the new symbol entry. (See more about Declaration below.)
If name is already defined at the current scope level, go ahead and push a new record for it onto the stack.
Symbol lookup(String name);
Searches for a symbol entry with name, starting at the top of the stack and working to the bottom. Returns the first Symbol entry found, or null if none.
void beginScope();
Increments the current scope level.
void endScope();
Pops all symbol entries in the current scope level off the stack, then decrements the current scope level. Raise an exception if the scope level would drop below 0.
int getScope();
Returns the current scope level.
Before trying to use this in your compiler, it would be a good idea to write a small JUnit test to verify that it works properly.
Sample SymbolTableTest.java:
import junit.framework.TestCase;
public class SymbolTableTest extends TestCase {
public void testSymbolTable() {
SymbolTable table = new SymbolTable();
Symbol s = table.push("x", new VarDecl(Type.INT));
assertTrue(s.getName().equals("x"));
// ...
}
}
Declaration objects store information about their symbol. You will have a hierarchy of Declaration classes, because there are different kinds of identifiers. I suggest starting with the following class structure:
class Declaration {
Type type; // all symbols have a Type
}
class VarDecl extends Declaration {
// more will go here eventually ...
}
class MethodDecl extends Declaration {
// more will go here eventually ...
}
class ClassDecl extends Declaration {
// more will go here eventually ...
}
Every symbol has a type. Dream has some built-in types, but it also allows the user to create new types.
You should create a Type class that represents a data type.
class Type {
String name;
// Optional class declaration, if this type was defined by a class
ClassDecl classDecl;
protected Type(String name) {
this.name = name;
}
// built-in types
public static Type ERROR = new Type("<ERROR>");
public static Type INT = new Type("INT");
...
}
You’ll also need a class that manages a collection of types (maybe call it TypeRegistry). I recommend a singleton class that is globally accessible. It should contain methods to add a new type and lookup a type by name.
class TypeRegistry {
private HashMap<String, Type> types = new HashMap<>();
private TypeRegistry() {
types.put("<ERROR>", Type.ERROR);
types.put("int", Type.INT);
...
}
public getType(String name) { }
public addType(String name) { }
}