Clarification of a few issues

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Clarification of a few issues

ollieisamuppet
I'm hoping someone can help me out a bit with this.

I notice that in the proposed SymbolTable spec, "kindof", "typeof" and "varCount" all refer to the "current scope", however there's no mention that I can see of how the current scope should be known. For example, two of these functions simply take the variable name as a single argument, not passing the current scope along with them.

Should I take it from this that I should add a function to the SymbolTable module such as:

setScope(scope currentScope)

So that each respective function in the class can check the current scope before returning a value, with this function called by the compilationEngine module?


Also, it's recommended that you first replace the XML output of the identifiers with information about each variable identifier. However, this seems to suggest that when we hit an identifier, we already know what it belongs to.

If I come across an identifier "myVar", I can't know its scope and type without looking back up the tree. For example, in "static int nAccounts", I can't add nAccounts to the symbolTable until I hit the identifier, but by that point I've moved past "static" and "int", so how am I supposed to know these things?

As the tree is not explicitly stored, how are we supposed to get this information in order to fill in the symbol table or even just look up a value in the correct scope?

Am I missing something really obvious here?

Thanks in advance.
Reply | Threaded
Open this post in threaded view
|

Re: Clarification of a few issues

ivant
There are three kinds of scope:
  • Global -- the names of all classes (just one scope)
  • Class -- the names of all class variables, functions and methods (one for each class)
  • Subroutine -- the names of variables local to the method (one for each subroutine, and "points" or is "contained" in the respective class scope)

The book suggests to simplify that, by ignoring the global scope as well as the function and method names from the class scope. This means that every unknown symbol should be treated as a subroutine call.

Now, we have just two scopes: class and subroutine. The class scope is needed for as long as you compile the current class, so when you start a new class, just create a new symbol table.

Similarly, the subroutine scope is needed while you compile the subroutine. When you start a new one you call "startSubroutine", which creates a new subroutine scope.

Hope that helps.
Reply | Threaded
Open this post in threaded view
|

Re: Clarification of a few issues

cadet1620
Administrator
In reply to this post by ollieisamuppet
ollieisamuppet wrote
Should I take it from this that I should add a function to the SymbolTable module such as:

setScope(scope currentScope)

So that each respective function in the class can check the current scope before returning a value, with this function called by the compilationEngine module?
As Ivan said, startSubroutine() is your setScope() function. Also note the that the "kind" argument to SymbolTable.define() identifies which scope the symbol belongs to: field and static are class scope; argument and var are subroutine scope.
Also, it's recommended that you first replace the XML output of the identifiers with information about each variable identifier. However, this seems to suggest that when we hit an identifier, we already know what it belongs to.
I recommend that you leave you XML writing code intact and make the XML file output optional. In my compiler, XML writing is all in one class that gets the output filename in its constructor.  If the filename is null, then all the xml.Xxx() calls do nothing.  My compiler has a "-x" command line switch that enables the XML output.

I found it very handy for debugging my code generator to be able to tell exactly where things went bad.
If I come across an identifier "myVar", I can't know its scope and type without looking back up the tree. For example, in "static int nAccounts", I can't add nAccounts to the symbolTable until I hit the identifier, but by that point I've moved past "static" and "int", so how am I supposed to know these things?
Yes, as you parse, you need to save information about what you parse so that you can use it to generate code.

Here's a snippet from my Jack compiler written in Jack. Note the local variables "kind" and "type" that store that info so that it can be passed to define().
    /**
     *  Compiles a static declaration or a field declaration.
     *  <class-var-dec> :=
     *      ('static' | 'field') <type> <var-name> (',' <var-name>)* ';'
     *
     *  ENTRY: Tokenizer positioned on the initial keyword.
     *  EXIT:  Tokenizer positioned after the final ';'.
     *
     *  Returns false to abort compilation.
     */
        method boolean compileClassVarDec() {
            var boolean ok;
            var int kind;
    
            do xml.start("classVarDec");
    
            do xml.writeToken(token);
            let ok = _expectKeywordIn(JackTokenizer.KW_STATIC(),
                                      JackTokenizer.KW_FIELD(), 0, 0, 0);
            if (ok) {
                if (token.keyword() = JackTokenizer.KW_STATIC()) {
                    let kind = SymbolTable.STATIC(); }
                else {
                    let kind = SymbolTable.FIELD(); }
                do token.advance();
    
                let ok = CompileKindDec(kind);
            }
            if (ok) {
                do xml.end();
            }
            return ok;
        }
    
    /**
     *  Compiles a 'kind' declaration.
     *  <dec-list> :=
     *      <type> <var-name> (',' <var-name>)* ';'
     *
     *  ENTRY: Tokenizer positioned on <type>.
     *  'kind' is the kind of identifier being compiled:
     *      SymbolTable.STATIC(), FIELD() or VAR().
     *  EXIT:  Tokenizer positioned after the final ';'.
     *
     *  Returns false to abort compilation.
     */
        method boolean CompileKindDec(int kind) {
            var boolean ok, break;
            var String type;    // = null
    
            let ok = true;
            do xml.writeToken(token);
            if (_isKeywordIn(JackTokenizer.KW_INT(),
                             JackTokenizer.KW_CHAR(),
                             JackTokenizer.KW_BOOLEAN(), 0, 0)) {
                let type = StrUtil.duplicate(token.keywordString());
            } else {
                let ok = _expectIdentifier();
                if (ok) {
                    let type = StrUtil.duplicate(token.identifier());
                }
            }
    // type CONDITIONALLY allocated
            if (ok) {
                do token.advance();
    ...
            if (~(type = null)) {
                do type.dispose(); }
    // type deallocated
            if (ok) {
                let ok = _expectSymbol(59); // ';'
            } if (ok) {
                do token.advance();
            }
            return ok;
        }
Note that the somewhat tortured "if (Ok)" structure is to deal with the fact that Jack doesn't have and sort of try...finally which makes it hard to ensure all allocated objects get deallocated.

--Mark
Reply | Threaded
Open this post in threaded view
|

Re: Clarification of a few issues

ollieisamuppet
Thank you both, I understand it more clearly now.

It's been a fairly long journey, but I'm almost there