Alpha equivalence checker for arbitrary lambda terms
7

Configure Feed

Select the types of activity you want to include in your feed.

ego / ocaml / test / test_ego.ml
7.9 kB 264 lines
1open Ego 2 3let find_corpus () = 4 let candidates = 5 [ "../shared/corpus.json"; "shared/corpus.json"; 6 "/home/bulg/projects/ego/shared/corpus.json" ] 7 in 8 match List.find_opt Sys.file_exists candidates with 9 | Some p -> p 10 | None -> failwith "corpus.json not found" 11 12let check_bool name exp got = 13 if got <> exp then 14 failwith 15 (Printf.sprintf "%s: expected %b got %b" name exp got) 16 17let check_raises name f = 18 match f () with 19 | _ -> failwith (name ^ ": expected exception") 20 | exception _ -> () 21 22let check_with rep t1 t2 = 23 match rep with 24 | `Named -> Named.alpha_equivalent t1 t2 25 | `Debruijn -> 26 Debruijn.alpha_equivalent 27 (Debruijn.convert_from_named t1) 28 (Debruijn.convert_from_named t2) 29 | `Ln -> 30 Locally_nameless.alpha_equivalent 31 (Locally_nameless.convert_from_named t1) 32 (Locally_nameless.convert_from_named t2) 33 | `Subst -> 34 Explicit_subst.alpha_equivalent 35 (Explicit_subst.convert_from_named t1) 36 (Explicit_subst.convert_from_named t2) 37 38let reps = [ `Named; `Debruijn; `Ln; `Subst ] 39 40let test_corpus () = 41 let entries = Json_io.load_corpus (find_corpus ()) in 42 List.iter 43 (fun (e : Json_io.corpus_entry) -> 44 List.iter 45 (fun r -> 46 match e.Json_io.expected with 47 | Some exp -> 48 check_bool 49 (Printf.sprintf "%s [%s]" e.name 50 (match r with 51 | `Named -> "named" | `Debruijn -> "debruijn" 52 | `Ln -> "ln" | `Subst -> "subst")) 53 exp (check_with r e.t1 e.t2) 54 | None -> ()) 55 reps) 56 entries 57 58let rng = Random.State.make [| 42 |] 59 60let names = [| "a"; "b"; "c"; "d"; "e"; "f" |] 61let fvs = [| "p"; "q"; "g"; "h" |] 62 63let rec gen scope depth = 64 let pick arr = arr.(Random.State.int rng (Array.length arr)) in 65 if depth <= 0 then 66 match scope with 67 | [] -> Named.Var (pick fvs) 68 | _ -> 69 if Random.State.float rng 1.0 < 0.75 then Named.Var (pick (Array.of_list scope)) 70 else Named.Var (pick fvs) 71 else 72 let r = Random.State.float rng 1.0 in 73 if r < 0.45 then 74 let n = 75 if scope <> [] && Random.State.float rng 1.0 < 0.35 76 then pick (Array.of_list scope) 77 else pick names 78 in 79 Named.Lam (n, gen (n :: scope) (depth - 1)) 80 else if r < 0.75 then 81 Named.App (gen scope (depth - 1), gen scope (depth - 1)) 82 else if r < 0.85 then 83 let n = match scope with [] -> "u" | _ -> pick (Array.of_list scope) in 84 Named.Subst (n, gen scope (depth - 1), gen scope (depth - 1)) 85 else 86 match scope with 87 | [] -> Named.Var (pick fvs) 88 | _ -> Named.Var (pick (Array.of_list scope)) 89 90let random_term () = gen [] (2 + Random.State.int rng 4) 91 92let test_reflexivity () = 93 for _ = 1 to 200 do 94 let t = random_term () in 95 List.iter 96 (fun r -> 97 check_bool "reflexive" true (check_with r t t)) 98 reps 99 done 100 101let test_symmetry () = 102 for _ = 1 to 200 do 103 let t1 = random_term () in 104 let t2 = random_term () in 105 List.iter 106 (fun r -> 107 check_bool "symmetric" 108 (check_with r t1 t2) (check_with r t2 t1)) 109 reps 110 done 111 112let test_transitivity () = 113 for _ = 1 to 100 do 114 let t = random_term () in 115 List.iter 116 (fun r -> 117 let norm_named n = 118 match r with 119 | `Named -> Named.normalise n 120 | _ -> Named.normalise n 121 in 122 let a = check_with r t t in 123 let b = check_with r (norm_named t) (norm_named t) in 124 check_bool "transitive-self" a b) 125 reps 126 done 127 128let test_fv_preserved () = 129 for _ = 1 to 200 do 130 let t1 = random_term () in 131 let t2 = random_term () in 132 if Named.alpha_equivalent t1 t2 then 133 check_bool "fv equal" true 134 (Named.SSet.equal (Named.free_variables (Named.normalise t1)) 135 (Named.free_variables (Named.normalise t2))) 136 done 137 138let test_roundtrip () = 139 for _ = 1 to 200 do 140 let t = random_term () in 141 let t' = Debruijn.convert_to_named (Debruijn.convert_from_named t) in 142 check_bool "roundtrip debruijn" true 143 (Named.alpha_equivalent t' t); 144 let t'' = 145 Locally_nameless.convert_to_named 146 (Locally_nameless.convert_from_named t) 147 in 148 check_bool "roundtrip ln" true 149 (Named.alpha_equivalent t'' t); 150 let t''' = 151 Explicit_subst.convert_to_named 152 (Explicit_subst.convert_from_named t) 153 in 154 check_bool "roundtrip subst" true 155 (Named.alpha_equivalent t''' t) 156 done 157 158let test_cross_rep_normalise () = 159 for _ = 1 to 100 do 160 let t = random_term () in 161 let n1 = Named.normalise t in 162 let n2 = 163 Debruijn.convert_to_named 164 (Debruijn.normalise (Debruijn.convert_from_named t)) 165 in 166 let n3 = 167 Locally_nameless.convert_to_named 168 (Locally_nameless.normalise (Locally_nameless.convert_from_named t)) 169 in 170 let n4 = 171 Explicit_subst.convert_to_named 172 (Explicit_subst.normalise (Explicit_subst.convert_from_named t)) 173 in 174 check_bool "named vs debruijn" true 175 (Named.alpha_equivalent n1 n2); 176 check_bool "named vs ln" true 177 (Named.alpha_equivalent n1 n3); 178 check_bool "named vs subst" true 179 (Named.alpha_equivalent n1 n4) 180 done 181 182let test_parse_errors () = 183 List.iter 184 (fun s -> 185 match Syntax.parse_result s with 186 | Ok _ -> failwith ("should reject: " ^ s) 187 | Error _ -> ()) 188 [ ""; "\\"; "\\x"; "\\x."; "(\\x. x"; "\\x. x)"; "[x := ] x"; "[ := y] x"; 189 "((x"; "[x := y]"; "."; "\\. x" ] 190 191let test_malformed () = 192 let open Locally_nameless in 193 check_bool "dangling bvar" false (lc (BVar 0)); 194 check_bool "closed lam" true (lc (Lam (BVar 0))); 195 check_bool "dangling deep" false 196 (lc (Lam (Lam (BVar 2)))); 197 check_raises "convert raises" 198 (fun () -> ignore (convert_to_named (BVar 3))); 199 check_raises "alpha raises" 200 (fun () -> ignore (alpha_equivalent (BVar 1) (BVar 1))) 201 202let test_debruijn_scope () = 203 let open Debruijn in 204 check_bool "well scoped" true (well_scoped 0 (Lam (Var 0))); 205 check_bool "ill scoped" false (well_scoped 0 (Var 0)); 206 check_raises "to_named raises" 207 (fun () -> ignore (convert_to_named (Lam (Var 2)))) 208 209let test_capture_avoidance () = 210 let parse s = 211 match Syntax.parse_result s with 212 | Ok t -> t 213 | Error m -> failwith m 214 in 215 let cases = 216 [ ("[x := z] \\z. x", "\\w. z", true); 217 ("[x := z] \\z. x", "\\z. z", false); 218 ("[x := q] \\x. x", "\\x. x", true); 219 ("[x := y] \\y. x y", "\\w. y w", true); 220 ("[x := f y] \\y. \\z. x z", "\\a. \\b. f y b", true); 221 ("[x := a] [y := b] x y", "a b", true) ] 222 in 223 List.iter 224 (fun (s1, s2, exp) -> 225 let t1 = parse s1 in 226 let t2 = parse s2 in 227 List.iter 228 (fun r -> 229 check_bool (s1 ^ " ~ " ^ s2) exp (check_with r t1 t2)) 230 reps) 231 cases 232 233let test_explicit_susp () = 234 let open Explicit_subst in 235 let t = of_named (Named.Subst ("x", Named.Var "z", Named.Lam ("z", Named.Var "x"))) in 236 let n = normalise t in 237 (match n with 238 | Lam (y, Var z) -> 239 check_bool "binder renamed" true (y <> "z"); 240 check_bool "body is free z" true (z = "z") 241 | _ -> failwith "expected renamed lambda"); 242 check_bool "susp agrees with normal" true 243 (alpha_equivalent_susp t (normalise t)) 244 245let () = 246 let tests = 247 [ ("corpus", test_corpus); 248 ("reflexivity", test_reflexivity); 249 ("symmetry", test_symmetry); 250 ("transitivity", test_transitivity); 251 ("fv preserved", test_fv_preserved); 252 ("roundtrip", test_roundtrip); 253 ("cross-rep normalise", test_cross_rep_normalise); 254 ("parse errors", test_parse_errors); 255 ("local closure", test_malformed); 256 ("de bruijn scope", test_debruijn_scope); 257 ("capture avoidance", test_capture_avoidance); 258 ("suspended subst", test_explicit_susp) ] 259 in 260 List.iter 261 (fun (name, f) -> 262 f (); 263 Printf.printf "ok %s\n" name) 264 tests