sicp.io
5.1.5 · The explicit-control evaluator

Evaluator control becomes registers, labels, and stack protocol.

A register-machine controller implements eval and apply directly. The exp, env, val, proc, argl, continue, and unev registers expose the evaluator state.

Guiding question

What changes when the evaluator is no longer expressed by host-language recursion and must preserve every continuation explicitly?

  • Dispatch guest syntax through an explicit eval-dispatch controller
  • Carry evaluator data through exp, env, val, proc, argl, continue, and unev
  • Save and restore unfinished operator and operand work on a machine stack
  • Apply primitives and represented compound procedures through separate controller paths
  • Restore continue before evaluating the last expression of a sequence
  • Observe equal maximum stack depth for two tail-recursive runs of different lengths
  • Route if, set!, and define through explicit controller labels
  • Run a complete guest program and halt with an empty evaluator stack

The controller starts by loading the global environment and a final continuation, then repeatedly dispatches on the expression in exp. Simple values place their result in val and jump through continue. Applications save the caller continuation and environment, evaluate the operator and operands, build argl, and enter apply-dispatch. Primitive procedures call a host implementation; compound procedures install a new environment and send their body through ev-sequence.

Sequence evaluation is tail recursive because ev-sequence-last-exp restores the caller continuation before sending the final expression back to eval-dispatch. The two sum-iter runs therefore reach the same measured maximum evaluator-stack depth even though one performs more recursive calls. Separate controller paths preserve and restore the expression, environment, and continuation around if, set!, and define. The final guest program combines recursion, lexical closure state, mutation, and a clean halt. These observations describe this finite simulator and controller, not hardware timing or every possible evaluator implementation.

SICP code22,000 of 1,048,576 UTF-8 bytes
Examples
Result
Output
Value
Diagnostic
Execution trace0 / 0 events
    Programs run in the browser with their result and execution trace.
    Expected result

    The program returns ((core 42 #t #t) (tail 15 210 #t #t #t) (state 9 9 #t) (run (120 41 42) #t #t)). The tail equality records the measured maximum evaluator-stack depth for n = 5 and n = 20 in this controller.

    Trace focus

    Follow eval-dispatch into the syntax-specific labels, then locate save and restore around operator and operand evaluation. In the tail runs, watch ev-sequence-last-exp restore continue before the recursive application begins. In the state run, follow definition and assignment value evaluation before the global binding changes. In the final run, distinguish the recursive factorial stack from the counter closure’s mutated captured binding and verify the machine halts with no saved evaluator values left.

    Try it yourself

    Change the program and compare the result.

    Add a tail-recursive product-iter procedure and run it with two different input sizes. Compare maximum stack depth, final values, and empty-stack status with the shipped sum-iter observations.

    Show hint

    Keep the recursive call as the final expression of the procedure body. If another operation waits after that call, the controller must preserve additional unfinished work.

    Complete this lesson

    0 of 23 lessons complete in this chapter0%