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.)
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))))))