CSC 330 Assignment 6 -- A Lisp Interpreter in Lisp
In this assignment you are writing a simple interpreter for Lisp, in Lisp itself. As discussed in the Lecture notes and Lisp primer, the function name for the Lisp interpreter is "eval". To distinguish your version of eval from Lisp's, yours is named "xeval".
Given below is an outline for the xeval interpreter. All of the functions prefixed with "x" should behave as their Lisp counterparts of the same name behave. To get a working interpreter, convert all of the comments into code. Details of how to test and hand in your completed interpreter are given in the index of the test-files directory, a copy of which is attached.
The most important part of the assignment is figuring out how to use and lay out memory so that the interpreter operates properly. Since you are writing a functional version of the list interpreter, the memory is not a global variable, but rather a parameter to the xeval function. In order for memory to retain its values, it must also be a return value from the xeval function. Hence, memory is both an input and an output to and from xeval.
The xeval function also takes an expression to be evaluated and produces as output the value of that expression. Hence, xeval takes two inputs and produces two outputs. In Lisp, the way to produce two outputs is to return a list of two elements. Consider the following example call to xeval
This says to evaluate the expression '(xsetq x 10) with the memory '(), i.e., an empty memory. The return result from xeval for these inputs is the two-element list(xeval '(xsetq x 10) '())
The first element of the returned list is 10, which is the value produced by the xsetq. The second element of the returned list is an updated memory, with the value of x bound to 10.(10 ((x 10)))
The preceding example illustrates how global variables are bound in memory. The memory also has to hold activation records when functions are invoked. Hence, the layout of memory must be able to hold elements of a static pool and a runtime stack. Note that you do not need to represent a memory heap, since you'll rely on Lisp to take care of that.
There are a number of ways to lay out memory, and the exact details are left up to you. A simple and effective layout is to have a single list that combines both the static pool and the stack, put at opposite ends of the list. Global bindings for the static pool go on the front of the list, activation records go on the back of the list. When you look up a variable in this lay out, you start from the back end of the list, since you want to find local stack variables before global variables. I.e., the code for looking up a variable looks like this:
(assoc variable-name (reverse memory))
The sample memory dump shown in the test files uses this kind of two-sided memory layout. Note that you do not have to use this layout exactly in your implementation, you must only get the output values correct. Hence, your program must produce the same value outputs as those shown in the sample, but your memory dump does not have to look exactly the same as the sample.
The xeval outline and testing file index follow.
; ; Replace all of this top-level comment with a high-level description of your ; general approach to the solution. Also, add a brief explanatory comment ; above each function that you implement, describing your implementation. ; ; In the body of the outlined functions, replace all of the comment function ; calls with code. That is replace all of the expressions of the form ; "(comment -- ...)" with Lisp code that does what the comment says needs to be ; done. ; ; ; This macro is used to identify comments that you'll replace with code. ; There's nothing for you to do here but just leave this macro as is. ; (defmacro comment (&rest l) ()) ; ; Evaluate the given expr in the context of the given program memory. Return a ; two-element list consisting of the result of evaluation and a possibly ; updated memory. ; (defun xeval (expr memory) ; ; The body of xeval is a single conditional that checks the form of the ; input expr and handles it accordingly. ; (cond ; ; The special form ":dump" is for producing a memory dump for ; debugging purposes. There's nothing for you to do here. ; ( (eq expr ':dump) (terpri) (princ "MEMORY DUMP:") (pprint memory) (terpri) (list 'end dump memory) ) ; ; Handle an atomic expression input. ; ( (numberp expr) (comment -- return numbers as is) ) ( (stringp expr) (comment -- ditto for strings) ) ( (eq expr t) (comment -- ditto for t) ) ( (null expr) (comment -- and ditto for nil) ) ( (atom expr) (cond ( (comment -- check if the atom name is in memory) (comment -- and if so use its bound value) ) ( t (comment -- otherwise print an "Unbound Variable" error) ) ) ) ; ; All possibilities for an atomic expr have been checked to here. Now ; check for function calls. ; ( (atom (car expr)) (cond ( (eq (car expr) 'xquote) (comment -- return quotes as is) ) ( (eq (car expr) 'xsetq) (comment -- call eval-xsetq with appropriate args) ) ( (eq (car expr) 'xcond) (comment -- call eval-xcond with appropriate args) ) ( (eq (car expr) 'xdefun) (comment -- call eval-xdefun with appropriate args) ) ( (comment -- if memory value of (car expr) is a function body) (comment -- then call xapply to evaluate it) ) ( t (comment -- call eval-with-gcl for the rest) ) ) ) ( t (comment -- what if (car expr) is not an atom?) ) ) ) (defun eval-xsetq (atom-name value memory) (comment -- bind name to value in memory)) (defun eval-xcond (expr memory) (comment -- evaluate a conditional)) (defun eval-xdefun (fn-name formals fn-body memory) (comment -- add fn-name to along with its formal parms and body)) (defun xapply (fn-body formals actuals memory) (comment -- bind args and xeval fn-body)) (defun xbind (formals actuals memory) (comment -- extend the memory with new bindings (called from xapply))) (defun xeval-list (expr-list memory) (comment -- x-evaluate a list of exprs returning the value of the last (called from eval-xcond and xapply)) ) (defun eval-with-gcl (expr memory) (comment -- if expr is a function xevaluate each arg and then use gcl to eval. If expr is not a function send expr directly to gcl.) ) (defun xeval-list-list (expr-list memory) (comment -- x-evaluate a list of exprs returning a list of quoted values (called from eval-with-gcl)) ) (comment -- add additional auxiliary function you may need)
Description of Assignment 6 Testing Files
File | Description |
xeval-outline.l | This is the shell for the xeval function, exactly as it appears on the handout for assignment 6. Copy this file into a file named xeval.l and use it as the basis for your solution. |
xeval-tests.xl | This is the actual test data file that your completed version of xeval should read and evaluate. See below for further details on how to read it in. |
read-xeval-print.l | This is the utility file for testing your xeval function interactively. Use the function defined in this file to read interactive test data while you're debugging your solution. See the comments at the top of the file for further details. |
readfile-xeval-print.l | This is the utility for reading the test data file when you're ready to hand in the final result. See the comments at the top of the file for further details. |
xeval-test-demo | This is what your correct test output script should look like, except for the comments and the exact format of the memory dump. This file was generated by running a completed version of the assignment. Regarding the memory dump (the result of the :dump directive), the format of your memory may be different than the one that appears here, but the effects must be the same, such that all of the other output results are the same as those in this demo file. |