Lisp Interpreter in Lisp
In this assignment we will investigate how the Lisp evaluation function eval could itself be written in Lisp. In order to not get confused with the built-in function named eval, we will call your function myEval. We will not do all the features of eval, in particular we will not include user defined functions. But we will do enough that you will see how an interpreter can be written in its own language.
Our development of myEval will proceed in four major steps. As I describe the steps, I will give you some expressions that your interpreter should be able to handle after each step. The best way to solve this assignment is to create a file, named progThree, that will contain your solution as far as you have developed it. You can then load and reload the assignment as you work on it.
You are going to be creating a function named myEval, which is (nearly) equivalent to the Lisp function named eval. This function will take one argument, and return the result of evaluating the argument. Ultimately, for example, if you enter:
> (myEval `(+ 2 (* 3 4))) 14you should get back the result 14, which which is two more than the product of 3 and 4.
At the highest level the function divides into two cases; lists, and everything else.
Step 1. Your first version of myEval has as its body an if expression that tests if the argument is a list. You can use listp to do the test. For the moment, if it is a list return nil, otherwise return the argument. After writing this your function should correctly handle the following:
> (myEval `3) 3 > (myEval `t) t > (myEval `nil) nil > (myEval 'A) Astep 2. Next we will handle the evaluation of lists. The body of the list case will be a cond expression. We will be adding clauses to this as we go along. The first function we will handle is the special build-in function quote. Recall that this is the function that delays the evaluation of its argument. So the quote function simply returns its first argument.
To add quote to what you have done, you need to check that the first element of the list argument is the literal symbol quote, and if so then return the 2nd element of the list. Be careful. Think about where in the list the quote symbol is, and where in the list the first argument is. (By the way, there is a useful shorthand for combined car's and cdrs, the function caar is the car of the car, the function cadr is the car of the cdr, and so on).
After you have this you should be able to handle the following:
> (myEval `(quote 3)) 3 > (myEval `(quote (a b c))) (A B C)
We will create myApply in the next section, but so that you have a way of testing your program, let us make a dummy version of myApply that just returns its argument:
(defun myApply (fun lst) lst)In order to evaluate a list of arguments, we can use the function myMap you created in assignment 2. Make sure you understand how, if you have a list of unevaluated arguments, you can myMap the myEval function in order to create a list of evaluated arguments. Using this technique, extend the cond statement in your function myEval so that for the last case it strips off a function name, then passes the function name and the list of evaluated arguments to apply.
(if (listp expr) (cond ... (t (myApply (car expr) (myMap #`myEval (cdr expr)))) ) expr) ; the else part of the ifUsing the dummy version of myApply you should be able to generate the following results:
>(myEval `(+ 2 3)) (2 3) >(myEval `(car (quote (a b c)))) ((A B C))The last item is, of course, not correct, but it shows that the arguments have been evaluated. This is a problem we will now fix.
Write this cond function and include the following built-in operations:
At the very end of your conditional, if nothing else matches return the value nil. (The real lisp interpreter trips into the debugger at this point).
The real lisp system has many more built-in operations, of course. But these are sufficient for demonstration purposes. You can add more if are so inclined, but these are the only ones we will use in the grading script.
With your version of apply, you should be able to execute the following:
> (myEval `(+ 2 3)) 5 > (myEval `(+ 2 3 4)) 9 > (myEval `(car (quote (a b c)))) A > (myEval `(numberp (quote (a b c)))) nil (myEval `(+ 2 (print 3))) 3 5
Having written apply and eval, we can now go back and add in some more special forms, namely the boolean operators and and or, and the conditional statement cond.
First we handle and and or. Remember that these use short circuit evaluation. This means that they take two arguments (the real Lisp functions take any number of arguments, but we will handle only two) and only evaluate the second one if the result cannot be determined by the first argument.
Both functions work the same way. Evaluate the first argument. If the results can be determined by the first argument, then return it. Otherwise evaluate the second argument, and return the results.
A subtle question is; in a functional language how can you tell if an argument has or has not been evaluated. (This is philosophically related to the age old question -- if a tree falls in a forest and nobody is there to hear it, does it make any sound?). Well, we can use the one non-functional feature that you have implemented, namely the function print, to see this. If you surround an argument with print and you don't see it on the output, then you know it was not evaluated.
Under what conditions can you determine the result of an and looking at only the first argument? What about or? What do you need to do if the results are not determined? Armed with this information, write the statements that evaluate these two special forms. Here are some test cases:
> (myEval `(and t t)) T > (myEval `(and (< 2 4) (print (< 3 4)))) T T > (myEval `(and (< 4 2) (print (< 3 4)))) NIL > (myEval `(or (< 2 4) (print (< 3 4)))) T
The test cases will only use and and or as binary functions, that is, as functions that take exactly two arguments. In the real Lisp system these functions can take any number of arguments, including zero. If you want to impress the TA and the professor, you can write your functions to handle this case. (We will see it when we look at your code). If you do this, however, make sure you handle the binary case correctly.
Put the code to handle and and or into your version of myEval, handling it right before you invoke the general case where you call myApply.
The cond statement, you will recall, has an indefinite number of arguments. Each argument is assumed to be a pair, the first element a predicate and the second an expression. It examines each element in turn. If the predicate is true it returns the evaluation of the second argument. If the predicate is false, it looks at the rest of the list. If no element has a predicate that is true, then the function itself returns the empty list.
This is easily implemented in a recursive fashion, which we will call evalCond. The function evalCond will take a cond expression where the cond take has been stripped off, leaving only a list of predicate and expression pairs. This is a recursive function.
What is the base case? The empty list. What do you want to do in the base case? Return the empty list.
Now what about the induction case. Assuming you have a list that is not empty, what do you want to do? You want to call myEval on the predicate (which is found where? be careful here.). If myEval returns true then you want to call myEval on the expression part (again, which is found where? Be careful).
What if the evaluation of the predicate return false? What do you want to do then?
Adding cond to your function myEval right before the myApply case (that is, after you have handled numbers and quote, t and nil, but before you reach the case where you call myApply). Now you should be able to handle the following:
>(myEval `(cond ((< 2 3) 7) (t 8))) 7 (myEval `(cond ((< (car (quote (2 4))) 3) (+ 3 4)) (t 8))) 7 (myEval `(cond ((< 4 3) 7) ((< 8 5) 4))) nilThat`s it. This is the version of the assignment you are to hand in.
Hand in a file, named progThree, that contains the definitions for the functions myEval, myApply, and evalCond, and any other function definitions you require, such as myMap, and the definition of the recursive function sum used in the evaluation of the addition instruction. Your file, of course, should also have some identifying comments (but not your student ID number!).
What have we left out? Really not much. There are quite a few more built-in operations, and we have left out the functions that bind a name to a value, such as defun and set. The latter require one more data structure (to keep the name/value associations) and writing this data structure cannot be done in a functional fashion, so and we have omitted them. Still, it is pretty amazing how small a lisp interpreter in lisp really is.