CS 22

Clab 29: The Meta-circular evaluator II


Last clab, we built a mini-micro Scheme interpreter using a single-frame golbal environment with bindings implemented by dotted pairs.

We chose to represent a binding as a dotted pair and a frame as a list of bindings. This is attractive and we could proceed with this kind of binding. SICP chooses to represent a frame as a pair of lists. The car of the frame is a list of the variable names; the cdr of the frame is a list of the corresponding values. So the frame we were working with

((x . 17) (cat . 10) (p . 3)) would be represented by SICP as ((x cat p) . (17 10 3)) but it will be printed by drscheme as ((x cat p) 17 10 3) This is an artifact of the standard scheme printing. If you draw box and pointer diagrams you will see why.

Let's modify our stuff so that we have a mini-micro Scheme interpreter for frames represented the sicp way. Start a new file for the SICP style frames.

We need a lookup-variable-value function for the sicp way of representing a frame. It will be useful to have some procedures with mnemonic names here, so before you define lookup-variable-value, define the following for the SICP kind of frame.

;;; make-frame constructs an SICP style frame from a list of ;;; variables (the parameter variables) and a list of values ;;; (the parameter values) (define (make-frame variables values) .... ;;; given an SICP style frame (the parameter frame), ;;; frame-variables returns a list of the variable names ;;; in the frame (define (frame-variables frame) .... ;;; given an SICP style frame (the parameter frame), ;;; frame-values returns a list of the values ;;; in the frame (define (frame-values frame) .... These should behave as follows: > (define my-sicp-frame (make-frame '(x cat p) '(17 10 3))) > my-sicp-frame ((x cat p) 17 10 3) > (frame-variables my-sicp-frame) (x cat p) > (frame-values my-sicp-frame) (17 10 3) Now define ;;;returns the value of variable var in frame (define (lookup-variable-value var frame) ... It should behave as follows: > my-sicp-frame ((x cat p) 17 10 3) > (lookup-variable-value 'x my-sicp-frame) 17 > (lookup-variable-value 'cat my-sicp-frame) 10 Here is our old mini-eval, driver-loop, and a couple of helping procedures from last clab. (define atom? (lambda (x) (not (pair? x)))) ;;;returns #t if exp is a quote expression; returns #f otherwise (define (quoted? exp) (if (atom? exp) #f (eq? (car exp) 'quote))) ;;; if exp is a quoted expression, returns its value (define (text-of-quotation exp) (cadr exp)) ;;; returns #t if exp is a simple variable name; returns #f otherwise (define (variable? exp) (symbol? exp)) ;;; a really mini-mini eval. evaluates quoted expressions and ;;; simple variables only in one frame. The expression is ;;; parameter exp and the frame is parameter env. (define (mini-eval exp env) (cond ((quoted? exp) (text-of-quotation exp)) ((variable? exp) (lookup-variable-value exp env)) (else (error "unknown expression--mini-eval" exp)))) ;;; The driver loop reads an expression, evaluates it in the global ;;; environment, and prints the result. Note that the driver ;;; uses a prompt of "MC-EVAL==> " to help you avoid confusing typing to the ;;; metacircular evaluator with typing to the underlying SCHEME interpreter. ;;; (Also, we have called our evaluator procedures MINI-EVAL and MINI-APPLY ;;; to help you avoid confusing them with Scheme's EVAL and APPLY.) ;;; When/If your interaction with the evaluator bombs out in an error, ;;; you should restart it by calling DRIVER-LOOP again. ;;; make the global environment our little frame for now (define the-global-environment my-sicp-frame) (define (driver-loop) (newline) (display "MC-EVAL==> ") (let ((input (read))) (if (equal? input '(quit)) "exiting-mini-eval" (begin (display (mini-eval input ;;;;change display to user-print later the-global-environment)) (driver-loop))))) Copy in our old mini-eval and driver-loop and ehlping functions. Make the global environment my-sicp-frame and run driver-loop. You should have the same sort of micro world we had before but now with the SICP style frame.

Here is a sample run:

> my-sicp-frame ((x cat p) 17 10 3) > (driver-loop) MC-EVAL==> p 3 MC-EVAL==> (quote (my name is harry potter)) (my name is harry potter) MC-EVAL==> x 17 MC-EVAL==> zoo . Unbound variable zoo

In case you didn't complete this, here is possible code for managing bindings the SICP way:

(define the-empty-environment '()) ;;; make-frame constructs an SICP style frame from a list of ;;; variables (the parameter variables) and a list of values ;;; (the parameter values) (define (make-frame variables values) (cons variables values)) ;;; given an SICP style frame (the parameter frame), ;;; frame-variables returns a list of the variable names ;;; in the frame (define (frame-variables frame) (car frame)) ;;; given an SICP style frame (the parameter frame), ;;; frame-values returns a list of the values ;;; in the frame (define (frame-values frame) (cdr frame)) ;;;just a frame to test things with (define my-sicp-frame (make-frame '(x cat p) '(17 10 3))) ;;;returns the value of variable var in frame (define (lookup-variable-value var frame) (define (scan vars vals) (cond ((null? vars) (error "Unbound variable" var)) ((eq? var (car vars)) (car vals)) (else (scan (cdr vars) (cdr vals))))) (scan (frame-variables frame) (frame-values frame))) (define atom? (lambda (x) (not (pair? x)))) ;;;returns #t if exp is a quote expression; returns #f otherwise (define (quoted? exp) (if (atom? exp) #f (eq? (car exp) 'quote))) ;;; if exp is a quoted expression, returns its value (define (text-of-quotation exp) (cadr exp)) ;;; returns #t if exp is a simple variable name; returns #f otherwise (define (variable? exp) (symbol? exp)) ;;; a really mini-mini eval. evaluates quoted expressions and ;;; simple variables only in one frame. The expression is ;;; parameter exp and the frame is parameter env. (define (mini-eval exp env) (cond ((quoted? exp) (text-of-quotation exp)) ((variable? exp) (lookup-variable-value exp env)) (else (error "unknown expression--mini-eval" exp)))) ;;; The driver loop reads an expression, evaluates it in the global ;;; environment, and prints the result. Note that the driver ;;; uses a prompt of "MC-EVAL==> " to help you avoid confusing typing to the ;;; metacircular evaluator with typing to the underlying SCHEME interpreter. ;;; (Also, we have called our evaluator procedures MINI-EVAL and MINI-APPLY ;;; to help you avoid confusing them with Scheme's EVAL and APPLY.) ;;; When/If your interaction with the evaluator bombs out in an error, ;;; you should restart it by calling DRIVER-LOOP again. ;;; make the global environment our little frame for now (define the-global-environment my-sicp-frame) (define (driver-loop) (newline) (display "MC-EVAL==> ") (let ((input (read))) (if (equal? input '(quit)) "exiting-mini-eval" (begin (display (mini-eval input ;;;;change display to user-print later the-global-environment)) (driver-loop))))) Note how the scan helper function in lookup-variable-value works. In particular, scan assumes that a frame is well-formed (i.e. a dotted pair of two equal-length lists).

What we have here is a mini-micro Scheme interpreter. Once the user types (driver-loop), he is in our world. Our interpreter evaluates whatever expressions are typed by the user until the user quits (or bombs out by typing something our interpreter cannot handle). We have a lot of work to do to turn this into a reasonable interpreter, but this is a start. Make sure you understand how this little interpreter works and ask for help if you need it.

In order to have a really functional interpreter, we need to add a lot. Users should be able to define their own variables and give them values. We should provide some primitive operations that users can use. Users should be able to define their own functions and invoke them.


Here is the sicp eval function. (define (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)) ((if? exp) (eval-if exp env)) ((lambda? exp) (make-procedure (lambda-parameters exp) (lambda-body exp) env)) ((begin? exp) (eval-sequence (begin-actions exp) env)) ((cond? exp) (eval (cond->if exp) env)) ((application? exp) (apply (eval (operator exp) env) (list-of-values (operands exp) env))) (else (error "Unknown expression type -- EVAL" exp)))) I'll say a couple of words about this and then we will go on in a bottom-up fashion, making small changes, running our code, and getting lots of CS highs.

