Dashboard > USF Computer Science 652 - Programming Languages > ... > Symbol tables > Object-oriented aggregrates and inheritance solution
  USF Computer Science 652 - Programming Languages Log In | Sign Up   View a printable version of the current page.  
  Object-oriented aggregrates and inheritance solution
Added by Terence Parr, last edited by Terence Parr on Feb 19, 2008  (view change)
Labels: 
(None)

You worked on the problem of nested scopes for variables and functions. Then you solved the problem of data aggregates including aggregates within aggregates. Now we must tackle the major complexity of symbol table management for class inheritance in object-oriented languages. Fortunately, you can view inheritance as a variant of nested aggregates.

Data members. First, consider what inheritance means in terms of data members (i.e., fields). Inheritance is just a fancy compile-time include in terms of data members so inheritance is just like a nested struct. For example,

// Java code
class A {
  int x;
}

class B extends A {
  int y;
}

class C extends A {
  int z;
}

class D extends B {
  int d;
}

could be implemented as:

// C code
struct A {
  int x;
};

struct B {
  struct A super;
  int y;
};

struct C {
  struct A super;
  int z;
};

struct D {
  struct B super;
  int d;
};

If you draw the C data scope relationship, you can see the tree embodied by the _enclosingScope_back pointers:

Notice that this mirrors the class hierarchy exactly.

Interestingly, in C you must explicitly identify the inherited element:

struct A a;
struct C c;
struct D d;

void foo() {
  a.x = 1;
  c.super.x = 1;
  d.super.super.x = 1;
}

whereas in Java, you can just reference x and the compiler will walk up the enclosing scopes in the class hierarchy to find x :

void foo() {
  A a = new A();
  C c = new C();
  D d = new D();
  a.x = 1;  // x is in A's scope
  c.x = 1;  // x is in C's enclosing scope
  d.x = 1;  // must look up 2 levels into A to find x
}

If you added a rule to C that it should automatically look into the enclosing structure definitions, you'd have added data member inheritance. By adding a simple "superclass" pointer to a data aggregate type specifier, you arrive at a class type symbol.

Symbol table classes. Here is a diagram showing the relationship between Symbol objects in an OO language symbol table:

where ScopedSymbol implements interface Scope :

public interface Scope {
    /** Does this scope have a name?  Call it getScopeName not getName so
     *  things are not confusing in SymbolWithScope subclasses that have a
     *  symbol name and a scope name.
     */
    public String getScopeName();

    /** What is the enclosing scope? */
    public Scope getEnclosingScope();

    /** Return a Map of all contained members */
    public Map<String, Symbol> getMembers();

    /** Look up name in this scope or in enclosing scope if not here */
    public Symbol resolve(String name);

    public VariableSymbol resolveVariable(String name);

    /** Look up a typename in this scope or in enclosing scope if not here.
     *  Load from disk if necessary.
     */  
    public ClassSymbol resolveType(String name);

    /** To look up a method, we need to know number of arguments for overloading
     *  so we need separate method to distinguish from resolve().
     *  Don't return variables or classes.
     */
    public MethodSymbol resolveMethod(String name, int numargs);
        
    /** Remove a name from this scope */
    public Symbol remove(String name);
        
    /** Define a symbol in the current scope */
    public Symbol define(String name, Symbol sym);
}

The resolve methods that look for a particular kind of thing such as method or type are useful because often you know the kind of thing you're looking for. If you're looking for a type T, you do not want to find it as a variable. A pure resolve method would find T regardless of the kind of thing it is. Depending on the semantics of your language, you might also need to segregate types and variables into separate spaces. Java allows a package and class name to be the same, for example.

Method members. Method inheritance seems more complicated than data member inheritance, but there is actually no difference if you ignore method overloading (same method name, different parameters). To look up method foo, look in the current class' scope. If not found, keep walking up the class hierarchy towards Object looking for it.

Interaction of executable and class inheritance scopes. When you see a reference to x in a Java method, you cannot just look into the enclosing class for that field. x might be a local. The rule is essentially: look in current execution scope or enclosing execution scope until you fall out of the method, then start with the class member scope. If the current class does not have x , then look in the enclosing class scope. Java does not have global variables nor methods so that works. [C++ has globals which I believe is the outer most scope that you check after failing to find a simple name like x in the class hierarchy]. You can imagine hooking the enclosingScope link of the outermost method execution scope (usually the parameter scope) to the enclosing class scope. Consider the following simple bit of Java:

class A {
  int x;
  int y;
}

class B extends A {
  public void foo(int i, int j) {
    A a;
    int z;
    x = 3;
  }
}

The outermost execution scope for foo is the parameter scope. So instead of "falling off the edge of the World", it considers B's member scope as its enclosing scope (which is enclosed by A 's scope which is enclosed by the default package scope):

(again, dashed line implies type pointer)

Lookup proceeds from local to parameter to class member scope B to class member scope A where x is found. If you define a local called x , then it hides the instance variable and lookup stops in the local scope. Variable/method lookup stops at the class level and does not go up to the package scope; only class lookup keeps going to that level if not found earlier.

Qualified names. When you use a qualified name like c.f, you are explicitly naming the path to the scope where the final identifier, f, is found. You are altering the starting point for symbol resolution. Normally, you start resolution at the current scope, but a qualified name tells you to start resolution in a particular class's scope. Breaking c.f down further though, you realize that you must start looking for c in the current scope to find out the class name so you can look for f there.

If there were no packages and no nested classes, qualified names would always look like classname.membername or objectname.membername. Life is not so simple in Java; see below more a bit more on the complexity of Java's names.

Tip. The reason I draw scopes as scopes with enclosing scope pointers rather than a simple stack is that all scoping can be viewed as a tree of scopes: global/parameter/local, aggregates, and class inheritance. The more you can normalize the scoping and type specifications, the simpler your symbol table management routines will be (many fewer special cases).

Java name resolution. In Java, a qualified name is either a package, type expression name, or method. Most of the time, syntax alone dictates what you are looking at. For example, you can always see a method call--the parenthesis give it away.

So most of the time you can know what the overall entity is. Unfortunately, this only means you know what the farthest right ID is. The real work comes in when you must figure out what the ID or IDs to the left of the "." mean. The name(s) could identify a local, a package, a type, a field etc... The Java language spec identifies a sequences of test you must perform to identify what you are looking at. It looks "hairier" than it is in practice.

This is all made worse, however, by the fact that you can reuse names in different spaces (package and type with same name, for example)!

Reference:Java Language Spec: Determining the Meaning of a Name

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.1 Build:#806 May 06, 2007) - Bug/feature request - Contact Administrators