Extends match-pattern in spec/evaluator.sx with an ADT case: when the pattern is (CtorName var...) and the value is an ADT dict (:_adt true), check :_ctor matches, arity matches, then recursively bind field patterns. Supports nested patterns, wildcard _, variable binding, and zero-arg ctors. Changes step-sf-match to route no-clause errors through raise-eval-frame instead of direct error, allowing guard to catch non-exhaustive matches. 40/40 ADT tests pass (20 define-type + 20 match). Zero regressions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
279 lines
9.2 KiB
Plaintext
279 lines
9.2 KiB
Plaintext
(defsuite
|
|
"algebraic-data-types"
|
|
(deftest
|
|
"constructor creates dict with adt marker"
|
|
(do
|
|
(define-type Maybe (Just value) (Nothing))
|
|
(assert= true (get (Just 42) :_adt))))
|
|
(deftest
|
|
"constructor stores type name"
|
|
(do
|
|
(define-type Shape (Circle radius) (Square side))
|
|
(assert= "Shape" (get (Circle 5) :_type))
|
|
(assert= "Shape" (get (Square 3) :_type))))
|
|
(deftest
|
|
"constructor stores constructor name"
|
|
(do
|
|
(define-type Opt (Some val) (None))
|
|
(assert= "Some" (get (Some 1) :_ctor))
|
|
(assert= "None" (get (None) :_ctor))))
|
|
(deftest
|
|
"constructor stores fields as list"
|
|
(do
|
|
(define-type Pair (Pair-of fst snd))
|
|
(assert-equal
|
|
(list 1 2)
|
|
(get (Pair-of 1 2) :_fields))))
|
|
(deftest
|
|
"zero-arg constructor has empty fields"
|
|
(do
|
|
(define-type Flag (Set) (Unset))
|
|
(assert-equal (list) (get (Set) :_fields))
|
|
(assert-equal (list) (get (Unset) :_fields))))
|
|
(deftest
|
|
"type predicate true for all constructors"
|
|
(do
|
|
(define-type Expr (Num n) (Add left right) (Neg e))
|
|
(assert= true (Expr? (Num 5)))
|
|
(assert= true (Expr? (Add (Num 1) (Num 2))))
|
|
(assert= true (Expr? (Neg (Num 3))))))
|
|
(deftest
|
|
"type predicate false for non-adt values"
|
|
(do
|
|
(define-type Box (Box-of x))
|
|
(assert= false (Box? 42))
|
|
(assert= false (Box? "hello"))
|
|
(assert= false (Box? nil))
|
|
(assert= false (Box? (list 1 2)))
|
|
(assert= false (Box? {}))))
|
|
(deftest
|
|
"type predicate false for wrong adt type"
|
|
(do
|
|
(define-type AT (AV x))
|
|
(define-type BT (BV x))
|
|
(assert= false (AT? (BV 1)))
|
|
(assert= false (BT? (AV 1)))))
|
|
(deftest
|
|
"constructor predicate true for matching constructor"
|
|
(do
|
|
(define-type Result (Ok value) (Err msg))
|
|
(assert= true (Ok? (Ok 42)))
|
|
(assert= true (Err? (Err "bad")))))
|
|
(deftest
|
|
"constructor predicate false for wrong constructor"
|
|
(do
|
|
(define-type Coin (Heads) (Tails))
|
|
(assert= false (Heads? (Tails)))
|
|
(assert= false (Tails? (Heads)))))
|
|
(deftest
|
|
"constructor predicate false for non-adt"
|
|
(do
|
|
(define-type Wrap (Wrapped x))
|
|
(assert= false (Wrapped? 42))
|
|
(assert= false (Wrapped? nil))
|
|
(assert= false (Wrapped? "str"))))
|
|
(deftest
|
|
"single-field accessor returns field value"
|
|
(do
|
|
(define-type Holder (Held content))
|
|
(assert= 99 (Held-content (Held 99)))
|
|
(assert= "hello" (Held-content (Held "hello")))))
|
|
(deftest
|
|
"multi-field accessors return correct fields"
|
|
(do
|
|
(define-type Triple (Triple-of a b c))
|
|
(let
|
|
((t (Triple-of 10 20 30)))
|
|
(assert= 10 (Triple-of-a t))
|
|
(assert= 20 (Triple-of-b t))
|
|
(assert= 30 (Triple-of-c t)))))
|
|
(deftest
|
|
"tree constructors and accessors"
|
|
(do
|
|
(define-type Tree (Leaf) (Node left val right))
|
|
(let
|
|
((t (Node (Leaf) 5 (Node (Leaf) 3 (Leaf)))))
|
|
(assert= true (Node? t))
|
|
(assert= 5 (Node-val t))
|
|
(assert= true (Leaf? (Node-left t)))
|
|
(assert= true (Node? (Node-right t)))
|
|
(assert= 3 (Node-val (Node-right t))))))
|
|
(deftest
|
|
"arity error on too few args"
|
|
(do
|
|
(define-type Pair2 (Pair2-of a b))
|
|
(let
|
|
((ok false))
|
|
(guard (exn (else (set! ok true))) (Pair2-of 1))
|
|
(assert ok))))
|
|
(deftest
|
|
"arity error on too many args"
|
|
(do
|
|
(define-type Single (Single-of x))
|
|
(let
|
|
((ok false))
|
|
(guard
|
|
(exn (else (set! ok true)))
|
|
(Single-of 1 2))
|
|
(assert ok))))
|
|
(deftest
|
|
"multiple types are independent"
|
|
(do
|
|
(define-type Color2 (Red2) (Green2) (Blue2))
|
|
(define-type Suit (Hearts) (Diamonds) (Clubs) (Spades))
|
|
(assert= false (Color2? (Hearts)))
|
|
(assert= false (Suit? (Red2)))
|
|
(assert= true (Color2? (Blue2)))
|
|
(assert= true (Suit? (Spades)))))
|
|
(deftest
|
|
"adt fields can hold any value"
|
|
(do
|
|
(define-type Container (Hold x))
|
|
(assert-equal
|
|
(list 1 2 3)
|
|
(Hold-x (Hold (list 1 2 3))))
|
|
(assert-equal {:a 1} (Hold-x (Hold {:a 1})))))
|
|
(deftest
|
|
"adt-registry tracks type constructor names"
|
|
(do
|
|
(define-type Days (Mon) (Tue) (Wed) (Thu) (Fri))
|
|
(assert-equal
|
|
(list "Mon" "Tue" "Wed" "Thu" "Fri")
|
|
(get *adt-registry* "Days"))))
|
|
(deftest
|
|
"constructors with same field name in different types are independent"
|
|
(do
|
|
(define-type P1 (P1-ctor value))
|
|
(define-type P2 (P2-ctor value))
|
|
(assert= 10 (P1-ctor-value (P1-ctor 10)))
|
|
(assert= 20 (P2-ctor-value (P2-ctor 20)))))
|
|
(deftest
|
|
"match dispatches on first matching constructor"
|
|
(do
|
|
(define-type Color (Red) (Green) (Blue))
|
|
(assert= "red" (match (Red) ((Red) "red") ((Green) "green") ((Blue) "blue")))
|
|
(assert= "green" (match (Green) ((Red) "red") ((Green) "green") ((Blue) "blue")))
|
|
(assert= "blue" (match (Blue) ((Red) "red") ((Green) "green") ((Blue) "blue")))))
|
|
(deftest
|
|
"match binds field to variable"
|
|
(do
|
|
(define-type Wrapper (Wrap val))
|
|
(assert= 42 (match (Wrap 42) ((Wrap v) v)))
|
|
(assert= "hi" (match (Wrap "hi") ((Wrap v) v)))))
|
|
(deftest
|
|
"match zero-arg constructor"
|
|
(do
|
|
(define-type Signal (On) (Off))
|
|
(assert= "on" (match (On) ((On) "on") ((Off) "off")))
|
|
(assert= "off" (match (Off) ((On) "on") ((Off) "off")))))
|
|
(deftest
|
|
"match multi-field constructor binds all fields"
|
|
(do
|
|
(define-type Vec2 (V2 x y))
|
|
(let ((v (V2 3 4)))
|
|
(assert= 7 (match v ((V2 a b) (+ a b)))))))
|
|
(deftest
|
|
"match with else clause"
|
|
(do
|
|
(define-type Opt2 (Some2 val) (None2))
|
|
(assert= 10 (match (Some2 10) ((Some2 v) v) (else 0)))
|
|
(assert= 0 (match (None2) ((Some2 v) v) (else 0)))))
|
|
(deftest
|
|
"match else catches non-adt values"
|
|
(do
|
|
(assert= "other" (match 42 ((else) "other") (else "other")))
|
|
(assert= "other" (match "str" (else "other")))))
|
|
(deftest
|
|
"match returns body expression value"
|
|
(do
|
|
(define-type Num (Num-of n))
|
|
(assert= 100 (match (Num-of 10) ((Num-of n) (* n n))))))
|
|
(deftest
|
|
"match second arm fires when first does not match"
|
|
(do
|
|
(define-type Either (Left val) (Right val))
|
|
(assert= "left-1" (match (Left 1) ((Left v) (str "left-" v)) ((Right v) (str "right-" v))))
|
|
(assert= "right-2" (match (Right 2) ((Left v) (str "left-" v)) ((Right v) (str "right-" v))))))
|
|
(deftest
|
|
"match wildcard _ in constructor pattern"
|
|
(do
|
|
(define-type Pair3 (Pair3-of a b))
|
|
(assert= 5 (match (Pair3-of 5 99) ((Pair3-of x _) x)))
|
|
(assert= 99 (match (Pair3-of 5 99) ((Pair3-of _ y) y)))))
|
|
(deftest
|
|
"match nested adt constructor pattern"
|
|
(do
|
|
(define-type Tree2 (Leaf2) (Node2 left val right))
|
|
(let ((t (Node2 (Leaf2) 7 (Leaf2))))
|
|
(assert= 7 (match t ((Node2 _ v _) v)))
|
|
(assert= true (match t ((Node2 (Leaf2) _ _) true) (else false))))))
|
|
(deftest
|
|
"match literal pattern"
|
|
(do
|
|
(assert= "zero" (match 0 (0 "zero") (else "nonzero")))
|
|
(assert= "hello" (match "hello" ("hello" "hello") (else "other")))))
|
|
(deftest
|
|
"match symbol binding pattern"
|
|
(do
|
|
(assert= 42 (match 42 (x x)))))
|
|
(deftest
|
|
"match no matching clause raises error"
|
|
(do
|
|
(define-type AB (A-val) (B-val))
|
|
(let ((ok false))
|
|
(guard (exn (else (set! ok true)))
|
|
(match (A-val) ((B-val) "b")))
|
|
(assert ok))))
|
|
(deftest
|
|
"match result used in further computation"
|
|
(do
|
|
(define-type Num2 (N v))
|
|
(assert= 30
|
|
(+
|
|
(match (N 10) ((N v) v))
|
|
(match (N 20) ((N v) v))))))
|
|
(deftest
|
|
"match with define"
|
|
(do
|
|
(define-type Tag (Tagged label value))
|
|
(define get-label (fn (t) (match t ((Tagged lbl _) lbl))))
|
|
(define get-value (fn (t) (match t ((Tagged _ val) val))))
|
|
(let ((t (Tagged "name" 99)))
|
|
(assert= "name" (get-label t))
|
|
(assert= 99 (get-value t)))))
|
|
(deftest
|
|
"match three-field constructor"
|
|
(do
|
|
(define-type Triple2 (T3 a b c))
|
|
(assert= 6 (match (T3 1 2 3) ((T3 a b c) (+ a b c))))))
|
|
(deftest
|
|
"match clauses tried in order"
|
|
(do
|
|
(define-type Expr2 (Lit n) (Add l r) (Mul l r))
|
|
(define eval-expr2 (fn (e)
|
|
(match e
|
|
((Lit n) n)
|
|
((Add l r) (+ (eval-expr2 l) (eval-expr2 r)))
|
|
((Mul l r) (* (eval-expr2 l) (eval-expr2 r))))))
|
|
(assert= 7 (eval-expr2 (Add (Lit 3) (Lit 4))))
|
|
(assert= 12 (eval-expr2 (Mul (Lit 3) (Lit 4))))
|
|
(assert= 11 (eval-expr2 (Add (Lit 2) (Mul (Lit 3) (Lit 3)))))))
|
|
(deftest
|
|
"match else binding captures value"
|
|
(do
|
|
(define-type Coin2 (Heads2) (Tails2))
|
|
(assert= "Tails2" (match (Tails2) ((Heads2) "Heads2") (x (get x :_ctor))))))
|
|
(deftest
|
|
"match on adt with string field"
|
|
(do
|
|
(define-type Msg (Hello name) (Bye name))
|
|
(assert= "Hello, Alice" (match (Hello "Alice") ((Hello n) (str "Hello, " n)) ((Bye n) (str "Bye, " n))))
|
|
(assert= "Bye, Bob" (match (Bye "Bob") ((Hello n) (str "Hello, " n)) ((Bye n) (str "Bye, " n))))))
|
|
(deftest
|
|
"match nested pattern with variable binding"
|
|
(do
|
|
(define-type Box2 (Box2-of v))
|
|
(define-type Inner (Inner-of n))
|
|
(assert= 5 (match (Box2-of (Inner-of 5)) ((Box2-of (Inner-of n)) n)))))
|
|
)
|