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>
163 lines
4.9 KiB
Plaintext
163 lines
4.9 KiB
Plaintext
;; parse-recover.sx — Parser with skipped-token restart
|
|
;;
|
|
;; Classic CL pattern: a simple token parser that signals a condition
|
|
;; when it encounters an unexpected token. The :skip-token restart
|
|
;; allows the parser to continue past the offending token.
|
|
;;
|
|
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
|
|
|
;; ── condition type ─────────────────────────────────────────────────────────
|
|
|
|
(cl-define-condition "parse-error" (list "error") (list "token" "position"))
|
|
|
|
;; ── simple token parser ────────────────────────────────────────────────────
|
|
;;
|
|
;; parse-numbers: given a list of tokens (strings), parse integers.
|
|
;; Non-integer tokens signal parse-error with two restarts:
|
|
;; skip-token — skip the bad token and continue
|
|
;; use-zero — use 0 in place of the bad token
|
|
|
|
(define
|
|
parse-numbers
|
|
(fn
|
|
(tokens)
|
|
(define result (list))
|
|
(define
|
|
process
|
|
(fn
|
|
(toks)
|
|
(if
|
|
(empty? toks)
|
|
result
|
|
(let
|
|
((tok (first toks)) (rest-toks (rest toks)))
|
|
(let
|
|
((n (string->number tok 10)))
|
|
(if
|
|
n
|
|
(begin
|
|
(set! result (append result (list n)))
|
|
(process rest-toks))
|
|
(cl-restart-case
|
|
(fn
|
|
()
|
|
(cl-signal
|
|
(cl-make-condition
|
|
"parse-error"
|
|
"token"
|
|
tok
|
|
"position"
|
|
(len result)))
|
|
(set! result (append result (list 0)))
|
|
(process rest-toks))
|
|
(list "skip-token" (list) (fn () (process rest-toks)))
|
|
(list
|
|
"use-zero"
|
|
(list)
|
|
(fn
|
|
()
|
|
(begin
|
|
(set! result (append result (list 0)))
|
|
(process rest-toks)))))))))))
|
|
(process tokens)
|
|
result))
|
|
|
|
;; ── 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))))
|
|
|
|
;; All valid tokens
|
|
(reset-stacks!)
|
|
(check
|
|
"all valid: 1 2 3"
|
|
(cl-handler-bind
|
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
|
(fn () (parse-numbers (list "1" "2" "3"))))
|
|
(list 1 2 3))
|
|
|
|
;; Skip bad token
|
|
(reset-stacks!)
|
|
(check
|
|
"skip bad token: 1 x 3 -> (1 3)"
|
|
(cl-handler-bind
|
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
|
(fn () (parse-numbers (list "1" "x" "3"))))
|
|
(list 1 3))
|
|
|
|
;; Use zero for bad token
|
|
(reset-stacks!)
|
|
(check
|
|
"use-zero for bad: 1 x 3 -> (1 0 3)"
|
|
(cl-handler-bind
|
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "use-zero"))))
|
|
(fn () (parse-numbers (list "1" "x" "3"))))
|
|
(list 1 0 3))
|
|
|
|
;; Multiple bad tokens, all skipped
|
|
(reset-stacks!)
|
|
(check
|
|
"skip multiple bad: a 2 b 4 -> (2 4)"
|
|
(cl-handler-bind
|
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
|
(fn () (parse-numbers (list "a" "2" "b" "4"))))
|
|
(list 2 4))
|
|
|
|
;; handler-case: abort on first bad token
|
|
(reset-stacks!)
|
|
(check
|
|
"handler-case: abort on first bad"
|
|
(cl-handler-case
|
|
(fn () (parse-numbers (list "1" "bad" "3")))
|
|
(list
|
|
"parse-error"
|
|
(fn
|
|
(c)
|
|
(str
|
|
"parse error at position "
|
|
(cl-condition-slot c "position")
|
|
": "
|
|
(cl-condition-slot c "token")))))
|
|
"parse error at position 1: bad")
|
|
|
|
;; Verify condition type hierarchy
|
|
(reset-stacks!)
|
|
(check
|
|
"parse-error isa error"
|
|
(cl-condition-of-type?
|
|
(cl-make-condition "parse-error" "token" "x" "position" 0)
|
|
"error")
|
|
true)
|
|
|
|
;; ── summary ────────────────────────────────────────────────────────────────
|
|
|
|
(define parse-passed passed)
|
|
(define parse-failed failed)
|
|
(define parse-failures failures) |