R7RS compat library + 45-test suite (27 passing, 17 need lib load fix)

lib/r7rs.sx: guard/with-exception-handler macros, error objects
(make-error-object, error-object?, error-message, error-object-irritants),
R7RS aliases (car/cdr/cadr/null?/pair?/procedure?/boolean=?/symbol->string/
number->string/string->number), string->symbol.

spec/tests/test-r7rs.sx: 9 suites covering call/cc (7), raise (4),
guard (5), with-exception-handler (1), error-objects (4), multi-map (6),
cond=> (4), do-iteration (4), r7rs-aliases (10). 27/44 pass — the 17
failures need r7rs.sx auto-load in the test runner (currently commented
out pending transpiled evaluator hang investigation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 08:29:57 +00:00
parent 67c4a6a14d
commit d4244b47bf
3 changed files with 263 additions and 0 deletions

View File

@@ -1159,6 +1159,8 @@ let run_spec_tests env test_files =
with e -> Printf.eprintf "Warning: %s: %s\n%!" name (Printexc.to_string e))
end
in
(* R7RS compatibility library — TODO: debug hang in transpiled eval *)
(* load_module "r7rs.sx" lib_dir; *)
(* Render adapter for test-render-html.sx *)
load_module "render.sx" spec_dir;
load_module "canonical.sx" spec_dir;

63
lib/r7rs.sx Normal file
View File

@@ -0,0 +1,63 @@
(define make-error-object (fn (message irritants) {:irritants irritants :type "error-object" :message message}))
(define
error-object?
(fn (x) (and (dict? x) (= (get x "type") "error-object"))))
(define
error-message
(fn (x) (if (error-object? x) (get x "message") (str x))))
(define
error-object-irritants
(fn (x) (if (error-object? x) (get x "irritants") (list))))
(defmacro
with-exception-handler
(handler thunk)
(quasiquote
(handler-bind (((fn (c) true) (unquote handler))) ((unquote thunk)))))
(defmacro
guard
(var-and-clauses &rest body)
(let
((var (first var-and-clauses)) (clauses (rest var-and-clauses)))
(quasiquote
(handler-bind
(((fn (c) true) (fn ((unquote var)) (cond (splice-unquote clauses) (else (raise (unquote var)))))))
(splice-unquote body)))))
(define car first)
(define cdr rest)
(define cadr (fn (x) (first (rest x))))
(define cddr (fn (x) (rest (rest x))))
(define caar (fn (x) (first (first x))))
(define cdar (fn (x) (rest (first x))))
(define caddr (fn (x) (first (rest (rest x)))))
(define cadddr (fn (x) (first (rest (rest (rest x))))))
(define null? nil?)
(define pair? (fn (x) (and (list? x) (not (empty? x)))))
(define procedure? (fn (x) (or (lambda? x) (callable? x))))
(define boolean=? (fn (a b) (eq? a b)))
(define symbol->string symbol-name)
(define string->symbol make-symbol)
(define number->string (fn (n) (str n)))
(define
string->number
(fn (s) (if (string-contains? s ".") (parse-float s) (parse-int s))))

198
spec/tests/test-r7rs.sx Normal file
View File