We will begin by working on the environment handling functions so that users can define their own variables inside our interpreter. Since we know that eventually we will want to work with environments that consist of more than one frame, we will define environment functions that allow for such complex environments.

We can represent an environment as a list of frames. Of course it is up to us to handle things correctly. If we have an environment that looks like the list (frame1 frame2 frame3), we will understand this to be an environemt that we would draw in an environment diagram as frame1 in a box with an arrow from frame 1 to a box with frame2 in it and an arrow from the frame 2 box to a box frame3 in it. Usually we would draw frame3 on the top although it is the direction of the arrows that is important.

Let's keep our good old my-sicp-frame and add another say:

(define funframe (make-frame '(buchanan nader cat bush gore) '((name too long) lost 56 maybeb maybeg))) which will look like: ((buchanan nader cat bush gore) (name too long) lost 56 maybeb maybeg)

We can define a simple two level environment that would be illustrated by an arrow from funframe to my-sicp-frame by evaluating:

(define 2levenv (list funframe my-sicp-frame)) Now define the following environment procedures thinking about what they do and testing them as you go along: (define (enclosing-environment env) (cdr env)) (define (first-frame env) (car env)) (define (extend-environment vars vals base-env) (if (= (length vars) (length vals)) (cons (make-frame vars vals) base-env) (if (< (length vars) (length vals)) (error "Too many arguments supplied" vars vals) (error "Too few arguments supplied" vars vals)))) (define (add-binding-to-frame! var val frame) (set-car! frame (cons var (car frame))) (set-cdr! frame (cons val (cdr frame)))) Let's use these to construct a new frame, newframe and a 3 level environment that that extends 2levenv by newframe. > (define newframe (list '())) > newframe (()) > (add-binding-to-frame! 'y 2 newframe) > newframe ((y) 2) > (add-binding-to-frame! 'k 'isover newframe) > newframe ((k y) isover 2) > (define 3levenv (extend-environment (frame-variables newframe) (frame-values newframe) 2levenv)) > 3levenv (((k y) isover 2) ((buchanan nader cat bush gore) (name too long) lost 56 maybeb maybeg) ((x cat p) 17 10 3)) For testing, move this construction to the definitions window so that you can use my-sicp-frame, funframe, newframe, 2levenv, and 3levenv without having to retype.

Suppose we try to look up the value of variable k in 3levenv. We get:

> (lookup-variable-value 'k (first-frame 3levenv)) isover We got the right answer but there is a problem here. Try: > (lookup-variable-value 'k 3levenv) cdr: expects argument of type <pair>; given () We got an error on because 3levenv is an environment, not a frame and we wrote lookup-variable-value assuming the second parameter was a frame. We can get around this by manually picking frames from an environment as above. The problem is that, once complicated recursions are allowed, we won't usually know how many frames are in an environment. So we need to redefine lookup-variable-value to work in a multi-frame environment. SICP does this on page 379. (define (lookup-variable-value var env) (define (env-loop env) (define (scan vars vals) (cond ((null? vars) (env-loop (enclosing-environment env))) ((eq? var (car vars)) (car vals)) (else (scan (cdr vars) (cdr vals))))) (if (eq? env the-empty-environment) (error "Unbound variable" var) (let ((frame (first-frame env))) (scan (frame-variables frame) (frame-values frame))))) (env-loop env)) Study this definition for a few minutes, making sure you understand how it works. Then replace your old single-frame version with this one in your definitions window and test it for variables in different levels of 3levenv. > 3levenv (((k y) isover 2) ((buchanan nader cat bush gore) (name too long) lost 56 maybeb maybeg) ((x cat p) 17 10 3)) > (lookup-variable-value 'k 3levenv) isover > (lookup-variable-value 'gore 3levenv) maybeg > (lookup-variable-value 'p 3levenv) 3 A less direct but easier way to check it out is to use our driver loop with 3levenv as the-global environment. > (define the-global-environment 3levenv) > the-global-environment (((k y) isover 2) ((buchanan nader cat bush gore) (name too long) lost 56 maybeb maybeg) ((x cat p) 17 10 3)) > (driver-loop) MC-EVAL==> k isover MC-EVAL==> y 2 MC-EVAL==> nadar Unbound variable nadar > (driver-loop) MC-EVAL==> nader lost MC-EVAL==> cat 56 MC-EVAL==> x 17 MC-EVAL==> cat 56 MC-EVAL==> p 3 Note that the value of cat returned was that in the most recently added frame of the environment. This is as it should be.