Go to the first, previous, next, last section, table of contents.

Interpreting let

The procedure eval-let will be stored in the binding for the special form let. In the case of a let expression, eval-let (above) will extract this procedure from the binding and call it to evaluate the expression.

(define (eval-let let-form envt)

   ;; extract the relevant portions of the let form
   (let ((binding-forms (cadr let-form))
         (body-forms (cddr let-form)))
         
      ;; break up the bindings part of the form
      (let ((var-list (map car binding-forms))
            (init-expr-list (map cadr binding-forms)))

         ;; evaluate initial value expressions in old envt, create a
         ;; new envt to bind values,
         (let ((new-envt (make-envt var-list
                                    (eval-multi init-expr-list envt)
                                    envt)))
            ;; evaluate the body in new envt
            (eval-sequence body-forms new-envt)))))

The first thing let does is to extract the list of variable binding clauses and the list of body expressions from the overall let expression. Then it further decomposes the variable binding clauses, extracting a list of names and a corresponding list of initial value expressions. (Notice how easy this is using map to create lists of car's and cadr's of the original clause list.)

eval-let then calls a helper procedure, eval-multi, to recursively evaluate the list of initial value expressions and return a list of the actual values.

Then it calls make-envt to make the new environment. This creates a new environment frame, scoped inside the old environment--i.e., with a scope link to it--with variable bindings for each of the variables, initialized with the corresponding values.

Then eval-let calls eval-sequence to recursively evaluate the body expressions in the new environment, in sequential order, and return the value of the last expression. This value is returned from eval-let as the value of the let expression.

Here's the code for eval-multi, which just uses map to evaluate each expression and accumulate a list of results.

(define (eval-multi arg-forms envt)
   (map (lambda (x)
           (eval x envt))
        arg-forms))

eval-multi calls eval recursively to evaluate each subexpression in the given environment. To do this, it must pass two arguments to eval. It uses map to iterate over the list of expressions, but instead of calling eval directly, map calls a helper procedure that takes an expression as its argument, and then passes the expression and the environment to eval.

Recall from section [ whatever ] that technique is known as currying. We use lambda to create a specialized version of a procedure (in this case eval), which automatically supplies one of the arguments. In effect, we create a specialized, one-argument version of eval that evaluates expressions in a particular environment, and then map that procedure over the list of expressions.

Here's the code for eval-sequence, which is very much like eval-multi---it just evaluates a list of expressions in a given environment. It's different from eval-multi in that it returns only the value of the last expression in the list, rather than a list of all of the values.

(define (eval-sequence arg-forms envt)
   (if (pair? arg-forms)
       (cond ((pair? (cdr arg-forms))
              (eval (car arg-forms) envt)
              (eval-sequence (cdr arg-forms) envt))
             (else
              (eval (car arg-forms) envt)))
       '*undefined-value*)) ; the value of an empty sequence

(Notice that we've written eval-sequence tail-recursively, and we've been careful to evaluate the last expression using a tail-call to eval. This ensures that we won't have to return to eval-sequence, so if the expression we're interpreting is a tail-call, we won't lose tail-recursiveness in the interpreter.)

Variable References and set!

eval-symbol handles variable references. It looks up the binding of the symbol, if there is one--if not, it signals an unbound variable error--and checks to see that it's a variable reference and not a special form or macro. If it is a normal variable, it fetches the value from the binding and returns it.

(define (eval-symbol name-symbol envt)
   (let ((binding-info (envt-lexical-lookup envt name-symbol)))
      (cond ((not binding-info)
             (error "Unbound variable" name-symbol))
            ((eq? (binding-type binding-info) '<variable>)
             (bdg-variable-ref binding info))
            (else
             (error "non-variable name referenced as variable"
                    name-symbol)))))

eval-set! handles the set! special form. It will be stored in a special form binding of the name set!, and extracted and called (by eval-list) to evaluate set! expressions.

(define (eval-set! set-form envt)
   (let ((name (cadr set-form))
         (value-expr (caddr set-form)))
      (let ((binding-info (envt-lexical-lookup envt name)))
         (cond ((not binding-info)
                (error "Attempt to set! unbound variable" name))
               ((eq? (binding-type binding-info) '<variable>)
                (bdg-variable-set! binding-info (eval value-expr envt)))
               (else
                (error "Attempt to set! a non-variable" name))))))

Go to the first, previous, next, last section, table of contents.