;;; Handle fatal errors in a sensible way. -*- Scheme -*-

;;; This file is part of the Scheme Untergrund Networking package.

;;; Copyright (c) 1995 by Olin Shivers.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.

;;; (with-fatal-error-handler* handler thunk)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Call THUNK, and return whatever it returns. If THUNK signals a condition,
;;; and that condition is an error condition (or a subtype of error), then
;;; HANDLER gets a chance to handle it.
;;; The HANDLER proc is applied to two values: 
;;;     (HANDLER condition decline)
;;; HANDLER's continuation is WITH-FATAL-ERROR-HANDLER*'s; whatever HANDLER
;;; returns is returned from WITH-FATAL-ERROR-HANDLER. HANDLER declines to
;;; handle the error by throwing to DECLINE, a nullary continuation.
;;;
;;; Why is it called with-FATAL-error-handler*? Because returning to the
;;; guy that signalled the error is not an option.
;;;
;;; Why the nested outer pair of CALL/CC's? Well, what happens if the user's
;;; error handler *itself* raises an error? This could potentially give
;;; rise to an infinite loop, because WITH-HANDLER runs its handler in
;;; the original condition-signaller's context, so you'd search back for a
;;; handler, and find yourself again. For example, here is an infinite loop:
;;;
;;;   (with-handler (lambda (condition more)
;;;                   (display "Loop!")
;;;                   (error "ouch"))	; Get back, Loretta.
;;;     (lambda () (error "start me up")))
;;;
;;; I could require W-F-E-H* users to code carefully, but instead I make sure
;;; the user's fatal-error handler runs in w-f-e-h*'s handler context, so
;;; if it signals a condition, we'll start the search from there. That's the
;;; point of continuation K. When the original thunk completes successfully,
;;; we dodge the K hackery by using ACCEPT to make a normal return.

(define (with-fatal-error-handler* handler thunk)
  (call-with-current-continuation
    (lambda (accept)
      ((call-with-current-continuation
         (lambda (k)
	   (with-handler (lambda (condition more)
			   (if (error? condition)
			       (call-with-current-continuation
				 (lambda (decline)
				   (k (lambda () (handler condition decline))))))
			   (more))	; Keep looking for a handler.
	      (lambda () (call-with-values thunk accept)))))))))
		  
(define-syntax with-fatal-error-handler 
  (syntax-rules ()
    ((with-fatal-error-handler handler body ...)
     (with-fatal-error-handler* handler
       (lambda () body ...)))))

;This one ran HANDLER in the signaller's condition-handler context.
;It was therefore susceptible to infinite loops if you didn't code 
;your handler's carefully.
;
;(define (with-fatal-error-handler* handler thunk)
;  (call-with-current-continuation
;    (lambda (accept)
;      (with-handler (lambda (condition more)
;		      (if (error? condition)
;			  (call-with-current-continuation
;		            (lambda (decline)
;			      (accept (handler condition decline)))))
;		      (more))	; Keep looking for a handler.
;        thunk))))

;;; (%error-handler-cond kont eh-clauses cond-clauses)
;;; Transform error-handler clauses into COND clauses by wrapping continuation
;;; KONT around the body of each e-h clause, so that if it fires, the result
;;; is thrown to KONT, but if no clause fires, the cond returns to the default
;;; continuation.

;(define-syntax %error-handler-cond
;  (syntax-rules (=> else)
;
;   ((%error-handler-cond kont ((test => proc) clause ...) (ans ...))
;    (%error-handler-cond kont
;			 (clause ...)
;			 ((test => (lambda (v) (kont (proc v)))) ans ...)))
;
;   ((%error-handler-cond kont ((test body ...) clause ...) (ans ...))
;    (%error-handler-cond kont
;			 (clause ...)
;			 ((test (kont (begin body ...))) ans ...)))
;
;   ((%error-handler-cond kont ((else body ...)) (ans-clause ...))
;    (cond (else body ...) ans-clause ...))
;
;   ((%error-handler-cond kont () (ans-clause ...))
;    (cond ans-clause ...))))