error-handling.scm 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. ;; FIXME: Use #:key instead
  96. (define* (with-exception-handler handler thunk
  97. #:optional keyword (unwind? #f))
  98. #;
  99. (unless (procedure? handler) ; ; ; ;
  100. (error "not a procedure" handler))
  101. (cond
  102. (unwind?
  103. (let ((tag (make-prompt-tag "exception handler")))
  104. (call-with-prompt
  105. tag
  106. (lambda ()
  107. (with-fluids ((%exception-handler (cons #t tag)))
  108. (thunk)))
  109. (lambda (k exn)
  110. (handler exn)))))
  111. (else
  112. (let ((running? (make-fluid #f)))
  113. (with-fluids ((%exception-handler (cons running? handler)))
  114. (thunk))))))
  115. ;; FIXME: Use #:key instead
  116. (define* (raise-exception exn #:optional keyword continuable?)
  117. (let lp ((depth 0))
  118. ;; FIXME: fluid-ref* takes time proportional to depth, which
  119. ;; makes this loop quadratic.
  120. (match (fluid-ref* %exception-handler depth)
  121. (#f
  122. ;; No exception handlers bound; fall back.
  123. (let ((port (current-error-port)))
  124. (format-exception exn port)
  125. (newline port)
  126. (flush-output-port port))
  127. (%inline-wasm
  128. '(func (param $exn (ref eq))
  129. (call $die (string.const "uncaught exception")
  130. (local.get $exn))
  131. (unreachable))
  132. exn))
  133. ((#t . prompt-tag)
  134. (abort-to-prompt prompt-tag exn)
  135. (raise (make-non-continuable-violation)))
  136. ((running? . handler)
  137. (if (fluid-ref running?)
  138. (begin
  139. (lp (1+ depth)))
  140. (with-fluids ((running? #t))
  141. (cond
  142. (continuable?
  143. (handler exn))
  144. (else
  145. (handler exn)
  146. (raise (make-non-continuable-violation))))))))))
  147. (define-syntax-rule (initialize-globals (global type proc) ...)
  148. (%inline-wasm
  149. '(func (param global type) ...
  150. (global.set global (local.get global)) ...)
  151. proc ...))
  152. (define-syntax-rule (initialize-proc-globals (global proc) ...)
  153. (initialize-globals (global (ref $proc) proc) ...))
  154. (initialize-proc-globals
  155. ($with-exception-handler with-exception-handler)
  156. ($raise-exception raise-exception))))
  157. (hoot-aux)))