@@ -0,0 +1,198 @@
(defsuite
"callcc-basic"
(deftest "simple escape" (assert= (call/cc (fn (k) (k 42))) 42))
(deftest "normal return (k unused)" (assert= (call/cc (fn (k) 99)) 99))
(deftest
"escape from nested expression"
(assert= (+ 1 (call/cc (fn (k) (+ 10 (k 42))))) 43))
(deftest
"call-with-current-continuation alias"
(assert= (call-with-current-continuation (fn (k) (k 77))) 77))
(deftest
"call/cc in let binding"
(assert= (let ((x (call/cc (fn (k) (k 5))))) (+ x 10)) 15))
(deftest
"call/cc in tail position"
(assert= (if true (call/cc (fn (k) (k 1))) 2) 1))
(deftest
"call/cc with no args to k"
(assert= (call/cc (fn (k) (k))) nil)))
(defsuite
"raise-basic"
(deftest
"raise with handler-bind"
(assert=
(handler-bind
(((fn (c) true) (fn (c) c)))
(+ 1 (raise-continuable 42)))
43))
(deftest
"raise-continuable returns to call site"
(assert=
(handler-bind
(((fn (c) true) (fn (c) (+ c 100))))
(+ 1 (raise-continuable 42)))
143))
(deftest
"raise non-continuable errors on handler return"
(assert=
(try-catch
(fn () (handler-bind (((fn (c) true) (fn (c) c))) (raise 42)))
(fn (e) "caught"))
"caught"))
(deftest
"unhandled raise gives host error"
(assert=
(try-catch (fn () (raise 99)) (fn (e) "unhandled"))
"unhandled")))
(defsuite
"guard-basic"
(deftest
"guard catches with matching clause"
(assert= (guard (exn ((number? exn) (+ exn 1))) (raise 41)) 42))
(deftest
"guard with else clause"
(assert= (guard (exn (else "caught")) (raise "boom")) "caught"))
(deftest
"guard passes through on no exception"
(assert= (guard (exn (else "caught")) (+ 1 2)) 3))
(deftest
"guard re-raises when no clause matches"
(assert=
(try-catch
(fn
()
(guard (exn ((number? exn) "number")) (raise "string-value")))
(fn (e) "re-raised"))
"re-raised"))
(deftest
"nested guard"
(assert=
(guard
(outer (else (str "outer: " (error-message outer))))
(guard (inner ((number? inner) (+ inner 1))) (raise 41)))
42)))
(defsuite
"with-exception-handler"
(deftest
"basic catch with continuable"
(assert=
(with-exception-handler
(fn (c) (+ c 100))
(fn () (+ 1 (raise-continuable 42))))
143)))
(defsuite
"error-objects"
(deftest
"make-error-object creates dict"
(assert (error-object? (make-error-object "test" (list)))))
(deftest
"error-message accessor"
(assert= (error-message (make-error-object "hello" (list))) "hello"))
(deftest
"error-object-irritants accessor"
(assert=
(error-object-irritants (make-error-object "msg" (list 1 2 3)))
(list 1 2 3)))
(deftest
"raise error object caught by guard"
(assert=
(guard
(exn ((error-object? exn) (error-message exn)))
(raise (make-error-object "test error" (list 1 2))))
"test error")))
(defsuite
"multi-map"
(deftest
"map over two lists"
(assert= (map + (list 1 2 3) (list 10 20 30)) (list 11 22 33)))
(deftest
"map over three lists"
(assert=
(map + (list 1 2) (list 10 20) (list 100 200))
(list 111 222)))
(deftest
"stops at shortest list"
(assert= (map + (list 1 2 3) (list 10 20)) (list 11 22)))
(deftest
"empty list returns empty"
(assert= (map + (list) (list 1 2)) (list)))
(deftest
"single list backwards compat"
(assert= (map (fn (x) (* x 2)) (list 1 2 3)) (list 2 4 6)))
(deftest
"map list constructor over two lists"
(assert=
(map (fn (a b) (list a b)) (list 1 2) (list 3 4))
(list (list 1 3) (list 2 4)))))
(defsuite
"cond-arrow"
(deftest
"basic arrow clause"
(assert= (cond (1 => (fn (x) (+ x 10)))) 11))
(deftest "arrow with identity" (assert= (cond (42 => (fn (x) x))) 42))
(deftest
"false clause skipped, else taken"
(assert= (cond (false => (fn (x) x)) (else 99)) 99))
(deftest
"arrow with complex expression"
(assert= (cond (42 => (fn (x) (* x 2)))) 84)))
(defsuite
"do-iteration"
(deftest "basic count" (assert= (do ((i 0 (+ i 1))) ((= i 5) i)) 5))
(deftest
"accumulator"
(assert= (do ((i 0 (+ i 1)) (sum 0 (+ sum i))) ((= i 4) sum)) 6))
(deftest
"collect into list"
(assert=
(do ((v (list) (append v (list i))) (i 0 (+ i 1))) ((= i 3) v))
(list 0 1 2)))
(deftest
"do as begin still works"
(assert= (let ((x 0)) (do (set! x 42)) x) 42)))
(defsuite
"r7rs-aliases"
(deftest
"car/cdr"
(assert= (car (list 1 2 3)) 1)
(assert= (cdr (list 1 2 3)) (list 2 3)))
(deftest "cadr" (assert= (cadr (list 1 2 3)) 2))
(deftest
"null?"
(assert (null? nil))
(assert (null? (list)))
(assert (not (null? (list 1)))))
(deftest
"pair?"
(assert (pair? (list 1)))
(assert (not (pair? (list))))
(assert (not (pair? 42))))
(deftest
"procedure?"
(assert (procedure? (fn () 1)))
(assert (procedure? +))
(assert (not (procedure? 42))))
(deftest
"integer?"
(assert (integer? 42))
(assert (integer? 0))
(assert (not (integer? 3.14)))
(assert (not (integer? "hello"))))
(deftest
"symbol->string"
(assert= (symbol->string (quote hello)) "hello"))
(deftest "number->string" (assert= (number->string 42) "42"))
(deftest
"boolean=?"
(assert (boolean=? true true))
(assert (boolean=? false false))
(assert (not (boolean=? true false)))))