123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767(** Compilation of rewriting rules to decision trees.
This module handles the compilation of rewriting rules to “decision trees”
based on the method described by Luc Maranget.
@see <https://dblp.uni-trier.de/rec/html/conf/ml/Maranget08> *)openLplibopenBaseopenExtraopenCommonopenTimedopenTermopenLibTermopenTree_typeletlog=Logger.make'd'"tree""compilation of decision trees"letlog=log.pp(** {1 Types for decision trees}
The types involved in the definition of decision trees are given in module
{!module:Tree_type} (they could not be defined here as this would lead to
a cyclic dependency).
{b Example:} let us consider the rewrite system for symbol [f] defined as:
- [f Z (S m) ↪ S m],
- [f n Z ↪ n] and
- [f (S n) (S m) ↪ S (S m)].
A possible decision tree might be
{v
├─?─∘─Z─∘ ↪ n
├─Z─∘─Z─∘ ↪ n
│ └─S─∘─?─∘ ↪ S m
├─S─∘─Z─∘ ↪ n
└─S─∘─?─∘ ↪ S (S m)
v}
with [∘] being a node (with an omitted label) and [─u─] being an edge with
a matching on symbol [u] or a variable or wildcard when [?]. Typically the
portion [S─∘─Z] is made possible by a swap. *)(** Representation of a tree (see {!type:Tree_type.tree}). *)typetree=(rhs*int)Tree_type.tree(** {1 Conditions for decision trees}
The decision trees used for pattern matching include binary nodes carrying
conditions (see constructor {!constructor:Tree_type.tree.Cond}) that must
be tested during evaluation to select which of the two subsequent branches
to follow. There are two forms of conditions:
- convertibility conditions ({!constructor:Tree_type.tree_cond.CondNL}),
- free variable conditions ({!constructor:Tree_type.tree_cond.CondFV}).
Convertibility conditions are used whenever we try to apply a rule that is
not left-linear, for example [f $x $x (s $y) ↪ r]. In this case we need to
test whether the two terms at position [{0}] and [{1}] (that correspond to
pattern variable [$x]) are convertible: the rule may only apply if that is
the case. Note that in general there may be more than two occurrences of a
variable, so we may need to check convertibility several times.
Free variable constraints are used to verify which variables are free in a
term. If there is a rule of the form [f (λ x y, $Y[y]) ↪ $Y], then we need
to check that the term at position [{0.0}] does not depend on [x] (or, put
in other words, that among [x] and [y], only [y] is allowed). *)(** Module providing a representation for pools of conditions. *)moduleCP=struct(** Functional sets of pairs of integers. *)modulePSet=Set.Make(structtypet=int*intletcompare:t->t->int=fun(i1,i2)(j1,j2)->matchi1-j1with0->i2-j2|k->kend)(** A pool of (convertibility and free variable) conditions. *)typet={variables:intIntMap.t(** An association [(e, v)] maps the slot of a pattern variable (the first
argument of a {!constructor:Term.term.Patt}) to its slot in the array
[vars] of the {!val:Eval.tree_walk} function. It is used to remember
non linearity constraints. *);nl_conds:PSet.t(** Set of convertibility constraints [(i,j)] with [i < j]. The constraint
[(i,j)] is satisfied if the terms stored at indices [i] and [j] in the
[vars] array of the {!val:Eval.tree_walk} function are convertible. *);fv_conds:intarrayIntMap.t(** A mapping of [i] to [xs] represents a free variable condition that can
only be satisfied if only the free variables of [x] appear in the term
stored at slot [i] in the [vars] array of {!val:Eval.tree_walk}. *)}(** [empty] is the condition pool holding no condition. *)letempty:t={variables=IntMap.empty;nl_conds=PSet.empty;fv_conds=IntMap.empty}(** [is_empty pool] tells whether the pool of constraints is empty. *)letis_empty:t->bool=funpool->PSet.is_emptypool.nl_conds&&IntMap.is_emptypool.fv_conds(** [register_nl slot i pool] registers the fact that the slot [slot] in the
[vars] array correspond to a term stored at index [i] in the environment
for the RHS. The first time that such a slot is associated to [i], it is
registered to serve as a reference point for testing convertibility when
(and if) another such slot is (ever) encountered. When that is the case,
a convertibility constraint is registered between the term stored in the
slot [slot] and the term stored in the reference slot. *)letregister_nl:int->int->t->t=funslotipool->try(* We build a new condition if there is already a point of reference. *)letcond=(IntMap.findipool.variables,slot)in{poolwithnl_conds=PSet.addcondpool.nl_conds}withNot_found->(* First occurence of [i], register the slot as a point of reference. *){poolwithvariables=IntMap.addislotpool.variables}(** [register_fv slot xs pool] registers a free variables constraint for the
variables in [xs] on the slot [slot] of the [vars] array in [pool]. *)letregister_fv:int->intarray->t->t=funivspool->{poolwithfv_conds=IntMap.addivspool.fv_conds}(** [constrained_nl i pool] tells whether index [i] in the RHS's environment
has already been associated to a variable of the [vars] array. *)letconstrained_nl:int->t->bool=funslotpool->IntMap.memslotpool.variables(** [is_contained cond pool] tells whether the condition [cond] is contained
in the pool [pool]. *)letis_contained:tree_cond->t->bool=funcondpool->matchcondwith|CondNL(i,j)->PSet.mem(i,j)pool.nl_conds|CondFV(i,x)->tryArray.eq(=)x(IntMap.findipool.fv_conds)withNot_found->false(** [remove cond pool] removes condition [cond] from the pool [pool]. *)letremovecondpool=matchcondwith|CondNL(i,j)->{poolwithnl_conds=PSet.remove(i,j)pool.nl_conds}|CondFV(i,xs)->tryletys=IntMap.findipool.fv_condsinifnot(Array.eq(=)xsys)thenpoolelse{poolwithfv_conds=IntMap.removeipool.fv_conds}withNot_found->pool(** [choose pools] selects a condition to check in the pools of [pools]. The
heuristic is to first select convertibility constraints. *)letchoose:tlist->tree_condoption=funpools->letrecchoose_nlpools=letexport(i,j)=CondNL(i,j)inmatchpoolswith|[]->None|p::ps->trySome(export(PSet.choosep.nl_conds))withNot_found->choose_nlpsinletrecchoose_vfpools=letexport(i,vs)=CondFV(i,vs)inmatchpoolswith|[]->None|p::ps->trySome(export(IntMap.choosep.fv_conds))withNot_found->choose_vfpsinletres=choose_nlpoolsinifres=Nonethenchoose_vfpoolselseresend(** {1 Clause matrix and pattern matching problem} *)(** {b NOTE} We ideally need the type [stack] of terms used during evaluation
(argument [stk] of {!val:Eval.tree_walk}) to have fast element access (for
swaps) as well as fast destruction and reconstruction operations (for more
information on these operations have a look at {!val:Lplib.List.destruct}
and {!val:Lplib.List.reconstruct}). These operations are intuitively used
to inspect a particular element, reduce it, and then reinsert it. It seems
that in practice, the naive representation based on lists performs better
than more elaborate solutions, unless there are rules with many arguments.
Alternatives to a list-based implementation would be cat-enable lists (or
deques), finger trees (Paterson & Hinze) or random access lists. With the
current implementation, [destruct e i] has a time complexity of [Θ(i)] and
[reconstruct l m r] has a time complexity of [Θ(length l + length m)]. *)(** Clause matrices encode pattern matching problems. The clause matrix {i C}
can be denoted {i C = P ↪ A} where {i P} is a {e pattern matrix} and {i A}
is a column of {e RHS}. Each line of a matrix is a pattern to which a RHS
is attached. When reducing a term, if a line filters the term (i.e., the
term matches the pattern) then term is rewritten to the RHS. *)moduleCM=struct(** [head ppf t] prints head of term [t]. *)lethead:termpp=funppft->stringppf(matchget_argstwith|Symbs,_->s.sym_name|Patt_,_->"$"|Vari_,_->"x"|Abst_,_->"λ"|Prod_,_->"Π"|_->assertfalse(* Terms that souldn't appear in lhs *))(** Representation of a subterm in argument position in a pattern. *)typearg={arg_path:intlist(** Reversed path to the subterm. *);arg_rank:int(** Number of binders along the path. *)}(** [arg_path ppf pth] prints path [pth] as a dotted list of integers to
formatter [ppf]. *)letarg_path:intlistpp=funppfpth->outppf"{%a}"(List.ppint".")(List.revpth)(** {b NOTE} the {!field:arg_path} describes a path to the represented term.
The idea is that each index of the list tells under which argument to go
next (counting from [0]), starting from the end of the list. For example
let us consider the term [f x (g a b)]. The subterm [a] is accessed with
the path [[0 ; 1]] (go under the second argument [g a b] first, and then
take its first argument). Similarly, [b] is encoded as [[1 ; 1]] and [x]
as [[0]]. Note that a value [[]] does not describe a valid path. Also, a
[0] index is used when going under abstractions. In that case, the field
{!field:arg_rank} is incremented. *)(** A clause matrix row (schematically {i c_lhs ↪ c_rhs if cond_pool}). *)typeclause={c_lhs:termarray(** Left hand side of a rule. *);c_rhs:rhs*int(** Right hand side of a rule. *);c_subst:rhs_substit(** Substitution of RHS variables. *);xvars_nb:int(** Number of extra variables in the rule RHS. *);cond_pool:CP.t(** Condition pool with convertibility and free variable constraints. *)}(** [clause ppf c] pretty prints clause [c] to formatter [ppf]. *)letclause:clausepp=funppf{c_lhs;_}->outppf"| @[<h>%a@] |"(List.pphead" | ")(Array.to_listc_lhs)(** Type of clause matrices. *)typet={clauses:clauselist(** The rules. *);slot:int(** Index of next slot to use in [vars] array to store variables. *);positions:arglist(** Positions of the elements of the matrix in the initial term.
We must have [length positions = max {length cl | cl ∈ clauses}]. *)}(** [pp ppf cm] pretty prints clause matrix [cm] to formatter [ppf]. *)letpp:tpp=funppf{clauses;_}->outppf"[@[<v>%a@]]"Format.(pp_print_list~pp_sep:pp_print_spaceclause)clauses(** Available operations on clause matrices. Every operation corresponds to
decision made during evaluation. This type is not used outside of the
compilation process. *)typedecision=|Yieldofclause(** Rewrite to a RHS when a LHS filters the input. *)|Check_stack(** Check whether the stack is empty (used to handle multiple arities with
the sequential strategy set). *)|Specialiseofint(** Specialise the matrix against constructors on a given column. *)|Conditionoftree_cond(** Binary decision on the result of the test of a condition. A condition
[CondNL(c, s)] indicates a non-linearity constraint on column [c] with
respect to slot [s]. A condition [CondFV(vs, i)] corresponds to a free
variable condition: only variables of [vs] are in the matched term. *)(** [is_treecons t] tells whether term [t] corresponds to a constructor (see
{!type:Tree_type.TC.t}) that is a candidate for a specialization. *)letis_treecons:term->bool=funt->matchfst(get_argst)with|Patt(_)->false|Vari(_)|Abst(_)|Prod(_)|Symb(_)->true|_->assertfalse(** [of_rules rs] transforms rewriting rules [rs] into a clause matrix. *)letof_rules:rulelist->t=funrs->letr2r{lhs;rhs;xvars_nb;_}=letc_lhs=Array.of_listlhsin{c_lhs;c_rhs=(rhs,xvars_nb);cond_pool=CP.empty;c_subst=[];xvars_nb}inletsize=(* Get length of longest rule *)ifrs=[]then0elseList.max~cmp:Int.compare(List.map(funr->Term.(r.arity))rs)inletpositions=ifsize=0then[]elseList.init(size-1)(funi->{arg_path=[i];arg_rank=0})in{clauses=List.mapr2rrs;slot=0;positions}(** [is_empty m] returns whether matrix [m] is empty. Note that a matrix is
empty when it has {e no} row; not when it has empty rows. *)letis_empty:t->bool=funm->m.clauses=[](** [get_col n m] retrieves column [n] of matrix [m]. *)letget_col:int->t->termlist=funindm->List.map(fun{c_lhs;_}->c_lhs.(ind))m.clauses(** [score c] returns the score heuristic for column [c]. This score is the
number of tree constructors divided by the number of conditions. *)letscore:termlist->float=funts->letrecloop((ncons,nst)asacc)=function|[]->acc|x::xswhenis_treeconsx->loop(ncons+1,nst)xs|Patt(Some(_),_,_)::xs->loop(ncons,nst+1)xs|Patt(_,_,e)::xswhene<>[||]->loop(ncons,nst+1)xs|_::xs->loopaccxsinletnc,ns=loop(0,0)tsinfloat_of_intnc/.(float_of_intns)(** [discard_cons_free r] returns the indexes of columns on which a
specialisation can be performed. They are the columns with at least one
symbol as head term. *)letdiscard_cons_free:clauselist->intarray=funclauses->letncols=letarities=List.map(funcl->Array.lengthcl.c_lhs)clausesinList.max~cmp:Int.comparearitiesinletswitchable=(* [can_switch_on k] is true if a switch can be performed on col [k]. *)letcan_switch_onk=List.for_all(funr->Array.lengthr.c_lhs>=k+1)clauses&&List.exists(funr->is_treeconsr.c_lhs.(k))clausesinList.initncolscan_switch_oninletswitchable2indie=ifethenSome(i)elseNoneinArray.of_list(List.filteri_mapswitchable2indswitchable)(** [choose m] returns the index of column to switch on. *)letchoose:t->intoption=funm->letkept=discard_cons_freem.clausesinifkept=[||]thenNoneelse(* Select the "best" (with higher [score]) column. *)letbest=letscores=Array.map(funci->score(get_colcim))keptinArray.max_index~cmp:Stdlib.comparescoresinSome(kept.(best))(** [is_exhausted p c] tells whether clause [r] whose terms are at positions
in [p] can be applied or not. *)letis_exhausted:arglist->clause->bool=funpositions{c_lhs=lhs;cond_pool;_}->letnonllhs=(* Verify that there are no non linearity constraints in the remaining
terms. We must check that there are no constraints in the remaining
terms and no constraints left partially instantiated. *)letslots=letfnt=matchtwithPatt(i,_,_)->i|_->NoneinList.filter_mapfn(Array.to_listlhs)inletslots_uniq=List.sort_uniqInt.compareslotsinletfns=CP.constrained_nlscond_poolinList.same_lengthslotsslots_uniq&¬(List.existsfnslots_uniq)inletdepths=Array.of_list(List.map(funi->i.arg_rank)positions)inletripelhs=(* [ripe l] returns whether [lhs] can be applied. *)letde=Array.subdepths0(Array.lengthlhs)inletcheckptde=(* We verify that there are no variable constraints, that is, if
abstractions are traversed, then variable must be allowed in pattern
variables. *)matchptwith|Patt(_,_,e)->Array.lengthe=de|_->falseinArray.for_all2checklhsde&&nonllhsinCP.is_emptycond_pool&&(lhs=[||]||ripelhs)(** [yield m] returns the next operation to carry out on matrix [m], that
is, either specialising, solving a constraint or rewriting to a rule. *)letyield:match_strat->t->decision=funmstrat({clauses;positions;_}asm)->(* If a line is empty and priority is given to the topmost rule, we have
to eliminate ¨empty¨ rules. *)ifmstrat=Sequen&&List.exists(funx->x.c_lhs=[||])clauses&&List.exists(funx->x.c_lhs<>[||])clausesthenCheck_stackelsetryifmstrat=Sequenthen(* [List.hd] won't fail because if the matrix is empty, then we don't
enter the function (see {!val:compile}). If it is not, then it has
at least one clause. *)letfc=List.hdclausesinifis_exhaustedpositionsfcthenYield(fc)elseraiseNot_foundelseletr=List.find(is_exhaustedpositions)clausesinYield(r)withNot_found->(* Here is the heuristic: process in priority specialisations, then
convertibility constraints, then closedness constraints. *)matchchoosemwith|Some(c)->Specialise(c)|None->matchCP.choose(List.map(funr->r.cond_pool)m.clauses)with|Some(c)->Condition(c)|None->Specialise(0)(** [get_cons id l] returns a list of unique (and sorted) tuples containing
tree construcors from [l] and the original term. Variables are assigned
id [id]. *)letget_cons:intVarMap.t->termlist->(TC.t*term)list=funvars_idtelst->letkeep_treeconse=leth,_,arity=get_args_leneinmatchhwith|Symb({sym_name;sym_path;_})->Some(TC.Symb(sym_path,sym_name,arity),e)|Vari(x)->Some(TC.Vari(VarMap.findxvars_id),e)|_->Noneinlettc_fst_cmp(tca,_)(tcb,_)=TC.comparetcatcbinList.sort_uniqtc_fst_cmp(List.filter_mapkeep_treeconstelst)(** [store m c] is true iff a term of column [c] of matrix [m] contains a
pattern variable. In that case, the term needs to be stored into the
[vars] array (in order to build the substitution). *)letstore:t->int->bool=funcmci->let(_,a,_)=List.destructcm.positionsciinletst_rr=matchr.c_lhs.(ci)with|Patt(Some(_),_,_)->true|Patt(_,_,e)->Array.lengthe<a.arg_rank|_->falseinList.existsst_rcm.clausesletindex_var:intVarMap.t->term->int=funvit->VarMap.find(to_tvart)vi(** [mk_wildcard vs] creates a pattern variable that accepts anything and in
which variables [vs] can appear free. There is no order on [vs] because
such created wildcard are not bound anywhere, so terms filtered by such
wildcards are not kept. *)letmk_wildcard:VarSet.t->term=funvs->letenv=vs|>VarSet.to_seq|>Seq.mapmk_Vari|>Array.of_seqinmk_Patt(None,"",env)(** [update_aux col mem clause] the condition pool and the substitution of
clause [clause] assuming that column [col] is inspected and [mem]
subterms have to be memorised. *)letupdate_aux:int->int->arglist->intVarMap.t->clause->clause=funcimemposvir->matchfst(get_argsr.c_lhs.(ci))with|Patt(i,_,e)->let(_,a,_)=List.destructposciinletcond_pool=if(Array.lengthe)<>a.arg_rankthenCP.register_fvmem(Array.map(index_varvi)e)r.cond_poolelser.cond_poolinletcond_pool=matchiwith|Some(i)->ifLogger.log_enabled()thenlog"Registering non linearity constraint on position [%a] \
on %d"arg_patha.arg_pathi;CP.register_nlmemicond_pool|None->cond_poolinletc_subst=(* REVIEW: patterns may have slots in the RHS although they are not
bound. *)(* If the pattern has a slot, either it is used in the RHS, in which
case a tuple [(j,vs)] consisting of the index [j] to which the
matched term is bound in the binder of the RHS and the variables
making up the environment of the term (if any) as indexes among
the variables collected. Or the variable is subject to a
non-linearity constraint. In this case, it is added to the
substitution only if the substitution does not already contain
a variable bound to slot [i]. *)letsei(_,(j,_))=i=jinmatchiwith|Some(i)whennot(List.exists(sei)r.c_subst)->(mem,(i,Array.map(index_varvi)e))::r.c_subst|_->r.c_substin{rwithc_subst;cond_pool}|_->r(** [specialize free_vars pat col pos cls] filters and transforms LHS of
clauses [cls] whose column [col] contain a pattern that can filter
elements filtered by pattern [pat], with [pos] being the position of
arguments in clauses and [free_vars] is the set of variables that can
appear free in patterns. *)letspecialize:VarSet.t->term->int->arglist->clauselist->arglist*clauselist=funfree_varspatcolposcls->letpos=letl,m,r=List.destructposcolinlet_,_,nargs=get_args_lenpatinletreplace=ifnargs=0then[]elseList.init(nargs-1)(funi->{mwitharg_path=i::m.arg_path})inList.reconstructlreplacerinletinsertre=Array.concat[Array.subr.c_lhs0col;e;Array.drop(col+1)r.c_lhs]inletfiltransr=letinsert=insertrinletph,pargs,lenp=get_args_lenpatinleth,args,lenh=get_args_lenr.c_lhs.(col)inmatchph,hwith|Symb(f),Symb(g)->iflenh=lenp&&f==gthenSome({rwithc_lhs=insert(Array.of_listargs)})elseNone|Vari(x),Vari(y)->iflenh=lenp&&Bindlib.eq_varsxythenSome({rwithc_lhs=insert(Array.of_listargs)})elseNone|_,Patt(_)->letarity=List.lengthpargsinlete=Array.makearity(mk_wildcardfree_vars)inSome({rwithc_lhs=inserte})|Symb(_),Vari(_)|Vari(_),Symb(_)|_,Prod(_)|_,Abst(_)->None|_,_->assertfalse(* Term matched (in this case) should not appear in matrices. *)in(pos,List.filter_mapfiltranscls)(** [default col pos cls] selects and transforms clauses [cls] assuming that
terms on column [col] does not match any symbol being the head structure
of another term in column [col]; [pos] is the positions of terms in
clauses. *)letdefault:int->arglist->clauselist->arglist*clauselist=funcolposcls->letpos=let(l,_,r)=List.destructposcolinList.rev_appendlrinlettransfr=matchr.c_lhs.(col)with|Patt(_)->letc_lhs=Array.append(Array.subr.c_lhs0col)(Array.drop(col+1)r.c_lhs)inSome({rwithc_lhs})|Prod(_)|Symb(_)|Abst(_)|Vari(_)|Appl(_)->None|_->assertfalsein(pos,List.filter_maptransfcls)(** [binder get free_vars col v pos cls] selects and transforms clauses
[cls] assuming that each term [t] in column [col] is a binder such that
[get t] returns [Some(a, b)]. The term [b] under the binder has its
bound variable substituted by [v]; [pos] is the position of terms in
clause [cls]. The domain [a] of the binder and [b[v/x]] are put back
into the stack (in that order), with [a] with argument position 0 and
[b[v/x]] as argument 1. *)letbinder:(term->(term*tbinder))->VarSet.t->int->tvar->arglist->clauselist->arglist*clauselist=fungetfree_varscolvposcls->let(l,{arg_path;arg_rank},r)=List.destructposcolinletab={arg_path=1::arg_path;arg_rank=arg_rank+1}inletat={arg_path=0::arg_path;arg_rank}inletpos=List.rev_appendl(at::ab::r)inletinsertre=Array.concat[Array.subr.c_lhs0col;e;Array.drop(col+1)r.c_lhs]inlettransf(r:clause)=letph,pargs=get_argsr.c_lhs.(col)inmatchphwith|Patt(_)->letdomain=mk_wildcardfree_varsinletbody=mk_wildcard(VarSet.addvfree_vars)inSome{rwithc_lhs=insertr[|domain;body|]}|Symb(_)|Vari(_)->None|t->let(a,b)=gettinassert(pargs=[]);(* Patterns in β-normal form *)letb=Bindlib.substb(mk_Variv)inSome({rwithc_lhs=insertr[|a;b|]})in(pos,List.filter_maptransfcls)(** [abstract free_vars col v pos cls] selects and transforms clauses [cls]
keeping lines whose column [col] is able to filter an abstraction. The
body of the abstraction has its bound variable substituted by [v]; [pos]
is the position of terms in clauses [cls] and [free_vars] is the set of
{e free} variables introduced by other binders that may appear in
patterns. *)letabstract:VarSet.t->int->tvar->arglist->clauselist->arglist*clauselist=binder(functionAbst(a,b)->a,b|_->assertfalse)(** [product free_vars col v pos cls] is like [abstract free_vars col v pos
cls] for products. *)letproduct:VarSet.t->int->tvar->arglist->clauselist->arglist*clauselist=binder(functionProd(a,b)->a,b|_->assertfalse)(** [cond_ok cond cls] updates the clause list [cls] assuming that condition
[cond] is satisfied. *)letcond_ok:tree_cond->clauselist->clauselist=funcond->List.map(funr->{rwithcond_pool=CP.removecondr.cond_pool})(** [cond_fail cond cls] updates the clause list [cls] with the information
that the condition [cond] is not satisfied. *)letcond_fail:tree_cond->clauselist->clauselist=funcond->List.filter(funr->not(CP.is_containedcondr.cond_pool))(** [empty_stack c] keeps the empty clauses from [c]. *)letempty_stack:clauselist->arglist*clauselist=funcs->([],List.filter(funr->r.c_lhs=[||])cs)(** [not_empty_stack c] keeps the not empty clauses from [c]. *)letnot_empty_stack:clauselist->clauselist=List.filter(funr->r.c_lhs<>[||])end(** [harvest lhs rhs subst vi slot] exhausts linearly the LHS [lhs]
composed only of pattern variables with no constraints, to yield a leaf
with RHS [rhs] and the substitution [subst] (which is completed). Mapping
[vi] contains variables that may appear free in patterns. [slot] is the
number of subterms that must be memorised. *)letharvest:termarray->rhs*int->rhs_substit->intVarMap.t->int->tree=funlhsrhssubstvislot->letdefault_nodestorechild=Node{swap=0;store;children=TCMap.empty;abstraction=None;product=None;default=Some(child)}inletreclooplhssubstslot=matchlhswith|[]->Leaf(subst,rhs)|Patt(Some(i),_,e)::ts->letsubst=(slot,(i,Array.map(CM.index_varvi)e))::substindefault_nodetrue(looptssubst(slot+1))|Patt(None,_,_)::ts->default_nodefalse(looptssubstslot)|_->assertfalseinloop(Array.to_listlhs)substslot(** {b NOTE} {!val:compile} produces a decision tree from a set of rewriting
rules (in practice, they all belong to a same symbol). This tree is
designed to be used in the reduction process, in function
{!val:Eval.tree_walk}. The purpose of the trees is to
- declare efficiently whether the input term (during evaluation) matches
some LHS from the orginal rules (the one from which the tree is built);
- build a substitution mapping some (sub) terms of the filtered term to
the rewritten term.
The first bullet is handled by the definition of the trees, e.g. if a
switch node contains in its mapping a tree constructor [s], then terms
having (as head structure) symbol [s] will be accepted.
The second bullet is managed by the substitution of type
{!type:Tree_type.rhs_substit}. If a LHS contains a named pattern variable,
then it
is used in the RHS. Sub-terms that are filtered by named variables that
are bound in the RHS are saved into an array during evaluation. When a
leaf is reached, the substitution is applied on the RHS, copying terms
from that array to the RHS. Subterms are saved into the array when field
[store] of nodes is true. *)(** [compile mstrat m] translates the pattern matching problem encoded by the
matrix [m] into a decision tree following strategy [mstrat]. *)letcompile:match_strat->CM.t->tree=funmstratm->(* [compile count vars_id cm] compiles clause matrix [cm]. The mapping
[vars_id] maps variables bound in the pattern (either by abstractions
[Abst] or products [Prod]) that may appear free in patterns to integers.
Outermost variable is tagged 0, and hence variables are represented with
DeBruijn levels. *)letreccompile:intVarMap.t->CM.t->tree=funvars_id({clauses;positions;slot}ascm)->ifCM.is_emptycmthenFailelseletcompile_cv=compilevars_idinifLogger.log_enabled()thenlog"Compile@ %a"CM.ppcm;matchCM.yieldmstratcmwith|Yield({c_rhs;c_subst;c_lhs;_})->harvestc_lhsc_rhsc_substvars_idslot|Condition(cond)->ifLogger.log_enabled()thenlog"Condition [%a]"tree_condcond;letok=compile_cv{cmwithclauses=CM.cond_okcondclauses}inletfail=compile_cv{cmwithclauses=CM.cond_failcondclauses}inCond({ok;cond;fail})|Check_stack->ifLogger.log_enabled()thenlog"Check stack";letleft=letpositions,clauses=CM.empty_stackclausesincompile_cv{cmwithclauses;positions}inletright=compile_cv{cmwithclauses=CM.not_empty_stackclauses}inEos(left,right)|Specialise(swap)->ifLogger.log_enabled()thenlog"Swap on column %d"swap;letstore=CM.storecmswapinletupdated=List.map(CM.update_auxswapslotpositionsvars_id)clausesinletslot=ifstorethenslot+1elseslotinletcolumn=CM.get_colswapcminletcons=CM.get_consvars_idcolumnin(* Get variables of a [VarMap]. *)letkeysm=VarMap.to_seqm|>Seq.mapfst|>VarSet.of_seqin(* Constructors specialisation *)letchildren=letfnacc(tr_cons,te_cons)=let(positions,clauses)=CM.specialize(keysvars_id)te_consswappositionsupdatedinTCMap.addtr_cons(compile_cvCM.{clauses;slot;positions})accinList.fold_leftfnTCMap.emptyconsin(* Default child *)letdefault=let(positions,clauses)=CM.defaultswappositionsupdatedinletncm=CM.{clauses;slot;positions}inifCM.is_emptyncmthenNoneelseSome(compile_cvncm)inletbinderreconmat_transf=ifList.for_all(funx->not(reconx))columnthenNoneelseletv_lvl=VarMap.cardinalvars_idin(* Level of the variable. *)letvar=new_tvar(Printf.sprintf"d%dv"v_lvl)inlet(positions,clauses)=mat_transf(keysvars_id)swapvarpositionsupdatedin(* Add [var] to the variables that may appear free in patterns. *)letvars_id=VarMap.addvarv_lvlvars_idinletnext=compilevars_idCM.{clauses;slot;positions}inSome(v_lvl,next)inletabstraction=binderis_abstCM.abstractinletproduct=binderis_prodCM.productinNode({swap;store;children;abstraction;default;product})incompileVarMap.emptym(** [update_dtree s rs] updates the decision tree of the symbol [s] by adding
rules [rs]. *)letupdate_dtree:sym->rulelist->unit=funsymbrs->letrs=ifrs=[]then!(symb.sym_rules)else!(symb.sym_rules)@rsinletcm=lazy(CM.of_rulesrs)inlettree=lazy(compilesymb.sym_mstrat(Lazy.forcecm))inletcap=lazy(Tree_type.tree_capacity(Lazy.forcetree))insymb.sym_dtree:=(cap,tree)