Next: , Previous: , Up: dynamic environment   [Index]


D.4 On the implementation of guard

Vicare’s implementation of the guard syntax is really sophisticated because it has to deal with both the dynamic environment requirements of R6RS and the unwind protection mechanism defined by Vicare itself.

About the dynamic environment

In a syntax use like:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1)
          (else   ?expr2))
  ?body0 ?body ...)

if the ?body raises an exception: one of the clauses will certainly be executed because there is an else clause. The ?body might mutate the dynamic environment; all the ?test and ?expr expressions must be evaluated in the dynamic environment of the use of guard.

In a syntax use like:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1))
  ?body0 ?body ...)

if all the ?test expressions evaluate to false: we must re–raise the exception using raise-continuable; so the syntax is “almost” equivalent to:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1)
          (else   (raise-continuable E)))
  ?body0 ?body ...)

but: ?body might mutate the dynamic environment; all the ?test and ?expr expressions must be evaluated in the dynamic environment of the use of guard; the raise-continuable in the else clause must be evaluated the dynamic environment of the ?body.

We must remember that, when using:

(with-exception-handler ?handler ?thunk)

the ?handler procedure is evaluated in the dynamic environment of the ?thunk, minus the exception handler itself. So, in pseudo–code, a syntax use with else clause must be expanded as follows:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1)
          (else   ?expr2))
  ?body0 ?body ...)
→ (save-guard-continuation
     (with-exception-handler
         (lambda (E)
           (reinstate-guard-continuation
            (cond (?test0 ?expr0)
                  (?test1 ?expr1)
                  (else   ?expr2))))
       (lambda () ?body0 ?body ...)))

and, also in pseudo–code, a syntax use without else clause must be expanded as follows:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1))
  ?body0 ?body ...)
→ (save-guard-continuation
     (with-exception-handler
         (lambda (E)
           (save-exception-handler-continuation
            (reinstate-guard-continuation
             (cond (?test0 ?expr0)
                   (?test1 ?expr1)
                   (else
                    (reinstate-exception-handler-continuation
                      (raise-continuable E)))))))
       (lambda () ?body0 ?body ...)))

notice how, in the exception handler, we have to jump out and in the dynamic environment of the exception handler itself.

About the unwind–protection mechanism

Let’s focus on unwind proteciton in the case of raised exception. When using:

(with-unwind-protection ?cleanup ?thunk)

the ?cleanup is associated to the dynamic extent of the call to ?thunk: when the dynamic extent is terminated (as defined by Vicare) the ?cleanup is called.

Vicare defines as termination event of a guard’s ?body the execution of a guard’s clause that does not re–raise the exception. For a guard use like:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1)
          (else   ?expr2))
  ?body0 ?body ...)

we can imagine the pseudo–code:

(guard (E (?test0 (run-unwind-protection-cleanups) ?expr0)
          (?test1 (run-unwind-protection-cleanups) ?expr1)
          (else   (run-unwind-protection-cleanups) ?expr2))
  ?body0 ?body ...)

and for a guard use like:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1))
  ?body0 ?body ...)

we can imagine the pseudo–code:

(guard (E (?test0 (run-unwind-protection-cleanups) ?expr0)
          (?test1 (run-unwind-protection-cleanups) ?expr1)
          (else   (raise-continuable E)))
  ?body0 ?body ...)

By doing things this way: an exception raised by an ?expr does not impede the execution of the cleanups. If a ?test raises an exception the cleanups will not be run, and there is nothing we can do about it; ?test expressions are usually calls to predicates that recognise the condition type of E, so the risk of error is reduced.

So, in pseudo–code, a syntax use with else clause must be expanded as follows:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1)
          (else   ?expr2))
  ?body0 ?body ...)
→ (save-guard-continuation
     (with-exception-handler
         (lambda (E)
           (reinstate-guard-continuation
            (cond (?test0 (run-unwind-protection-cleanups) ?expr0)
                  (?test1 (run-unwind-protection-cleanups) ?expr1)
                  (else   (run-unwind-protection-cleanups) ?expr2))))
       (lambda () ?body0 ?body ...)))

and, also in pseudo–code, a syntax use without else clause must be expanded as follows:

(guard (E (?test0 ?expr0)
          (?test1 ?expr1))
  ?body0 ?body ...)
→ (save-guard-continuation
     (with-exception-handler
         (lambda (E)
           (save-exception-handler-continuation
            (reinstate-guard-continuation
             (cond (?test0 (run-unwind-protection-cleanups) ?expr0)
                   (?test1 (run-unwind-protection-cleanups) ?expr1)
                   (else   (reinstate-exception-handler-continuation
                            (raise-continuable E)))))))
       (lambda () ?body0 ?body ...)))

But how is run-unwind-protection-cleanups implemented? To cause the cleanups to be called we must cause an exit from the dynamic extent of the ?thunks. This is a sophisticated operation implemented as follows:

(define (run-unwind-protection-cleanups)
  (run-unwind-protection-cleanup-upon-exit? #t)
  (save-clause-expression-continuation
   (reinstate-exception-handler-continuation
    (reinstate-clause-expression-continuation))))

we jump in guard’s exception handler dynamic environment then immediately jump out in the guard’s clause expression dynamic environment.


Next: , Previous: , Up: dynamic environment   [Index]