error-handling.scm 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. ;;; Catching errors.
  2. ;;; Copyright (C) 2024 Igalia, S.L.
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; Commentary:
  16. ;;;
  17. ;;; with-exception-handler, guard, and all that.
  18. ;;;
  19. ;;; Code:
  20. (library (hoot error-handling)
  21. (export guard format-exception)
  22. (import (hoot primitives)
  23. (hoot cond-expand)
  24. (hoot pairs)
  25. (hoot errors)
  26. (hoot exceptions)
  27. (hoot fluids)
  28. (only (hoot control)
  29. make-prompt-tag call-with-prompt abort-to-prompt)
  30. (hoot match)
  31. (hoot numbers)
  32. (hoot ports)
  33. (hoot write))
  34. ;; Snarfed from Guile's (ice-9 exceptions). Deviates a bit from R7RS.
  35. (define-syntax guard
  36. (lambda (stx)
  37. (define (dispatch tag exn clauses)
  38. (define (build-clause test handler clauses)
  39. #`(let ((t #,test))
  40. (if t
  41. (abort-to-prompt #,tag #,handler t)
  42. #,(dispatch tag exn clauses))))
  43. (syntax-case clauses (=> else)
  44. (() #`(raise-continuable #,exn))
  45. (((test => f) . clauses)
  46. (build-clause #'test #'(lambda (res) (f res)) #'clauses))
  47. (((else e e* ...) . clauses)
  48. (build-clause #'#t #'(lambda (res) e e* ...) #'clauses))
  49. (((test) . clauses)
  50. (build-clause #'test #'(lambda (res) res) #'clauses))
  51. (((test e* ...) . clauses)
  52. (build-clause #'test #'(lambda (res) e* ...) #'clauses))))
  53. (syntax-case stx ()
  54. ((guard (exn clause clause* ...) body body* ...)
  55. (identifier? #'exn)
  56. #`(let ((tag (make-prompt-tag)))
  57. (call-with-prompt
  58. tag
  59. (lambda ()
  60. (with-exception-handler
  61. (lambda (exn)
  62. #,(dispatch #'tag #'exn #'(clause clause* ...)))
  63. (lambda () body body* ...)))
  64. (lambda (_ h v)
  65. (h v))))))))
  66. (define (format-exception exception port)
  67. (display "Scheme error:\n")
  68. (match (simple-exceptions exception)
  69. (() (display "Empty exception object" port))
  70. (components
  71. (let loop ((i 1) (components components))
  72. (define (format-numbered-exception exception)
  73. (display " " port)
  74. (display i port)
  75. (display ". " port)
  76. (write exception port))
  77. (match components
  78. ((component)
  79. (format-numbered-exception component))
  80. ((component . rest)
  81. (format-numbered-exception component)
  82. (newline port)
  83. (loop (+ i 1) rest)))))))
  84. (cond-expand
  85. (guile-vm)
  86. (hoot-main
  87. (let ()
  88. (define %exception-handler (make-fluid #f))
  89. (define (fluid-ref* fluid depth)
  90. (%inline-wasm
  91. '(func (param $fluid (ref $fluid)) (param $depth i32)
  92. (result (ref eq))
  93. (call $fluid-ref* (local.get $fluid) (local.get $depth)))
  94. fluid depth))
  95. (define* (with-exception-handler handler thunk #:key (unwind? #f))
  96. #;
  97. (unless (procedure? handler) ; ; ; ;
  98. (error "not a procedure" handler))
  99. (cond
  100. (unwind?
  101. (let ((tag (make-prompt-tag "exception handler")))
  102. (call-with-prompt
  103. tag
  104. (lambda ()
  105. (with-fluids ((%exception-handler (cons #t tag)))
  106. (thunk)))
  107. (lambda (k exn)
  108. (handler exn)))))
  109. (else
  110. (let ((running? (make-fluid #f)))
  111. (with-fluids ((%exception-handler (cons running? handler)))
  112. (thunk))))))
  113. (define (raise-non-continuable-exception)
  114. (raise (make-exception (make-non-continuable-violation)
  115. (make-exception-with-message
  116. "unhandled non-continuable exception"))))
  117. ;; FIXME: Use #:key instead
  118. (define* (raise-exception exn #:optional keyword continuable?)
  119. (let lp ((depth 0))
  120. ;; FIXME: fluid-ref* takes time proportional to depth, which
  121. ;; makes this loop quadratic.
  122. (match (fluid-ref* %exception-handler depth)
  123. (#f
  124. ;; No exception handlers bound; fall back.
  125. (let ((port (current-error-port)))
  126. (format-exception exn port)
  127. (newline port)
  128. (flush-output-port port))
  129. (%inline-wasm
  130. '(func (param $exn (ref eq))
  131. (call $die (string.const "uncaught exception")
  132. (local.get $exn))
  133. (unreachable))
  134. exn))
  135. ((#t . prompt-tag)
  136. (abort-to-prompt prompt-tag exn)
  137. (raise-non-continuable-exception))
  138. ((running? . handler)
  139. (if (fluid-ref running?)
  140. (begin
  141. (lp (1+ depth)))
  142. (with-fluids ((running? #t))
  143. (cond
  144. (continuable?
  145. (handler exn))
  146. (else
  147. (handler exn)
  148. (raise-non-continuable-exception)))))))))
  149. (define-syntax-rule (initialize-globals (global type proc) ...)
  150. (%inline-wasm
  151. '(func (param global type) ...
  152. (global.set global (local.get global)) ...)
  153. proc ...))
  154. (define-syntax-rule (initialize-proc-globals (global proc) ...)
  155. (initialize-globals (global (ref $proc) proc) ...))
  156. (initialize-proc-globals
  157. ($with-exception-handler with-exception-handler)
  158. ($raise-exception raise-exception))))
  159. (hoot-aux)))