restart-demo.sx: safe-divide with division-by-zero condition, use-zero and retry restarts. Demonstrates handler-bind invoking a restart to resume computation with a corrected value. parse-recover.sx: token parser signalling parse-error on non-integer tokens, skip-token and use-zero restarts. Demonstrates recovery-via- restart and handler-case abort patterns. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
141 lines
3.6 KiB
Plaintext
141 lines
3.6 KiB
Plaintext
;; restart-demo.sx — Classic CL condition system demo
|
|
;;
|
|
;; Demonstrates resumable exceptions via restarts.
|
|
;; The `safe-divide` function signals a division-by-zero condition
|
|
;; and offers two restarts:
|
|
;; :use-zero — return 0 as the result
|
|
;; :retry — call safe-divide again with a corrected divisor
|
|
;;
|
|
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
|
|
|
;; ── safe-divide ────────────────────────────────────────────────────────────
|
|
;;
|
|
;; Divides numerator by denominator.
|
|
;; When denominator is 0, signals division-by-zero with two restarts.
|
|
|
|
(define
|
|
safe-divide
|
|
(fn
|
|
(n d)
|
|
(if
|
|
(= d 0)
|
|
(cl-restart-case
|
|
(fn
|
|
()
|
|
(cl-signal
|
|
(cl-make-condition
|
|
"division-by-zero"
|
|
"operation"
|
|
"/"
|
|
"operands"
|
|
(list n d)))
|
|
(error "division by zero — no restart invoked"))
|
|
(list "use-zero" (list) (fn () 0))
|
|
(list "retry" (list "d") (fn (d2) (safe-divide n d2))))
|
|
(/ n d))))
|
|
|
|
;; ── tests ─────────────────────────────────────────────────────────────────
|
|
|
|
(define passed 0)
|
|
(define failed 0)
|
|
(define failures (list))
|
|
|
|
(define
|
|
check
|
|
(fn
|
|
(label got expected)
|
|
(if
|
|
(= got expected)
|
|
(set! passed (+ passed 1))
|
|
(begin
|
|
(set! failed (+ failed 1))
|
|
(set!
|
|
failures
|
|
(append
|
|
failures
|
|
(list
|
|
(str
|
|
"FAIL ["
|
|
label
|
|
"]: got="
|
|
(inspect got)
|
|
" expected="
|
|
(inspect expected)))))))))
|
|
|
|
(define
|
|
reset-stacks!
|
|
(fn () (set! cl-handler-stack (list)) (set! cl-restart-stack (list))))
|
|
|
|
;; Normal division
|
|
(reset-stacks!)
|
|
(check "10 / 2 = 5" (safe-divide 10 2) 5)
|
|
|
|
;; Invoke use-zero restart
|
|
(reset-stacks!)
|
|
(check
|
|
"10 / 0 -> use-zero"
|
|
(cl-handler-bind
|
|
(list
|
|
(list "division-by-zero" (fn (c) (cl-invoke-restart "use-zero"))))
|
|
(fn () (safe-divide 10 0)))
|
|
0)
|
|
|
|
;; Invoke retry restart with a corrected denominator
|
|
(reset-stacks!)
|
|
(check
|
|
"10 / 0 -> retry with 2"
|
|
(cl-handler-bind
|
|
(list
|
|
(list
|
|
"division-by-zero"
|
|
(fn (c) (cl-invoke-restart "retry" 2))))
|
|
(fn () (safe-divide 10 0)))
|
|
5)
|
|
|
|
;; Nested calls: outer handles the inner divide-by-zero
|
|
(reset-stacks!)
|
|
(check
|
|
"nested: 20 / (0->4) = 5"
|
|
(cl-handler-bind
|
|
(list
|
|
(list
|
|
"division-by-zero"
|
|
(fn (c) (cl-invoke-restart "retry" 4))))
|
|
(fn () (let ((r1 (safe-divide 20 0))) r1)))
|
|
5)
|
|
|
|
;; handler-case — unwinding version
|
|
(reset-stacks!)
|
|
(check
|
|
"handler-case: catches division-by-zero"
|
|
(cl-handler-case
|
|
(fn () (safe-divide 9 0))
|
|
(list "division-by-zero" (fn (c) "caught!")))
|
|
"caught!")
|
|
|
|
;; Verify use-zero is idempotent (two uses)
|
|
(reset-stacks!)
|
|
(check
|
|
"two use-zero invocations"
|
|
(cl-handler-bind
|
|
(list
|
|
(list "division-by-zero" (fn (c) (cl-invoke-restart "use-zero"))))
|
|
(fn
|
|
()
|
|
(+
|
|
(safe-divide 10 0)
|
|
(safe-divide 3 0))))
|
|
0)
|
|
|
|
;; No restart needed for normal division
|
|
(reset-stacks!)
|
|
(check
|
|
"no restart needed for 8/4"
|
|
(safe-divide 8 4)
|
|
2)
|
|
|
|
;; ── summary ────────────────────────────────────────────────────────────────
|
|
|
|
(define demo-passed passed)
|
|
(define demo-failed failed)
|
|
(define demo-failures failures) |