Discussion 12 The MCE (solutions) ;;;;METACIRCULAR EVALUATOR FROM CHAPTER 4 (SECTIONS 4.1.1-4.1.4) of ;;;; STRUCTURE AND INTERPRETATION OF COMPUTER PROGRAMS ;;;from section 4.1.4 -- must precede def of metacircular apply (define apply-in-underlying-scheme apply) (define (mc-eval exp env) (cond ((self-evaluating? exp) exp) ((variable? exp) (lookup-variable-value exp env)) ((quoted? exp) (text-of-quotation exp)) ((assignment? exp) (eval-assignment exp env)) ((definition? exp) (eval-definition exp env)) ((lambda? exp) (make-procedure (lambda-parameters exp) (lambda-body exp) env)) ((cond? exp) (mc-eval (cond->if exp) env)) ((MAKE-FEXPR? EXP) (EVAL-MAKE-FEXPR EXP ENV)) ((application? exp) (LET ((OP (MC-EVAL (OPERATOR EXP) ENV))) (IF (FEXPR? OP) (MC-APPLY (FEXPR-PROC OP) (LIST EXP ENV)) (mc-apply op (list-of-values (operands exp) env)) ))) (else (error "Unknown expression type -- EVAL" exp)))) (define (mc-apply procedure arguments) (cond ((primitive-procedure? procedure) (apply-primitive-procedure procedure arguments)) ((compound-procedure? procedure) (eval-sequence (procedure-body procedure) (extend-environment (procedure-parameters procedure) arguments (procedure-environment procedure)))) (else (error "Unknown procedure type -- APPLY" procedure)))) (define (list-of-values exps env) (if (no-operands? exps) '() (cons (mc-eval (first-operand exps) env) (list-of-values (rest-operands exps) env)))) (define (eval-sequence exps env) (cond ((last-exp? exps) (mc-eval (first-exp exps) env)) (else (mc-eval (first-exp exps) env) (eval-sequence (rest-exps exps) env))))
(define (eval-definition exp env) (define-variable! (definition-variable exp) (mc-eval (definition-value exp) env) env) 'ok) (define (true? x) (not (eq? x false))) (define (false? x) (eq? x false)) (define (make-procedure parameters body env) (list 'procedure parameters body env)) (define (make-frame variables values) (cons variables values)) (define (extend-environment vars vals base-env) (cons (make-frame vars vals) base-env) (define (primitive-procedure? proc) (tagged-list? proc 'primitive)) (define (primitive-implementation proc) (cadr proc)) (define (driver-loop) (prompt-for-input input-prompt) (let ((input (read))) (let ((output (mc-eval input the-global-environment))) (announce-output output-prompt) (user-print output))) (driver-loop)) (define the-global-environment '()) (define (mce) (set! the-global-environment (setup-environment)) (driver-loop)) (DEFINE (MAKE-FEXPR? EXP) (TAGGED-LIST? EXP 'MAKE-FEXPR) )) (DEFINE (EVAL-MAKE-FEXPR EXP ENV) (LIST 'FEXPR EXP ENV)) (DEFINE (FEXPR? OP) (TAGGED-LIST? EXP FEXPR)) (DEFINE (FEXPR-PROC OP) (MC-EVAL (CAR (CADDR (CADR OP))) ENV) )
QUESTIONS 1. Recall that mceval.scm tests true or false using the true? and false? procedure: (define (true? x) (not (eq? x false))) (define (false? x) (eq? x false)) Suppose we type the following definition into MCE: MCE> (define true false) What would be returned by the following expression: MCE> (if (= 2 2) 3 4) A: 3 B: 4 C: ERROR 2. Suppose we type the following into mc-eval: MCE> (define x (* x x)) This expression evaluates without error. What would be returned by the following expressions? MCE> quote A: ERROR: unbound variable quote B: (compound-procedure quote ((* x x)) <procedure-env>) MCE> (quote 10) A: 10 B: 100 3. Suppose we just loaded mceval.scm into STk, and erroneously started MCE using (driverloop) instead of (mce). What is the return value of the following sequence of expressions typed into MCE? If there will be an error, just write ERROR: MCE> 2 2 MCE> (+ 2 3) ERROR MCE> (define x 10) ERROR
4. Most dialects of Lisp have some way for users to create their own new special forms. We ll add to the metacircular evaluator one such way, the one that was used in the very earliest version of Lisp. As an example, suppose we want to create a while looping mechanism. The notation should be (while <condition> <action>) ;general pattern (while (< x 10) (set! x (+ x 3))) ;example This has to be a special form because we want the subexpressions to be evaluated repeatedly, not just once. An FEXPR (pronounced eff exper ) is a special kind of procedure defined with two formal parameters. When it s invoked, the entire invoking expression (unevaluated) becomes the first argument, and the current environment becomes the second argument. (The expression that invokes the FEXPR can have any number of subexpressions, but that doesn t matter because the subexpressions aren t individual arguments to the FEXPR.) Here s what the user should say to create the while special form: (define while (make-fexpr (exp env) (real-while exp env))) (define (real-while exp env) (if (eval (cadr exp) env) ; evaluate the condition (begin (eval (caddr exp) env) ; if true: evaluate the action (real-while exp env)) ; do it again done)) ; if false: we re finished If the user then says (while (< x 10) (set! x (+ x 3))) then while will be invoked with that entire expression as its first argument, and the current environment as its second argument. In this case, (cadr exp) will be the condition (< x 10) and similarly the caddr will be the action to be repeated. (Notice that the while FEXPR invokes an ordinary procedure called real-while to do the work. This is necessary because real-while is recursive. A recursive call to an FEXPR won t work because the actual arguments seen in the recursive call would again be the entire expression and the current environment, which isn t what you want!) You must make two modifications to the evaluator: (1) invent the make-fexpr form that creates an FEXPR, which is just like a procedure but with a different type tag; and (2) make the evaluator recognize an FEXPR when it s invoked, and do the right thing about its arguments. (a) Is each of these a change primarily to eval or apply? change (1): is a change to EVAL because we are creating a new special form.
change (2): is also a change to EVAL because each FEXPR is a user-defined special form, so we have to prevent the evaluation of its arguments. Checking in APPLY is too late, although some smaller changes might be needed there too, depending on how you represent FEXPRs. (b) More specifically, what procedure(s) will you invent or change for each of them? change (1): is a change to EVAL itself, adding a clause to the COND, plus inventing a new EVAL-MAKE-FEXPR procedure and other details. change (2): is a change to EVAL itself, making the COND recognize a FEXPR, and then there are several possibilities for other details. (c) Make the actual changes on the first page. See solution on the first page. You will also have to add the following to the list of primitive-procedures: (list < <) (list eval mc-eval) (list cadr cadr) (list caddr caddr) Note: The point about FEXPR-PROC is that it makes the FEXPR look just like any other compound procedure by the time APPLY sees it. This makes APPLY work without any changes. Another way would be to say (APPLY OP (LIST EXP ENV)) back in eval, and then in apply we'd have to change the test for a compound procedure so that it recognizes things whose manifest type is FEXPR as well as those whose manifest type is PROCEDURE. That would mean either modifying COMPOUND-PROCEDURE? or else adding another clause to the cond in apply.