Alpha equivalence checker for arbitrary lambda terms
7

Configure Feed

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

ego / ocaml / bin / main.ml
7.8 kB 264 lines
1open Ego 2 3let reps_of_string = function 4 | "named" -> [ `Named ] 5 | "debruijn" -> [ `Debruijn ] 6 | "ln" -> [ `Ln ] 7 | "subst" -> [ `Subst ] 8 | "all" -> [ `Named; `Debruijn; `Ln; `Subst ] 9 | r -> failwith ("unknown representation: " ^ r) 10 11let rep_name = function 12 | `Named -> "named" 13 | `Debruijn -> "debruijn" 14 | `Ln -> "ln" 15 | `Subst -> "subst" 16 17let check_with rep t1 t2 = 18 match rep with 19 | `Named -> Named.alpha_equivalent t1 t2 20 | `Debruijn -> 21 Debruijn.alpha_equivalent 22 (Debruijn.convert_from_named t1) 23 (Debruijn.convert_from_named t2) 24 | `Ln -> 25 Locally_nameless.alpha_equivalent 26 (Locally_nameless.convert_from_named t1) 27 (Locally_nameless.convert_from_named t2) 28 | `Subst -> 29 Explicit_subst.alpha_equivalent 30 (Explicit_subst.convert_from_named t1) 31 (Explicit_subst.convert_from_named t2) 32 33let cmd_check t1s t2s rep = 34 match (Syntax.parse_result t1s, Syntax.parse_result t2s) with 35 | Error m, _ | _, Error m -> 36 Printf.printf "PARSE-ERROR: %s\n" m; 37 exit 2 38 | Ok t1, Ok t2 -> 39 List.iter 40 (fun r -> 41 let eq = check_with r t1 t2 in 42 Printf.printf "%s: %s\n" (rep_name r) (if eq then "EQUIV" else "NOT-EQUIV")) 43 (reps_of_string rep) 44 45let cmd_corpus path = 46 let entries = Json_io.load_corpus path in 47 let failures = ref 0 in 48 List.iter 49 (fun (e : Json_io.corpus_entry) -> 50 let results = 51 List.map (fun r -> (r, check_with r e.t1 e.t2)) 52 [ `Named; `Debruijn; `Ln; `Subst ] 53 in 54 List.iter 55 (fun (r, got) -> 56 match e.expected with 57 | Some exp when got <> exp -> 58 incr failures; 59 Printf.printf "FAIL %s [%s]: expected %s got %s\n" e.name 60 (rep_name r) 61 (if exp then "EQUIV" else "NOT-EQUIV") 62 (if got then "EQUIV" else "NOT-EQUIV") 63 | _ -> ()) 64 results; 65 let agree = 66 match results with 67 | (_, a) :: tl -> List.for_all (fun (_, b) -> b = a) tl 68 | [] -> true 69 in 70 if not agree then begin 71 incr failures; 72 let rs = 73 String.concat ", " 74 (List.map (fun (r, g) -> rep_name r ^ "=" ^ string_of_bool g) results) 75 in 76 Printf.printf "DISAGREE %s: %s\n" e.name rs 77 end) 78 entries; 79 if !failures > 0 then begin 80 Printf.printf "%d failure(s)\n" !failures; 81 exit 1 82 end 83 else Printf.printf "corpus: %d entries, 0 failures\n" (List.length entries) 84 85let now_us () = int_of_float (Unix.gettimeofday () *. 1e6) 86 87let median arr = 88 Array.sort compare arr; 89 arr.(Array.length arr / 2) 90 91type bench_result = { 92 alpha_us : int; 93 norm_us : int; 94 traversals : int; 95 max_depth : int; 96} 97 98let json_escape s = 99 let b = Buffer.create (String.length s + 2) in 100 String.iter 101 (function 102 | '"' -> Buffer.add_string b "\\\"" 103 | '\\' -> Buffer.add_string b "\\\\" 104 | '\n' -> Buffer.add_string b "\\n" 105 | '\r' -> Buffer.add_string b "\\r" 106 | '\t' -> Buffer.add_string b "\\t" 107 | c -> Buffer.add_char b c) 108 s; 109 Buffer.contents b 110 111let write_results path impl reps = 112 let oc = open_out_bin path in 113 Fun.protect 114 ~finally:(fun () -> close_out_noerr oc) 115 (fun () -> 116 Printf.fprintf oc "{\"%s\":{" (json_escape impl); 117 reps 118 |> List.iteri (fun ri (rep, benches) -> 119 if ri > 0 then output_char oc ','; 120 Printf.fprintf oc "\"%s\":{" (json_escape rep); 121 benches 122 |> List.iteri (fun bi (name, r) -> 123 if bi > 0 then output_char oc ','; 124 Printf.fprintf oc 125 "\"%s\":{\"alpha_us\":%d,\"norm_us\":%d,\"traversals\":%d,\"max_depth\":%d}" 126 (json_escape name) r.alpha_us r.norm_us r.traversals 127 r.max_depth); 128 output_char oc '}'); 129 output_string oc "}}") 130 131let bench_rep rep t1 t2 iters = 132 let alpha_ts = Array.make iters 0 in 133 let norm_ts = Array.make iters 0 in 134 let trav = ref 0 in 135 let depth = ref 0 in 136 for i = 0 to iters - 1 do 137 let t0 = now_us () in 138 let (r, c, d) = 139 match rep with 140 | `Named -> Named.alpha_equivalent_counted t1 t2 141 | `Debruijn -> 142 Debruijn.alpha_equivalent_counted 143 (Debruijn.convert_from_named t1) 144 (Debruijn.convert_from_named t2) 145 | `Ln -> 146 Locally_nameless.alpha_equivalent_counted 147 (Locally_nameless.convert_from_named t1) 148 (Locally_nameless.convert_from_named t2) 149 | `Subst -> 150 Explicit_subst.alpha_equivalent_counted 151 (Explicit_subst.convert_from_named t1) 152 (Explicit_subst.convert_from_named t2) 153 in 154 alpha_ts.(i) <- now_us () - t0; 155 ignore r; 156 if c > !trav then trav := c; 157 if d > !depth then depth := d; 158 let t0 = now_us () in 159 let (_, c, d) = 160 let drop (x, c, d) = (ignore x, c, d) in 161 match rep with 162 | `Named -> drop (Named.normalise_counted t1) 163 | `Debruijn -> 164 drop (Debruijn.normalise_counted (Debruijn.convert_from_named t1)) 165 | `Ln -> 166 drop 167 (Locally_nameless.normalise_counted 168 (Locally_nameless.convert_from_named t1)) 169 | `Subst -> 170 drop 171 (Explicit_subst.normalise_counted 172 (Explicit_subst.convert_from_named t1)) 173 in 174 norm_ts.(i) <- now_us () - t0; 175 if c > !trav then trav := c; 176 if d > !depth then depth := d 177 done; 178 { 179 alpha_us = median alpha_ts; 180 norm_us = median norm_ts; 181 traversals = !trav; 182 max_depth = !depth; 183 } 184 185let cmd_bench path iters out_path = 186 let benches = Json_io.load_bench path in 187 let reps = [ `Named; `Debruijn; `Ln; `Subst ] in 188 let pair_for name t = 189 if Filename.check_suffix name "-a" then 190 let base = String.sub name 0 (String.length name - 2) in 191 match List.assoc_opt (base ^ "-b") benches with Some other -> other | None -> t 192 else t 193 in 194 let rep_json = 195 List.map 196 (fun rep -> 197 let bench_json = 198 List.map 199 (fun (name, t) -> 200 let other = pair_for name t in 201 (name, bench_rep rep t other iters)) 202 benches 203 in 204 (rep_name rep, bench_json)) 205 reps 206 in 207 write_results out_path "ocaml" rep_json; 208 Printf.printf "bench: wrote %s\n" out_path 209 210let () = 211 let argv = Sys.argv in 212 let argc = Array.length argv in 213 if argc < 2 then begin 214 prerr_endline "usage: ego check <t1> <t2> [-r rep] | ego corpus <file> | ego bench <file> [--iters N] [--out file]"; 215 exit 2 216 end; 217 match argv.(1) with 218 | "check" -> 219 let rep = ref "named" in 220 let terms = ref [] in 221 let i = ref 2 in 222 while !i < argc do 223 (match argv.(!i) with 224 | "-r" -> 225 incr i; 226 rep := argv.(!i) 227 | s -> terms := s :: !terms); 228 incr i 229 done; 230 (match List.rev !terms with 231 | [ t1; t2 ] -> cmd_check t1 t2 !rep 232 | _ -> 233 prerr_endline "check requires exactly two terms"; 234 exit 2) 235 | "corpus" -> 236 if argc < 3 then begin 237 prerr_endline "corpus requires a file"; 238 exit 2 239 end; 240 cmd_corpus argv.(2) 241 | "bench" -> 242 let iters = ref 5 in 243 let out = ref "results.json" in 244 let file = ref None in 245 let i = ref 2 in 246 while !i < argc do 247 (match argv.(!i) with 248 | "--iters" -> 249 incr i; 250 iters := int_of_string argv.(!i) 251 | "--out" -> 252 incr i; 253 out := argv.(!i) 254 | s -> file := Some s); 255 incr i 256 done; 257 (match !file with 258 | Some f -> cmd_bench f !iters !out 259 | None -> 260 prerr_endline "bench requires a file"; 261 exit 2) 262 | _ -> 263 prerr_endline "unknown command"; 264 exit 2