|
| 1 | +From Stdlib Require Import Classes.RelationClasses Lists.List. |
| 2 | +From Ltac2 Require Import Ltac2 FMap Constr Printf List Fresh. |
| 3 | +Import ListNotations. |
| 4 | + |
| 5 | +(* A complete solver for StrictOrder relations *) |
| 6 | +(* solves by finding paths between edges of a graph induced *) |
| 7 | +(* by the relation assertions from hypotheses. *) |
| 8 | +(* Additionally proves contradictions by finding cycles. *) |
| 9 | + |
| 10 | +(* Finds an item and returns its index *) |
| 11 | +Ltac2 rec findi (f : 'a -> bool) (xs : 'a list) : int option := |
| 12 | + let rec aux f xs i := |
| 13 | + match xs with |
| 14 | + | [] => None |
| 15 | + | x :: xs => |
| 16 | + if f x then Some i else aux f xs (Int.add i 1) |
| 17 | + end in |
| 18 | + aux f xs 0. |
| 19 | + |
| 20 | +(* turns a term into some normalized string representation *) |
| 21 | +Ltac2 rec term_to_string (t: constr): string := |
| 22 | + let msg := fprintf "%t" (eval cbv in $t) in |
| 23 | + Message.to_string msg. |
| 24 | + |
| 25 | + |
| 26 | +Ltac2 rec apply_first (tacs: (unit -> constr) list): constr := |
| 27 | + match tacs with |
| 28 | + | [] => Control.zero (Tactic_failure (Some (fprintf "No tactic succeeded"))) |
| 29 | + | tac :: rest => |
| 30 | + Control.plus tac (fun _ => apply_first rest) |
| 31 | + end. |
| 32 | + |
| 33 | +(* constructs a term that is the transitive path between two elements *) |
| 34 | +(* the parameter is a list of hypothesis identifiers *) |
| 35 | +Ltac2 rec mk_trans (hyp_list: ident list): constr := |
| 36 | + match hyp_list with |
| 37 | + | [] => Control.throw (Tactic_failure (Some (fprintf "No hypotheses to apply for transitivity"))) |
| 38 | + | [h] => Control.hyp h |
| 39 | + | h :: rest => |
| 40 | + let h_constr := Control.hyp h in |
| 41 | + let rest_constr := mk_trans rest in |
| 42 | + Control.plus |
| 43 | + (fun _ => constr:(transitivity $h_constr $rest_constr)) |
| 44 | + (fun _ => |
| 45 | + Control.throw (Tactic_failure (Some ( |
| 46 | + fprintf "Failed to compose a transitive step. Does your relation implement %t?" open_constr:(Transitive))))) |
| 47 | + end. |
| 48 | + |
| 49 | +(* tries to interpret `h` as `rel a b` and returns a string representation of `a` and `b` *) |
| 50 | +Ltac2 get_elements (h: constr) (rel: constr): (string * string) option := |
| 51 | + match! h with |
| 52 | + | ?r ?a ?b => |
| 53 | + if Constr.equal r rel then |
| 54 | + Some (term_to_string a, term_to_string b) |
| 55 | + else None |
| 56 | + | _ => None |
| 57 | + end. |
| 58 | + |
| 59 | +(* tried to normalize a term into the form `rel a b` for some a and b *) |
| 60 | +Ltac2 normalize (h: constr) (rel: constr): constr option := |
| 61 | + Control.plus |
| 62 | + (fun _ => |
| 63 | + let a := open_constr:(_) in |
| 64 | + let b := open_constr:(_) in |
| 65 | + let rel_ab := open_constr:($rel $a $b) in |
| 66 | + let rel_ab_nf := eval cbv in $rel_ab in |
| 67 | + let h_nf := eval cbv in $h in |
| 68 | + Std.unify rel_ab_nf h_nf; |
| 69 | + Some constr:($rel $a $b)) |
| 70 | + (fun _ => None). |
| 71 | + |
| 72 | +(* rewrite all hypotheses and the goal using `normalize` *) |
| 73 | +Ltac2 normalize_relations (rel: constr): unit := |
| 74 | + let goal := Control.goal () in |
| 75 | + (match normalize goal rel with |
| 76 | + | Some h => |
| 77 | + let cl := { |
| 78 | + Std.on_hyps := Some []; |
| 79 | + Std.on_concl := Std.AllOccurrences |
| 80 | + } in |
| 81 | + Std.change None (fun _ => h) cl |
| 82 | + | None => () |
| 83 | + end); |
| 84 | + List.iter (fun ((id, _, tp)) => |
| 85 | + match normalize tp rel with |
| 86 | + | Some h => |
| 87 | + let cl := { |
| 88 | + Std.on_hyps := Some [(id, Std.AllOccurrences, Std.InHyp)]; |
| 89 | + Std.on_concl := Std.NoOccurrences |
| 90 | + } in |
| 91 | + Std.change None (fun _ => h) cl |
| 92 | + | None => () |
| 93 | + end |
| 94 | + ) (Control.hyps ()). |
| 95 | + |
| 96 | +(* constructs the graph from relations in hypotheses *) |
| 97 | +Ltac2 build_graph (rel: constr): (string, (string * ident) list) FMap.t := |
| 98 | + List.fold_left (fun acc ((id, _, tp)) => |
| 99 | + match get_elements tp rel with |
| 100 | + | Some (a_str, b_str) => |
| 101 | + match FMap.find_opt a_str acc with |
| 102 | + | Some existing => |
| 103 | + FMap.add a_str ((b_str, id) :: existing) acc |
| 104 | + | None => |
| 105 | + FMap.add a_str [(b_str, id)] acc |
| 106 | + end |
| 107 | + | None => acc |
| 108 | + end |
| 109 | + ) (FMap.empty FSet.Tags.string_tag) (Control.hyps ()). |
| 110 | + |
| 111 | +(* given a proof of a self-loop, proves anything by irreflexivity *) |
| 112 | +Ltac2 prove_contradiction self_loop: constr := |
| 113 | + let proof_false := |
| 114 | + Control.plus |
| 115 | + (fun _ => constr:(irreflexivity $self_loop)) |
| 116 | + (fun _ => |
| 117 | + Control.throw (Tactic_failure (Some ( |
| 118 | + fprintf "Failed to derive a contradiction. Does your relation implement %t?" open_constr:(Irreflexive))))) in |
| 119 | + open_constr:(ltac2:(ltac1:(exfalso); exact $proof_false)). |
| 120 | + |
| 121 | +(* in the graph `hyps_map` tries to find a path from `from` to `to` *) |
| 122 | +Ltac2 find_path hyps_map from to: constr := |
| 123 | + let rec dfs visited goal current path := |
| 124 | + match findi (String.equal current) visited with |
| 125 | + | Some idx => |
| 126 | + (* we have found a cycle *) |
| 127 | + let cycle_proof := mk_trans (List.rev (List.firstn (Int.add idx 1) path)) in |
| 128 | + prove_contradiction cycle_proof |
| 129 | + | None => |
| 130 | + let visited := current :: visited in |
| 131 | + match FMap.find_opt current hyps_map with |
| 132 | + | Some lst => |
| 133 | + let try_path (next, hyp_id) () := |
| 134 | + if String.equal next goal then |
| 135 | + mk_trans (List.rev (hyp_id :: path)) |
| 136 | + else |
| 137 | + dfs visited goal next (hyp_id :: path) in |
| 138 | + apply_first (List.map try_path lst) |
| 139 | + | None => |
| 140 | + Control.zero (Tactic_failure (Some (fprintf "No path found from %s to %s" current goal))) |
| 141 | + end |
| 142 | + end in |
| 143 | + dfs [] to from []. |
| 144 | + |
| 145 | +Ltac2 print_full_goal () := |
| 146 | + let hyps := Control.hyps () in |
| 147 | + List.iter (fun h => |
| 148 | + match h with |
| 149 | + | (id, body, ty) => |
| 150 | + (match body with |
| 151 | + | None => printf "%I : %t" id ty |
| 152 | + | Some b => printf "%I := %t : %t" id b ty |
| 153 | + end) |
| 154 | + end |
| 155 | + ) hyps; |
| 156 | + printf "----------------------------------------"; |
| 157 | + let g := Control.goal () in |
| 158 | + printf "|- %t" g. |
| 159 | + |
| 160 | +(* tries to find a cycle in the graph that will be a contradiction *) |
| 161 | +Ltac2 find_contradiction hyps_map: constr := |
| 162 | + let try_contra hyps_map name := |
| 163 | + prove_contradiction (find_path hyps_map name name) in |
| 164 | + let rec try_contras hyps_map names := |
| 165 | + match names with |
| 166 | + | name :: rest => |
| 167 | + Control.plus (fun _=> try_contra hyps_map name) (fun _ => try_contras hyps_map rest) |
| 168 | + | [] => |
| 169 | + Control.throw (Tactic_failure (Some (fprintf "No contradiction found"))) |
| 170 | + end in |
| 171 | + try_contras hyps_map (List.map fst (FMap.bindings hyps_map)). |
| 172 | + |
| 173 | +(* collect all hyps of the rel relation, put them in a map, do a dfs until we find the required one *) |
| 174 | +Ltac2 solve_strict_order rel: constr := |
| 175 | + let hyps_map := build_graph rel in |
| 176 | + match get_elements (Control.goal ()) rel with |
| 177 | + | Some (a_str, b_str) => |
| 178 | + Control.plus (fun _ => find_path hyps_map a_str b_str) (fun _ => find_contradiction hyps_map) |
| 179 | + | _ => |
| 180 | + find_contradiction hyps_map |
| 181 | + end. |
| 182 | + |
| 183 | +Ltac2 Notation "strict_order" rel(constr) := |
| 184 | + normalize_relations rel; |
| 185 | + Control.refine (fun _ => solve_strict_order rel). |
0 commit comments