The portion of the explicit-control evaluator at `ev-sequence` is
analogous to the metacircular evaluator's `eval_sequence`
function. It
handles sequences of expressions in
function
bodies or in explicit
`begin` expressions.

Explicit `begin` expressions are evaluated by placing the sequence
of expressions to be evaluated in `unev`, saving `continue` on the
stack, and jumping to `ev_sequence`.

The implicit sequences in
function
bodies are handled by jumping to
`ev_sequence` from `compound_apply`, at which point `continue` is already on the stack, having been saved at
`ev_application`.

The entries at `ev_sequence`
and `ev_sequence_continue` form a loop that
successively evaluates each expression in a sequence. The list of
unevaluated expressions is kept in `unev`. Before evaluating each
expression, we check to see if there are additional expressions to be
evaluated in the sequence. If so, we save the rest of the unevaluated
expressions (held in `unev`) and the environment in which these
must be evaluated (held in `env`) and call `eval_dispatch` to
evaluate the expression. The two saved registers are restored upon
the return from this evaluation, at `ev_sequence_continue`.

The final expression in the sequence is handled differently, at the
entry point `ev_sequence_last_exp`. Since there are no more
expressions to be evaluated after this one, we need not save `unev` or `env` before going to `eval_dispatch`. The value of
the whole sequence is the value of the last expression, so after the
evaluation of the last expression there is nothing left to do except
continue at the entry point currently held on the stack (which was saved
by `ev_application` or `ev_begin`.)
Rather than setting up `continue` to arrange for `eval_dispatch` to return here and then restoring `continue` from
the stack and continuing at that entry point, we restore `continue` from
the stack before going to `eval_dispatch`, so that `eval_dispatch` will continue at that entry point after evaluating the
expression.

"ev_sequence", assign(exp(op("first_exp"), reg("unev"))), test(op("is_last_exp"), reg("unev")), branch(label("ev_sequence_last_exp")), save("unev"), save("env"), assign("continue", label("ev_sequence_continue")), go_to(label("eval_dispatch")), "ev_sequence_continue", restore("env"), restore("unev"), assign("unev", op("rest_exps"), reg("unev")), go_to(label("ev_sequence")), "ev_sequence_last_exp", restore("continue"), go_to(label("eval_dispatch")),

In chapter 1 we said that the process described by a function such as

function sqrt_iter(guess, x) { return is_good_enough(guess, x) ? guess : sqrt_iter(improve(guess, x), x); }

Our evaluator is tail-recursive, because in order to evaluate the final
expression of a sequence we transfer directly to `eval_dispatch` without
saving any information on the stack. Hence, evaluating the final expression
in a sequence—even if it is a
function
call (as in `sqrt_iter`, where
the conditional expression, which is the last expression in the
function
body,
reduces to a call to `sqrt_iter`)—will not cause any information to be
accumulated on the stack.[2]

If we did not think to take advantage of the fact that it was
unnecessary to save information in this case, we might have
implemented `eval_sequence` by treating all the expressions in a
sequence in the same way—saving the registers, evaluating the expression,
returning to restore the registers, and repeating this until all the
expressions have been evaluated:[3]

"ev_sequence", test(op("has_no_more_exps"), reg("unev")), branch(label("ev_sequence_end")), assign(exp(op("first_exp"), reg("unev")), save("unev"), save("env"), assign(continue(label("ev_sequence_continue"))), go_to(label("eval_dispatch")), "ev_sequence_continue", restore("env"), restore("unev"), assign("unev", op("rest_exps"), reg("unev")), go_to(label("ev_sequence")), "ev_sequence_end", restore("continue"), go_to(reg("continue")),

This may seem like a minor change to our previous code for evaluation
of a sequence: The only difference is that we go through the
save-restore cycle for the last expression in a sequence as well as
for the
others. The interpreter will still give the same value for
any expression. But this change is fatal to the tail-recursive
implementation, because we must now return after evaluating the final
expression in a sequence in order to undo the (useless) register
saves. These extra saves will accumulate during a nest of
function
calls. Consequently, processes such as `sqrt_iter` will require
space proportional to the number of iterations rather than requiring
constant space. This difference can be significant. For example,
with tail recursion, an infinite loop can be expressed using only the
function-call mechanism:

function count(n) { display(n, "\n"); count(n + 1); }

[1] We saw in
section 5.1 how to implement such a
process with a register machine that had no stack; the state of the
process was stored in a fixed set of registers.

[2] This implementation of tail recursion in
`ev_sequence` is one variety of a well-known optimization technique used
by many compilers. In compiling a
function
that ends with a
function
call,
one can replace the call by a jump to the called
function's entry point.
Building this strategy into the interpreter, as we have done in this section,
provides the optimization uniformly throughout the language.

[3] We can define `has_no_more_exps` as follows:

function has_no_more_exps(seq) { return is_null(seq); }

5.4.2
Sequence Evaluation and Tail Recursion