summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Benjamin Barenblat <bbaren@google.com>2019-02-13 20:40:51 -0500
committerGravatar Benjamin Barenblat <bbaren@google.com>2019-02-13 20:40:51 -0500
commit8018e923c75eb5504310864f821972f794b7d554 (patch)
tree88a55f7c23fcbbea0ff80e406555292049b48dec /src
parent76f9b4cdc5693a6313961e2f91b39ba311857e72 (diff)
New upstream version 8.8.0+1.gbp069dc3bupstream/8.8.0+1.gbp069dc3bupstream
Diffstat (limited to 'src')
-rw-r--r--src/aac.ml479
-rw-r--r--src/aac_plugin.mlpack8
-rw-r--r--src/aac_rewrite.ml425
-rw-r--r--src/aac_rewrite.mli24
-rw-r--r--src/coq.ml594
-rw-r--r--src/coq.mli232
-rw-r--r--src/helper.ml41
-rw-r--r--src/helper.mli33
-rw-r--r--src/matcher.ml1293
-rw-r--r--src/matcher.mli189
-rw-r--r--src/print.ml104
-rw-r--r--src/print.mli23
-rw-r--r--src/search_monad.ml70
-rw-r--r--src/search_monad.mli42
-rw-r--r--src/theory.ml1153
-rw-r--r--src/theory.mli197
16 files changed, 4507 insertions, 0 deletions
diff --git a/src/aac.ml4 b/src/aac.ml4
new file mode 100644
index 0000000..e879a69
--- /dev/null
+++ b/src/aac.ml4
@@ -0,0 +1,79 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** aac -- Reasoning modulo associativity and commutativity *)
+
+DECLARE PLUGIN "aac_plugin"
+
+
+open Ltac_plugin
+open Pcoq.Prim
+open Pcoq.Constr
+open Stdarg
+open Aac_rewrite
+open Extraargs
+open Genarg
+
+ARGUMENT EXTEND aac_args
+TYPED AS ((string * int) list )
+PRINTED BY pr_aac_args
+| [ "at" integer(n) aac_args(q) ] -> [ add "at" n q ]
+| [ "subst" integer(n) aac_args(q) ] -> [ add "subst" n q ]
+| [ "in_right" aac_args(q) ] -> [ add "in_right" 0 q ]
+| [ ] -> [ [] ]
+END
+
+let pr_constro _ _ _ = fun b ->
+ match b with
+ | None -> Pp.str ""
+ | Some o -> Pp.str "<constr>"
+
+ARGUMENT EXTEND constro
+TYPED AS (constr option)
+PRINTED BY pr_constro
+| [ "[" constr(c) "]" ] -> [ Some c ]
+| [ ] -> [ None ]
+END
+
+TACTIC EXTEND _aac_reflexivity_
+| [ "aac_reflexivity" ] -> [ Proofview.V82.tactic aac_reflexivity ]
+END
+
+TACTIC EXTEND _aac_normalise_
+| [ "aac_normalise" ] -> [ Proofview.V82.tactic aac_normalise ]
+END
+
+TACTIC EXTEND _aac_rewrite_
+| [ "aac_rewrite" orient(l2r) constr(c) aac_args(args) constro(extra)] ->
+ [ Proofview.V82.tactic (fun gl -> aac_rewrite ?extra ~args ~l2r ~strict:true c gl) ]
+END
+
+TACTIC EXTEND _aac_pattern_
+| [ "aac_pattern" orient(l2r) constr(c) aac_args(args) constro(extra)] ->
+ [ Proofview.V82.tactic (fun gl -> aac_rewrite ?extra ~args ~l2r ~strict:true ~abort:true c gl) ]
+END
+
+TACTIC EXTEND _aac_instances_
+| [ "aac_instances" orient(l2r) constr(c) aac_args(args) constro(extra)] ->
+ [ Proofview.V82.tactic (fun gl -> aac_rewrite ?extra ~args ~l2r ~strict:true ~show:true c gl) ]
+END
+
+TACTIC EXTEND _aacu_rewrite_
+| [ "aacu_rewrite" orient(l2r) constr(c) aac_args(args) constro(extra)] ->
+ [ Proofview.V82.tactic (fun gl -> aac_rewrite ?extra ~args ~l2r ~strict:false c gl) ]
+END
+
+TACTIC EXTEND _aacu_pattern_
+| [ "aacu_pattern" orient(l2r) constr(c) aac_args(args) constro(extra)] ->
+ [ Proofview.V82.tactic (fun gl -> aac_rewrite ?extra ~args ~l2r ~strict:false ~abort:true c gl) ]
+END
+
+TACTIC EXTEND _aacu_instances_
+| [ "aacu_instances" orient(l2r) constr(c) aac_args(args) constro(extra)] ->
+ [ Proofview.V82.tactic (fun gl -> aac_rewrite ?extra ~args ~l2r ~strict:false ~show:true c gl) ]
+END
diff --git a/src/aac_plugin.mlpack b/src/aac_plugin.mlpack
new file mode 100644
index 0000000..ae61b0d
--- /dev/null
+++ b/src/aac_plugin.mlpack
@@ -0,0 +1,8 @@
+Coq
+Helper
+Search_monad
+Matcher
+Theory
+Print
+Aac_rewrite
+Aac
diff --git a/src/aac_rewrite.ml b/src/aac_rewrite.ml
new file mode 100644
index 0000000..697c15c
--- /dev/null
+++ b/src/aac_rewrite.ml
@@ -0,0 +1,425 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** aac_rewrite -- rewriting modulo A or AC*)
+
+open Ltac_plugin
+
+module Control = struct
+ let debug = false
+ let printing = false
+ let time = false
+end
+
+module Debug = Helper.Debug (Control)
+open Debug
+
+let time_tac msg tac =
+ if Control.time then Coq.tclTIME msg tac else tac
+
+let tac_or_exn tac exn msg = fun gl ->
+ try tac gl with e ->
+ let env = Tacmach.pf_env gl in
+ let sigma = Tacmach.project gl in
+ pr_constr env sigma "last goal" (Tacmach.pf_concl gl);
+ exn msg e
+
+
+let retype = Coq.retype
+
+open EConstr
+open Names
+open Proof_type
+
+
+(** aac_lift : the ideal type beyond AAC_rewrite.v/Lift
+
+ A base relation r, together with an equivalence relation, and the
+ proof that the former lifts to the later. Howver, we have to
+ ensure manually the invariant : r.carrier == e.carrier, and that
+ lift connects the two things *)
+type aac_lift =
+ {
+ r : Coq.Relation.t;
+ e : Coq.Equivalence.t;
+ lift : constr
+ }
+
+type rewinfo =
+ {
+ hypinfo : Coq.Rewrite.hypinfo;
+
+ in_left : bool; (** are we rewriting in the left hand-sie of the goal *)
+ pattern : constr;
+ subject : constr;
+ morph_rlt : Coq.Relation.t; (** the relation we look for in morphism *)
+ eqt : Coq.Equivalence.t; (** the equivalence we use as workbase *)
+ rlt : Coq.Relation.t; (** the relation in the goal *)
+ lifting: aac_lift
+ }
+
+let infer_lifting (rlt: Coq.Relation.t) (k : lift:aac_lift -> Proof_type.tactic) : Proof_type.tactic =
+ Coq.cps_evar_relation rlt.Coq.Relation.carrier (fun e ->
+ let lift_ty =
+ mkApp (Lazy.force Theory.Stubs.lift,
+ [|
+ rlt.Coq.Relation.carrier;
+ rlt.Coq.Relation.r;
+ e
+ |]
+ ) in
+ Coq.cps_resolve_one_typeclass ~error:(Pp.strbrk "Cannot infer a lifting")
+ lift_ty (fun lift goal ->
+ let x = rlt.Coq.Relation.carrier in
+ let r = rlt.Coq.Relation.r in
+ let eq = (Coq.nf_evar goal e) in
+ let equiv = Coq.lapp Theory.Stubs.lift_proj_equivalence [| x;r;eq; lift |] in
+ let lift =
+ {
+ r = rlt;
+ e = Coq.Equivalence.make x eq equiv;
+ lift = lift;
+ }
+ in
+ k ~lift:lift goal
+ ))
+
+(** Builds a rewinfo, once and for all *)
+let dispatch in_left (left,right,rlt) hypinfo (k: rewinfo -> Proof_type.tactic ) : Proof_type.tactic=
+ let l2r = hypinfo.Coq.Rewrite.l2r in
+ infer_lifting rlt
+ (fun ~lift ->
+ let eq = lift.e in
+ k {
+ hypinfo = hypinfo;
+ in_left = in_left;
+ pattern = if l2r then hypinfo.Coq.Rewrite.left else hypinfo.Coq.Rewrite.right;
+ subject = if in_left then left else right;
+ morph_rlt = Coq.Equivalence.to_relation eq;
+ eqt = eq;
+ lifting = lift;
+ rlt = rlt
+ }
+ )
+
+
+
+(** {1 Tactics} *)
+
+
+(** Build the reifiers, the reified terms, and the evaluation fonction *)
+let handle eqt zero envs (t : Matcher.Terms.t) (t' : Matcher.Terms.t) k =
+
+ let (x,r,_) = Coq.Equivalence.split eqt in
+ Theory.Trans.mk_reifier (Coq.Equivalence.to_relation eqt) zero envs
+ (fun (maps, reifier) ->
+ (* fold through a term and reify *)
+ let t = Theory.Trans.reif_constr_of_t reifier t in
+ let t' = Theory.Trans.reif_constr_of_t reifier t' in
+ (* Some letins *)
+ let eval = (mkApp (Lazy.force Theory.Stubs.eval, [|x;r; maps.Theory.Trans.env_sym; maps.Theory.Trans.env_bin; maps.Theory.Trans.env_units|])) in
+
+ Coq.cps_mk_letin "eval" eval (fun eval ->
+ Coq.cps_mk_letin "left" t (fun t ->
+ Coq.cps_mk_letin "right" t' (fun t' ->
+ k maps eval t t'))))
+
+(** [by_aac_reflexivity] is a sub-tactic that closes a sub-goal that
+ is merely a proof of equality of two terms modulo AAC *)
+let by_aac_reflexivity zero
+ eqt envs (t : Matcher.Terms.t) (t' : Matcher.Terms.t) : Proof_type.tactic =
+ handle eqt zero envs t t'
+ (fun maps eval t t' ->
+ let (x,r,e) = Coq.Equivalence.split eqt in
+ let decision_thm = Coq.lapp Theory.Stubs.decide_thm
+ [|x;r;e;
+ maps.Theory.Trans.env_sym;
+ maps.Theory.Trans.env_bin;
+ maps.Theory.Trans.env_units;
+ t;t';
+ |]
+ in
+ (* This convert is required to deal with evars in a proper
+ way *)
+ let convert_to = mkApp (r, [| mkApp (eval,[| t |]); mkApp (eval, [|t'|])|]) in
+ let convert = Proofview.V82.of_tactic (Tactics.convert_concl convert_to Constr.VMcast) in
+ let apply_tac = Proofview.V82.of_tactic (Tactics.apply decision_thm) in
+ (Tacticals.tclTHENLIST
+ [ retype decision_thm; retype convert_to;
+ convert ;
+ tac_or_exn apply_tac Coq.user_error (Pp.strbrk "unification failure");
+ tac_or_exn (time_tac "vm_norm" (Proofview.V82.of_tactic (Tactics.normalise_in_concl))) Coq.anomaly "vm_compute failure";
+ Tacticals.tclORELSE (Proofview.V82.of_tactic Tactics.reflexivity)
+ (Tacticals.tclFAIL 0 (Pp.str "Not an equality modulo A/AC"))
+ ])
+ )
+
+let by_aac_normalise zero lift ir t t' =
+ let eqt = lift.e in
+ let rlt = lift.r in
+ handle eqt zero ir t t'
+ (fun maps eval t t' ->
+ let (x,r,e) = Coq.Equivalence.split eqt in
+ let normalise_thm = Coq.lapp Theory.Stubs.lift_normalise_thm
+ [|x;r;e;
+ maps.Theory.Trans.env_sym;
+ maps.Theory.Trans.env_bin;
+ maps.Theory.Trans.env_units;
+ rlt.Coq.Relation.r;
+ lift.lift;
+ t;t';
+ |]
+ in
+ (* This convert is required to deal with evars in a proper
+ way *)
+ let convert_to = mkApp (rlt.Coq.Relation.r, [| mkApp (eval,[| t |]); mkApp (eval, [|t'|])|]) in
+ let convert = Proofview.V82.of_tactic (Tactics.convert_concl convert_to Constr.VMcast) in
+ let apply_tac = Proofview.V82.of_tactic (Tactics.apply normalise_thm) in
+ (Tacticals.tclTHENLIST
+ [ retype normalise_thm; retype convert_to;
+ convert ;
+ apply_tac;
+ ])
+
+ )
+
+(** A handler tactic, that reifies the goal, and infer the liftings,
+ and then call its continuation *)
+let aac_conclude
+ (k : constr -> aac_lift -> Theory.Trans.ir -> Matcher.Terms.t -> Matcher.Terms.t -> Proof_type.tactic) = fun goal ->
+
+ let (equation : types) = Tacmach.pf_concl goal in
+ let envs = Theory.Trans.empty_envs () in
+ let left, right,r =
+ match Coq.match_as_equation goal equation with
+ | None -> Coq.user_error @@ Pp.strbrk "The goal is not an applied relation"
+ | Some x -> x in
+ try infer_lifting r
+ (fun ~lift goal ->
+ let eq = Coq.Equivalence.to_relation lift.e in
+ let tleft,tright, goal = Theory.Trans.t_of_constr goal eq envs (left,right) in
+ let goal, ir = Theory.Trans.ir_of_envs goal eq envs in
+ let concl = Tacmach.pf_concl goal in
+ let env = Tacmach.pf_env goal in
+ let sigma = Tacmach.project goal in
+ let _ = pr_constr env sigma "concl "concl in
+ let evar_map = Tacmach.project goal in
+ Tacticals.tclTHEN
+ (Refiner.tclEVARS evar_map)
+ (k left lift ir tleft tright)
+ goal
+ )goal
+ with
+ | Not_found -> Coq.user_error @@ Pp.strbrk "No lifting from the goal's relation to an equivalence"
+
+open Tacexpr
+
+let aac_normalise = fun goal ->
+ let ids = Tacmach.pf_ids_of_hyps goal in
+ let mp = MPfile (DirPath.make (List.map Id.of_string ["AAC"; "AAC_tactics"])) in
+ let norm_tac = KerName.make2 mp (Label.make "internal_normalize") in
+ let norm_tac = Locus.ArgArg (None, norm_tac) in
+ Tacticals.tclTHENLIST
+ [
+ aac_conclude by_aac_normalise;
+ Proofview.V82.of_tactic (Tacinterp.eval_tactic (TacArg (None, TacCall (None, (norm_tac, [])))));
+ Proofview.V82.of_tactic (Tactics.keep ids)
+ ] goal
+
+let aac_reflexivity = fun goal ->
+ aac_conclude
+ (fun zero lift ir t t' ->
+ let x,r = Coq.Relation.split (lift.r) in
+ let r_reflexive = (Coq.Classes.mk_reflexive x r) in
+ Coq.cps_resolve_one_typeclass ~error:(Pp.strbrk "The goal's relation is not reflexive")
+ r_reflexive
+ (fun reflexive ->
+ let lift_reflexivity =
+ mkApp (Lazy.force (Theory.Stubs.lift_reflexivity),
+ [|
+ x;
+ r;
+ lift.e.Coq.Equivalence.eq;
+ lift.lift;
+ reflexive
+ |])
+ in
+ Tacticals.tclTHEN
+
+ (Tacticals.tclTHEN (retype lift_reflexivity) (Proofview.V82.of_tactic (Tactics.apply lift_reflexivity)))
+ (fun goal ->
+ let concl = Tacmach.pf_concl goal in
+ let env = Tacmach.pf_env goal in
+ let sigma = Tacmach.project goal in
+ let _ = pr_constr env sigma "concl "concl in
+ by_aac_reflexivity zero lift.e ir t t' goal)
+ )
+ ) goal
+
+(** A sub-tactic to lift the rewriting using Lift *)
+let lift_transitivity in_left (step:constr) preorder lifting (using_eq : Coq.Equivalence.t): tactic =
+ fun goal ->
+ (* catch the equation and the two members*)
+ let concl = Tacmach.pf_concl goal in
+ let (left, right, _ ) = match Coq.match_as_equation goal concl with
+ | Some x -> x
+ | None -> Coq.user_error @@ Pp.strbrk "The goal is not an equation"
+ in
+ let lift_transitivity =
+ let thm =
+ if in_left
+ then
+ Lazy.force Theory.Stubs.lift_transitivity_left
+ else
+ Lazy.force Theory.Stubs.lift_transitivity_right
+ in
+ mkApp (thm,
+ [|
+ preorder.Coq.Relation.carrier;
+ preorder.Coq.Relation.r;
+ using_eq.Coq.Equivalence.eq;
+ lifting;
+ step;
+ left;
+ right;
+ |])
+ in
+ Tacticals.tclTHENLIST
+ [ retype lift_transitivity;
+ Proofview.V82.of_tactic (Tactics.apply lift_transitivity)
+ ] goal
+
+
+(** The core tactic for aac_rewrite. Env and sigma are for the constr *)
+let core_aac_rewrite ?abort
+ rewinfo
+ subst
+ (by_aac_reflexivity : Matcher.Terms.t -> Matcher.Terms.t -> Proof_type.tactic)
+ env sigma (tr : constr) t left : tactic =
+ pr_constr env sigma "transitivity through" tr;
+ let tran_tac =
+ lift_transitivity rewinfo.in_left tr rewinfo.rlt rewinfo.lifting.lift rewinfo.eqt
+ in
+ Coq.Rewrite.rewrite ?abort rewinfo.hypinfo subst (fun rew ->
+ Tacticals.tclTHENSV
+ (tac_or_exn (tran_tac) Coq.anomaly "Unable to make the transitivity step")
+ (
+ if rewinfo.in_left
+ then [| by_aac_reflexivity left t ; rew |]
+ else [| by_aac_reflexivity t left ; rew |]
+ )
+ )
+
+exception NoSolutions
+
+
+(** Choose a substitution from a
+ [(int * Terms.t * Env.env Search_monad.m) Search_monad.m ] *)
+(* WARNING: Beware, since the printing function can change the order of the
+ printed monad, this function has to be updated accordingly *)
+let choose_subst subterm sol m=
+ try
+ let (depth,pat,envm) = match subterm with
+ | None -> (* first solution *)
+ List.nth ( List.rev (Search_monad.to_list m)) 0
+ | Some x ->
+ List.nth ( List.rev (Search_monad.to_list m)) x
+ in
+ let env = match sol with
+ None ->
+ List.nth ( (Search_monad.to_list envm)) 0
+ | Some x -> List.nth ( (Search_monad.to_list envm)) x
+ in
+ pat, env
+ with
+ | _ -> raise NoSolutions
+
+(** rewrite the constr modulo AC from left to right in the left member
+ of the goal *)
+let aac_rewrite_wrap ?abort rew ?(l2r=true) ?(show = false) ?(in_left=true) ?strict ~occ_subterm ~occ_sol ?extra : Proof_type.tactic = fun goal ->
+ let envs = Theory.Trans.empty_envs () in
+ let (concl : types) = Tacmach.pf_concl goal in
+ let (_,_,rlt) as concl =
+ match Coq.match_as_equation goal concl with
+ | None -> Coq.user_error @@ Pp.strbrk "The goal is not an applied relation"
+ | Some (left, right, rlt) -> left,right,rlt
+ in
+ let check_type x =
+ Tacmach.pf_conv_x goal x rlt.Coq.Relation.carrier
+ in
+ Coq.Rewrite.get_hypinfo rew ~l2r ?check_type:(Some check_type)
+ (fun hypinfo ->
+ dispatch in_left concl hypinfo
+ (
+ fun rewinfo ->
+ let goal =
+ match extra with
+ | Some t -> Theory.Trans.add_symbol goal rewinfo.morph_rlt envs (EConstr.to_constr (Tacmach.project goal) t)
+ | None -> goal
+ in
+ let pattern, subject, goal =
+ Theory.Trans.t_of_constr goal rewinfo.morph_rlt envs
+ (rewinfo.pattern , rewinfo.subject)
+ in
+ let goal, ir = Theory.Trans.ir_of_envs goal rewinfo.morph_rlt envs in
+
+ let units = Theory.Trans.ir_to_units ir in
+ let m = Matcher.subterm ?strict units pattern subject in
+ (* We sort the monad in increasing size of contet *)
+ let m = Search_monad.sort (fun (x,_,_) (y,_,_) -> x - y) m in
+
+ if show
+ then
+ Print.print rewinfo.morph_rlt ir m (hypinfo.Coq.Rewrite.context)
+
+ else
+ try
+ let pat,subst = choose_subst occ_subterm occ_sol m in
+ let tr_step = Matcher.Subst.instantiate subst pat in
+ let tr_step_raw = Theory.Trans.raw_constr_of_t ir rewinfo.morph_rlt [] tr_step in
+
+ let conv = (Theory.Trans.raw_constr_of_t ir rewinfo.morph_rlt (hypinfo.Coq.Rewrite.context)) in
+ let subst = Matcher.Subst.to_list subst in
+ let subst = List.map (fun (x,y) -> x, conv y) subst in
+ let by_aac_reflexivity = (by_aac_reflexivity rewinfo.subject rewinfo.eqt ir) in
+ let env = Tacmach.pf_env goal in
+ let sigma = Tacmach.project goal in
+ (* I'm not sure whether this is the right env/sigma for printing tr_step_raw *)
+ core_aac_rewrite ?abort rewinfo subst by_aac_reflexivity env sigma tr_step_raw tr_step subject
+
+ with
+ | NoSolutions ->
+ Tacticals.tclFAIL 0
+ (Pp.str (if occ_subterm = None && occ_sol = None
+ then "No matching occurrence modulo AC found"
+ else "No such solution"))
+ )
+ ) goal
+
+let get k l = try Some (List.assoc k l) with Not_found -> None
+
+let get_lhs l = try ignore (List.assoc "in_right" l); false with Not_found -> true
+
+let aac_rewrite ~args =
+ aac_rewrite_wrap ~occ_subterm:(get "at" args) ~occ_sol:(get "subst" args) ~in_left:(get_lhs args)
+
+
+
+let rec add k x = function
+ | [] -> [k,x]
+ | k',_ as ky::q ->
+ if k'=k then Coq.user_error @@ Pp.strbrk ("redondant argument ("^k^")")
+ else ky::add k x q
+
+let pr_aac_args _ _ _ l =
+ List.fold_left
+ (fun acc -> function
+ | ("in_right" as s,_) -> Pp.(++) (Pp.str s) acc
+ | (k,i) -> Pp.(++) (Pp.(++) (Pp.str k) (Pp.int i)) acc
+ ) (Pp.str "") l
+
diff --git a/src/aac_rewrite.mli b/src/aac_rewrite.mli
new file mode 100644
index 0000000..80134af
--- /dev/null
+++ b/src/aac_rewrite.mli
@@ -0,0 +1,24 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** aac_rewrite -- rewriting modulo A or AC*)
+
+val aac_reflexivity : Coq.goal_sigma -> Proof_type.goal list Evd.sigma
+val aac_normalise : Coq.goal_sigma -> Proof_type.goal list Evd.sigma
+
+val aac_rewrite :
+ args:(string * int) list ->
+ ?abort:bool ->
+ EConstr.constr ->
+ ?l2r:bool ->
+ ?show:bool ->
+ ?strict:bool -> ?extra:EConstr.t -> Proof_type.tactic
+
+val add : string -> 'a -> (string * 'a) list -> (string * 'a) list
+
+val pr_aac_args : 'a -> 'b -> 'c -> (string * int) list -> Pp.t
diff --git a/src/coq.ml b/src/coq.ml
new file mode 100644
index 0000000..ae1803a
--- /dev/null
+++ b/src/coq.ml
@@ -0,0 +1,594 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Interface with Coq *)
+
+open Constr
+open EConstr
+open Names
+open Context.Rel.Declaration
+
+(* The contrib name is used to locate errors when loading constrs *)
+let contrib_name = "aac_tactics"
+
+(* Getting constrs (primitive Coq terms) from existing Coq
+ libraries. *)
+let find_constant contrib dir s =
+ UnivGen.constr_of_global (Coqlib.find_reference contrib dir s)
+
+let init_constant_constr dir s = find_constant contrib_name dir s
+
+let init_constant dir s = EConstr.of_constr (find_constant contrib_name dir s)
+
+(* A clause specifying that the [let] should not try to fold anything
+ in the goal *)
+let nowhere =
+ { Locus.onhyps = Some [];
+ Locus.concl_occs = Locus.NoOccurrences
+ }
+
+let retype c gl =
+ let sigma, _ = Tacmach.pf_apply Typing.type_of gl c in
+ Refiner.tclEVARS sigma gl
+
+let cps_mk_letin
+ (name:string)
+ (c: constr)
+ (k : constr -> Proof_type.tactic)
+: Proof_type.tactic =
+ fun goal ->
+ let name = (Id.of_string name) in
+ let name = Tactics.fresh_id Id.Set.empty name goal in
+ let letin = (Proofview.V82.of_tactic (Tactics.letin_tac None (Name name) c None nowhere)) in
+ Tacticals.tclTHENLIST [retype c; letin; (k (mkVar name))] goal
+
+(** {1 General functions} *)
+
+type goal_sigma = Proof_type.goal Evd.sigma
+let goal_update (goal : goal_sigma) evar_map : goal_sigma=
+ let it = Tacmach.sig_it goal in
+ Tacmach.re_sig it evar_map
+
+let resolve_one_typeclass goal ty : constr*goal_sigma=
+ let env = Tacmach.pf_env goal in
+ let evar_map = Tacmach.project goal in
+ let em,c = Typeclasses.resolve_one_typeclass env evar_map ty in
+ c, (goal_update goal em)
+
+let cps_resolve_one_typeclass ?error : types -> (constr -> Proof_type.tactic) -> Proof_type.tactic = fun t k goal ->
+ Tacmach.pf_apply
+ (fun env em -> let em ,c =
+ try Typeclasses.resolve_one_typeclass env em t
+ with Not_found ->
+ begin match error with
+ | None -> CErrors.anomaly (Pp.str "Cannot resolve a typeclass : please report")
+ | Some x -> CErrors.user_err x
+ end
+ in
+ Tacticals.tclTHENLIST [Refiner.tclEVARS em; k c] goal
+ ) goal
+
+
+let nf_evar goal c : constr=
+ let evar_map = Tacmach.project goal in
+ Evarutil.nf_evar evar_map c
+
+ (* TODO: refactor following similar functions*)
+
+let evar_unit (gl : goal_sigma) (x : constr) : constr * goal_sigma =
+ let env = Tacmach.pf_env gl in
+ let evar_map = Tacmach.project gl in
+ let (em,x) = Evarutil.new_evar env evar_map x in
+ x,(goal_update gl em)
+
+let evar_binary (gl: goal_sigma) (x : constr) =
+ let env = Tacmach.pf_env gl in
+ let evar_map = Tacmach.project gl in
+ let ty = mkArrow x (mkArrow x x) in
+ let (em,x) = Evarutil.new_evar env evar_map ty in
+ x,( goal_update gl em)
+
+let evar_relation (gl: goal_sigma) (x: constr) =
+ let env = Tacmach.pf_env gl in
+ let evar_map = Tacmach.project gl in
+ let ty = mkArrow x (mkArrow x (mkSort Sorts.prop)) in
+ let (em,r) = Evarutil.new_evar env evar_map ty in
+ r,( goal_update gl em)
+
+let cps_evar_relation (x: constr) k = fun goal ->
+ Tacmach.pf_apply
+ (fun env em ->
+ let ty = mkArrow x (mkArrow x (mkSort Sorts.prop)) in
+ let (em, r) = Evarutil.new_evar env em ty in
+ Tacticals.tclTHENLIST [Refiner.tclEVARS em; k r] goal
+ ) goal
+
+let decomp_term sigma c = kind sigma (Termops.strip_outer_cast sigma c)
+
+let lapp c v = mkApp (Lazy.force c, v)
+
+(** {2 Bindings with Coq' Standard Library} *)
+module Std = struct
+(* Here we package the module to be able to use List, later *)
+
+module Pair = struct
+
+ let path = ["Coq"; "Init"; "Datatypes"]
+ let typ = lazy (init_constant path "prod")
+ let pair = lazy (init_constant path "pair")
+ let of_pair t1 t2 (x,y) =
+ mkApp (Lazy.force pair, [| t1; t2; x ; y|] )
+end
+
+module Bool = struct
+ let path = ["Coq"; "Init"; "Datatypes"]
+ let typ = lazy (init_constant path "bool")
+ let ctrue = lazy (init_constant path "true")
+ let cfalse = lazy (init_constant path "false")
+ let of_bool b =
+ if b then Lazy.force ctrue else Lazy.force cfalse
+end
+
+module Comparison = struct
+ let path = ["Coq"; "Init"; "Datatypes"]
+ let typ = lazy (init_constant path "comparison")
+ let eq = lazy (init_constant path "Eq")
+ let lt = lazy (init_constant path "Lt")
+ let gt = lazy (init_constant path "Gt")
+end
+
+module Leibniz = struct
+ let path = ["Coq"; "Init"; "Logic"]
+ let eq_refl = lazy (init_constant path "eq_refl")
+ let eq_refl ty x = lapp eq_refl [| ty;x|]
+end
+
+module Option = struct
+ let path = ["Coq"; "Init"; "Datatypes"]
+ let typ = lazy (init_constant path "option")
+ let some = lazy (init_constant path "Some")
+ let none = lazy (init_constant path "None")
+ let some t x = mkApp (Lazy.force some, [| t ; x|])
+ let none t = mkApp (Lazy.force none, [| t |])
+ let of_option t x = match x with
+ | Some x -> some t x
+ | None -> none t
+end
+
+module Pos = struct
+
+ let path = ["Coq" ; "Numbers"; "BinNums"]
+ let typ = lazy (init_constant path "positive")
+ let xI = lazy (init_constant path "xI")
+ let xO = lazy (init_constant path "xO")
+ let xH = lazy (init_constant path "xH")
+
+ (* A coq positive from an int *)
+ let of_int n =
+ let rec aux n =
+ begin match n with
+ | n when n < 1 -> assert false
+ | 1 -> Lazy.force xH
+ | n -> mkApp
+ (
+ (if n mod 2 = 0
+ then Lazy.force xO
+ else Lazy.force xI
+ ), [| aux (n/2)|]
+ )
+ end
+ in
+ aux n
+end
+
+module Nat = struct
+ let path = ["Coq" ; "Init"; "Datatypes"]
+ let typ = lazy (init_constant path "nat")
+ let _S = lazy (init_constant path "S")
+ let _O = lazy (init_constant path "O")
+ (* A coq nat from an int *)
+ let of_int n =
+ let rec aux n =
+ begin match n with
+ | n when n < 0 -> assert false
+ | 0 -> Lazy.force _O
+ | n -> mkApp
+ (
+ (Lazy.force _S
+ ), [| aux (n-1)|]
+ )
+ end
+ in
+ aux n
+end
+
+(** Lists from the standard library*)
+module List = struct
+ let path = ["Coq"; "Init"; "Datatypes"]
+ let typ = lazy (init_constant path "list")
+ let nil = lazy (init_constant path "nil")
+ let cons = lazy (init_constant path "cons")
+ let cons ty h t =
+ mkApp (Lazy.force cons, [| ty; h ; t |])
+ let nil ty =
+ (mkApp (Lazy.force nil, [| ty |]))
+ let rec of_list ty = function
+ | [] -> nil ty
+ | t::q -> cons ty t (of_list ty q)
+ let type_of_list ty =
+ mkApp (Lazy.force typ, [|ty|])
+end
+
+(** Morphisms *)
+module Classes =
+struct
+ let classes_path = ["Coq";"Classes"; ]
+ let morphism = lazy (init_constant (classes_path@["Morphisms"]) "Proper")
+ let equivalence = lazy (init_constant (classes_path@ ["RelationClasses"]) "Equivalence")
+ let reflexive = lazy (init_constant (classes_path@ ["RelationClasses"]) "Reflexive")
+ let transitive = lazy (init_constant (classes_path@ ["RelationClasses"]) "Transitive")
+
+ (** build the type [Equivalence ty rel] *)
+ let mk_equivalence ty rel =
+ mkApp (Lazy.force equivalence, [| ty; rel|])
+
+
+ (** build the type [Reflexive ty rel] *)
+ let mk_reflexive ty rel =
+ mkApp (Lazy.force reflexive, [| ty; rel|])
+
+ (** build the type [Proper rel t] *)
+ let mk_morphism ty rel t =
+ mkApp (Lazy.force morphism, [| ty; rel; t|])
+
+ (** build the type [Transitive ty rel] *)
+ let mk_transitive ty rel =
+ mkApp (Lazy.force transitive, [| ty; rel|])
+end
+
+module Relation = struct
+ type t =
+ {
+ carrier : constr;
+ r : constr;
+ }
+
+ let make ty r = {carrier = ty; r = r }
+ let split t = t.carrier, t.r
+end
+
+module Transitive = struct
+ type t =
+ {
+ carrier : constr;
+ leq : constr;
+ transitive : constr;
+ }
+ let make ty leq transitive = {carrier = ty; leq = leq; transitive = transitive}
+ let infer goal ty leq =
+ let ask = Classes.mk_transitive ty leq in
+ let transitive , goal = resolve_one_typeclass goal ask in
+ make ty leq transitive, goal
+ let from_relation goal rlt =
+ infer goal (rlt.Relation.carrier) (rlt.Relation.r)
+ let cps_from_relation rlt k =
+ let ty =rlt.Relation.carrier in
+ let r = rlt.Relation.r in
+ let ask = Classes.mk_transitive ty r in
+ cps_resolve_one_typeclass ask
+ (fun trans -> k (make ty r trans) )
+ let to_relation t =
+ {Relation.carrier = t.carrier; Relation.r = t.leq}
+
+end
+
+module Equivalence = struct
+ type t =
+ {
+ carrier : constr;
+ eq : constr;
+ equivalence : constr;
+ }
+ let make ty eq equivalence = {carrier = ty; eq = eq; equivalence = equivalence}
+ let infer goal ty eq =
+ let ask = Classes.mk_equivalence ty eq in
+ let equivalence , goal = resolve_one_typeclass goal ask in
+ make ty eq equivalence, goal
+ let from_relation goal rlt =
+ infer goal (rlt.Relation.carrier) (rlt.Relation.r)
+ let cps_from_relation rlt k =
+ let ty =rlt.Relation.carrier in
+ let r = rlt.Relation.r in
+ let ask = Classes.mk_equivalence ty r in
+ cps_resolve_one_typeclass ask (fun equiv -> k (make ty r equiv) )
+ let to_relation t =
+ {Relation.carrier = t.carrier; Relation.r = t.eq}
+ let split t =
+ t.carrier, t.eq, t.equivalence
+end
+end
+(**[ match_as_equation goal eqt] see [eqt] as an equation. An
+ optionnal rel-context can be provided to ensure that the term
+ remains typable*)
+let match_as_equation ?(context = Context.Rel.empty) goal equation : (constr*constr* Std.Relation.t) option =
+ let env = Tacmach.pf_env goal in
+ let env = EConstr.push_rel_context context env in
+ let evar_map = Tacmach.project goal in
+ begin
+ match decomp_term evar_map equation with
+ | App(c,ca) when Array.length ca >= 2 ->
+ let n = Array.length ca in
+ let left = ca.(n-2) in
+ let right = ca.(n-1) in
+ let r = (mkApp (c, Array.sub ca 0 (n - 2))) in
+ let carrier = Typing.unsafe_type_of env evar_map left in
+ let rlt =Std.Relation.make carrier r
+ in
+ Some (left, right, rlt )
+ | _ -> None
+ end
+
+
+(** {1 Tacticals} *)
+
+let tclTIME msg tac = fun gl ->
+ let u = Sys.time () in
+ let r = tac gl in
+ let _ = Feedback.msg_notice (Pp.str (Printf.sprintf "%s:%fs" msg (Sys.time ()-. u))) in
+ r
+
+let tclDEBUG msg tac = fun gl ->
+ let _ = Feedback.msg_debug (Pp.str msg) in
+ tac gl
+
+let tclPRINT tac = fun gl ->
+ let env = Tacmach.pf_env gl in
+ let sigma = Tacmach.project gl in
+ let _ = Feedback.msg_notice (Printer.pr_econstr_env env sigma (Tacmach.pf_concl gl)) in
+ tac gl
+
+
+(** {1 Error related mechanisms} *)
+(* functions to handle the failures of our tactic. Some should be
+ reported [anomaly], some are on behalf of the user [user_error]*)
+let anomaly msg =
+ CErrors.anomaly ~label:"[aac_tactics]" (Pp.str msg)
+
+let user_error msg =
+ CErrors.user_err Pp.(str "[aac_tactics] " ++ msg)
+
+let warning msg =
+ Feedback.msg_warning (Pp.str ("[aac_tactics]" ^ msg))
+
+
+(** {1 Rewriting tactics used in aac_rewrite} *)
+module Rewrite = struct
+(** Some informations about the hypothesis, with an (informal)
+ invariant:
+ - [typeof hyp = hyptype]
+ - [hyptype = forall context, body]
+ - [body = rel left right]
+
+*)
+
+type hypinfo =
+ {
+ hyp : constr; (** the actual constr corresponding to the hypothese *)
+ hyptype : constr; (** the type of the hypothesis *)
+ context : EConstr.rel_context; (** the quantifications of the hypothese *)
+ body : constr; (** the body of the type of the hypothesis*)
+ rel : Std.Relation.t; (** the relation *)
+ left : constr; (** left hand side *)
+ right : constr; (** right hand side *)
+ l2r : bool; (** rewriting from left to right *)
+ }
+
+let get_hypinfo c ~l2r ?check_type (k : hypinfo -> Proof_type.tactic) : Proof_type.tactic = fun goal ->
+ let ctype = Tacmach.pf_unsafe_type_of goal c in
+ let evar_map = Tacmach.project goal in
+ let (rel_context, body_type) = decompose_prod_assum evar_map ctype in
+ let rec check f e =
+ match decomp_term evar_map e with
+ | Rel i -> f (get_type (Context.Rel.lookup i rel_context))
+ | _ -> fold evar_map (fun acc x -> acc && check f x) true e
+ in
+ begin match check_type with
+ | None -> ()
+ | Some f ->
+ if not (check f body_type)
+ then user_error @@ Pp.strbrk "Unable to deal with higher-order or heterogeneous patterns";
+ end;
+ begin
+ match match_as_equation ~context:rel_context goal body_type with
+ | None ->
+ user_error @@ Pp.strbrk "The hypothesis is not an applied relation"
+ | Some (hleft,hright,hrlt) ->
+ k {
+ hyp = c;
+ hyptype = ctype;
+ body = body_type;
+ l2r = l2r;
+ context = rel_context;
+ rel = hrlt ;
+ left =hleft;
+ right = hright;
+ }
+ goal
+ end
+
+
+(* The problem : Given a term to rewrite of type [H :forall xn ... x1,
+ t], we have to instanciate the subset of [xi] of type
+ [carrier]. [subst : (int * constr)] is the mapping the debruijn
+ indices in [t] to the [constrs]. We need to handle the rest of the
+ indexes. Two ways :
+
+ - either using fresh evars and rewriting [H tn ?1 tn-2 ?2 ]
+ - either building a term like [fun 1 2 => H tn 1 tn-2 2]
+
+ Both these terms have the same type.
+*)
+
+
+(* Fresh evars for everyone (should be the good way to do this
+ recompose in Coq v8.4) *)
+let recompose_prod
+ (context : rel_context)
+ (subst : (int * constr) list)
+ env
+ em
+ : Evd.evar_map * constr list =
+ (* the last index of rel relevant for the rewriting *)
+ let min_n = List.fold_left
+ (fun acc (x,_) -> min acc x)
+ (List.length context) subst in
+ let rec aux context acc em n =
+ let _ = Printf.printf "%i\n%!" n in
+ match context with
+ | [] ->
+ env, em, acc
+ | t::q ->
+ let env, em, acc = aux q acc em (n+1) in
+ if n >= min_n
+ then
+ let em,x =
+ try em, List.assoc n subst
+ with | Not_found ->
+ let (em, r) = Evarutil.new_evar env em (Vars.substl acc (get_type t)) in
+ (em, r)
+ in
+ (EConstr.push_rel t env), em,x::acc
+ else
+ env,em,acc
+ in
+ let _,em,acc = aux context [] em 1 in
+ em, acc
+
+(* no fresh evars : instead, use a lambda abstraction around an
+ application to handle non-instantiated variables. *)
+
+let recompose_prod'
+ (context : rel_context)
+ (subst : (int *constr) list)
+ c
+ =
+ let rec popn pop n l =
+ if n <= 0 then l
+ else match l with
+ | [] -> []
+ | t::q -> pop t :: (popn pop (n-1) q)
+ in
+ let pop_rel_decl = map_type Termops.pop in
+ let rec aux sign n next app ctxt =
+ match sign with
+ | [] -> List.rev app, List.rev ctxt
+ | decl::sign ->
+ try let term = (List.assoc n subst) in
+ aux sign (n+1) next (term::app) (None :: ctxt)
+ with
+ | Not_found ->
+ let term = mkRel next in
+ aux sign (n+1) (next+1) (term::app) (Some decl :: ctxt)
+ in
+ let app,ctxt = aux context 1 1 [] [] in
+ (* substitutes in the context *)
+ let rec update ctxt app =
+ match ctxt,app with
+ | [],_ -> []
+ | _,[] -> []
+ | None :: sign, _ :: app ->
+ None :: update sign (List.map (Termops.pop) app)
+ | Some decl :: sign, _ :: app ->
+ Some (Vars.substl_decl app decl)::update sign (List.map (Termops.pop) app)
+ in
+ let ctxt = update ctxt app in
+ (* updates the rel accordingly, taking some care not to go to far
+ beyond: it is important not to lift indexes homogeneously, we
+ have to update *)
+ let rec update ctxt res n =
+ match ctxt with
+ | [] -> List.rev res
+ | None :: sign ->
+ (update (sign) (popn pop_rel_decl n res) 0)
+ | Some decl :: sign ->
+ update sign (decl :: res) (n+1)
+ in
+ let ctxt = update ctxt [] 0 in
+ let c = applist (c,List.rev app) in
+ let c = it_mkLambda_or_LetIn c ctxt in
+ c
+
+(* Version de Matthieu
+let subst_rel_context k cstr ctx =
+ let (_, ctx') =
+ List.fold_left (fun (k, ctx') (id, b, t) -> (succ k, (id, Option.map
+ (Term.substnl [cstr] k) b, Term.substnl [cstr] k t) :: ctx')) (k, [])
+ ctx in List.rev ctx'
+
+let recompose_prod' context subst c =
+ let len = List.length context in
+ let rec aux sign n next app ctxt =
+ match sign with
+ | [] -> List.rev app, List.rev ctxt
+ | decl::sign ->
+ try let term = (List.assoc n subst) in
+ aux (subst_rel_context 0 term sign) (pred n) (succ next)
+ (term::List.map (Term.lift (-1)) app) ctxt
+ with Not_found ->
+ let term = Term.mkRel (len - next) in
+ aux sign (pred n) (succ next) (term::app) (decl :: ctxt)
+ in
+ let app,ctxt = aux (List.rev context) len 0 [] [] in
+ Term.it_mkLambda_or_LetIn (Term.applistc c(app)) (List.rev ctxt)
+*)
+
+let build
+ (h : hypinfo)
+ (subst : (int *constr) list)
+ (k :constr -> Proof_type.tactic)
+ : Proof_type.tactic = fun goal ->
+ let c = recompose_prod' h.context subst h.hyp in
+ Tacticals.tclTHENLIST [k c] goal
+
+let build_with_evar
+ (h : hypinfo)
+ (subst : (int *constr) list)
+ (k :constr -> Proof_type.tactic)
+ : Proof_type.tactic
+ = fun goal ->
+ Tacmach.pf_apply
+ (fun env em ->
+ let evar_map, acc = recompose_prod h.context subst env em in
+ let c = applist (h.hyp,List.rev acc) in
+ Tacticals.tclTHENLIST [Refiner.tclEVARS evar_map; k c] goal
+ ) goal
+
+
+let rewrite ?(abort=false)hypinfo subst k =
+ build hypinfo subst
+ (fun rew ->
+ let tac =
+ if not abort then
+ Proofview.V82.of_tactic begin
+ Equality.general_rewrite_bindings
+ hypinfo.l2r
+ Locus.AllOccurrences
+ true (* tell if existing evars must be frozen for instantiation *)
+ false
+ (rew,Tactypes.NoBindings)
+ true
+ end
+ else
+ Tacticals.tclIDTAC
+ in k tac
+ )
+
+
+end
+
+include Std
diff --git a/src/coq.mli b/src/coq.mli
new file mode 100644
index 0000000..9cf0db7
--- /dev/null
+++ b/src/coq.mli
@@ -0,0 +1,232 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Interface with Coq where we define some handlers for Coq's API,
+ and we import several definitions from Coq's standard library.
+
+ This general purpose library could be reused by other plugins.
+
+ Some salient points:
+ - we use Caml's module system to mimic Coq's one, and avoid cluttering
+ the namespace;
+ - we also provide several handlers for standard coq tactics, in
+ order to provide a unified setting (we replace functions that
+ modify the evar_map by functions that operate on the whole
+ goal, and repack the modified evar_map with it).
+
+*)
+
+(** {2 Getting Coq terms from the environment} *)
+
+val init_constant_constr : string list -> string -> Constr.t
+val init_constant : string list -> string -> EConstr.constr
+
+(** {2 General purpose functions} *)
+
+type goal_sigma = Proof_type.goal Evd.sigma
+val resolve_one_typeclass : Proof_type.goal Evd.sigma -> EConstr.types -> EConstr.constr * goal_sigma
+val cps_resolve_one_typeclass: ?error:Pp.t -> EConstr.types -> (EConstr.constr -> Proof_type.tactic) -> Proof_type.tactic
+val nf_evar : goal_sigma -> EConstr.constr -> EConstr.constr
+val evar_unit :goal_sigma ->EConstr.constr -> EConstr.constr* goal_sigma
+val evar_binary: goal_sigma -> EConstr.constr -> EConstr.constr* goal_sigma
+val evar_relation: goal_sigma -> EConstr.constr -> EConstr.constr* goal_sigma
+val cps_evar_relation : EConstr.constr -> (EConstr.constr -> Proof_type.tactic) -> Proof_type.tactic
+(** [cps_mk_letin name v] binds the constr [v] using a letin tactic *)
+val cps_mk_letin : string -> EConstr.constr -> ( EConstr.constr -> Proof_type.tactic) -> Proof_type.tactic
+
+val retype : EConstr.constr -> Proof_type.tactic
+
+val decomp_term : Evd.evar_map -> EConstr.constr -> (EConstr.constr , EConstr.types, EConstr.ESorts.t, EConstr.EInstance.t) Constr.kind_of_term
+val lapp : EConstr.constr lazy_t -> EConstr.constr array -> EConstr.constr
+
+(** {2 Bindings with Coq' Standard Library} *)
+
+(** Coq lists *)
+module List:
+sig
+ (** [of_list ty l] *)
+ val of_list:EConstr.constr ->EConstr.constr list ->EConstr.constr
+
+ (** [type_of_list ty] *)
+ val type_of_list:EConstr.constr ->EConstr.constr
+end
+
+(** Coq pairs *)
+module Pair:
+sig
+ val typ:EConstr.constr lazy_t
+ val pair:EConstr.constr lazy_t
+ val of_pair : EConstr.constr -> EConstr.constr -> EConstr.constr * EConstr.constr -> EConstr.constr
+end
+
+module Bool : sig
+ val typ : EConstr.constr lazy_t
+ val of_bool : bool -> EConstr.constr
+end
+
+
+module Comparison : sig
+ val typ : EConstr.constr lazy_t
+ val eq : EConstr.constr lazy_t
+ val lt : EConstr.constr lazy_t
+ val gt : EConstr.constr lazy_t
+end
+
+module Leibniz : sig
+ val eq_refl : EConstr.types -> EConstr.constr -> EConstr.constr
+end
+
+module Option : sig
+ val typ : EConstr.constr lazy_t
+ val some : EConstr.constr -> EConstr.constr -> EConstr.constr
+ val none : EConstr.constr -> EConstr.constr
+ val of_option : EConstr.constr -> EConstr.constr option -> EConstr.constr
+end
+
+(** Coq positive numbers (pos) *)
+module Pos:
+sig
+ val typ:EConstr.constr lazy_t
+ val of_int: int ->EConstr.constr
+end
+
+(** Coq unary numbers (peano) *)
+module Nat:
+sig
+ val typ:EConstr.constr lazy_t
+ val of_int: int ->EConstr.constr
+end
+
+(** Coq typeclasses *)
+module Classes:
+sig
+ val mk_morphism: EConstr.constr -> EConstr.constr -> EConstr.constr -> EConstr.constr
+ val mk_equivalence: EConstr.constr -> EConstr.constr -> EConstr.constr
+ val mk_reflexive: EConstr.constr -> EConstr.constr -> EConstr.constr
+ val mk_transitive: EConstr.constr -> EConstr.constr -> EConstr.constr
+end
+
+module Relation : sig
+ type t = {carrier : EConstr.constr; r : EConstr.constr;}
+ val make : EConstr.constr -> EConstr.constr -> t
+ val split : t -> EConstr.constr * EConstr.constr
+end
+
+module Transitive : sig
+ type t =
+ {
+ carrier : EConstr.constr;
+ leq : EConstr.constr;
+ transitive : EConstr.constr;
+ }
+ val make : EConstr.constr -> EConstr.constr -> EConstr.constr -> t
+ val infer : goal_sigma -> EConstr.constr -> EConstr.constr -> t * goal_sigma
+ val from_relation : goal_sigma -> Relation.t -> t * goal_sigma
+ val cps_from_relation : Relation.t -> (t -> Proof_type.tactic) -> Proof_type.tactic
+ val to_relation : t -> Relation.t
+end
+
+module Equivalence : sig
+ type t =
+ {
+ carrier : EConstr.constr;
+ eq : EConstr.constr;
+ equivalence : EConstr.constr;
+ }
+ val make : EConstr.constr -> EConstr.constr -> EConstr.constr -> t
+ val infer : goal_sigma -> EConstr.constr -> EConstr.constr -> t * goal_sigma
+ val from_relation : goal_sigma -> Relation.t -> t * goal_sigma
+ val cps_from_relation : Relation.t -> (t -> Proof_type.tactic) -> Proof_type.tactic
+ val to_relation : t -> Relation.t
+ val split : t -> EConstr.constr * EConstr.constr * EConstr.constr
+end
+
+(** [match_as_equation ?context goal c] try to decompose c as a
+ relation applied to two terms. An optionnal rel-context can be
+ provided to ensure that the term remains typable *)
+val match_as_equation : ?context:EConstr.rel_context -> goal_sigma -> EConstr.constr -> (EConstr.constr * EConstr.constr * Relation.t) option
+
+(** {2 Some tacticials} *)
+
+(** time the execution of a tactic *)
+val tclTIME : string -> Proof_type.tactic -> Proof_type.tactic
+
+(** emit debug messages to see which tactics are failing *)
+val tclDEBUG : string -> Proof_type.tactic -> Proof_type.tactic
+
+(** print the current goal *)
+val tclPRINT : Proof_type.tactic -> Proof_type.tactic
+
+
+(** {2 Error related mechanisms} *)
+
+val anomaly : string -> 'a
+val user_error : Pp.t -> 'a
+val warning : string -> unit
+
+
+(** {2 Rewriting tactics used in aac_rewrite} *)
+
+module Rewrite : sig
+
+(** The rewriting tactics used in aac_rewrite, build as handlers of the usual [setoid_rewrite]*)
+
+
+(** {2 Datatypes} *)
+
+(** We keep some informations about the hypothesis, with an (informal)
+ invariant:
+ - [typeof hyp = typ]
+ - [typ = forall context, body]
+ - [body = rel left right]
+
+*)
+type hypinfo =
+ {
+ hyp : EConstr.constr; (** the actual constr corresponding to the hypothese *)
+ hyptype : EConstr.constr; (** the type of the hypothesis *)
+ context : EConstr.rel_context; (** the quantifications of the hypothese *)
+ body : EConstr.constr; (** the body of the hypothese*)
+ rel : Relation.t; (** the relation *)
+ left : EConstr.constr; (** left hand side *)
+ right : EConstr.constr; (** right hand side *)
+ l2r : bool; (** rewriting from left to right *)
+ }
+
+(** [get_hypinfo H l2r ?check_type k] analyse the hypothesis H, and
+ build the related hypinfo, in CPS style. Moreover, an optionnal
+ function can be provided to check the type of every free
+ variable of the body of the hypothesis. *)
+val get_hypinfo :EConstr.constr -> l2r:bool -> ?check_type:(EConstr.types -> bool) -> (hypinfo -> Proof_type.tactic) -> Proof_type.tactic
+
+(** {2 Rewriting with bindings}
+
+ The problem : Given a term to rewrite of type [H :forall xn ... x1,
+ t], we have to instanciate the subset of [xi] of type
+ [carrier]. [subst : (int * constr)] is the mapping the De Bruijn
+ indices in [t] to the [constrs]. We need to handle the rest of the
+ indexes. Two ways :
+
+ - either using fresh evars and rewriting [H tn ?1 tn-2 ?2 ]
+ - either building a term like [fun 1 2 => H tn 1 tn-2 2]
+
+ Both these terms have the same type.
+*)
+
+(** build the constr to rewrite, in CPS style, with lambda abstractions *)
+val build : hypinfo -> (int * EConstr.constr) list -> (EConstr.constr -> Proof_type.tactic) -> Proof_type.tactic
+
+(** build the constr to rewrite, in CPS style, with evars *)
+val build_with_evar : hypinfo -> (int * EConstr.constr) list -> (EConstr.constr -> Proof_type.tactic) -> Proof_type.tactic
+
+(** [rewrite ?abort hypinfo subst] builds the rewriting tactic
+ associated with the given [subst] and [hypinfo], and feeds it to
+ the given continuation. If [abort] is set to [true], we build
+ [tclIDTAC] instead. *)
+val rewrite : ?abort:bool -> hypinfo -> (int * EConstr.constr) list -> (Proof_type.tactic -> Proof_type.tactic) -> Proof_type.tactic
+end
diff --git a/src/helper.ml b/src/helper.ml
new file mode 100644
index 0000000..c1cae2a
--- /dev/null
+++ b/src/helper.ml
@@ -0,0 +1,41 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+module type CONTROL = sig
+ val debug : bool
+ val time : bool
+ val printing : bool
+end
+
+module Debug (X : CONTROL) = struct
+ open X
+ let debug x =
+ if debug then
+ Printf.printf "%s\n%!" x
+
+
+ let time f x fmt =
+ if time then
+ let t = Sys.time () in
+ let r = f x in
+ Printf.printf fmt (Sys.time () -. t);
+ r
+ else f x
+
+ let pr_constr env evd msg constr =
+ if printing then
+ ( Feedback.msg_notice (Pp.str (Printf.sprintf "=====%s====" msg));
+ Feedback.msg_notice (Printer.pr_econstr_env env evd constr);
+ )
+
+
+ let debug_exception msg e =
+ debug (msg ^ (Printexc.to_string e))
+
+
+end
diff --git a/src/helper.mli b/src/helper.mli
new file mode 100644
index 0000000..400e847
--- /dev/null
+++ b/src/helper.mli
@@ -0,0 +1,33 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Debugging functions, that can be triggered on a per-file base. *)
+
+module type CONTROL =
+sig
+ val debug : bool
+ val time : bool
+ val printing : bool
+end
+module Debug :
+ functor (X : CONTROL) ->
+sig
+ (** {!debug} prints the string and end it with a newline *)
+ val debug : string -> unit
+ val debug_exception : string -> exn -> unit
+
+ (** {!time} computes the time spent in a function, and then
+ print it using the given format *)
+ val time :
+ ('a -> 'b) -> 'a -> (float -> unit, out_channel, unit) format -> 'b
+
+ (** {!pr_constr} print a Coq constructor, that can be labelled
+ by a string *)
+ val pr_constr : Environ.env -> Evd.evar_map -> string -> EConstr.constr -> unit
+
+ end
diff --git a/src/matcher.ml b/src/matcher.ml
new file mode 100644
index 0000000..cc66961
--- /dev/null
+++ b/src/matcher.ml
@@ -0,0 +1,1293 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** This module defines our matching functions, modulo associativity
+ and commutativity (AAC).
+
+ The basic idea is to find a substitution [env] such that the
+ pattern [p] instantiated by [env] is equal to [t] modulo AAC.
+
+ We proceed by structural decomposition of the pattern, and try all
+ possible non-deterministic split of the subject when needed. The
+ function {!matcher} is limited to top-level matching, that is, the
+ subject must make a perfect match against the pattern ([x+x] do
+ not match [a+a+b] ). We use a search monad {!Search} to perform
+ non-deterministic splits in an almost transparent way. We also
+ provide a function {!subterm} for finding a match that is a
+ subterm modulo AAC of the subject. Therefore, we are able to solve
+ the aforementioned case [x+x] against [a+b+a].
+
+ This file is structured as follows. First, we define the search
+ monad. Then,we define the two representations of terms (one
+ representing the AST, and one in normal form ), and environments
+ from variables to terms. Then, we use these parts to solve
+ matching problem. Finally, we wrap this function {!matcher} into
+ {!subterm}
+*)
+
+
+module Control =
+struct
+ let debug = false
+ let time = false
+ let printing = false
+end
+
+module Debug = Helper.Debug (Control)
+open Debug
+
+module Search = Search_monad (* a handle *)
+
+type symbol = int
+type var = int
+type units = (symbol * symbol) list (* from AC/A symbols to the unit *)
+type ext_units =
+ {
+ unit_for : units;
+ is_ac : (symbol * bool) list
+ }
+
+let print_units units=
+ List.iter (fun (op,unit) -> Printf.printf "%i %i\t" op unit) units;
+ Printf.printf "\n%!"
+
+exception NoUnit
+
+let get_unit units op =
+ try List.assoc op units
+ with
+ | Not_found -> raise NoUnit
+
+let is_unit units op unit = List.mem (op,unit) units
+
+
+open Search
+
+type 'a mset = ('a * int) list
+let linear p =
+ let rec ncons t l = function
+ | 0 -> l
+ | n -> t::ncons t l (n-1)
+ in
+ let rec aux = function
+ [ ] -> []
+ | (t,n)::q -> let q = aux q in
+ ncons t q n
+ in aux p
+
+
+
+(** The module {!Terms} defines two different types for expressions.
+
+ - a public type {!Terms.t} that represent abstract syntax trees of
+ expressions with binary associative (and commutative) operators
+
+ - a private type {!Terms.nf_term} that represent an equivalence
+ class for terms that are equal modulo AAC. The constructions
+ functions on this type ensure the property that the term is in
+ normal form (that is, no sum can appear as a subterm of the same
+ sum, no trailing units, etc...).
+
+*)
+
+module Terms : sig
+
+ (** {1 Abstract syntax tree of terms}
+
+ Terms represented using this datatype are representation of the
+ AST of an expression. *)
+
+ type t =
+ Dot of (symbol * t * t)
+ | Plus of (symbol * t * t)
+ | Sym of (symbol * t array)
+ | Var of var
+ | Unit of symbol
+
+ val equal_aac : units -> t -> t -> bool
+ val size: t -> int
+
+ (** {1 Terms in normal form}
+
+ A term in normal form is the canonical representative of the
+ equivalence class of all the terms that are equal modulo
+ Associativity and Commutativity. Outside the {!Matcher} module,
+ one does not need to access the actual representation of this
+ type. *)
+
+
+ type nf_term = private
+ | TAC of symbol * nf_term mset
+ | TA of symbol * nf_term list
+ | TSym of symbol * nf_term list
+ | TUnit of symbol
+ | TVar of var
+
+ (** {2 Fresh variables: we provide some functions to pick some fresh variables with respect to a term} *)
+ val fresh_var_term : t -> int
+ val fresh_var_nfterm : nf_term -> int
+
+
+ (** {2 Constructors: we ensure that the terms are always
+ normalised
+
+ braibant - Fri 27 Aug 2010, 15:11
+ Moreover, we assure that we will not build empty sums or empty
+ products, leaving this task to the caller function.
+ }
+ *)
+ val mk_TAC : units -> symbol -> nf_term mset -> nf_term
+ val mk_TA : units -> symbol -> nf_term list -> nf_term
+ val mk_TSym : symbol -> nf_term list -> nf_term
+ val mk_TVar : var -> nf_term
+ val mk_TUnit : symbol -> nf_term
+
+ (** {2 Comparisons} *)
+ val nf_term_compare : nf_term -> nf_term -> int
+ val nf_equal : nf_term -> nf_term -> bool
+
+ (** {2 Printing function} *)
+ val sprint_nf_term : nf_term -> string
+
+ (** {2 Conversion functions} *)
+ val term_of_t : units -> t -> nf_term
+ val t_of_term : nf_term -> t (* we could return the units here *)
+end
+ = struct
+
+ type t =
+ Dot of (symbol * t * t)
+ | Plus of (symbol * t * t)
+ | Sym of (symbol * t array)
+ | Var of var
+ | Unit of symbol
+
+ let rec size = function
+ | Dot (_,x,y)
+ | Plus (_,x,y) -> size x+ size y + 1
+ | Sym (_,v)-> Array.fold_left (fun acc x -> size x + acc) 1 v
+ | _ -> 1
+
+
+ type nf_term =
+ | TAC of symbol * nf_term mset
+ | TA of symbol * nf_term list
+ | TSym of symbol * nf_term list
+ | TUnit of symbol
+ | TVar of var
+
+
+ (** {2 Picking fresh variables} *)
+
+ (** [fresh_var_term] picks a fresh variable with respect to a term *)
+ let fresh_var_term t =
+ let rec aux = function
+ | Dot (_,t1,t2)
+ | Plus (_,t1,t2) -> max (aux t1) (aux t2)
+ | Sym (_,v) -> Array.fold_left (fun acc x -> max acc (aux x)) 0 v
+ | Var v -> assert (v >= 0); v
+ | Unit _ -> 0
+ in
+ aux t
+
+ (** [fresh_var_nfterm] picks a fresh_variable with respect to a term *)
+ let fresh_var_nfterm t =
+ let rec aux = function
+ | TAC (_,l) -> List.fold_left (fun acc (x,_) -> max acc (aux x)) 0 l
+ | TA (_,l)
+ | TSym (_,l) -> List.fold_left (fun acc x -> max acc (aux x)) 0 l
+ | TVar v -> assert (v >= 0); v
+ | TUnit _ -> 0
+ in
+ aux t
+
+ (** {2 Constructors: we ensure that the terms are always
+ normalised} *)
+
+ (** {3 Pre constructors : These constructors ensure that sums and
+ products are not degenerated (no trailing units)} *)
+ let mk_TAC' units (s : symbol) l = match l with
+ | [] -> TUnit (get_unit units s )
+ | [_,0] -> assert false
+ | [t,1] -> t
+ | _ -> TAC (s,l)
+ let mk_TA' units (s : symbol) l = match l with
+ | [] -> TUnit (get_unit units s )
+ | [t] -> t
+ | _ -> TA (s,l)
+
+
+ (** {2 Comparison} *)
+
+ let nf_term_compare = Pervasives.compare
+ let nf_equal a b =
+ a = b
+
+ (** [merge_ac comp l1 l2] merges two lists of terms with coefficients
+ into one. Terms that are equal modulo the comparison function
+ [comp] will see their arities added. *)
+
+ (* This function could be improved by the use of sorted msets *)
+ let merge_ac (compare : 'a -> 'a -> int) (l : 'a mset) (l' : 'a mset) : 'a mset =
+ let rec aux l l'=
+ match l,l' with
+ | [], _ -> l'
+ | _, [] -> l
+ | (t,tar)::q, (t',tar')::q' ->
+ begin match compare t t' with
+ | 0 -> ( t,tar+tar'):: aux q q'
+ | -1 -> (t, tar):: aux q l'
+ | _ -> (t', tar'):: aux l q'
+ end
+ in aux l l'
+
+ (** [merge_map f l] uses the combinator [f] to combine the head of the
+ list [l] with the merge_maped tail of [l] *)
+ let rec merge_map (f : 'a -> 'b list -> 'b list) (l : 'a list) : 'b list =
+ match l with
+ | [] -> []
+ | t::q -> f t (merge_map f q)
+
+
+ (** This function has to deal with the arities *)
+ let merge (l : nf_term mset) (l' : nf_term mset) : nf_term mset=
+ merge_ac nf_term_compare l l'
+
+ let extract_A units s t =
+ match t with
+ | TA (s',l) when s' = s -> l
+ | TUnit u when is_unit units s u -> []
+ | _ -> [t]
+
+ let extract_AC units s (t,ar) : nf_term mset =
+ match t with
+ | TAC (s',l) when s' = s -> List.map (fun (x,y) -> (x,y*ar)) l
+ | TUnit u when is_unit units s u -> []
+ | _ -> [t,ar]
+
+
+ (** {3 Constructors of {!nf_term}}*)
+ let mk_TAC units (s : symbol) (l : (nf_term *int) list) =
+ mk_TAC' units s
+ (merge_map (fun u l -> merge (extract_AC units s u) l) l)
+ let mk_TA units s l =
+ mk_TA' units s
+ (merge_map (fun u l -> (extract_A units s u) @ l) l)
+ let mk_TSym s l = TSym (s,l)
+ let mk_TVar v = TVar v
+ let mk_TUnit s = TUnit s
+
+
+ (** {2 Printing function} *)
+ let print_binary_list (single : 'a -> string)
+ (unit : string)
+ (binary : string -> string -> string) (l : 'a list) =
+ let rec aux l =
+ match l with
+ [] -> unit
+ | [t] -> single t
+ | t::q ->
+ let r = aux q in
+ Printf.sprintf "%s" (binary (single t) r)
+ in
+ aux l
+
+ let print_symbol s =
+ match s with
+ | s, None -> Printf.sprintf "%i" s
+ | s , Some u -> Printf.sprintf "%i(unit %i)" s u
+
+ let sprint_ac (single : 'a -> string) (l : 'a mset) =
+ (print_binary_list
+ (fun (x,t) ->
+ if t = 1
+ then single x
+ else Printf.sprintf "%i*%s" t (single x)
+ )
+ "0"
+ (fun x y -> x ^ " , " ^ y)
+ l
+ )
+
+ let print_symbol single s l =
+ match l with
+ [] -> Printf.sprintf "%i" s
+ | _ ->
+ Printf.sprintf "%i(%s)"
+ s
+ (print_binary_list single "" (fun x y -> x ^ "," ^ y) l)
+
+
+ let print_ac single s l =
+ Printf.sprintf "[%s:AC]{%s}"
+ (string_of_int s )
+ (sprint_ac
+ single
+ l
+ )
+
+ let print_a single s l =
+ Printf.sprintf "[%s:A]{%s}"
+ (string_of_int s)
+ (print_binary_list single "1" (fun x y -> x ^ " , " ^ y) l)
+
+ let rec sprint_nf_term = function
+ | TSym (s,l) -> print_symbol sprint_nf_term s l
+ | TAC (s,l) -> print_ac sprint_nf_term s l
+ | TA (s,l) -> print_a sprint_nf_term s l
+ | TVar v -> Printf.sprintf "x%i" v
+ | TUnit s -> Printf.sprintf "unit%i" s
+
+
+
+
+ (** {2 Conversion functions} *)
+
+ (* rebuilds a tree out of a list, under the assumption that the list is not empty *)
+ let binary_of_list f comb l =
+ let l = List.rev l in
+ let rec aux = function
+ | [] -> assert false
+ | [t] -> f t
+ | t::q -> comb (aux q) (f t)
+ in
+ aux l
+
+ let term_of_t units : t -> nf_term =
+ let rec term_of_t = function
+ | Dot (s,l,r) ->
+ let l = term_of_t l in
+ let r = term_of_t r in
+ mk_TA units s [l;r]
+ | Plus (s,l,r) ->
+ let l = term_of_t l in
+ let r = term_of_t r in
+ mk_TAC units s [l,1;r,1]
+ | Unit x ->
+ mk_TUnit x
+ | Sym (s,t) ->
+ let t = Array.to_list t in
+ let t = List.map term_of_t t in
+ mk_TSym s t
+ | Var i ->
+ mk_TVar ( i)
+ in
+ term_of_t
+
+ let rec t_of_term : nf_term -> t = function
+ | TAC (s,l) ->
+ (binary_of_list
+ t_of_term
+ (fun l r -> Plus ( s,l,r))
+ (linear l)
+ )
+ | TA (s,l) ->
+ (binary_of_list
+ t_of_term
+ (fun l r -> Dot ( s,l,r))
+ l
+ )
+ | TSym (s,l) ->
+ let v = Array.of_list l in
+ let v = Array.map (t_of_term) v in
+ Sym ( s,v)
+ | TVar x -> Var x
+ | TUnit s -> Unit s
+
+
+ let equal_aac units x y =
+ nf_equal (term_of_t units x) (term_of_t units y)
+ end
+
+ (** Terms environments defined as association lists from variables to
+ terms in normal form {! Terms.nf_term} *)
+ module Subst : sig
+ type t
+
+ val find : t -> var -> Terms.nf_term option
+ val add : t -> var -> Terms.nf_term -> t
+ val empty : t
+ val instantiate : t -> Terms.t -> Terms.t
+ val sprint : t -> string
+ val to_list : t -> (var*Terms.t) list
+ end
+ =
+ struct
+ open Terms
+
+ (** Terms environments, with nf_terms, to avoid costly conversions
+ of {!Terms.nf_terms} to {!Terms.t}, that will be mostly discarded*)
+ type t = (var * nf_term) list
+
+ let find : t -> var -> nf_term option = fun t x ->
+ try Some (List.assoc x t) with | _ -> None
+ let add t x v = (x,v) :: t
+ let empty = []
+
+ let sprint (l : t) =
+ match l with
+ | [] -> Printf.sprintf "Empty environment\n"
+ | _ ->
+ let s = List.fold_left
+ (fun acc (x,y) ->
+ Printf.sprintf "%sX%i -> %s\n"
+ acc
+ x
+ (sprint_nf_term y)
+ )
+ ""
+ (List.rev l) in
+ Printf.sprintf "%s\n%!" s
+
+
+
+ (** [instantiate] is an homomorphism except for the variables*)
+ let instantiate (t: t) (x:Terms.t) : Terms.t =
+ let rec aux = function
+ | Unit _ as x -> x
+ | Sym (s,t) -> Sym (s,Array.map aux t)
+ | Plus (s,l,r) -> Plus (s, aux l, aux r)
+ | Dot (s,l,r) -> Dot (s, aux l, aux r)
+ | Var i ->
+ begin match find t i with
+ | None -> CErrors.user_err (Pp.strbrk "aac_tactics: instantiate failure")
+ | Some x -> t_of_term x
+ end
+ in aux x
+
+ let to_list t = List.map (fun (x,y) -> x,Terms.t_of_term y) t
+ end
+
+ (******************)
+ (* MATCHING UTILS *)
+ (******************)
+
+ open Terms
+
+ (** Since most of the folowing functions require an extra parameter,
+ the units, we package them in a module. This functor will then be
+ applied to a module containing the units, in the exported
+ functions. *)
+ module M (X : sig
+ val units : units
+ val is_ac : (symbol * bool) list
+ val strict : bool (* variables cannot be instantiated with units *)
+ end) = struct
+
+ open X
+
+ let print_units ()=
+ List.iter (fun (op,unit) -> Printf.printf "%i %i\t" op unit) units;
+ Printf.printf "\n%!"
+
+ let mk_TAC s l = mk_TAC units s l
+ let mk_TA s l = mk_TA units s l
+ let mk_TAC' s l =
+ try return( mk_TAC s l)
+ with _ -> fail ()
+ let mk_TA' s l =
+ try return( mk_TA s l)
+ with _ -> fail ()
+
+ (** First, we need to be able to perform non-deterministic choice of
+ term splitting to satisfy a pattern. Indeed, we want to show that:
+ (x+a*b)*c <= a*b*c
+ *)
+ let a_nondet_split_raw t : ('a list * 'a list) m =
+ let rec aux l l' =
+ match l' with
+ | [] ->
+ return ( l,[])
+ | t::q ->
+ return ( l,l' )
+ >>| aux (l @ [t]) q
+ in
+ aux [] t
+
+ (** Same as the previous [a_nondet_split], but split the list in 3
+ parts *)
+ let a_nondet_middle t : ('a list * 'a list * 'a list) m =
+ a_nondet_split_raw t >>
+ (fun (left, right) ->
+ a_nondet_split_raw left >>
+ (fun (left, middle) -> return (left, middle, right))
+ )
+
+ (** Non deterministic splits of ac lists *)
+ let dispatch f n =
+ let rec aux k =
+ if k = 0 then return (f n 0)
+ else return (f (n-k) k) >>| aux (k-1)
+ in
+ aux (n )
+
+ let add_with_arith x ar l =
+ if ar = 0 then l else (x,ar) ::l
+
+ let ac_nondet_split_raw (l : 'a mset) : ('a mset * 'a mset) m =
+ let rec aux = function
+ | [] -> return ([],[])
+ | (t,tar)::q ->
+ aux q
+ >>
+ (fun (left,right) ->
+ dispatch (fun arl arr ->
+ add_with_arith t arl left,
+ add_with_arith t arr right
+ )
+ tar
+ )
+ in
+ aux l
+
+ let a_nondet_split current t : (nf_term * nf_term list) m=
+ a_nondet_split_raw t
+ >>
+ (fun (l,r) ->
+ if strict && (l=[])
+ then fail()
+ else
+ mk_TA' current l >>
+ fun t -> return (t, r)
+ )
+
+ let ac_nondet_split current t : (nf_term * nf_term mset) m=
+ ac_nondet_split_raw t
+ >>
+ (fun (l,r) ->
+ if strict && (l=[])
+ then fail()
+ else
+ mk_TAC' current l >>
+ fun t -> return (t, r)
+ )
+
+
+ (** Try to affect the variable [x] to each left factor of [t]*)
+ let var_a_nondet_split env current x t =
+ a_nondet_split current t
+ >>
+ (fun (t,res) ->
+ return ((Subst.add env x t), res)
+ )
+
+ (** Try to affect variable [x] to _each_ subset of t. *)
+ let var_ac_nondet_split (current: symbol) env (x : var) (t : nf_term mset) : (Subst.t * (nf_term mset)) m =
+ ac_nondet_split current t
+ >>
+ (fun (t,res) ->
+ return ((Subst.add env x t), res)
+ )
+
+ (** See the term t as a given AC symbol. Unwrap the first constructor
+ if necessary *)
+ let get_AC (s : symbol) (t : nf_term) : (nf_term *int) list =
+ match t with
+ | TAC (s',l) when s' = s -> l
+ | TUnit u when is_unit units s u -> []
+ | _ -> [t,1]
+
+ (** See the term t as a given A symbol. Unwrap the first constructor
+ if necessary *)
+ let get_A (s : symbol) (t : nf_term) : nf_term list =
+ match t with
+ | TA (s',l) when s' = s -> l
+ | TUnit u when is_unit units s u -> []
+ | _ -> [t]
+
+ (** See the term [t] as an symbol [s]. Fail if it is not such
+ symbol. *)
+ let get_Sym s t =
+ match t with
+ | TSym (s',l) when s' = s -> return l
+ | _ -> fail ()
+
+ (*************)
+ (* A Removal *)
+ (*************)
+
+ (** We remove the left factor v in a term list. This function runs
+ linearly with respect to the size of the first pattern symbol *)
+
+ let left_factor current (v : nf_term) (t : nf_term list) =
+ let rec aux a b =
+ match a,b with
+ | t::q , t' :: q' when nf_equal t t' -> aux q q'
+ | [], q -> return q
+ | _, _ -> fail ()
+ in
+ match v with
+ | TA (s,l) when s = current -> aux l t
+ | TUnit u when is_unit units current u -> return t
+ | _ ->
+ begin match t with
+ | [] -> fail ()
+ | t::q ->
+ if nf_equal v t
+ then return q
+ else fail ()
+ end
+
+
+ (**************)
+ (* AC Removal *)
+ (**************)
+
+ (** {!pick_sym} gather all elements of a list that satisfies a
+ predicate, and combine them with the residual of the list. That
+ is, each element of the residual contains exactly one element less
+ than the original term.
+
+ TODO : This function not as efficient as it could be, using a
+ proper data-structure *)
+
+ let pick_sym (s : symbol) (t : nf_term mset ) =
+ let rec aux front back =
+ match back with
+ | [] -> fail ()
+ | (t,tar)::q ->
+ begin match t with
+ | TSym (s',v') when s = s' ->
+ let back =
+ if tar > 1
+ then (t,tar -1) ::q
+ else q
+ in
+ return (v' , List.rev_append front back )
+ >>| aux ((t,tar)::front) q
+ | _ -> aux ((t,tar)::front) q
+ end
+ in
+ aux [] t
+
+
+
+ (** We have to check if we are trying to remove a unit from a term. Could also be located in Terms*)
+ let is_unit_AC s t =
+ try nf_equal t (mk_TAC s [])
+ with | NoUnit -> false
+
+ let is_same_AC s t : nf_term mset option=
+ match t with
+ TAC (s',l) when s = s' -> Some l
+ | _ -> None
+
+ (** We want to remove the term [v] from the term list [t] under an AC
+ symbol *)
+ let single_AC_factor (s : symbol) (v : nf_term) v_ar (t : nf_term mset) : (nf_term mset) m =
+ let rec aux front back =
+ match back with
+ | [] -> fail ()
+ | (t,tar)::q ->
+ begin
+ if nf_equal v t
+ then
+ match () with
+ | _ when tar < v_ar -> fail ()
+ | _ when tar = v_ar -> return (List.rev_append front q)
+ | _ -> return (List.rev_append front ((t,tar-v_ar)::q))
+ else
+ aux ((t,tar) :: front) q
+ end
+ in
+ if is_unit_AC s v
+ then
+ return t
+ else
+ aux [] t
+
+ (* Remove a constant from a mset. If this constant is also a mset for
+ the same symbol, we remove every elements, one at a time (and we do
+ care of the arities) *)
+ let factor_AC (s : symbol) (v: nf_term) (t : nf_term mset) : ( nf_term mset ) m =
+ match is_same_AC s v with
+ | None -> single_AC_factor s v 1 t
+ | Some l ->
+ (* We are trying to remove an AC factor *)
+ List.fold_left (fun acc (v,v_ar) ->
+ acc >> (single_AC_factor s v v_ar)
+ )
+ (return t)
+ l
+
+
+(** [tri_fold f l acc] folds on the list [l] and give to f the
+ beginning of the list in reverse order, the considered element, and
+ the last part of the list
+
+ as an exemple, on the list [1;2;3;4], we get the trace
+ f () [] 1 [2; 3; 4]
+ f () [1] 2 [3; 4]
+ f () [2;1] 3 [ 4]
+ f () [3;2;1] 4 []
+
+ it is the duty of the user to reverse the front if needed
+*)
+
+let tri_fold f (l : 'a list) (acc : 'b)= match l with
+ [] -> acc
+ | _ ->
+ let _,_,acc = List.fold_left (fun acc (t : 'a) ->
+ let l,r,acc = acc in
+ let r = List.tl r in
+ t::l,r,f acc l t r
+ ) ([], l,acc) l
+ in acc
+
+(* let test = tri_fold (fun acc l t r -> (l,t,r) :: acc) [1;2;3;4] [] *)
+
+
+
+ (*****************************)
+ (* ENUMERATION DES POSITIONS *)
+ (*****************************)
+
+
+(** The pattern is a unit: we need to try to make it appear at each
+ position. We do not need to go further with a real matching, since
+ the match should be trivial. Hence, we proceed recursively to
+ enumerate all the positions. *)
+
+module Positions = struct
+
+
+ let ac (l: 'a mset) : ('a mset * 'a )m =
+ let rec aux = function
+ | [] -> assert false
+ | [t,1] -> return ([],t)
+ | [t,tar] -> return ([t,tar -1],t)
+ | (t,tar) as h :: q ->
+ (aux q >> (fun (c,x) -> return (h :: c,x)))
+ >>| (if tar > 1 then return ((t,tar-1) :: q,t) else return (q,t))
+ in
+ aux l
+
+ let ac' current (l: 'a mset) : ('a mset * 'a )m =
+ ac_nondet_split_raw l >>
+ (fun (l,r) ->
+ if l = [] || r = []
+ then fail ()
+ else
+ mk_TAC' current r >>
+ fun t -> return (l, t)
+ )
+
+ let a (l : 'a list) : ('a list * 'a * 'a list) m =
+ let rec aux left right : ('a list * 'a * 'a list) m =
+ match right with
+ | [] -> assert false
+ | [t] -> return (left,t,[])
+ | t::q ->
+ aux (left@[t]) q
+ >>| return (left,t,q)
+ in
+ aux [] l
+end
+
+let build_ac (current : symbol) (context : nf_term mset) (p : t) : t m=
+ if context = []
+ then return p
+ else
+ mk_TAC' current context >>
+ fun t -> return (Plus (current,t_of_term t,p))
+
+let build_a (current : symbol)
+ (left : nf_term list) (right : nf_term list) (p : t) : t m=
+ let right_pat p =
+ if right = []
+ then return p
+ else
+ mk_TA' current right >>
+ fun t -> return (Dot (current,p,t_of_term t))
+ in
+ let left_pat p=
+ if left = []
+ then return p
+ else
+ mk_TA' current left >>
+ fun t -> return (Dot (current,t_of_term t,p))
+ in
+ right_pat p >> left_pat >> (fun p -> return p)
+
+
+let conts (hole : t) (l : symbol list) p : t m =
+ let p = t_of_term p in
+ (* - aller chercher les symboles ac et les symboles a
+ - construire pour chaque
+ * * +
+ / \ / \ / \
+ 1 p p 1 p 1
+ *)
+ let ac,a = List.partition (fun s -> List.assoc s is_ac) l in
+ let acc = fail () in
+ let acc = List.fold_left
+ (fun acc s ->
+ acc >>| return (Plus (s,p,hole))
+ ) acc ac in
+ let acc =
+ List.fold_left
+ (fun acc s ->
+ acc >>| return (Dot (s,p,hole)) >>| return (Dot (s,hole,p))
+ ) acc a
+ in acc
+
+
+(**
+ Return the same thing as subterm :
+ - The size of the context
+ - The context
+ - A collection of substitutions (here == return Subst.empty)
+*)
+let unit_subterm (t : nf_term) (unit : symbol) (hole : t): (int * t * Subst.t m) m =
+ let symbols = List.fold_left
+ (fun acc (ac,u) -> if u = unit then ac :: acc else acc ) [] units
+ in
+ (* make a unit appear at each strict sub-position of the term*)
+ let rec positions (t : nf_term) : t m =
+ match t with
+ (* with a final unit at the end *)
+ | TAC (s,l) ->
+ let symbols' = List.filter (fun x -> x <> s) symbols in
+ (
+ ac_nondet_split_raw l >>
+ (fun (l,r) -> if l = [] || r = [] then fail () else
+ (
+ match r with
+ | [p,1] ->
+ positions p >>| conts hole symbols' p
+ | _ ->
+ mk_TAC' s r >> conts hole symbols'
+ ) >> build_ac s l ))
+ | TA (s,l) ->
+ let symbols' = List.filter (fun x -> x <> s) symbols in
+ (
+ (* first the other symbols, and then the more simple case of
+ this particular symbol *)
+ a_nondet_middle l >>
+ (fun (l,m,r) ->
+ (* meant to break the symmetry *)
+ if (l = [] && r = [])
+ then fail ()
+ else
+ (
+ match m with
+ | [p] ->
+ positions p >>| conts hole symbols' p
+ | _ ->
+ mk_TA' s m >> conts hole symbols'
+ ) >> build_a s l r ))
+ >>|
+ (
+ if List.mem s symbols then
+ begin match l with
+ | [a] -> assert false
+ | [a;b] -> build_a s [a] [b] (hole)
+ | _ ->
+ (* on ne construit que les elements interieurs,
+ d'ou la disymetrie *)
+ (Positions.a l >>
+ (fun (left,p,right) ->
+ if left = [] then fail () else
+ (build_a s left right (Dot (s,hole,t_of_term p)))))
+ end
+ else fail ()
+ )
+
+ | TSym (s,l) ->
+ tri_fold (fun acc l t r ->
+ ((positions t) >>
+ (fun (p) ->
+ let l = List.map t_of_term l in
+ let r = List.map t_of_term r in
+ return (Sym (s, Array.of_list (List.rev_append l (p::r)))) ))
+ >>|
+ (
+ conts hole symbols t >>
+ (fun (p) ->
+ let l = List.map t_of_term l in
+ let r = List.map t_of_term r in
+ return (Sym (s, Array.of_list (List.rev_append l (p::r)))) )
+ )
+ >>| acc
+ ) l (fail())
+ | TVar x -> assert false
+ | TUnit x when x = unit -> return (hole)
+ | TUnit x as t -> conts hole symbols t
+ in
+ (positions t
+ >>|
+ (match t with
+ | TSym _ -> conts hole symbols t
+ | TAC (s,l) -> conts hole symbols t
+ | TA (s,l) -> conts hole symbols t
+ | _ -> fail())
+ )
+ >> fun (p) -> return (Terms.size p,p,return Subst.empty)
+
+
+
+
+ (************)
+ (* Matching *)
+ (************)
+
+
+
+(** {!matching} is the generic matching judgement. Each time a
+ non-deterministic split is made, we have to come back to this one.
+
+ {!matchingSym} is used to match two applications that have the
+ same (free) head-symbol.
+
+ {!matchingAC} is used to match two sums (with the subtlety that
+ [x+y] matches [f a] which is a function application or [a*b] which
+ is a product).
+
+ {!matchingA} is used to match two products (with the subtlety that
+ [x*y] matches [f a] which is a function application, or [a+b]
+ which is a sum).
+
+
+*)
+
+let matching : Subst.t -> nf_term -> nf_term -> Subst.t Search.m=
+ let rec matching env (p : nf_term) (t: nf_term) : Subst.t Search.m=
+ match p with
+ | TAC (s,l) ->
+ let l = linear l in
+ matchingAC env s l (get_AC s t)
+ | TA (s,l) ->
+ matchingA env s l (get_A s t)
+ | TSym (s,l) ->
+ (get_Sym s t)
+ >> (fun t -> matchingSym env l t)
+ | TVar x ->
+ begin match Subst.find env x with
+ | None -> return (Subst.add env x t)
+ | Some v -> if nf_equal v t then return env else fail ()
+ end
+ | TUnit s ->
+ if nf_equal p t then return env else fail ()
+ and
+ matchingAC (env : Subst.t) (current : symbol) (l : nf_term list) (t : (nf_term *int) list) =
+ match l with
+ | TSym (s,v):: q ->
+ pick_sym s t
+ >> (fun (v',t') ->
+ matchingSym env v v'
+ >> (fun env -> matchingAC env current q t'))
+
+ | TAC (s,v)::q when s = current ->
+ assert false
+ | TVar x:: q -> (* This is an optimization *)
+ begin match Subst.find env x with
+ | None ->
+ (var_ac_nondet_split current env x t)
+ >> (fun (env,residual) -> matchingAC env current q residual)
+ | Some v ->
+ (factor_AC current v t)
+ >> (fun residual -> matchingAC env current q residual)
+ end
+ | TUnit s as v :: q -> (* this is an optimization *)
+ (factor_AC current v t) >>
+ (fun residual -> matchingAC env current q residual)
+ | h :: q ->(* PAC =/= curent or PA or unit for this symbol*)
+ (ac_nondet_split current t)
+ >>
+ (fun (t,right) ->
+ matching env h t
+ >>
+ (
+ fun env ->
+ matchingAC env current q right
+ )
+ )
+ | [] -> if t = [] then return env else fail ()
+ and
+ matchingA (env : Subst.t) (current : symbol) (l : nf_term list) (t : nf_term list) =
+ match l with
+ | TSym (s,v) :: l ->
+ begin match t with
+ | TSym (s',v') :: r when s = s' ->
+ (matchingSym env v v')
+ >> (fun env -> matchingA env current l r)
+ | _ -> fail ()
+ end
+ | TA (s,v) :: l when s = current ->
+ assert false
+ | TVar x :: l ->
+ begin match Subst.find env x with
+ | None ->
+ debug (Printf.sprintf "var %i (%s)" x
+ (let b = Buffer.create 21 in List.iter (fun t -> Buffer.add_string b ( Terms.sprint_nf_term t)) t; Buffer.contents b ));
+ var_a_nondet_split env current x t
+ >> (fun (env,residual)-> debug (Printf.sprintf "pl %i %i" x(List.length residual)); matchingA env current l residual)
+ | Some v ->
+ (left_factor current v t)
+ >> (fun residual -> matchingA env current l residual)
+ end
+ | TUnit s as v :: q -> (* this is an optimization *)
+ (left_factor current v t) >>
+ (fun residual -> matchingA env current q residual)
+ | h :: l ->
+ a_nondet_split current t
+ >> (fun (t,r) ->
+ matching env h t
+ >> (fun env -> matchingA env current l r)
+ )
+ | [] -> if t = [] then return env else fail ()
+ and
+ matchingSym (env : Subst.t) (l : nf_term list) (t : nf_term list) =
+ List.fold_left2
+ (fun acc p t -> acc >> (fun env -> matching env p t))
+ (return env)
+ l
+ t
+
+ in
+ fun env l r ->
+ let _ = debug (Printf.sprintf "pattern :%s\nterm :%s\n%!" (Terms.sprint_nf_term l) (Terms.sprint_nf_term r)) in
+ let m = matching env l r in
+ let _ = debug (Printf.sprintf "count %i" (Search.count m)) in
+ m
+
+
+(** [unitifiable p : Subst.t m] *)
+let unitifiable p : (symbol * Subst.t m) m =
+ let m = List.fold_left
+ (fun acc (_,unit) -> acc >>|
+ let m = matching Subst.empty p (mk_TUnit unit) in
+ if Search.is_empty m then
+ fail ()
+ else
+ begin
+ return (unit,m)
+ end
+ ) (fail ()) units
+ in
+ m
+;;
+
+let nullifiable p =
+ let nullable = not strict in
+ let has_unit s =
+ try let _ = get_unit units s in true
+ with NoUnit -> false
+ in
+ let rec aux = function
+ | TA (s,l) -> has_unit s && List.for_all (aux) l
+ | TAC (s,l) -> has_unit s && List.for_all (fun (x,n) -> aux x) l
+ | TSym _ -> false
+ | TVar _ -> nullable
+ | TUnit _ -> true
+ in aux p
+
+let unit_warning p ~nullif ~unitif =
+ assert ((Search.is_empty unitif) || nullif);
+ if not (Search.is_empty unitif)
+ then
+ begin
+ Feedback.msg_warning
+ (Pp.str
+ "[aac_tactics] This pattern can be instantiated to match units, some solutions can be missing");
+ end
+
+;;
+
+
+
+
+(***********)
+(* Subterm *)
+(***********)
+
+
+
+(** [subterm] solves a sub-term pattern matching.
+
+ This function is more high-level than {!matcher}, thus takes {!t}
+ as arguments rather than terms in normal form {!nf_term}.
+
+ We use three mutually recursive functions {!subterm},
+ {!subterm_AC}, {!subterm_A} to find the matching subterm, making
+ non-deterministic choices to split the term into a context and an
+ intersting sub-term. Intuitively, the only case in which we have to
+ go in depth is when we are left with a sub-term that is atomic.
+
+ Indeed, rewriting [H: b = c |- a+b+a = a+a+c], we do not want to
+ find recursively the sub-terms of [a+b] and [b+a], since they will
+ overlap with the sub-terms of [a+b+a].
+
+ We rebuild the context on the fly, leaving the variables in the
+ pattern uninstantiated. We do so in order to allow interaction
+ with the user, to choose the env.
+
+ Strange patterms like x*y*x can be instantiated by nothing, inside
+ a product. Therefore, we need to check that all the term is not
+ going into the context. With proper support for interaction with
+ the user, we should lift these tests. However, at the moment, they
+ serve as heuristics to return "interesting" matchings
+*)
+
+ let return_non_empty raw_p m =
+ if is_empty m
+ then
+ fail ()
+ else
+ return (raw_p ,m)
+
+ let subterm (raw_p:t) (raw_t:t): (int * t * Subst.t m) m=
+ let _ = debug (String.make 40 '=') in
+ let p = term_of_t units raw_p in
+ let t = term_of_t units raw_t in
+ let nullif = nullifiable p in
+ let unitif = unitifiable p in
+ let _ = unit_warning p ~nullif ~unitif in
+ let _ = debug (Printf.sprintf "%s" (Terms.sprint_nf_term p)) in
+ let _ = debug (Printf.sprintf "%s" (Terms.sprint_nf_term t)) in
+ let filter_right current right (p,m) =
+ if right = []
+ then return (p,m)
+ else
+ mk_TAC' current right >>
+ fun t -> return (Plus (current,p,t_of_term t),m)
+ in
+ let filter_middle current left right (p,m) =
+ let right_pat p =
+ if right = []
+ then return p
+ else
+ mk_TA' current right >>
+ fun t -> return (Dot (current,p,t_of_term t))
+ in
+ let left_pat p=
+ if left = []
+ then return p
+ else
+ mk_TA' current left >>
+ fun t -> return (Dot (current,t_of_term t,p))
+ in
+ right_pat p >> left_pat >> (fun p -> return (p,m))
+ in
+ let rec subterm (t:nf_term) : (t * Subst.t m) m=
+ match t with
+ | TAC (s,l) ->
+ ((ac_nondet_split_raw l) >>
+ (fun (left,right) ->
+ (subterm_AC s left) >> (filter_right s right)
+ ))
+ | TA (s,l) ->
+ (a_nondet_middle l) >>
+ (fun (left, middle, right) ->
+ (subterm_A s middle) >>
+ (filter_middle s left right)
+ )
+ | TSym (s, l) ->
+ let init =
+ return_non_empty raw_p (matching Subst.empty p t)
+ in
+ tri_fold (fun acc l t r ->
+ ((subterm t) >>
+ (fun (p,m) ->
+ let l = List.map t_of_term l in
+ let r = List.map t_of_term r in
+ let p = Sym (s, Array.of_list (List.rev_append l (p::r))) in
+ return (p,m)
+ )) >>| acc
+ ) l init
+ | TVar x -> assert false
+ (* this case is superseded by the later disjunction *)
+ | TUnit s -> fail ()
+
+ and subterm_AC s tl =
+ match tl with
+ [x,1] -> subterm x
+ | _ ->
+ mk_TAC' s tl >> fun t ->
+ return_non_empty raw_p (matching Subst.empty p t)
+ and subterm_A s tl =
+ match tl with
+ [x] -> subterm x
+ | _ ->
+ mk_TA' s tl >>
+ fun t ->
+ return_non_empty raw_p (matching Subst.empty p t)
+ in
+ match p with
+ | TUnit unit -> unit_subterm t unit raw_p
+ | _ when not (Search.is_empty unitif) ->
+ let unit_matches =
+ Search.fold
+ (fun (unit,inst) acc ->
+ Search.fold
+ (fun subst acc' ->
+ let m = unit_subterm t unit (Subst.instantiate subst raw_p)
+ in
+ m>>| acc'
+ )
+ inst
+ acc
+ )
+ unitif
+ (fail ())
+ in
+ let nullifies (t : Subst.t) =
+ List.for_all (fun (_,x) ->
+ List.exists (fun (_,y) -> Unit y = x ) units
+ ) (Subst.to_list t)
+ in
+ let nonunit_matches =
+ subterm t >>
+ (
+ fun (p,m) ->
+ let m = Search.filter (fun subst -> not( nullifies subst)) m in
+ if Search.is_empty m
+ then fail ()
+ else return (Terms.size p,p,m)
+ )
+ in
+ unit_matches >>| nonunit_matches
+
+ | _ -> (subterm t >> fun (p,m) -> return (Terms.size p,p,m))
+
+
+ end
+
+
+(* The functions we export, handlers for the previous ones. Some debug
+ information also *)
+let subterm ?(strict = false) units raw t =
+ let module M = M (struct
+ let is_ac = units.is_ac
+ let units = units.unit_for
+ let strict = strict
+ end) in
+ let sols = time (M.subterm raw) t "%fs spent in subterm (including matching)\n" in
+ debug
+ (Printf.sprintf "%i possible solution(s)\n"
+ (Search.fold (fun (_,_,envm) acc -> count envm + acc) sols 0));
+ sols
+
+
+let matcher ?(strict = false) units p t =
+ let module M = M (struct
+ let is_ac = units.is_ac
+ let units = units.unit_for
+ let strict = false
+ end) in
+ let units = units.unit_for in
+ let sols = time
+ (fun (p,t) ->
+ let p = (Terms.term_of_t units p) in
+ let t = (Terms.term_of_t units t) in
+ M.matching Subst.empty p t) (p,t)
+ "%fs spent in the matcher\n"
+ in
+ debug (Printf.sprintf "%i solutions\n" (count sols));
+ sols
+
diff --git a/src/matcher.mli b/src/matcher.mli
new file mode 100644
index 0000000..a6b6f46
--- /dev/null
+++ b/src/matcher.mli
@@ -0,0 +1,189 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Standalone module containing the algorithm for matching modulo
+ associativity and associativity and commutativity
+ (AAC). Additionnaly, some A or AC operators can have units (U).
+
+ This module could be reused outside of the Coq plugin.
+
+ Matching a pattern [p] against a term [t] modulo AACU boils down
+ to finding a substitution [env] such that the pattern [p]
+ instantiated with [env] is equal to [t] modulo AACU.
+
+ We proceed by structural decomposition of the pattern, trying all
+ possible non-deterministic splittings of the subject, when needed. The
+ function {!matcher} is limited to top-level matching, that is, the
+ subject must make a perfect match against the pattern ([x+x] does
+ not match [a+a+b] ).
+
+ We use a search monad {!Search_monad} to perform non-deterministic
+ choices in an almost transparent way.
+
+ We also provide a function {!subterm} for finding a match that is
+ a subterm of the subject modulo AACU. In particular, this function
+ gives a solution to the aforementioned case ([x+x] against
+ [a+b+a]).
+
+ On a slightly more involved level :
+ - it must be noted that we allow several AC/A operators to share
+ the same units, but that a given AC/A operator can have at most
+ one unit.
+
+ - if the pattern does not contain "hard" symbols (like constants,
+ function symbols, AC or A symbols without units), there can be
+ infinitely many subterms such that the pattern matches: it is
+ possible to build "subterms" modulo AAC and U that make the size
+ of the term increase (by making neutral elements appear in a
+ layered fashion). Hence, in this case, a warning is issued, and
+ some solutions are omitted.
+
+*)
+
+(** {2 Utility functions} *)
+
+type symbol = int
+type var = int
+
+(** Relationship between units and operators. This is a sparse
+ representation of a matrix of couples [(op,unit)] where [op] is
+ the index of the operation, and [unit] the index of the relevant
+ unit. We make the assumption that any operation has 0 or 1 unit,
+ and that operations can share a unit). *)
+
+type units =(symbol * symbol) list (* from AC/A symbols to the unit *)
+type ext_units =
+ {
+ unit_for : units; (* from AC/A symbols to the unit *)
+ is_ac : (symbol * bool) list
+ }
+
+
+(** The arguments of sums (or AC operators) are represented using finite multisets.
+ (Typically, [a+b+a] corresponds to [2.a+b], i.e. [Sum[a,2;b,1]]) *)
+type 'a mset = ('a * int) list
+
+(** [linear] expands a multiset into a simple list *)
+val linear : 'a mset -> 'a list
+
+(** Representations of expressions
+
+ The module {!Terms} defines two different types for expressions.
+ - a public type {!Terms.t} that represents abstract syntax trees
+ of expressions with binary associative and commutative operators
+ - a private type {!Terms.nf_term}, corresponding to a canonical
+ representation for the above terms modulo AACU. The construction
+ functions on this type ensure that these terms are in normal form
+ (that is, no sum can appear as a subterm of the same sum, no
+ trailing units, lists corresponding to multisets are sorted,
+ etc...).
+
+*)
+module Terms :
+sig
+
+ (** {2 Abstract syntax tree of terms and patterns}
+
+ We represent both terms and patterns using the following datatype.
+
+ Values of type [symbol] are used to index symbols. Typically,
+ given two associative operations [(^)] and [( * )], and two
+ morphisms [f] and [g], the term [f (a^b) (a*g b)] is represented
+ by the following value
+ [Sym(0,[| Dot(1, Sym(2,[||]), Sym(3,[||]));
+ Dot(4, Sym(2,[||]), Sym(5,[|Sym(3,[||])|])) |])]
+ where the implicit symbol environment associates
+ [f] to [0], [(^)] to [1], [a] to [2], [b] to [3], [( * )] to [4], and [g] to [5],
+
+ Accordingly, the following value, that contains "variables"
+ [Sym(0,[| Dot(1, Var x, Unit (1); Dot(4, Var x,
+ Sym(5,[|Sym(3,[||])|])) |])] represents the pattern [forall x, f
+ (x^1) (x*g b)]. The relationship between [1] and [( * )] is only
+ mentionned in the units table. *)
+
+ type t =
+ Dot of (symbol * t * t)
+ | Plus of (symbol * t * t)
+ | Sym of (symbol * t array)
+ | Var of var
+ | Unit of symbol
+
+ (** Test for equality of terms modulo AACU (relies on the following
+ canonical representation of terms). Note than two different
+ units of a same operator are not considered equal. *)
+ val equal_aac : units -> t -> t -> bool
+
+
+ (** {2 Normalised terms (canonical representation) }
+
+ A term in normal form is the canonical representative of the
+ equivalence class of all the terms that are equal modulo AACU.
+ This representation is only used internally; it is exported here
+ for the sake of completeness *)
+
+ type nf_term
+
+ (** {3 Comparisons} *)
+
+ val nf_term_compare : nf_term -> nf_term -> int
+ val nf_equal : nf_term -> nf_term -> bool
+
+ (** {3 Printing function} *)
+
+ val sprint_nf_term : nf_term -> string
+
+ (** {3 Conversion functions} *)
+
+ (** we have the following property: [a] and [b] are equal modulo AACU
+ iif [nf_equal (term_of_t a) (term_of_t b) = true] *)
+ val term_of_t : units -> t -> nf_term
+ val t_of_term : nf_term -> t
+
+end
+
+
+(** Substitutions (or environments)
+
+ The module {!Subst} contains infrastructure to deal with
+ substitutions, i.e., functions from variables to terms. Only a
+ restricted subsets of these functions need to be exported.
+
+ As expected, a particular substitution can be used to
+ instantiate a pattern.
+*)
+module Subst :
+sig
+ type t
+ val sprint : t -> string
+ val instantiate : t -> Terms.t-> Terms.t
+ val to_list : t -> (var*Terms.t) list
+end
+
+
+(** {2 Main functions exported by this module} *)
+
+(** [matcher p t] computes the set of solutions to the given top-level
+ matching problem ([p] is the pattern, [t] is the term). If the
+ [strict] flag is set, solutions where units are used to
+ instantiate some variables are excluded, unless this unit appears
+ directly under a function symbol (e.g., f(x) still matches f(1),
+ while x+x+y does not match a+b+c, since this would require to
+ assign 1 to x).
+*)
+val matcher : ?strict:bool -> ext_units -> Terms.t -> Terms.t -> Subst.t Search_monad.m
+
+(** [subterm p t] computes a set of solutions to the given
+ subterm-matching problem.
+
+ Return a collection of possible solutions (each with the
+ associated depth, the context, and the solutions of the matching
+ problem). The context is actually a {!Terms.t} where the variables
+ are yet to be instantiated by one of the associated substitutions
+*)
+val subterm : ?strict:bool -> ext_units -> Terms.t -> Terms.t -> (int * Terms.t * Subst.t Search_monad.m) Search_monad.m
+
diff --git a/src/print.ml b/src/print.ml
new file mode 100644
index 0000000..6800200
--- /dev/null
+++ b/src/print.ml
@@ -0,0 +1,104 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(* A very basic way to interact with the envs, to choose a possible
+ solution *)
+open Pp
+open Matcher
+open Context.Rel.Declaration
+open Names
+
+type named_env = (Name.t * Terms.t) list
+
+
+
+(** {pp_env} prints a substitution mapping names to terms, using the
+provided printer *)
+let pp_env pt : named_env -> Pp.t = fun env ->
+ List.fold_left
+ (fun acc (v,t) ->
+ begin match v with
+ | Names.Name s -> str (Printf.sprintf "%s: " (Id.to_string s))
+ | Names.Anonymous -> str ("_")
+ end
+ ++ pt t ++ str "; " ++ acc
+ )
+ (str "")
+ env
+
+(** {pp_envm} prints a collection of possible environments, and number
+them. This number must remain compatible with the parameters given to
+{!aac_rewrite} *)
+let pp_envm pt : named_env Search_monad.m -> Pp.t = fun m ->
+ let _,s =
+ Search_monad.fold
+ (fun env (n,acc) ->
+ n+1, h 0 (str (Printf.sprintf "%i:\t[" n) ++pp_env pt env ++ str "]") ++ fnl () :: acc
+ ) m (0,[])
+ in
+ List.fold_left (fun acc s -> s ++ acc) (str "") (s)
+
+let trivial_substitution envm =
+ match Search_monad.choose envm with
+ | None -> true (* assert false *)
+ | Some l -> l=[]
+
+(** {pp_all} prints a collection of possible contexts and related
+possibles substitutions, giving a number to each. This number must
+remain compatible with the parameters of {!aac_rewrite} *)
+let pp_all pt : (int * Terms.t * named_env Search_monad.m) Search_monad.m -> Pp.t = fun m ->
+ let _,s = Search_monad.fold
+ (fun (size,context,envm) (n,acc) ->
+ let s = str (Printf.sprintf "occurrence %i: transitivity through " n) in
+ let s = s ++ pt context ++ str "\n" in
+ let s = if trivial_substitution envm then s else
+ s ++ str (Printf.sprintf "%i possible(s) substitution(s)" (Search_monad.count envm) )
+ ++ fnl () ++ pp_envm pt envm
+ in
+ n+1, s::acc
+ ) m (0,[]) in
+ List.fold_left (fun acc s -> s ++ str "\n" ++ acc) (str "") (s)
+
+(** The main printing function. {!print} uses the debruijn_env the
+rename the variables, and rebuilds raw Coq terms (for the context, and
+the terms in the environment). In order to do so, it requires the
+information gathered by the {!Theory.Trans} module.*)
+let print rlt ir m (context : EConstr.rel_context) goal =
+ if Search_monad.count m = 0
+ then
+ (
+ Tacticals.tclFAIL 0 (Pp.str "No subterm modulo AC") goal
+ )
+ else
+ let _ = Feedback.msg_notice (Pp.str "All solutions:") in
+ let m = Search_monad.(>>) m
+ (fun (i,t,envm) ->
+ let envm = Search_monad.(>>) envm ( fun env ->
+ let l = Matcher.Subst.to_list env in
+ let l = List.sort (fun (n,_) (n',_) -> Pervasives.compare n n') l in
+ let l =
+ List.map (fun (v,t) ->
+ get_name (Context.Rel.lookup v context), t
+ ) l
+ in
+ Search_monad.return l
+ )
+ in
+ Search_monad.return (i,t,envm)
+ )
+ in
+ let m = Search_monad.sort (fun (x,_,_) (y,_,_) -> x - y) m in
+ let env = Tacmach.pf_env goal in
+ let sigma = Tacmach.project goal in
+ let _ = Feedback.msg_notice
+ (pp_all
+ (fun t -> Printer.pr_letype_env env sigma (Theory.Trans.raw_constr_of_t ir rlt context t)) m
+ )
+ in
+ Tacticals.tclIDTAC goal
+
diff --git a/src/print.mli b/src/print.mli
new file mode 100644
index 0000000..5ca6e27
--- /dev/null
+++ b/src/print.mli
@@ -0,0 +1,23 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Pretty printing functions we use for the aac_instances
+ tactic. *)
+
+
+(** The main printing function. {!print} uses the rel-context
+ to rename the variables, and rebuilds raw Coq terms (for the given
+ context, and the terms in the environment). In order to do so, it
+ requires the information gathered by the {!Theory.Trans} module.*)
+val print :
+ Coq.Relation.t ->
+ Theory.Trans.ir ->
+ (int * Matcher.Terms.t * Matcher.Subst.t Search_monad.m) Search_monad.m ->
+ EConstr.rel_context ->
+ Proof_type.tactic
+
diff --git a/src/search_monad.ml b/src/search_monad.ml
new file mode 100644
index 0000000..09a6455
--- /dev/null
+++ b/src/search_monad.ml
@@ -0,0 +1,70 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+type 'a m = | F of 'a
+ | N of 'a m list
+
+let fold (f : 'a -> 'b -> 'b) (m : 'a m) (acc : 'b) =
+ let rec aux acc = function
+ F x -> f x acc
+ | N l ->
+ (List.fold_left (fun acc x ->
+ match x with
+ | (N []) -> acc
+ | x -> aux acc x
+ ) acc l)
+ in
+ aux acc m
+
+
+let rec (>>) : 'a m -> ('a -> 'b m) -> 'b m =
+ fun m f ->
+ match m with
+ | F x -> f x
+ | N l ->
+ N (List.fold_left (fun acc x ->
+ match x with
+ | (N []) -> acc
+ | x -> (x >> f)::acc
+ ) [] l)
+
+let (>>|) (m : 'a m) (n :'a m) : 'a m = match (m,n) with
+ | N [],_ -> n
+ | _,N [] -> m
+ | F x, N l -> N (F x::l)
+ | N l, F x -> N (F x::l)
+ | x,y -> N [x;y]
+
+let return : 'a -> 'a m = fun x -> F x
+let fail : unit -> 'a m = fun () -> N []
+
+let sprint f m =
+ fold (fun x acc -> Printf.sprintf "%s\n%s" acc (f x)) m ""
+let rec count = function
+ | F _ -> 1
+ | N l -> List.fold_left (fun acc x -> acc+count x) 0 l
+
+let opt_comb f x y = match x with None -> f y | _ -> x
+
+let rec choose = function
+ | F x -> Some x
+ | N l -> List.fold_left (fun acc x ->
+ opt_comb choose acc x
+ ) None l
+
+let is_empty = fun x -> choose x = None
+
+let to_list m = (fold (fun x acc -> x::acc) m [])
+
+let sort f m =
+ N (List.map (fun x -> F x) (List.sort f (to_list m)))
+
+(* preserve the structure of the heap *)
+let filter f m =
+ fold (fun x acc -> (if f x then return x else fail ()) >>| acc) m (N [])
+
diff --git a/src/search_monad.mli b/src/search_monad.mli
new file mode 100644
index 0000000..7e2a910
--- /dev/null
+++ b/src/search_monad.mli
@@ -0,0 +1,42 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Search monad that allows to express non-deterministic algorithms
+ in a legible maner, or programs that solve combinatorial problems.
+
+ @see <http://spivey.oriel.ox.ac.uk/mike/search-jfp.pdf> the
+ inspiration of this module
+*)
+
+(** A data type that represent a collection of ['a] *)
+type 'a m
+
+ (** {2 Monadic operations} *)
+
+(** bind and return *)
+val ( >> ) : 'a m -> ('a -> 'b m) -> 'b m
+val return : 'a -> 'a m
+
+(** non-deterministic choice *)
+val ( >>| ) : 'a m -> 'a m -> 'a m
+
+(** failure *)
+val fail : unit -> 'a m
+
+(** folding through the collection *)
+val fold : ('a -> 'b -> 'b) -> 'a m -> 'b -> 'b
+
+(** {2 Derived facilities } *)
+
+val sprint : ('a -> string) -> 'a m -> string
+val count : 'a m -> int
+val choose : 'a m -> 'a option
+val to_list : 'a m -> 'a list
+val sort : ('a -> 'a -> int) -> 'a m -> 'a m
+val is_empty: 'a m -> bool
+val filter : ('a -> bool) -> 'a m -> 'a m
diff --git a/src/theory.ml b/src/theory.ml
new file mode 100644
index 0000000..7871fe4
--- /dev/null
+++ b/src/theory.ml
@@ -0,0 +1,1153 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Constr from the theory of this particular development
+
+ The inner-working of this module is highly correlated with the
+ particular structure of {b AAC_rewrite.v}, therefore, it should
+ be of little interest to most readers.
+*)
+open EConstr
+
+module Control = struct
+ let printing = true
+ let debug = false
+ let time = false
+end
+
+module Debug = Helper.Debug (Control)
+open Debug
+
+(** {1 HMap : Specialized Hashtables based on constr} *)
+
+
+ (* TODO module HMap = Hashtbl, du coup ? *)
+module HMap = Hashtbl.Make(Constr)
+
+let ac_theory_path = ["AAC_tactics"; "AAC"]
+let ac_util_path = ["AAC_tactics"; "Utils"]
+
+module Stubs = struct
+ let path = ac_theory_path@["Internal"]
+
+ (** The constants from the inductive type *)
+ let _Tty = lazy (Coq.init_constant path "T")
+ let vTty = lazy (Coq.init_constant path "vT")
+
+ let rsum = lazy (Coq.init_constant path "sum")
+ let rprd = lazy (Coq.init_constant path "prd")
+ let rsym = lazy (Coq.init_constant path "sym")
+ let runit = lazy (Coq.init_constant path "unit")
+
+ let vnil = lazy (Coq.init_constant path "vnil")
+ let vcons = lazy (Coq.init_constant path "vcons")
+ let eval = lazy (Coq.init_constant path "eval")
+
+
+ let decide_thm = lazy (Coq.init_constant path "decide")
+ let lift_normalise_thm = lazy (Coq.init_constant path "lift_normalise")
+
+ let lift =
+ lazy (Coq.init_constant ac_theory_path "AAC_lift")
+ let lift_proj_equivalence=
+ lazy (Coq.init_constant ac_theory_path "aac_lift_equivalence")
+ let lift_transitivity_left =
+ lazy(Coq.init_constant ac_theory_path "lift_transitivity_left")
+ let lift_transitivity_right =
+ lazy(Coq.init_constant ac_theory_path "lift_transitivity_right")
+ let lift_reflexivity =
+ lazy(Coq.init_constant ac_theory_path "lift_reflexivity")
+end
+
+module Classes = struct
+ module Associative = struct
+ let path = ac_theory_path
+ let typ = lazy (Coq.init_constant path "Associative")
+ let ty (rlt : Coq.Relation.t) (value : constr) =
+ mkApp (Lazy.force typ, [| rlt.Coq.Relation.carrier;
+ rlt.Coq.Relation.r;
+ value
+ |] )
+ let infer goal rlt value =
+ let ty = ty rlt value in
+ Coq.resolve_one_typeclass goal ty
+ end
+
+ module Commutative = struct
+ let path = ac_theory_path
+ let typ = lazy (Coq.init_constant path "Commutative")
+ let ty (rlt : Coq.Relation.t) (value : constr) =
+ mkApp (Lazy.force typ, [| rlt.Coq.Relation.carrier;
+ rlt.Coq.Relation.r;
+ value
+ |] )
+
+ end
+
+ module Proper = struct
+ let path = ac_theory_path @ ["Internal";"Sym"]
+ let typeof = lazy (Coq.init_constant path "type_of")
+ let relof = lazy (Coq.init_constant path "rel_of")
+ let mk_typeof : Coq.Relation.t -> int -> constr = fun rlt n ->
+ let x = rlt.Coq.Relation.carrier in
+ mkApp (Lazy.force typeof, [| x ; Coq.Nat.of_int n |])
+ let mk_relof : Coq.Relation.t -> int -> constr = fun rlt n ->
+ let (x,r) = Coq.Relation.split rlt in
+ mkApp (Lazy.force relof, [| x;r ; Coq.Nat.of_int n |])
+
+ let ty rlt op ar =
+ let typeof = mk_typeof rlt ar in
+ let relof = mk_relof rlt ar in
+ Coq.Classes.mk_morphism typeof relof op
+ let infer goal rlt op ar =
+ let ty = ty rlt op ar in
+ Coq.resolve_one_typeclass goal ty
+ end
+
+ module Unit = struct
+ let path = ac_theory_path
+ let typ = lazy (Coq.init_constant path "Unit")
+ let ty (rlt : Coq.Relation.t) (value : constr) (unit : constr)=
+ mkApp (Lazy.force typ, [| rlt.Coq.Relation.carrier;
+ rlt.Coq.Relation.r;
+ value;
+ unit
+ |] )
+ end
+
+end
+
+(* Non empty lists *)
+module NEList = struct
+ let path = ac_util_path
+ let typ = lazy (Coq.init_constant path "list")
+ let nil = lazy (Coq.init_constant path "nil")
+ let cons = lazy (Coq.init_constant path "cons")
+ let cons ty h t =
+ mkApp (Lazy.force cons, [| ty; h ; t |])
+ let nil ty x =
+ (mkApp (Lazy.force nil, [| ty ; x|]))
+ let rec of_list ty = function
+ | [] -> invalid_arg "NELIST"
+ | [x] -> nil ty x
+ | t::q -> cons ty t (of_list ty q)
+
+ let type_of_list ty =
+ mkApp (Lazy.force typ, [|ty|])
+end
+
+(** a [mset] is a ('a * pos) list *)
+let mk_mset ty (l : (constr * int) list) =
+ let pos = Lazy.force Coq.Pos.typ in
+ let pair (x : constr) (ar : int) =
+ Coq.Pair.of_pair ty pos (x, Coq.Pos.of_int ar)
+ in
+ let pair_ty = Coq.lapp Coq.Pair.typ [| ty ; pos|] in
+ let rec aux = function
+ | [ ] -> assert false
+ | [x,ar] -> NEList.nil pair_ty (pair x ar)
+ | (t,ar)::q -> NEList.cons pair_ty (pair t ar) (aux q)
+ in
+ aux l
+
+module Sigma = struct
+ let sigma = lazy (Coq.init_constant ac_theory_path "sigma")
+ let sigma_empty = lazy (Coq.init_constant ac_theory_path "sigma_empty")
+ let sigma_add = lazy (Coq.init_constant ac_theory_path "sigma_add")
+ let sigma_get = lazy (Coq.init_constant ac_theory_path "sigma_get")
+
+ let add ty n x map =
+ mkApp (Lazy.force sigma_add,[|ty; n; x ; map|])
+ let empty ty =
+ mkApp (Lazy.force sigma_empty,[|ty |])
+ let typ ty =
+ mkApp (Lazy.force sigma, [|ty|])
+
+ let to_fun ty null map =
+ mkApp (Lazy.force sigma_get, [|ty;null;map|])
+
+ let of_list ty null l =
+ match l with
+ | (_,t)::q ->
+ let map =
+ List.fold_left
+ (fun acc (i,t) ->
+ assert (i > 0);
+ add ty (Coq.Pos.of_int i) ( t) acc)
+ (empty ty)
+ q
+ in to_fun ty (t) map
+ | [] -> debug "of_list empty" ; to_fun ty (null) (empty ty)
+
+
+end
+
+
+module Sym = struct
+ type pack = {ar: Constr.t; value: Constr.t ; morph: Constr.t}
+ let path = ac_theory_path @ ["Internal";"Sym"]
+ let typ = lazy (Coq.init_constant path "pack")
+ let mkPack = lazy (Coq.init_constant path "mkPack")
+ let value = lazy (Coq.init_constant path "value")
+ let null = lazy (Coq.init_constant path "null")
+ let mk_pack (rlt: Coq.Relation.t) s =
+ let (x,r) = Coq.Relation.split rlt in
+ mkApp (Lazy.force mkPack, [|x;r; EConstr.of_constr s.ar;EConstr.of_constr s.value;EConstr.of_constr s.morph|])
+ let null rlt =
+ let x = rlt.Coq.Relation.carrier in
+ let r = rlt.Coq.Relation.r in
+ mkApp (Lazy.force null, [| x;r;|])
+
+ let mk_ty : Coq.Relation.t -> constr = fun rlt ->
+ let (x,r) = Coq.Relation.split rlt in
+ mkApp (Lazy.force typ, [| x; r|] )
+end
+
+module Bin =struct
+ type pack = {value : Constr.t;
+ compat : Constr.t;
+ assoc : Constr.t;
+ comm : Constr.t option;
+ }
+
+ let path = ac_theory_path @ ["Internal";"Bin"]
+ let typ = lazy (Coq.init_constant path "pack")
+ let mkPack = lazy (Coq.init_constant path "mk_pack")
+
+ let mk_pack: Coq.Relation.t -> pack -> constr = fun (rlt) s ->
+ let (x,r) = Coq.Relation.split rlt in
+ let comm_ty = Classes.Commutative.ty rlt (EConstr.of_constr s.value) in
+ mkApp (Lazy.force mkPack , [| x ; r;
+ EConstr.of_constr s.value;
+ EConstr.of_constr s.compat ;
+ EConstr.of_constr s.assoc;
+ Coq.Option.of_option comm_ty (Option.map EConstr.of_constr s.comm)
+ |])
+ let mk_ty : Coq.Relation.t -> constr = fun rlt ->
+ let (x,r) = Coq.Relation.split rlt in
+ mkApp (Lazy.force typ, [| x; r|] )
+end
+
+module Unit = struct
+ let path = ac_theory_path @ ["Internal"]
+ let unit_of_ty = lazy (Coq.init_constant path "unit_of")
+ let unit_pack_ty = lazy (Coq.init_constant path "unit_pack")
+ let mk_unit_of = lazy (Coq.init_constant path "mk_unit_for")
+ let mk_unit_pack = lazy (Coq.init_constant path "mk_unit_pack")
+
+ type unit_of =
+ {
+ uf_u : Constr.t; (* u *)
+ uf_idx : Constr.t;
+ uf_desc : Constr.t; (* Unit R (e_bin uf_idx) u *)
+ }
+
+ type pack = {
+ u_value : constr; (* X *)
+ u_desc : (unit_of) list (* unit_of u_value *)
+ }
+
+ let ty_unit_of rlt e_bin u =
+ let (x,r) = Coq.Relation.split rlt in
+ mkApp ( Lazy.force unit_of_ty, [| x; r; e_bin; u |])
+
+ let ty_unit_pack rlt e_bin =
+ let (x,r) = Coq.Relation.split rlt in
+ mkApp (Lazy.force unit_pack_ty, [| x; r; e_bin |])
+
+ let mk_unit_of rlt e_bin u unit_of =
+ let (x,r) = Coq.Relation.split rlt in
+ mkApp (Lazy.force mk_unit_of , [| x;
+ r;
+ e_bin ;
+ u;
+ EConstr.of_constr unit_of.uf_idx;
+ EConstr.of_constr unit_of.uf_desc
+ |])
+
+ let mk_pack rlt e_bin pack : constr =
+ let (x,r) = Coq.Relation.split rlt in
+ let ty = ty_unit_of rlt e_bin pack.u_value in
+ let mk_unit_of = mk_unit_of rlt e_bin pack.u_value in
+ let u_desc =Coq.List.of_list ( ty ) (List.map mk_unit_of pack.u_desc) in
+ mkApp (Lazy.force mk_unit_pack, [|x;r;
+ e_bin ;
+ pack.u_value;
+ u_desc
+ |])
+
+ let default x : pack =
+ { u_value = x;
+ u_desc = []
+ }
+
+end
+
+let anomaly msg =
+ CErrors.anomaly ~label:"aac_tactics" (Pp.str msg)
+
+let user_error msg =
+ CErrors.user_err Pp.(strbrk "aac_tactics: " ++ msg)
+
+module Trans = struct
+
+ (** {1 From Coq to Abstract Syntax Trees (AST)}
+
+ This module provides facilities to interpret a Coq term with
+ arbitrary operators as an abstract syntax tree. Considering an
+ application, we try to infer instances of our classes.
+
+ We consider that [A] operators are coarser than [AC] operators,
+ which in turn are coarser than raw morphisms. That means that
+ [List.append], of type [(A : Type) -> list A -> list A -> list
+ A] can be understood as an [A] operator.
+
+ During this reification, we gather some informations that will
+ be used to rebuild Coq terms from AST ( type {!envs})
+
+ We use a main hash-table from [constr] to [pack], in order to
+ discriminate the various constructors. All these are mixed in
+ order to improve on the number of comparisons in the tables *)
+
+ (* 'a * (unit, op_unit) option *)
+ type 'a with_unit = 'a * (Unit.unit_of) option
+ type pack =
+ (* used to infer the AC/A structure in the first pass {!Gather} *)
+ | Bin of Bin.pack with_unit
+ (* will only be used in the second pass : {!Parse}*)
+ | Sym of Sym.pack
+ | Unit of Constr.t (* to avoid confusion in bloom *)
+
+ module PackHash =
+ struct
+ open Hashset.Combine
+
+ type t = pack
+
+ let eq_sym_pack p1 p2 =
+ let open Sym in
+ Constr.equal p1.ar p2.ar &&
+ Constr.equal p1.value p2.value &&
+ Constr.equal p1.morph p2.morph
+
+ let hash_sym_pack p =
+ let open Sym in
+ combine3 (Constr.hash p.ar) (Constr.hash p.value) (Constr.hash p.morph)
+
+ let eq_bin_pack p1 p2 =
+ let open Bin in
+ Constr.equal p1.value p2.value &&
+ Constr.equal p1.compat p2.compat &&
+ Constr.equal p1.assoc p2.assoc &&
+ Option.equal Constr.equal p1.comm p2.comm
+
+ let hash_bin_pack p =
+ let open Bin in
+ combine4 (Constr.hash p.value) (Constr.hash p.compat)
+ (Constr.hash p.assoc) (Option.hash Constr.hash p.comm)
+
+ let eq_unit_of u1 u2 =
+ let open Unit in
+ Constr.equal u1.uf_u u2.uf_u &&
+ Constr.equal u1.uf_idx u2.uf_idx &&
+ Constr.equal u1.uf_desc u2.uf_desc
+
+ let hash_unit_of u =
+ let open Unit in
+ combine3 (Constr.hash u.uf_u) (Constr.hash u.uf_idx)
+ (Constr.hash u.uf_desc)
+
+ let equal p1 p2 = match p1, p2 with
+ | Bin (p1, o1), Bin (p2, o2) ->
+ eq_bin_pack p1 p2 && Option.equal eq_unit_of o1 o2
+ | Sym p1, Sym p2 -> eq_sym_pack p1 p2
+ | Unit c1, Unit c2 -> Constr.equal c1 c2
+ | _ -> false
+
+ let hash p = match p with
+ | Bin (p, o) ->
+ combinesmall 1 (combine (hash_bin_pack p) (Option.hash hash_unit_of o))
+ | Sym p ->
+ combinesmall 2 (hash_sym_pack p)
+ | Unit c ->
+ combinesmall 3 (Constr.hash c)
+
+ end
+
+ module PackTable = Hashtbl.Make(PackHash)
+
+ (** discr is a map from {!constr} to {!pack}.
+
+ [None] means that it is not possible to instantiate this partial
+ application to an interesting class.
+
+ [Some x] means that we found something in the past. This means
+ in particular that a single [constr] cannot be two things at the
+ same time.
+
+ The field [bloom] allows to give a unique number to each class we
+ found. *)
+ type envs =
+ {
+ discr : (pack option) HMap.t ;
+ bloom : int PackTable.t;
+ bloom_back : (int, pack ) Hashtbl.t;
+ bloom_next : int ref;
+ }
+
+ let empty_envs () =
+ {
+ discr = HMap.create 17;
+ bloom = PackTable.create 17;
+ bloom_back = Hashtbl.create 17;
+ bloom_next = ref 1;
+ }
+
+
+
+ let add_bloom envs pack =
+ if PackTable.mem envs.bloom pack
+ then ()
+ else
+ let x = ! (envs.bloom_next) in
+ PackTable.add envs.bloom pack x;
+ Hashtbl.add envs.bloom_back x pack;
+ incr (envs.bloom_next)
+
+ let find_bloom envs pack =
+ try PackTable.find envs.bloom pack
+ with Not_found -> assert false
+
+ (*********************************************)
+ (* (\* Gather the occurring AC/A symbols *\) *)
+ (*********************************************)
+
+ (** This modules exhibit a function that memoize in the environment
+ all the AC/A operators as well as the morphism that occur. This
+ staging process allows us to prefer AC/A operators over raw
+ morphisms. Moreover, for each AC/A operators, we need to try to
+ infer units. Otherwise, we do not have [x * y * x <= a * a] since 1
+ does not occur.
+
+ But, do we also need to check whether constants are
+ units. Otherwise, we do not have the ability to rewrite [0 = a +
+ a] in [a = ...]*)
+ module Gather : sig
+ val gather : Coq.goal_sigma -> Coq.Relation.t -> envs -> constr -> Coq.goal_sigma
+ end
+ = struct
+
+ let memoize envs t pack : unit =
+ begin
+ HMap.add envs.discr t (Some pack);
+ add_bloom envs pack;
+ match pack with
+ | Bin (_, None) | Sym _ -> ()
+ | Bin (_, Some (unit_of)) ->
+ let unit = unit_of.Unit.uf_u in
+ HMap.add envs.discr unit (Some (Unit unit));
+ add_bloom envs (Unit unit);
+ | Unit _ -> assert false
+ end
+
+
+ let get_unit (rlt : Coq.Relation.t) op goal :
+ (Coq.goal_sigma * constr * constr ) option=
+ let x = (rlt.Coq.Relation.carrier) in
+ let unit, goal = Coq.evar_unit goal x in
+ let ty =Classes.Unit.ty rlt op unit in
+ let result =
+ try
+ let t,goal = Coq.resolve_one_typeclass goal ty in
+ Some (goal,t,unit)
+ with Not_found -> None
+ in
+ match result with
+ | None -> None
+ | Some (goal,s,unit) ->
+ let unit = Coq.nf_evar goal unit in
+ Some (goal, unit, s)
+
+
+
+ (** gives back the class and the operator *)
+ let is_bin (rlt: Coq.Relation.t) (op: constr) ( goal: Coq.goal_sigma)
+ : (Coq.goal_sigma * Bin.pack) option =
+ let assoc_ty = Classes.Associative.ty rlt op in
+ let comm_ty = Classes.Commutative.ty rlt op in
+ let proper_ty = Classes.Proper.ty rlt op 2 in
+ try
+ let proper , goal = Coq.resolve_one_typeclass goal proper_ty in
+ let assoc, goal = Coq.resolve_one_typeclass goal assoc_ty in
+ let comm , goal =
+ try
+ let comm, goal = Coq.resolve_one_typeclass goal comm_ty in
+ Some comm, goal
+ with Not_found ->
+ None, goal
+ in
+ let bin =
+ {Bin.value = EConstr.to_constr (Tacmach.project goal) op;
+ Bin.compat = EConstr.to_constr (Tacmach.project goal) proper;
+ Bin.assoc = EConstr.to_constr (Tacmach.project goal) assoc;
+ Bin.comm = Option.map (EConstr.to_constr (Tacmach.project goal)) comm }
+ in
+ Some (goal,bin)
+ with |Not_found -> None
+
+ let is_bin (rlt : Coq.Relation.t) (op : constr) (goal : Coq.goal_sigma)=
+ match is_bin rlt op goal with
+ | None -> None
+ | Some (goal, bin_pack) ->
+ match get_unit rlt op goal with
+ | None -> Some (goal, Bin (bin_pack, None))
+ | Some (gl, unit,s) ->
+ let unit_of =
+ {
+ Unit.uf_u = EConstr.to_constr (Tacmach.project goal) unit;
+ (* TRICK : this term is not well-typed by itself,
+ but it is okay the way we use it in the other
+ functions *)
+ Unit.uf_idx = EConstr.to_constr (Tacmach.project goal) op;
+ Unit.uf_desc = EConstr.to_constr (Tacmach.project goal) s;
+ }
+ in Some (gl,Bin (bin_pack, Some (unit_of)))
+
+
+ (** {is_morphism} try to infer the kind of operator we are
+ dealing with *)
+ let is_morphism goal (rlt : Coq.Relation.t) (papp : constr) (ar : int) : (Coq.goal_sigma * pack ) option =
+ let typeof = Classes.Proper.mk_typeof rlt ar in
+ let relof = Classes.Proper.mk_relof rlt ar in
+ let m = Coq.Classes.mk_morphism typeof relof papp in
+ try
+ let m,goal = Coq.resolve_one_typeclass goal m in
+ let pack = {Sym.ar = EConstr.to_constr (Tacmach.project goal) (Coq.Nat.of_int ar);
+ Sym.value= EConstr.to_constr (Tacmach.project goal) papp;
+ Sym.morph= EConstr.to_constr (Tacmach.project goal) m} in
+ Some (goal, Sym pack)
+ with
+ | Not_found -> None
+
+
+ (* [crop_app f [| a_0 ; ... ; a_n |]]
+ returns Some (f a_0 ... a_(n-2), [|a_(n-1); a_n |] )
+ *)
+ let crop_app t ca : (constr * constr array) option=
+ let n = Array.length ca in
+ if n <= 1
+ then None
+ else
+ let papp = mkApp (t, Array.sub ca 0 (n-2) ) in
+ let args = Array.sub ca (n-2) 2 in
+ Some (papp, args )
+
+ let fold goal (rlt : Coq.Relation.t) envs t ca cont =
+ let fold_morphism t ca =
+ let nb_params = Array.length ca in
+ let rec aux n =
+ assert (n < nb_params && 0 < nb_params );
+ let papp = mkApp (t, Array.sub ca 0 n) in
+ match is_morphism goal rlt papp (nb_params - n) with
+ | None ->
+ (* here we have to memoize the failures *)
+ HMap.add envs.discr (EConstr.to_constr (Tacmach.project goal) papp) None;
+ if n < nb_params - 1 then aux (n+1) else goal
+ | Some (goal, pack) ->
+ let args = Array.sub ca (n) (nb_params -n)in
+ let goal = Array.fold_left cont goal args in
+ memoize envs (EConstr.to_constr (Tacmach.project goal) papp) pack;
+ goal
+ in
+ if nb_params = 0 then goal else aux 0
+ in
+ let is_aac t = is_bin rlt t in
+ match crop_app t ca with
+ | None ->
+ fold_morphism t ca
+ | Some (papp, args) ->
+ begin match is_aac papp goal with
+ | None -> fold_morphism t ca
+ | Some (goal, pack) ->
+ memoize envs (EConstr.to_constr (Tacmach.project goal) papp) pack;
+ Array.fold_left cont goal args
+ end
+
+ (* update in place the envs of known stuff, using memoization. We
+ have to memoize failures, here. *)
+ let gather goal (rlt : Coq.Relation.t ) envs t : Coq.goal_sigma =
+ let rec aux goal x =
+ match Coq.decomp_term (Tacmach.project goal) x with
+ | Constr.App (t,ca) ->
+ fold goal rlt envs t ca (aux )
+ | _ -> goal
+ in
+ aux goal t
+ end
+
+ (** We build a term out of a constr, updating in place the
+ environment if needed (the only kind of such updates are the
+ constants). *)
+ module Parse :
+ sig
+ val t_of_constr : Coq.goal_sigma -> Coq.Relation.t -> envs -> constr -> Matcher.Terms.t * Coq.goal_sigma
+ end
+ = struct
+
+ (** [discriminates goal envs rlt t ca] infer :
+
+ - its {! pack } (is it an AC operator, or an A operator, or a
+ Symbol ?),
+
+ - the relevant partial application,
+
+ - the vector of the remaining arguments
+
+ We use an expansion to handle the special case of units,
+ before going back to the general discrimination
+ procedure. That means that a unit is more coarse than a raw
+ morphism
+
+ This functions is prevented to go through [ar < 0] by the fact
+ that a constant is a morphism. But not an eva. *)
+
+ let is_morphism goal (rlt : Coq.Relation.t) (papp : constr) (ar : int) : (Coq.goal_sigma * pack ) option =
+ let typeof = Classes.Proper.mk_typeof rlt ar in
+ let relof = Classes.Proper.mk_relof rlt ar in
+ let m = Coq.Classes.mk_morphism typeof relof papp in
+ try
+ let m,goal = Coq.resolve_one_typeclass goal m in
+ let pack = {Sym.ar = EConstr.to_constr ~abort_on_undefined_evars:(false)(Tacmach.project goal) (Coq.Nat.of_int ar);
+ Sym.value= EConstr.to_constr ~abort_on_undefined_evars:(false)(Tacmach.project goal) papp;
+ Sym.morph= EConstr.to_constr ~abort_on_undefined_evars:(false)(Tacmach.project goal) m} in
+ Some (goal, Sym pack)
+ with
+ | e -> None
+
+ exception NotReflexive
+ let discriminate goal envs (rlt : Coq.Relation.t) t ca : Coq.goal_sigma * pack * constr * constr array =
+ let nb_params = Array.length ca in
+ let rec fold goal ar :Coq.goal_sigma * pack * constr * constr array =
+ begin
+ assert (0 <= ar && ar <= nb_params);
+ let p_app = mkApp (t, Array.sub ca 0 (nb_params - ar)) in
+ begin
+ try
+ begin match HMap.find envs.discr (EConstr.to_constr ~abort_on_undefined_evars:(false) (Tacmach.project goal) p_app) with
+ | None ->
+ fold goal (ar-1)
+ | Some pack ->
+ (goal, pack, p_app, Array.sub ca (nb_params -ar ) ar)
+ end
+ with
+ Not_found -> (* Could not find this constr *)
+ memoize (is_morphism goal rlt p_app ar) p_app ar
+ end
+ end
+ and memoize (x) p_app ar =
+ assert (0 <= ar && ar <= nb_params);
+ match x with
+ | Some (goal, pack) ->
+ HMap.add envs.discr (EConstr.to_constr ~abort_on_undefined_evars:(false) (Tacmach.project goal) p_app) (Some pack);
+ add_bloom envs pack;
+ (goal, pack, p_app, Array.sub ca (nb_params-ar) ar)
+ | None ->
+
+ if ar = 0 then raise NotReflexive;
+ begin
+ (* to memoise the failures *)
+ HMap.add envs.discr (EConstr.to_constr ~abort_on_undefined_evars:(false) (Tacmach.project goal) p_app) None;
+ (* will terminate, since [const] is capped, and it is
+ easy to find an instance of a constant *)
+ fold goal (ar-1)
+ end
+ in
+ try match HMap.find envs.discr (EConstr.to_constr ~abort_on_undefined_evars:(false) (Tacmach.project goal) (mkApp (t,ca))) with
+ | None -> fold goal (nb_params)
+ | Some pack -> goal, pack, (mkApp (t,ca)), [| |]
+ with Not_found -> fold goal (nb_params)
+
+ let discriminate goal envs rlt x =
+ try
+ match Coq.decomp_term (Tacmach.project goal) x with
+ | Constr.App (t,ca) ->
+ discriminate goal envs rlt t ca
+ | _ -> discriminate goal envs rlt x [| |]
+ with
+ | NotReflexive -> user_error @@ Pp.strbrk "The relation to which the goal was lifted is not Reflexive"
+ (* TODO: is it the only source of invalid use that fall
+ into this catch_all ? *)
+ | e ->
+ user_error @@ Pp.strbrk "Cannot handle this kind of hypotheses (variables occurring under a function symbol which is not a proper morphism)."
+
+ (** [t_of_constr goal rlt envs cstr] builds the abstract syntax tree
+ of the term [cstr]. Doing so, it modifies the environment of
+ known stuff [envs], and eventually creates fresh
+ evars. Therefore, we give back the goal updated accordingly *)
+ let t_of_constr goal (rlt: Coq.Relation.t ) envs : constr -> Matcher.Terms.t * Coq.goal_sigma =
+ let r_goal = ref (goal) in
+ let rec aux x =
+ match Coq.decomp_term (Tacmach.project goal) x with
+ | Constr.Rel i -> Matcher.Terms.Var i
+ | _ ->
+ let goal, pack , p_app, ca = discriminate (!r_goal) envs rlt x in
+ r_goal := goal;
+ let k = find_bloom envs pack
+ in
+ match pack with
+ | Bin (pack,_) ->
+ begin match pack.Bin.comm with
+ | Some _ ->
+ assert (Array.length ca = 2);
+ Matcher.Terms.Plus ( k, aux ca.(0), aux ca.(1))
+ | None ->
+ assert (Array.length ca = 2);
+ Matcher.Terms.Dot ( k, aux ca.(0), aux ca.(1))
+ end
+ | Unit _ ->
+ assert (Array.length ca = 0);
+ Matcher.Terms.Unit ( k)
+ | Sym _ ->
+ Matcher.Terms.Sym ( k, Array.map aux ca)
+ in
+ (
+ fun x -> let r = aux x in r, !r_goal
+ )
+
+ end (* Parse *)
+
+ let add_symbol goal rlt envs l =
+ let goal = Gather.gather goal rlt envs (EConstr.of_constr (Constr.mkApp (l, [| Constr.mkRel 0;Constr.mkRel 0|]))) in
+ goal
+
+ (* [t_of_constr] buils a the abstract syntax tree of a constr,
+ updating in place the environment. Doing so, we infer all the
+ morphisms and the AC/A operators. It is mandatory to do so both
+ for the pattern and the term, since AC symbols can occur in one
+ and not the other *)
+ let t_of_constr goal rlt envs (l,r) =
+ let goal = Gather.gather goal rlt envs l in
+ let goal = Gather.gather goal rlt envs r in
+ let l,goal = Parse.t_of_constr goal rlt envs l in
+ let r, goal = Parse.t_of_constr goal rlt envs r in
+ l, r, goal
+
+ (* An intermediate representation of the environment, with association lists for AC/A operators, and also the raw [packer] information *)
+
+ type ir =
+ {
+ packer : (int, pack) Hashtbl.t; (* = bloom_back *)
+ bin : (int * Bin.pack) list ;
+ units : (int * Unit.pack) list;
+ sym : (int * constr) list ;
+ matcher_units : Matcher.ext_units
+ }
+
+ let ir_to_units ir = ir.matcher_units
+
+ let ir_of_envs goal (rlt : Coq.Relation.t) envs =
+ let add x y l = (x,y)::l in
+ let nil = [] in
+ let sym ,
+ (bin : (int * Bin.pack with_unit) list),
+ (units : (int * Constr.t) list) =
+ Hashtbl.fold
+ (fun int pack (sym,bin,units) ->
+ match pack with
+ | Bin s ->
+ sym, add (int) s bin, units
+ | Sym s ->
+ add (int) s sym, bin, units
+ | Unit s ->
+ sym, bin, add int s units
+ )
+ envs.bloom_back
+ (nil,nil,nil)
+ in
+ let matcher_units =
+ let unit_for , is_ac =
+ List.fold_left
+ (fun ((unit_for,is_ac) as acc) (n,(bp,wu)) ->
+ match wu with
+ | None -> acc
+ | Some (unit_of) ->
+ let unit_n = PackTable.find envs.bloom
+ (Unit unit_of.Unit.uf_u)
+ in
+ ( n, unit_n)::unit_for,
+ (n, bp.Bin.comm <> None )::is_ac
+
+ )
+ ([],[]) bin
+ in
+ {Matcher.unit_for = unit_for; Matcher.is_ac = is_ac}
+
+ in
+ let units : (int * Unit.pack) list =
+ List.fold_left
+ (fun acc (n,u) ->
+ (* first, gather all bins with this unit *)
+ let all_bin =
+ List.fold_left
+ ( fun acc (nop,(bp,wu)) ->
+ match wu with
+ | None -> acc
+ | Some unit_of ->
+ if Constr.equal (unit_of.Unit.uf_u) u
+ then
+ {unit_of with
+ Unit.uf_idx = EConstr.to_constr (Tacmach.project goal) (Coq.Pos.of_int nop)} :: acc
+ else
+ acc
+ )
+ [] bin
+ in
+ (n,{
+ Unit.u_value = EConstr.of_constr u;
+ Unit.u_desc = all_bin
+ })::acc
+ )
+ [] units
+
+ in
+ goal, {
+ packer = envs.bloom_back;
+ bin = (List.map (fun (n,(s,_)) -> n, s) bin);
+ units = units;
+ sym = (List.map (fun (n,s) -> n,(Sym.mk_pack rlt s)) sym);
+ matcher_units = matcher_units
+ }
+
+
+
+ (** {1 From AST back to Coq }
+
+ The next functions allow one to map OCaml abstract syntax trees
+ to Coq terms *)
+
+ (** {2 Building raw, natural, terms} *)
+
+ (** [raw_constr_of_t_debruijn] rebuilds a term in the raw
+ representation, without products on top, and maybe escaping free
+ debruijn indices (in the case of a pattern for example). *)
+ let raw_constr_of_t_debruijn ir (t : Matcher.Terms.t) : constr * int list =
+ let add_set,get =
+ let r = ref [] in
+ let rec add x = function
+ [ ] -> [x]
+ | t::q when t = x -> t::q
+ | t::q -> t:: (add x q)
+ in
+ (fun x -> r := add x !r),(fun () -> !r)
+ in
+ (* Here, we rely on the invariant that the maps are well formed:
+ it is meanigless to fail to find a symbol in the maps, or to
+ find the wrong kind of pack in the maps *)
+ let rec aux t =
+ match t with
+ | Matcher.Terms.Plus (s,l,r) ->
+ begin match Hashtbl.find ir.packer s with
+ | Bin (s,_) ->
+ mkApp (EConstr.of_constr s.Bin.value , [|(aux l);(aux r)|])
+ | _ -> Printf.printf "erreur:%i\n%!"s;
+ assert false
+ end
+ | Matcher.Terms.Dot (s,l,r) ->
+ begin match Hashtbl.find ir.packer s with
+ | Bin (s,_) ->
+ mkApp (EConstr.of_constr s.Bin.value, [|(aux l);(aux r)|])
+ | _ -> assert false
+ end
+ | Matcher.Terms.Sym (s,t) ->
+ begin match Hashtbl.find ir.packer s with
+ | Sym s ->
+ mkApp (EConstr.of_constr s.Sym.value, Array.map aux t)
+ | _ -> assert false
+ end
+ | Matcher.Terms.Unit x ->
+ begin match Hashtbl.find ir.packer x with
+ | Unit s -> EConstr.of_constr s
+ | _ -> assert false
+ end
+ | Matcher.Terms.Var i -> add_set i;
+ mkRel (i)
+ in
+ let t = aux t in
+ t , get ( )
+
+ (** [raw_constr_of_t] rebuilds a term in the raw representation *)
+ let raw_constr_of_t ir rlt (context:rel_context) t =
+ (** cap rebuilds the products in front of the constr *)
+ let rec cap c = function [] -> c
+ | t::q ->
+ let i = Context.Rel.lookup t context in
+ cap (mkProd_or_LetIn i c) q
+ in
+ let t,indices = raw_constr_of_t_debruijn ir t in
+ cap t (List.sort (Pervasives.compare) indices)
+
+
+ (** {2 Building reified terms} *)
+
+ (* Some informations to be able to build the maps *)
+ type reif_params =
+ {
+ bin_null : Bin.pack; (* the default A operator *)
+ sym_null : constr;
+ unit_null : Unit.pack;
+ sym_ty : constr; (* the type, as it appears in e_sym *)
+ bin_ty : constr
+ }
+
+
+ (** A record containing the environments that will be needed by the
+ decision procedure, as a Coq constr. Contains the functions
+ from the symbols (as ints) to indexes (as constr) *)
+
+ type sigmas = {
+ env_sym : constr;
+ env_bin : constr;
+ env_units : constr; (* the [idx -> X:constr] *)
+ }
+
+
+ type sigma_maps = {
+ sym_to_pos : int -> constr;
+ bin_to_pos : int -> constr;
+ units_to_pos : int -> constr;
+ }
+
+
+ (** infers some stuff that will be required when we will build
+ environments (our environments need a default case, so we need
+ an Op_AC, an Op_A, and a symbol) *)
+ (* Note : this function can fail if the user is using the wrong
+ relation, like proving a = b, while the classes are defined with
+ another relation (==) *)
+ let build_reif_params goal (rlt : Coq.Relation.t) (zero) :
+ reif_params * Coq.goal_sigma =
+ let carrier = rlt.Coq.Relation.carrier in
+ let bin_null =
+ try
+ let op,goal = Coq.evar_binary goal carrier in
+ let assoc,goal = Classes.Associative.infer goal rlt op in
+ let compat,goal = Classes.Proper.infer goal rlt op 2 in
+ let op = Coq.nf_evar goal op in
+ {
+ Bin.value = EConstr.to_constr (Tacmach.project goal) op;
+ Bin.compat = EConstr.to_constr (Tacmach.project goal) compat;
+ Bin.assoc = EConstr.to_constr (Tacmach.project goal) assoc;
+ Bin.comm = None
+ }
+ with Not_found -> user_error @@ Pp.strbrk "Cannot infer a default A operator (required at least to be Proper and Associative)"
+ in
+ let zero, goal =
+ try
+ let evar_op,goal = Coq.evar_binary goal carrier in
+ let evar_unit, goal = Coq.evar_unit goal carrier in
+ let query = Classes.Unit.ty rlt evar_op evar_unit in
+ let _, goal = Coq.resolve_one_typeclass goal query in
+ Coq.nf_evar goal evar_unit, goal
+ with _ -> zero, goal in
+ let sym_null = Sym.null rlt in
+ let unit_null = Unit.default zero in
+ let record =
+ {
+ bin_null = bin_null;
+ sym_null = sym_null;
+ unit_null = unit_null;
+ sym_ty = Sym.mk_ty rlt ;
+ bin_ty = Bin.mk_ty rlt
+ }
+ in
+ record, goal
+
+ (* We want to lift down the indexes of symbols. *)
+ let renumber (l: (int * 'a) list ) =
+ let _, l = List.fold_left (fun (next,acc) (glob,x) ->
+ (next+1, (next,glob,x)::acc)
+ ) (1,[]) l
+ in
+ let rec to_global loc = function
+ | [] -> assert false
+ | (local, global,x)::q when local = loc -> global
+ | _::q -> to_global loc q
+ in
+ let rec to_local glob = function
+ | [] -> assert false
+ | (local, global,x)::q when global = glob -> local
+ | _::q -> to_local glob q
+ in
+ let locals = List.map (fun (local,global,x) -> local,x) l in
+ locals, (fun x -> to_local x l) , (fun x -> to_global x l)
+
+ (** [build_sigma_maps] given a envs and some reif_params, we are
+ able to build the sigmas *)
+ let build_sigma_maps (rlt : Coq.Relation.t) zero ir (k : sigmas * sigma_maps -> Proof_type.tactic ) : Proof_type.tactic = fun goal ->
+ let rp,goal = build_reif_params goal rlt zero in
+ let renumbered_sym, to_local, to_global = renumber ir.sym in
+ let env_sym = Sigma.of_list
+ rp.sym_ty
+ (rp.sym_null)
+ renumbered_sym
+ in
+ Coq.cps_mk_letin "env_sym" env_sym
+ (fun env_sym ->
+ let bin = (List.map ( fun (n,s) -> n, Bin.mk_pack rlt s) ir.bin) in
+ let env_bin =
+ Sigma.of_list
+ rp.bin_ty
+ (Bin.mk_pack rlt rp.bin_null)
+ bin
+ in
+ Coq.cps_mk_letin "env_bin" env_bin
+ (fun env_bin ->
+ let units = (List.map (fun (n,s) -> n, Unit.mk_pack rlt env_bin s)ir.units) in
+ let env_units =
+ Sigma.of_list
+ (Unit.ty_unit_pack rlt env_bin)
+ (Unit.mk_pack rlt env_bin rp.unit_null )
+ units
+ in
+
+ Coq.cps_mk_letin "env_units" env_units
+ (fun env_units ->
+ let sigmas =
+ {
+ env_sym = env_sym ;
+ env_bin = env_bin ;
+ env_units = env_units;
+ } in
+ let f = List.map (fun (x,_) -> (x,Coq.Pos.of_int x)) in
+ let sigma_maps =
+ {
+ sym_to_pos = (let sym = f renumbered_sym in fun x -> (List.assoc (to_local x) sym));
+ bin_to_pos = (let bin = f bin in fun x -> (List.assoc x bin));
+ units_to_pos = (let units = f units in fun x -> (List.assoc x units));
+ }
+ in
+ k (sigmas, sigma_maps )
+ )
+ )
+ ) goal
+
+ (** builders for the reification *)
+ type reif_builders =
+ {
+ rsum: constr -> constr -> constr -> constr;
+ rprd: constr -> constr -> constr -> constr;
+ rsym: constr -> constr array -> constr;
+ runit : constr -> constr
+ }
+
+ (* donne moi une tactique, je rajoute ma part. Potentiellement, il
+ est possible d'utiliser la notation 'do' a la Haskell:
+ http://www.cas.mcmaster.ca/~carette/pa_monad/ *)
+ let (>>) : 'a * Proof_type.tactic -> ('a -> 'b * Proof_type.tactic) -> 'b * Proof_type.tactic =
+ fun (x,t) f ->
+ let b,t' = f x in
+ b, Tacticals.tclTHEN t t'
+
+ let return x = x, Tacticals.tclIDTAC
+
+ let mk_vect vnil vcons v =
+ let ar = Array.length v in
+ let rec aux = function
+ | 0 -> vnil
+ | n -> let n = n-1 in
+ mkApp( vcons,
+ [|
+ (Coq.Nat.of_int n);
+ v.(ar - 1 - n);
+ (aux (n))
+ |]
+ )
+ in aux ar
+
+ (* TODO: use a do notation *)
+ let mk_reif_builders (rlt: Coq.Relation.t) (env_sym:constr) (k: reif_builders -> Proof_type.tactic) =
+ let x = (rlt.Coq.Relation.carrier) in
+ let r = (rlt.Coq.Relation.r) in
+
+ let x_r_env = [|x;r;env_sym|] in
+ let tty = mkApp (Lazy.force Stubs._Tty, x_r_env) in
+ let rsum = mkApp (Lazy.force Stubs.rsum, x_r_env) in
+ let rprd = mkApp (Lazy.force Stubs.rprd, x_r_env) in
+ let rsym = mkApp (Lazy.force Stubs.rsym, x_r_env) in
+ let vnil = mkApp (Lazy.force Stubs.vnil, x_r_env) in
+ let vcons = mkApp (Lazy.force Stubs.vcons, x_r_env) in
+ Coq.cps_mk_letin "tty" tty
+ (fun tty ->
+ Coq.cps_mk_letin "rsum" rsum
+ (fun rsum ->
+ Coq.cps_mk_letin "rprd" rprd
+ (fun rprd ->
+ Coq.cps_mk_letin "rsym" rsym
+ (fun rsym ->
+ Coq.cps_mk_letin "vnil" vnil
+ (fun vnil ->
+ Coq.cps_mk_letin "vcons" vcons
+ (fun vcons ->
+ let r ={
+ rsum =
+ begin fun idx l r ->
+ mkApp (rsum, [| idx ; mk_mset tty [l,1 ; r,1]|])
+ end;
+ rprd =
+ begin fun idx l r ->
+ let lst = NEList.of_list tty [l;r] in
+ mkApp (rprd, [| idx; lst|])
+ end;
+ rsym =
+ begin fun idx v ->
+ let vect = mk_vect vnil vcons v in
+ mkApp (rsym, [| idx; vect|])
+ end;
+ runit = fun idx -> (* could benefit of a letin *)
+ mkApp (Lazy.force Stubs.runit , [|x;r;env_sym;idx; |])
+ }
+ in k r
+ ))))))
+
+
+
+ type reifier = sigma_maps * reif_builders
+
+
+ let mk_reifier rlt zero envs (k : sigmas *reifier -> Proof_type.tactic) =
+ build_sigma_maps rlt zero envs
+ (fun (s,sm) ->
+ mk_reif_builders rlt s.env_sym
+ (fun rb ->k (s,(sm,rb)) )
+
+ )
+
+ (** [reif_constr_of_t reifier t] rebuilds the term [t] in the
+ reified form. We use the [reifier] to minimise the size of the
+ terms (we make as much lets as possible)*)
+ let reif_constr_of_t (sm,rb) (t:Matcher.Terms.t) : constr =
+ let rec aux = function
+ | Matcher.Terms.Plus (s,l,r) ->
+ let idx = sm.bin_to_pos s in
+ rb.rsum idx (aux l) (aux r)
+ | Matcher.Terms.Dot (s,l,r) ->
+ let idx = sm.bin_to_pos s in
+ rb.rprd idx (aux l) (aux r)
+ | Matcher.Terms.Sym (s,t) ->
+ let idx = sm.sym_to_pos s in
+ rb.rsym idx (Array.map aux t)
+ | Matcher.Terms.Unit s ->
+ let idx = sm.units_to_pos s in
+ rb.runit idx
+ | Matcher.Terms.Var i ->
+ anomaly "call to reif_constr_of_t on a term with variables."
+ in aux t
+end
+
+
+
diff --git a/src/theory.mli b/src/theory.mli
new file mode 100644
index 0000000..e7bfbfe
--- /dev/null
+++ b/src/theory.mli
@@ -0,0 +1,197 @@
+(***************************************************************************)
+(* This is part of aac_tactics, it is distributed under the terms of the *)
+(* GNU Lesser General Public License version 3 *)
+(* (see file LICENSE for more details) *)
+(* *)
+(* Copyright 2009-2010: Thomas Braibant, Damien Pous. *)
+(***************************************************************************)
+
+(** Bindings for Coq constants that are specific to the plugin;
+ reification and translation functions.
+
+ Note: this module is highly correlated with the definitions of {i
+ AAC_rewrite.v}.
+
+ This module interfaces with the above Coq module; it provides
+ facilities to interpret a term with arbitrary operators as an
+ abstract syntax tree, and to convert an AST into a Coq term
+ (either using the Coq "raw" terms, as written in the starting
+ goal, or using the reified Coq datatypes we define in {i
+ AAC_rewrite.v}).
+*)
+
+(** Both in OCaml and Coq, we represent finite multisets using
+ weighted lists ([('a*int) list]), see {!Matcher.mset}.
+
+ [mk_mset ty l] constructs a Coq multiset from an OCaml multiset
+ [l] of Coq terms of type [ty] *)
+
+val mk_mset:EConstr.constr -> (EConstr.constr * int) list ->EConstr.constr
+
+(** {2 Packaging modules} *)
+
+(** Environments *)
+module Sigma:
+sig
+ (** [add ty n x map] adds the value [x] of type [ty] with key [n] in [map] *)
+ val add: EConstr.constr ->EConstr.constr ->EConstr.constr ->EConstr.constr ->EConstr.constr
+
+ (** [empty ty] create an empty map of type [ty] *)
+ val empty: EConstr.constr ->EConstr.constr
+
+ (** [of_list ty null l] translates an OCaml association list into a Coq one *)
+ val of_list: EConstr.constr -> EConstr.constr -> (int * EConstr.constr ) list -> EConstr.constr
+
+ (** [to_fun ty null map] converts a Coq association list into a Coq function (with default value [null]) *)
+ val to_fun: EConstr.constr ->EConstr.constr ->EConstr.constr ->EConstr.constr
+end
+
+
+(** Dynamically typed morphisms *)
+module Sym:
+sig
+ (** mimics the Coq record [Sym.pack] *)
+ type pack = {ar: Constr.t; value: Constr.t ; morph: Constr.t}
+
+ val typ: EConstr.constr lazy_t
+
+
+ (** [mk_pack rlt (ar,value,morph)] *)
+ val mk_pack: Coq.Relation.t -> pack -> EConstr.constr
+
+ (** [null] builds a dummy (identity) symbol, given an {!Coq.Relation.t} *)
+ val null: Coq.Relation.t -> EConstr.constr
+
+end
+
+
+(** We need to export some Coq stubs out of this module. They are used
+ by the main tactic, see {!Rewrite} *)
+module Stubs : sig
+ val lift : EConstr.constr Lazy.t
+ val lift_proj_equivalence : EConstr.constr Lazy.t
+ val lift_transitivity_left : EConstr.constr Lazy.t
+ val lift_transitivity_right : EConstr.constr Lazy.t
+ val lift_reflexivity : EConstr.constr Lazy.t
+ (** The evaluation fonction, used to convert a reified coq term to a
+ raw coq term *)
+ val eval: EConstr.constr lazy_t
+
+ (** The main lemma of our theory, that is
+ [compare (norm u) (norm v) = Eq -> eval u == eval v] *)
+ val decide_thm:EConstr.constr lazy_t
+
+ val lift_normalise_thm : EConstr.constr lazy_t
+end
+
+(** {2 Building reified terms}
+
+ We define a bundle of functions to build reified versions of the
+ terms (those that will be given to the reflexive decision
+ procedure). In particular, each field takes as first argument the
+ index of the symbol rather than the symbol itself. *)
+
+(** Tranlations between Coq and OCaml *)
+module Trans : sig
+
+ (** This module provides facilities to interpret a term with
+ arbitrary operators as an instance of an abstract syntax tree
+ {!Matcher.Terms.t}.
+
+ For each Coq application [f x_1 ... x_n], this amounts to
+ deciding whether one of the partial applications [f x_1
+ ... x_i], [i<=n] is a proper morphism, whether the partial
+ application with [i=n-2] yields an A or AC binary operator, and
+ whether the whole term is the unit for some A or AC operator. We
+ use typeclass resolution to test each of these possibilities.
+
+ Note that there are ambiguous terms:
+ - a term like [f x y] might yield a unary morphism ([f x]) and a
+ binary one ([f]); we select the latter one (unless [f] is A or
+ AC, in which case we declare it accordingly);
+ - a term like [S O] can be considered as a morphism ([S])
+ applied to a unit for [(+)], or as a unit for [( * )]; we
+ chose to give priority to units, so that the latter
+ interpretation is selected in this case;
+ - an element might be the unit for several operations
+ *)
+
+ (** To achieve this reification, one need to record informations
+ about the collected operators (symbols, binary operators,
+ units). We use the following imperative internal data-structure to
+ this end. *)
+
+ type envs
+ val empty_envs : unit -> envs
+
+
+ (** {2 Reification: from Coq terms to AST {!Matcher.Terms.t} } *)
+
+
+ (** [t_of_constr goal rlt envs (left,right)] builds the abstract
+ syntax tree of the terms [left] and [right]. We rely on the [goal]
+ to perform typeclasses resolutions to find morphisms compatible
+ with the relation [rlt]. Doing so, it modifies the reification
+ environment [envs]. Moreover, we need to create fresh
+ evars; this is why we give back the [goal], accordingly
+ updated. *)
+
+ val t_of_constr : Coq.goal_sigma -> Coq.Relation.t -> envs -> (EConstr.constr * EConstr.constr) -> Matcher.Terms.t * Matcher.Terms.t * Coq.goal_sigma
+
+ (** [add_symbol] adds a given binary symbol to the environment of
+ known stuff. *)
+ val add_symbol : Coq.goal_sigma -> Coq.Relation.t -> envs -> Constr.t -> Coq.goal_sigma
+
+ (** {2 Reconstruction: from AST back to Coq terms }
+
+ The next functions allow one to map OCaml abstract syntax trees
+ to Coq terms. We need two functions to rebuild different kind of
+ terms: first, raw terms, like the one encountered by
+ {!t_of_constr}; second, reified Coq terms, that are required for
+ the reflexive decision procedure. *)
+
+ type ir
+ val ir_of_envs : Coq.goal_sigma -> Coq.Relation.t -> envs -> Coq.goal_sigma * ir
+ val ir_to_units : ir -> Matcher.ext_units
+
+ (** {2 Building raw, natural, terms} *)
+
+ (** [raw_constr_of_t] rebuilds a term in the raw representation, and
+ reconstruct the named products on top of it. In particular, this
+ allow us to print the context put around the left (or right)
+ hand side of a pattern. *)
+ val raw_constr_of_t : ir -> Coq.Relation.t -> EConstr.rel_context -> Matcher.Terms.t -> EConstr.constr
+
+ (** {2 Building reified terms} *)
+
+ (** The reification environments, as Coq constrs *)
+
+ type sigmas = {
+ env_sym : EConstr.constr;
+ env_bin : EConstr.constr;
+ env_units : EConstr.constr; (* the [idx -> X:constr] *)
+ }
+
+
+
+ (** We need to reify two terms (left and right members of a goal)
+ that share the same reification envirnoment. Therefore, we need
+ to add letins to the proof context in order to ensure some
+ sharing in the proof terms we produce.
+
+ Moreover, in order to have as much sharing as possible, we also
+ add letins for various partial applications that are used
+ throughout the terms.
+
+ To achieve this, we decompose the reconstruction function into
+ two steps: first, we build the reification environment and then
+ reify each term successively.*)
+ type reifier
+
+ val mk_reifier : Coq.Relation.t -> EConstr.constr -> ir -> (sigmas * reifier -> Proof_type.tactic) -> Proof_type.tactic
+
+ (** [reif_constr_of_t reifier t] rebuilds the term [t] in the
+ reified form. *)
+ val reif_constr_of_t : reifier -> Matcher.Terms.t -> EConstr.constr
+
+end