Project 5: Interpreting NestFunJay

The goal of this project is to implement nested functions in the context of the Jay family languages.

1. Set up

Copy and untar the given code for this project.

cp ~tvandrun/Public/cs365/proj5.tar .
tar xvf proj5.tar

2. Understanding nested functions

The new language NestFunJay is like FunJay (with normal, pass-by-value semantics) but extended with nested functions. A nested function can use (both read and modify) the local variables (including formal parameters) of the enclosing scope. Notice in the grammar that nested functions can be declared in any block, but after the local variables.

In order for the nested function to interact with variables in the enclosing scope, the stack frame for the activation of the nested function must include a static link, that is a pointer to the beginning of the stack frame of the enclosing function. When a variable from the enclosing scope is used in a nested function, the interpreter must follow the static link back to the activation record of the enclosing function and read from or write to the appropriate offset within that stack frame.

Since functions may be nested arbitrarily deeply (ie, a nested function may have its own nested functions), we may need to traverse several static links to get to the right memory location.

Thus, for each variable use (either a variable as an expression or a variable as the lefthand side of an assignment), we need to know two things: (1) How many static links must we follow to get to the right stack frame (if the variable is local, then we follow 0 static links), and (2) what is the offset of this variable within the right stack frame?

3. Details of the given code

There are a few ways in which the code for NestFunJay differs from how we did things with FunJay. Note the following:

Statically computed information. In previous Jay-family languages, the interpreter visitor needed a whole bunch of "maps" computed ahead of time (mostly by the type checker) to do what it was doing. Most recently, the FunJay interpreters needed a printMap, a frameSizeMap, a paramMap, and a bodymap. NestFunJay would have needed even more.

Notice that these maps were basically externally-stored instance variables. The printMap associated each Print statement with a type. That was almost like having each Print statement have an instance variable indicating which type it use to display the information.

To reduce the proliferation of these maps, in NestFunJay such information actually is stored in instance variables. So the class funjay.abssyntree.Print has a public instance variable type which the type checker sets to the appropriate type and which the interpreter reads.

(Yes, I said public instance variable. Do you want to mess with perfunctory getter and setter methods for something like this? Me neither. You're old enough to use a public instance variable now and then.)

Local variables in the stack frame. In FunJay, variables declared in blocks within a procedure were handled by allowing the stack frame to grow and shrink as we entered and exited scope. In NestFunJay, we will give every local variable its own slot in the stack for the duration of the activation. The typechecker will compute how many slots are needed. The class nestfunjay.abssyntree.Procedure has a public instance variable frameSize which the interpreter will use when pushing a frame.

The disappearance of gamma. In FunJay we had structures equivalent to gamma from the formal semantics which allowed us to look up the memory address of a variable at runtime. However, that can be determined statically.

The class funjay.abssyntree.Variable has public instance variables depth and offset. Remember, this means every occurrence of a variable in the code has these. The former indicates how many static links need to be followed to find the right stack frame for this variable (consistent with this, 0 means the variable is local; as a special (hackish) case, -1 means the variable is global and the offset is in fact an absolute address).

The type environment. In JayJay, the type environment was a stack, each layer in the stack representing a nesting level for blocks---that is, when we entered a new block, we pushed a scope. In this type checker we handle blocks simply by adding and removing variables. The type environment is still a stack, but the layers in the stack represent the nesting of procedures. Every time we enter a nested procedure, we push a new scope in the stack. All of this is handled for you in the class nestfunjay.astvisitor.TypeEnv, summarized here:

public class nestfunjay.astvisitor.TypeEnv extends java.lang.Object{
    public nestfunjay.astvisitor.TypeEnv(java.lang.String, nestfunjay.astvisitor.TypeEnv);

    // add a local variable of given type to the current scope 
    public void addLocal(java.lang.String, jay.Type);

    // remove a local variable from the current scope
    public void removeLocal(java.lang.String);

    // does the current scope already contain a variable with this name?
    public boolean containsVar(java.lang.String);

    // get the type of a variable
    public jay.Type getType(java.lang.String);

    // get the "depth" of this variable---how many scope levels down
    // to find it. This is the number of static links we need to follow.
    // -1 means global 
    public int getVarDepth(java.lang.String);

    // get the offset of this variable within its frame (absolute address for globals)
    public int getVarOffset(java.lang.String);

    // does the current scope already contain a procedure with this name?
    public boolean containsProc(java.lang.String);

    // add a procedure with given name, return type, formal param types, and body
    public void putProc(java.lang.String, jay.Type, java.util.ArrayList, nestfunjay.abssyntree.Procedure);

    // get the return type for the procedure of this name
    public jay.Type getRetType(java.lang.String);

    // get the formal pram types for the procedure of this name
    public java.util.ArrayList getParamTypes(java.lang.String);

    // get the body for the procedure of this name
    public nestfunjay.abssyntree.Procedure getBody(java.lang.String);

    // get the nesting level of the procedure of this name---
    // how far back is the nesting level? 
    // 0 means it is declared in this scope; 1 means it is declared in the enclosing
    // scope, etc; -1 means it has global scope
    public int getProcDepth(java.lang.String);

    // how many locals (including formal parameters) does this scope have?
    // needed to compute stack frame size
    public int getLocalSize();

    // get the type environment for the enclosing scope
    public nestfunjay.astvisitor.TypeEnv getLower();
}

4. Your task

Your changes to the code will be spread between TypeCheckerVisitor and InterpreterVisitor. You will not need to change anything about the reporting of type errors. Your changes to TypeCheckerVisitor will involve setting the instance variables of the relevant abssyntree classes so that information is available for the interpreter.

Your concern is variable and calls. When the interpreter hits a variable, you need to make sure it finds the right memory location. When the interpreter hits a call, you need to set up the stack frame correctly.

Here are the methods you need to finish, along with the number of lines it took in my solution (including closing curlies):

Doesn't sound so bad when it's put that way, right? One important thing to note about all this. Although this is a challenging project, the challenge is almost all in understanding the set up of the language and the parts of the implementation you're given. Once you've done that, there is almost no original problem-solving required to finish it. Understand what the public instance variables in the abssyntree classes mean, and the remaining code will be "obvious".

5. Turn in

Copy your TypeCheckerVisitor.java and InterpreterVisitor.java to

/cslab.all/ubuntu/cs365/turnin/xxxxxx/proj5

where "xxxxxx" is [alisa|andrew|becca|cheney|daniel|drew|johncharles|kendall|turk]. I will grade your project by running it against a collection of test files.

DUE: Friday, Mar 2, 5:00 pm.

Note: Since this is the last day of A quad, special permission is required for using your late days for this project.


Thomas VanDrunen
Last modified: Wed Feb 15 16:36:14 CST 2012