Hints for Implementing to*

A procedure definition,

(to <proc> (<var 1> <var 2>...<var N>) <stmt 1> <stmt 2>...<stmt M>)

is interpreted by creating a procedure object consisting of the following things:

Procedure objects are stored in a procedure a-list. The parameter list and body associated with a given procedure name can be retrieved from the procedure a-list using assoc when the procedure is called.

A procedure call,

(<proc> <expr 1> <expr 2>...<expr N>)

is interpreted by performing the following steps:

The environment stack can be popped at the right point by inserting a special command, e.g., return, into the list of statements to be interpreted. This command should be inserted immediately after <stmt M> and before the statements comprising the rest of the program. Note that the values of any parameters/local variables, (<var 1> <var 2> ... <var N>), referenced in <stmt 1> <stmt 2> ... <stmt M> are found by searching (using assoc) the top frame on the environment stack, i.e., ((<var 1> <val 1>) (<var 2> <val 2>)...(<var N> <val N>)).

Detailed Example

Let's look at how LOBO interprets the following program:

((to bar (y) (repeat 4 (forward y) (right 90) ) ) (to foo (x) (forward x) (bar 10) (forward (* 1.5 x)) ) (foo 20) )

After the (to bar (y) ...) and (to foo (x) ...) statements have been interpreted (by adding the procedure objects for bar and foo to the procedure a-list) the environment stack, the program stack, and the graphic accumulator look like this:

environment stack program stack graphic accumulator
() ((foo 20))

The procedure call to foo is interpreted by pushing a frame (containing the binding between foo's parameter x and its value 20) onto the environment stack and pushing a return statement and the statements comprising the body of foo onto the program stack:

environment stack program stack graphic accumulator
(((x 20))) ((forward x) (bar 10) (forward (* 1.5 x)) (return))

The (forward x) statement can now be interpreted (using the top frame of the environment stack to resolve the reference to x). The call to bar in the body of foo is now interpreted:

environment stack program stack graphic accumulator
(((x 20))) ((bar 10) (forward (* 1.5 x)) (return))

This is accomplished by pushing a frame (containing the binding between bar's parameter y and its value 10) onto the environment stack; and pushing a return statement and the repeat statement which comprises bar's body onto the program stack:

environment stack program stack graphic accumulator
(((y 10)) ((x 20))) ((repeat 4 (forward y) (right 90)) (return) (forward (* 1.5 x)) (return))

After interpreting the repeat statement (using the top frame of the environment stack to resolve the reference to y in its body),

environment stack program stack graphic accumulator
(((y 10)) ((x 20))) ((return) (forward (* 1.5 x)) (return))

the return statement is interpreted by popping the environment stack. The effect of the return statement is to restore the environment to what it was before bar was called:

environment stack program stack graphic accumulator
((((x 20))) ((forward (* 1.5 x)) (return))

Now the (forward (* 1.5 x)) statement in the body of foo is interpreted. Note that the reference to x is resolved by looking up its value in the top frame of the restored environment. Afterwards, the only thing which needs to be done is interpret the return statement by popping the environment stack. This implements the return from the call to foo:

environment stack program stack graphic accumulator
((((x 20))) ((return))

We are done!!

environment stack program stack graphic accumulator
() ()

* Because procedure calls are handled this way, LOBO is an example of a dynamically scoped language. Scheme is lexically scoped.