A frame is a table that associates symbols (or strings) with type-value pairs:
class Frame extends HashMap<String, Pair>
implements Map<String, Pair>,
Cloneable, Serializable {
Frame defEnv; // look here if searching
this frame fails
Frame callEnv; // restore this frame
after pop
void put(String key, Type tp, Object
val) {
put(key,
new Pair(tp, val));
}
Type getType(String key) {
Pair
p = get(key);
if (p == null) return null;
return
p.type;
}
Object getValue(String key) {
Pair
p = get(key);
if (p == null) return null;
return
p.value;
}
// etc.
}
Where:
class Pair implements Serializable {
private static final long
serialVersionUID = 42L;
Pair(Type t, Object val) { type = t;
value = val; }
Type type;
Object value;
}
The global environment is a frame:
class Context {
Frame globalEnv, currentFrame =
globalEnv;
void push(Frame f) {
f.callingEnv
= currentFrame;
currentFrame
= f;
}
void pop() {
currentFrame
= currentFrame.callingEnv;
}
Object getValue(String name) {
Frame
f = currentFrame;
Pair
result = f.get(name);
while
(result == null) {
f
= f.defEnv;
result
= f.get(name);
}
return
result.value;
}
// etc.
}
Calling a function or procedure creates a temporary frame that binds parameters to arguments. We call these types of frames activation frames. This frame becomes the current frame. When the call ends, the old current frame once again becomes the current frame.
See the bottom of:
http://www.cs.sjsu.edu/faculty/pearce/cs152/abstracts.htm
for a discussion of static vs. dynamic scoping.
To implement the static scope rule we need to turn our abstracts into closures:
class Closure extends Abstract {
Frame defEnv;
}
Procedure and Function should now extend Closure instead of Abstract.
When an abstract is created, the defEnv field is set to the current frame. For example:
Function f = new Function();
f.defEnv = context.currentFrame;
Note that if the current frame is the activation frame of some procedure or function call, then this frame won't disappear with the next garbage collection because there will still be an active chain of references to this frame from the global environment. For example:
-> [define x Number 10]
ok
-> [define addX (map Number (map Number Number))
(function
((x Number))
(function
((y Number)) (+ x y)))]
ok
-> [define add3 (map Number Number) (addX 3)]
ok
Now add3 is a symbol on the global environment that points to a closure. The defEnv field of the closure points to the activation record of the call to (addX 3). This frame contains the binding of x to 3. When we call add3 we get 8 instead of 18:
-> (add3 5)
8
This happens because even though the calling environment of the add3 call is the global environment, the defining environment is the activation record of the call to (addX 3). By searching defining frames instead of calling frames for non-locals, we find the binding x = 3 before we find the binding x = 10.
Classes, objects, and packages are also frames. See
http://www.cs.sjsu.edu/faculty/pearce/cs152/modules.htm
for a discussion. A class contains bindings of symbols to fields or methods (and their types). An object contains bindings of symbols to fields, which are a clone of the fields of the class that instantiated the object.
class ThetaClass extends Frame {
ThetaClass superClass;
ThetaObject newInstance() {
ThetaObject
result;
if (superClass != null) {
result
= new ThetaObject();
} else {
result
= superClass.newInstance();
}
result.defEnv
= this;
for(String
field: keySet()) {
Type
tp = getType(field);
if
(!(tp instanceof MapType)) {
result.put(field,
get(field));
}
}
return
result;
}
}
Where:
class ThetaObject extends Frame {
// etc.
}
Of course methods are closures that live in classes. We can treat method calls the same way we treat function and procedure calls if we think of the implicit parameter as the defining environment of the method. In this way the method will search first the object, then the class for non-local symbols. For example, upon seeing the call:
myObject.myMethod(a, b, c);
the interpreter first searches the myObject frame for myMethod. This search will fail since object frames only contain bindings of fields. Next the defEnv will be searched. This points to the class frame when myMethod presumably resides. Next, an activation frame for myMethod is created. The defEnv field points to myObject while the callEnv field points to context.currentFrame. This activation frame becomes the new current frame.