CS170
Project Assignment #3
DUE : Thursday 11/19.
If you have not already done so, be sure that your program evaluates all levels
of function calls. It's a matter of having your function "eval" call itself at the correct times. In addition, for the rest of the project, be sure to continue to always evaluate arguments before evaluating a function. For example, (car (cdr '(a b c))) should return b, rather than cdr. (Of course, (car '(cdr (a b c))) WILL return cdr because the quote should continue to suppress evaluation.)
This assignment sets up the necessary preliminaries for function definition. We will concentrate here on simply defining symbols to have values, which is much the same as defining constant functions. This is analogous to assigning values to variables in imperative languages.
There are two parts to this.
Some New Functions
Add the following built-in Scheme functions to your interpreter. Some comments
are added for functions that you're not familiar with or that need some
clarification:
- append. This takes two arguments and returns a list that appends the second list to the end of the first. For example, (append '(a b c) '(d e f)) returns the list (a b c d e f). Note that append should not do any "surgery" on the lists, that is, it should not affect its arguments. Use the elegant recursive algorithm given in class.
- null?.
- equal?. This has two arguments, both s-expressions, and it returns #t if its arguments evaluate to the same value (in the case of lists, the structure returned
must be identical) and #f otherwise. This is trickier than it may sound, because it requires a simultaneous navigation of both s-expressions. Recursion is a must! For example,
(equal? '(a b) '(a b)) returns #t
(equal? '(a b) '(a (a b))) returns #f
- assoc. This has two arguments, the first of which is a symbol, the second a so-called "association list" (to be explained in class). It returns the pair associated with the symbol, and #f if the symbol is not the first element of any pair. For example,
(assoc 'joan '((john smith) (joan doe) (marcia law))) returns (joan doe)
(assoc 'john '((john smith) (joan doe) (marcia law))) returns (john smith)
(assoc 'jean '((john smith) (joan doe) (marcia law))) returns #f
- cond. The multiple-alternative conditional.
cond is, as you know, used for flow of control in defining Scheme functions, so it is extremely important that it works properly. (If you want to add the if
construct as well, please do.)
Binding of Values to Symbols
The Scheme syntax for binding a value to a symbol is,
(define < symbol-name > < value >)
For example, if you type the following into your interpreter,
(define name '(joan q public))
the value (joan q public) is now bound to the symbol name. Thus if you subsequently type the following into your interpreter,
name
the interpreter should respond with the list (joan q public). You have bound the "value" (joan q public) to the "variable" name. It is therefore now possible for the interpreter to evaluate symbols that have been bound to values. Eval's
main new job in this assignment is to evaluate symbols.
This can be implemented in a number of ways. The simplest one is probably the following. Define a global association list in your program that contains a list of symbol-value pairs. It should be an empty list when your program starts. We will refer to this list as the referencing environment. Here's how it is used:
(1) When a definition is made using define, we will ADD to the referencing environment. For example, assuming the referencing environment was (), the Scheme form,
(define name '(joan q public))
should have the following effect. The interpreter makes a pair out of the symbol name and the list (joan q public). The pair would look like (name (joan q public)). It then "conses" this onto the referencing environment, and the result becomes the new referencing environment. That is, the new referencing environment would be in this case ((name (joan q public))). Note that all of this could be regarded as a "side effect" to the "function call" to define. The "function" define should also return a value, namely the name it is defining. In this case, define should return the symbol name. The important thing, though, is its side effect that changes the referencing environment.
When subsequent definitions are made, MORE things are added to the referencing environment. Suppose we make the definition,
(define clark (cdr '(university main street worcester)))
Then the new referencing environment should be,
((clark (main street worcester)) (name (joan q public)))
(2) When eval comes across a symbol, it should try to "evaluate" it. How? It can use the assoc function with the given atom as the first argument and the referencing environment as the second. This gives the pair binding the name to a value. Then eval should return the value belonging to that name. Thus, as mentioned above, given the previous definitions,
name
evaluates to
(joan q public)
and
clark
evaluates to
(main street worcester)
But also, symbols like name and clark can appear in expressions. For example,
(cons (car (cdr (cdr name))) (cdr clark))
evaluates to
(public street worcester)
NOTE: There's not a lot of code to write here! For the referencing environment,
the two main necessary tools are: cons, for implementing define, and assoc, for evaluating symbols.
Testing: Here is a file to test your code.
If you run it with the command "./scheme < input.txt",
here is what you should expect (something
a lot like it, anyway.
Back to CS170 Assignments.