begin, if, cond
If you are still working on Part 3, please let me know if you need help. It is important that you don't fall behind.
To get the code for Part 4 and some wrap-up tests from Part 3, you will first need to run the command update37. Once you have done so, you will have created a new directory (cs37/labs/i-4) which has the new i-tests and interpreter pieces.
To join your old code and tests (from cs37/labs/i-3) with the new code and tests in cs37/labs/i-4, follow these steps:
The wrap-up tests for Part 3 that are provided for you simulate the following interaction with the interpreter. Your interpreter should successfully run the tests in i-tests.rkt, but it is strongly recommended that you type some or all of the code in by hand to verify that the interactions are working properly. In particular, i-tests.rkt can not test exit and exit! properly, so you must do this by hand.
> (repl) INTERPRETER> (define x (cons 1 (cons 2 null))) x INTERPRETER> x (1 2) INTERPRETER> (null? x) #f INTERPRETER> (null? (cdr (cdr x))) #t INTERPRETER> (= (car x) (- (car (cdr x)) 1)) #t INTERPRETER> (define y (cons (car x) x)) y INTERPRETER> y (1 1 2) INTERPRETER> (set! x 5) x INTERPRETER> (set! y (+ x 5)) y INTERPRETER> y 10 INTERPRETER> (* x y) 50 INTERPRETER> exit INTERPRETER done. > (repl) INTERPRETER> (set! x 10) x INTERPRETER> x 10 INTERPRETER> exit! INTERPRETER done. > (repl) INTERPRETER> (set! x 10) set! cannot set undefined identifier: x
If your code passes all the tests, you should turn in your completed version of Part 3 using handin37.
Recall that Racket's rule of evaluation states that all the arguments of a procedure must be evaluated before procedure application. We have seen in class that this rule cannot work for a set of "special forms" that includes define and set!, which you have already implemented in your interpreter.
Special forms are "special" because your interpreter must handle each of these forms differently. For example, in a define expression, such as (define x 5), the first argument, x, is never evaluated. An if expression, such as (if (< x y) x y), always evaluates its first argument, but will only evaluate one of its remaining two. Furthermore, since define, if, cond, etc. are not primitives, they have no binding in the environment. Therefore, with special forms, the operator is never evaluated.
On the other hand, all the primitive procedure applications (and, as we will see in Part 5, also lambda-procedure applications) are handled the same way by the your interpreter: The operator and all of the operands are evaluated, then the evaluated operator is applied to the evaluated operands. For example, in the expression (= x y), the operator =, and the operands x and y are all evaluated.
Extend your interpreter so that it can handle the special form begin. Though we have not used the begin special form, it is a relatively straightforward one to understand. The syntax of a begin expression is as follows:
(begin exp1 exp2 ... expn)
The begin expression evaluates each of the expressions (exp1 through expn) in order and returns the value of the last expression. (See the Racket documentation for more information on begin if you do not understand how it works.) If there are no expressions except the keyword begin, you should return void. (For now, your interpreter will print out "#<void>"; later, you will modify your interpreter so that it prints out nothing if the result is void.) For example:
INTERPRETER> (begin (define x 5) (+ x 3)) 8 INTERPRETER> (begin (display "x + 3 = ") (display (+ x 3)) (newline) 'done) x + 3 = 8 done INTERPRETER> (begin) ;this returns void #<void> INTERPRETER> (begin (set! x (* x 2)) x) 10 INTERPRETER> x 10To get the interpreter to return void, use the Racket primitive function void which you call by simply writing (void). You may also wish to add void to the list of primitive functions your interpreter supports.
To implement begin in your interpreter, you should continue using the same style of abstraction we have been using. Please reference the summary sheet so that the functions you write have the same names as those described.
Next, we will allow our interpreter to handle the special form if. The syntax of an if expression is:
(if test-expression then-expression else-expression)To evaluate an if expression, first the test-expression is evaluated. If the result is true, the then-expression is evaluated and returned as the result of the if expression. Otherwise, the else-expression is evaluated and returned. Important note: In Racket, anything other than #f is considered true. For example:
INTERPRETER> (if (cons 1 2) 'true 'false) true INTERPRETER> (if 'not-false #t #f) #t INTERPRETER> (if (< 5 4) 'less 'not-less) not-lessImplement all of the necessary procedures to handle if in your interpreter.
Your interpreter should now pass the following tests:
INTERPRETER> (define lst (cons 1 (cons 2 null))) lst INTERPRETER> (if (< (car lst) (car (cdr lst))) (car lst) (car (cdr lst))) 1 INTERPRETER> (* 5 (if (= (+ 3 3) (- 8 2)) 10 20)) 50In Racket, it is not possible to write an if expression that has a then-expression but has no else-expression. However, in our version of Racket, we will allow this to happen. Modify your implementation so that it can handle if expressions of this form. You should return #<void> (the result of calling (void)) if the test-expression evaluates as #f and there is no else-expression. For example:
INTERPRETER> (define x 5) x INTERPRETER> (if (= x 6) (+ x 1)) ;this returns void #<void> INTERPRETER> (if (= x 5) (+ x 1)) 6Important note: Of course you are allowed to use Racket's if to help define if in your interpreter! (The same is true for cond below.)
Reference the summary sheet for the names of the functions you should implement.
For the last portion of Part 4, we will add cond to our interpreter. Recall that the syntax of a cond is:
(cond (test1 exp1 exp2 ... expn) (test2 exp1 exp2 ... expn) ... (testn exp1 exp2 ... expn))To evaluate a cond expression, your interpreter should work as follows:
Here are some examples:
INTERPRETER> (cond ((= 3 5) "this is false" "so we skip this one") ((= 3 3) "this is true" "so we return this message") ((= 5 5) "this is also true" "but since an earlier test was true" "this never gets returned")) "so we return this message" INTERPRETER> (cond (1 "since 1 is not #f, this evaluates to be true") (else "and once again, we never get here")) "since 1 is not #f, this evaluates to be true" INTERPRETER> (cond ((< (+ 1 1) 0) 'nope) ((< 1 0) 'still-no) ((= 4 5) 'no-matches)) ;this returns void #<void> INTERPRETER> (cond ((< (+ 1 1) 0) 'nope) ((< 1 0) 'still-no) (else 'matched-else)) matched-else INTERPRETER> (define x 5) x INTERPRETER> (cond ((set! x (+ x 1)) x) (else "Note: be sure x is 6, not 7")) 6 INTERPRETER> x 6 INTERPRETER> (cond ((+ 1 1))) 2 INTERPRETER> (cond) ; returns void #<void>Implement all of the procedures necessary to handle cond in your interpreter. (See the summary sheet.) Be careful with first-cond-exp and rest-of-cond-exps and when using eval-begin in your implementation of eval-cond: the keywords "cond" and "begin" at the start of these expressions can cause you some difficulty, so be sure you check those functions if you seem to be having unexplained difficulties!