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's 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) (run-time-error "Symbol does not have value" 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) (run-time-error "Attempt to set! undeclared 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))))))