Evaluation step from a transitional state.
(step-from-trans stack program) → estate
If the stack of frames is empty, we go into an error state. It should be an invariant that the stack is never empty during an evaluation that starts from a suitably well-formed initial state. This will be proved formally eventually.
If the stack is not empty, we pick the top frame, which is the car in our formalization. We look at the term in the frame, which tells us what must be done next.
If the term is a variable, we look it up in the binding of the frame. If the variable is unbounded, we go into an error state; this should never happen in well-formed evaluations, as we plan to prove formally. If it is bounded, we quote the value it is bound to and replace the variable with that quoted constant in the frame. Note that no frame is pushed or popped in this evaluation step in this case.
If the term is not a variable, it is either a quoted constant or a call of a named function or lambda expression. We explain the case of the call first, and then the case of a quoted constant.
If the term is a call of any function other than if
(whose treatment is explained later),
there are two subcases to consider.
The first subcase is that not all the argument terms of the call
are quoted constants.
In this subcase, we push a new frame with
the leftmost argument term that is not a quoted constant;
the binding is copied.
This is because that, in the left-to-right order of evaluation,
that argument is the one that needs to be evaluated next,
while the ones that precede it are fully evaluated
(i.e. they are quoted constants where no further evaluation is possible).
The second subcase is that all the argument terms are quoted constants.
In this subcase, all the arguments of the call are fully evaluated
(i.e. they are quoted constants where no further evaluation is possible),
and so we need to proceed to evaluate the call itself,
which we do as follows.
If the function is a primitive one,
we run it in one step:
we use call-primitive-function,
and if it returns
Note that, as just explained, a frame with a call always pushes a new frame, whether it is for the leftmost non-quoted-constant argument of the call, or for the body of the named function or lambda expression called. Successive evaluation steps will operate on the new frame, possibly pushing further frames, and so on, until the originally pushed frame (if the computation terminates) ends up with a quoted constant in it, which is the final result of evaluating the term in the originally pushed frame. This is where a frame with just a quoted constant comes into play, whose treatment we describe next (recall that, above, we had deferred this explanation).
If the term in a frame is a quoted constant, we pop the frame, and incorporate the quoted constant into the frame just below, if any. If there is no frame just below, i.e. the frame with the quoted constant, just popped, was the only one, then we go into the final evaluation state, which only contains a value, namely the value of the quoted constant. This means that the overall computation, which started from an initial evaluation state, has terminated, and this value is the result. If instead there is a frame below, we proceed as follows. If the term in the frame below is a variable or quoted constant, we go into the error state: this will never happen in well-formed evaluations; we will prove this formally eventually. If instead the term is a call, there are two subcases, the same that were described above for pushing a new frame. In the first subcase, the call in the frame has a leftmost argument term that is not a quoted constant: then the frame just popped must have derived from that argument term, and thus we replace that argument term with the quoted constant. In the second subcase, the call in the frame has all quoted constants as arguments: the the frame just popped must have derived from the body of the function, and thus we replace the whole call with the quoted constant.
A call of if is treated non-strictly.
If the call has a number of arguments different from 3,
we go into an error state;
this will never happen in well-formed evaluations,
as will be proved eventually.
If the first argument (i.e. the test) is not a quoted constant,
it is pushed into a new frame, like any other function argument.
If instead it is a quoted constant,
it means that it has been already evaluated,
or that it does not need to be evaluated.
Based on whether its value is
Note that the arguments of functions are evaluated left-to-right. This is consistent with Section 5.1.5 of ``Common Lisp the Language, 2nd Edition''.
Function:
(defun step-from-trans (stack program) (declare (xargs :guard (and (stackp stack) (programp program)))) (let ((__function__ 'step-from-trans)) (declare (ignorable __function__)) (b* (((when (endp stack)) (eval-state-error)) (frame (car stack)) ((frame frame) frame)) (tterm-case frame.term :variable (b* ((var-val (omap::assoc frame.term.name frame.binding)) ((unless (consp var-val)) (eval-state-error)) (val (cdr var-val)) (new-frame (change-frame frame :term (tterm-constant val))) (new-stack (cons new-frame (cdr stack)))) (eval-state-trans new-stack)) :constant (b* ((value frame.term.value) (stack (cdr stack)) ((when (endp stack)) (eval-state-final value)) (frame (car stack)) ((frame frame) frame)) (tterm-case frame.term :variable (eval-state-error) :constant (eval-state-error) :call (b* (((mv foundp arguments) (put-leftmost-nonconst frame.term.arguments value)) (term (if foundp (make-tterm-call :function frame.term.function :arguments arguments) (tterm-constant value))) (frame (change-frame frame :term term)) (stack (cons frame (cdr stack)))) (eval-state-trans stack)))) :call (if (tfunction-equiv frame.term.function (tfunction-named (lift-symbol 'if))) (b* (((unless (= (len frame.term.arguments) 3)) (eval-state-error)) (test (first frame.term.arguments))) (if (tterm-case test :constant) (b* ((branch (if (value-equiv (tterm-constant->value test) (value-nil)) (third frame.term.arguments) (second frame.term.arguments))) (new-frame (change-frame frame :term branch)) (new-stack (cons new-frame (cdr stack)))) (eval-state-trans new-stack)) (eval-state-trans (cons (make-frame :term test :binding frame.binding) stack)))) (b* ((arg? (get-leftmost-nonconst frame.term.arguments))) (if (ttermp arg?) (eval-state-trans (cons (make-frame :term arg? :binding frame.binding) stack)) (tfunction-case frame.term.function :named (if (primitive-function-namep frame.term.function.name) (b* ((args (tterm-constant-list->value-list frame.term.arguments)) (value? (call-primitive-function frame.term.function.name args program))) (if (valuep value?) (eval-state-trans (cons (change-frame frame :term (tterm-constant value?)) (cdr stack))) (eval-state-error))) (b* ((function? (function-lookup frame.term.function.name (program->functions program))) ((unless (functionp function?)) (eval-state-error)) (params (function->params function?)) (body (function->body function?)) ((unless (= (len params) (len frame.term.arguments))) (eval-state-error)) (args (tterm-constant-list->value-list frame.term.arguments)) (binding (omap::from-lists params args))) (eval-state-trans (cons (make-frame :term body :binding binding) stack)))) :lambda (b* ((params frame.term.function.parameters) (body frame.term.function.body) ((unless (= (len params) (len frame.term.arguments))) (eval-state-error)) (args (tterm-constant-list->value-list frame.term.arguments)) (binding (omap::update* (omap::from-lists params args) frame.binding))) (eval-state-trans (cons (make-frame :term body :binding binding) stack)))))))))))
Theorem:
(defthm eval-state-p-of-step-from-trans (b* ((estate (step-from-trans stack program))) (eval-state-p estate)) :rule-classes :rewrite)
Theorem:
(defthm step-from-trans-of-stack-fix-stack (equal (step-from-trans (stack-fix stack) program) (step-from-trans stack program)))
Theorem:
(defthm step-from-trans-stack-equiv-congruence-on-stack (implies (stack-equiv stack stack-equiv) (equal (step-from-trans stack program) (step-from-trans stack-equiv program))) :rule-classes :congruence)
Theorem:
(defthm step-from-trans-of-program-fix-program (equal (step-from-trans stack (program-fix program)) (step-from-trans stack program)))
Theorem:
(defthm step-from-trans-program-equiv-congruence-on-program (implies (program-equiv program program-equiv) (equal (step-from-trans stack program) (step-from-trans stack program-equiv))) :rule-classes :congruence)