This assignment consists of reading, understanding, and modifying the metacircular evaluator in Chapter 4 of Abelson and Sussman. The assignment has lots of pieces. Be sure you start early. Moreover, some of the things you need to do are more subtle than you may first think.
Copy file ~offner/cs450/hw5/s450.scm to your hw6 subdirectory. That file contains all the relevant code from A&S, with small changes so that it runs in UMB Scheme. The name of the evaluator has been changed to s450. The invoking function is now (s450), not (driver-loop). This is the file we have been discussing in class.
You will create two files (which I will collect):
% scheme s450.scm ==> (s450) s450==> ...or
% scheme ==> (load "s450.scm") ==> (s450) s450==> ...See how much of real Scheme is there. Write (in notes.txt) about what you found.
Rewrite xeval so that the handling of special forms is data directed. (This is Exercise 4.3 on page 374.) For each special form ( lambda, set!, cond, define, quote, ... ) there is an appropriate semantic action—a scheme procedure A&S have already written. If you put those actions in a lookup table keyed by the symbol representing the special form then the cond in xeval will be much shorter, with a single table lookup for almost all the cases now listed separately. And you will be able to add special forms to s450 without editing xeval, which will now look something like this (in pseudocode):
let action = (lookup-action (type-of expression))
if lookup succeeded
invoke action, passing it the expression and the environment
else cond ... ;; check for a few other types of expressions
You should write scheme functions like lookup-action and type-of to hide the lookup table implementation. (There are various ways to build a lookup table. You might want to review Section 3.3.3 (pages 266--271) for some ideas.)
To insert a new special form in the table write the procedure install-special-form and call it this way (for example):
==> (install-special-form 'set! (lambda (exp env) ... ) )
Since install-special-form is not a special form in scheme, its first argument will be evaluated, and therefore generally needs to be quoted, as in the example above, to prevent this. (Indeed, it is possible to call install-special-form with the first argument being an unquoted expression that evaluates to the symbol for the form you are installing.) The second parameter, action, should be a lambda expression (or something that evaluates to a lambda expression) that takes two parameters, exp and env: xeval in s450 will arrange to pass those parameters when action is called. Since install-special-form is not a special form, that lambda expression will be evaluated in the environment in which install-special-form is called before being passed to the body of install-special-form.
Since install-special-form is called only for its side effect, it returns no useful information. However, to avoid generating garbage, have it return the name of the special form being installed.
Your implementation should ensure that a new special form cannot be installed using the name of a variable that is already defined.
Similarly, a special form (that is already in the table) cannot be "reinstalled" using install-special-form.
Optional: If you can, arrange matters so you can call install-special-form both from the scheme prompt before starting the s450 interpreter and from inside the s450 interpreter, so that you can install a new special form in a running interpreter. If you do this, install-special-form must be called exactly the same way inside as out. (But I will only test calling it with a quoted first argument.) It's up to you to decide whether inside s450 install-special-form should be a special form or a primitive procedure.
Once you have written install-special-form you can save yourself lots of time typing at the s450==> prompt to test your code by installing load as a special form. The code you need is in ~offner/cs450/hw6/load.s450.
If you do this, please include the text of load.s450 in your file s450.scm. Don't (load "load.s450").
s450==> if
Special form: if
s450==> (define if 3)
or
s450==> (set! if 3)
| (defined? <symbol>) | returns #t iff the <symbol> is defined in the current environment. |
| (locally-defined? <symbol>) | returns #t iff the <symbol> is defined in the first frame of the current environment. |
| (make-unbound! <symbol>) | removes the <symbol> binding(s) from the current environment. |
| (locally-make-unbound! <symbol>) | remove the <symbol> binding from the first frame of the current environment. |
(These last two special forms constitute Exercise 4.13 on p. 380 with the issue raised there resolved.)
locally-make-unbound! should not report an error if the <symbol> is not locally bound to begin with. There is just nothing to do in that case.
Design a way to test these four new special forms. Hint: in order to create a situation in which the local and global versions of your new special forms should behave differently, you will have to build some environments with more than one frame. The only way to do that from the s450 prompt is by applying a procedure. (And see the note above about loading s450 test files.) For instance, you might try defining something like this within s450:
(define f
(lambda (a b)
(display (locally-defined? a))
(display (locally-defined? b))
(locally-make-unbound! a)
(locally-make-unbound! b)
(display (locally-defined? a))
(display (locally-defined? b))
)
)
and then executing (f 3 4). What should happen?
(install-primitive-procedure <name> <action>)
For instance,
(install-primitive-procedure 'car car)
This procedure should insert the definition of the primitive procedure directly in the global environment. Make sure that it is impossible to reuse the name of a special form for the name of a primitive procedure.
Note that as before, since install-primitive-procedure is not a special form, the name generally needs to be quoted.
As in the case of install-special-form, this procedure is called purely for its side effect, so it does not return a useful value. However, to avoid generating a lot of garbage, have it return the name of the primitive procedure being installed.
After you have done this, change the startup code so that all the primitive procedures are created by calls to install-primitive-procedure. And then be sure to delete the original code that defined the primitive procedures.
Then install + and some other scheme procedures of your choice as primitive procedures in s450. Note that a primitive procedure in s450 need not be primitive in the underlying scheme.