/* * There are many design options here. This is one simple design * optimized for clarity and ease of use. */ class SymbolTable(val description: String, val parent: Option[SymbolTable]) { /* * The description instance variable is a message that you can use * to aid in viewing your symbol tables and debugging the * output. For example, A description could be "global", "Class * Cow", "Block on line 50", etc. This is really for your benefit, * and I encourage you to annotate your data structures with Strings * like this wherever you think having some more information would * be useful. The parent is the optional parent symbol table. */ /* * We could represent a symbol table using a single map for all * types of declarations, eg Map[String,ASTNode], but that leads to * many "instanceof" tests and type casts in the code. Also, the * same name can be used with more than one meaning in some scopes, * notably methods and fields can have the same name in a single * class definition. So, a simple solution is to just keep four * maps, one for each type of declaration we must handle, eg * Map[String,ClassDecl], Map[String,FieldDecl], etc. */ /* * The following four methods insert each type of declaration into * the scope. Each method will also enforce The scoping rules * related to name declarations. E.g.: no multiple declarations of * the same name in a scope except for field/methods, cannot shadow * a parameter with a local, cannot define a field with the same * name as one in the superclass, etc. */ def add(name : String, d: ClassDecl) def add(name : String, d: MethodDecl) def add(name : String, d: FieldDecl) def add(name : String, d: VariableDecl) /* * Similarly, we have four methods to look up each type of * declaration. This is preferable to a single method since it * avoids having to do type casts in the client, and since most of * the time we know exactly what type of declaration were looking * for. These methods all return an option in case the name is not * in scope. */ def getClassDecl(name: String): Option[ClassDecl] def getMethodDecl(name: String): Option[MethodDecl] def getFieldDecl(name: String): Option[FieldDecl] def getVariableDecl(name: String): Option[VariableDecl] /* * Return a string representation of the table */ def toString() def getParent() : Option[SymbolTable] } Symbol table building traversal: * Recursively traverse the abstract syntax tree. * Pass the current scope as an inherited attribute. class SymbolTableBuilder { def buildSymbols(currentScope: SymbolTable, node: ASTNode) : Unit = { // pattern match over node -- you may want to split // into several functions if it gets huge... } } * On entry to a new scope, create a new SymbolTable and link it with the current scope as a parent. That becomes the new current Scope. * For classes, however, use the scope of the superclass as the parent. * On each declaration, try to insert its name and AST node into the current table. The table should enforce the declaration rules and throw an exception if they are violated. * Don't forget to add "this" to the scope for a method! * Each AST node will have a scope instance variable that gets set by this traversal function.