123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943(************************************************************************)(* * The Rocq Prover / The Rocq Development Team *)(* v * Copyright INRIA, CNRS and contributors *)(* <O___,, * (see version control and CREDITS file for authors & dates) *)(* \VV/ **************************************************************)(* // * This file is distributed under the terms of the *)(* * GNU Lesser General Public License Version 2.1 *)(* * (see LICENSE file for the text of the license) *)(************************************************************************)openUtilopenCAstopenNamesopenLibnamesopenTac2expropenTac2substletcheck_lowercase{loc;v=id}=ifTac2env.is_constructor(Libnames.qualid_of_identid)thenCErrors.user_err?locPp.(str"The identifier "++Id.printid++str" must start with a lowercase letter.")letinternal_ltac2_expr=Procq.Entry.make"ltac2_expr"moduleTac2Scope=KerNamemoduleScopeV=structincludeTac2Scopeletis_var_=Noneletstage=Summary.Stage.Synterpletsummary_name="ltac2_scopetab"endmoduleScopeTab=Nametab.EasyNoWarn(ScopeV)()letfind_scopesc=tryScopeTab.locatescwithNot_found->CErrors.user_err?loc:sc.locPp.(str"Unknown Ltac2 scope "++Libnames.pr_qualidsc++str".")letload_scopei((sp,kn),())=ScopeTab.push(Untili)spknletimport_scopei((sp,kn),())=ScopeTab.push(Exactlyi)spknletcache_scopeo=load_scope1o;import_scope1oletinScope:Id.t->unit->Libobject.obj=letopenLibobjectindeclare_named_object{(default_object"Ltac2 notation scope")withobject_stage=Interp;cache_function=cache_scope;load_function=load_scope;open_function=filtered_openimport_scope;subst_function=(fun(_,())->());classify_function=(fun()->Substitute);}letdeclare_scopeid=let()=ifScopeTab.exists(Lib.make_pathid)thenCErrors.user_errPp.(str"Ltac2 notation scope "++Id.printid++str" already exists.")inLib.add_leaf(inScopeid())letcurrent_scopes=Summary.ref~name:"ltac2-current-scopes"[]typeopen_close_scope=Open|Closeletcache_open_close_scope(sc,openclose)=matchopenclosewith|Open->current_scopes:=sc::(List.removeTac2Scope.equalsc!current_scopes)|Close->current_scopes:=List.removeTac2Scope.equalsc!current_scopesletinOpenCloseScope=Libobject.declare_object@@Libobject.object_with_locality"Ltac2 open/close scope"~cache:cache_open_close_scope~subst:(Some(fun(subst,(sc,openclose))->Mod_subst.subst_knsubstsc,openclose))~discharge:(funx->x)letopen_close_scopelocalscopenclose=letsc=find_scopescinLib.add_leaf(inOpenCloseScope(local,(sc,openclose)))letopen_scopelocalsc=open_close_scopelocalscOpenletclose_scopelocalsc=open_close_scopelocalscCloseletdefault_scope=Summary.ref~name:"ltac2-default-scope"Noneletcache_default_scopesc=let()=ifOption.has_some!default_scopethenCErrors.user_errPp.(str"Declare ML Module for the Ltac2 plugin in multiple Rocq modules is not supported.")indefault_scope:=SomescletinDefaultScope=Libobject.declare_object@@Libobject.superglobal_object"ltac2 default scope"~cache:cache_default_scope~subst:None~discharge:(fun_->assertfalse)letdeclare_default_scope()=letsc=Id.of_string"core"indeclare_scopesc;letsc=ScopeTab.locate(Libnames.qualid_of_identsc)inLib.add_leaf(inDefaultScopesc)let()=Mltop.(declare_cache_obj_full(interp_only_objdeclare_default_scope)"rocq-runtime.plugins.ltac2")letdefault_scope()=match!default_scopewith|Somev->v|None->assertfalseletcurrent_scopes()=!current_scopesmoduleTac2Custom=KerNamemoduleCustomV=structincludeTac2Customletis_var_=Noneletstage=Summary.Stage.Synterpletsummary_name="ltac2_customentrytab"endmoduleCustomTab=Nametab.EasyNoWarn(CustomV)()letltac2_custom_map:raw_tacexprProcq.Entry.tTac2Custom.Map.tProcq.GramState.field=Procq.GramState.field"ltac2_custom_map"letltac2_custom_entry:(Tac2Custom.t,raw_tacexpr)Procq.entry_command=Procq.create_entry_command"ltac2"{eext_fun=(funknestate->letmap=Option.defaultTac2Custom.Map.empty(Procq.GramState.getstateltac2_custom_map)inletmap=Tac2Custom.Map.addknemapinProcq.GramState.setstateltac2_custom_mapmap);eext_name=(funkn->"custom-ltac2:"^Tac2Custom.to_stringkn);eext_eq=Tac2Custom.equal;}letfind_custom_entrykn=Tac2Custom.Map.getkn@@Option.get@@Procq.GramState.get(Procq.gramstate())ltac2_custom_maplet()=Metasyntax.register_custom_grammar_for_print@@funname->matchCustomTab.locatenamewith|exceptionNot_found->None|name->Some[Any(find_custom_entryname)]letload_custom_entryi((sp,kn),local)=let()=CustomTab.push(Untili)spkninlet()=Procq.extend_entry_commandltac2_custom_entrykninlet()=assert(notlocal)in()letimport_custom_entryi((sp,kn),local)=let()=CustomTab.push(Exactlyi)spknin()letcache_custom_entryo=load_custom_entry1o;import_custom_entry1oletinCustomEntry:Id.t->bool->Libobject.obj=letopenLibobjectindeclare_named_object{(default_object"Ltac2 custom entry")withobject_stage=Synterp;cache_function=cache_custom_entry;load_function=load_custom_entry;open_function=filtered_openimport_custom_entry;subst_function=(fun(_,x)->x);classify_function=(funlocal->iflocalthenDisposeelseSubstitute);}moduleSyntax=structmoduleDynEntry=Dyn.Make()moduleEntryMap=DynEntry.Map(structtype'at='aProcq.Entry.tend)letentries=refEntryMap.empty(* NB someday we may want to allow registering more custom entry kinds
instead of handling custom constr and custom ltac2 specially *)type'aentry=|RegisteredEntryof'aDynEntry.tag|CustomConstr:Globnames.CustomName.t->Constrexpr.constr_exprentry|CustomLtac2:Tac2Custom.t->raw_tacexprentryletregister_entry?nameentry=letname=Option.default(Procq.Entry.nameentry)nameinlettag=DynEntry.createnameinentries:=EntryMap.addtagentry!entries;RegisteredEntrytagletget_entry:typea.aentry->aProcq.Entry.t=function|RegisteredEntrye->EntryMap.finde!entries|CustomConstre->fst@@Egramrocq.find_custom_entrye|CustomLtac2e->find_custom_entryeletentry_equal:typeab.aentry->bentry->(a,b)Util.eqoption=funab->matcha,bwith|RegisteredEntrya,RegisteredEntryb->DynEntry.eqab|CustomConstra,CustomConstrb->ifGlobnames.CustomName.equalabthenSomeReflelseNone|CustomLtac2a,CustomLtac2b->ifTac2Custom.equalabthenSomeReflelseNone|(RegisteredEntry_|CustomConstr_|CustomLtac2_),_->Noneletentry_compare:typeab.aentry->bentry->int=funab->matcha,bwith|RegisteredEntrya,RegisteredEntryb->DynEntry.compareab|RegisteredEntry_,_->-1|_,RegisteredEntry_->1|CustomConstra,CustomConstrb->Globnames.CustomName.compareab|CustomConstr_,_->-1|_,CustomConstr_->1|CustomLtac2a,CustomLtac2b->Tac2Custom.compareabtype'at=|NTermof'aentry|NTermlof'aentry*string|List0:'at*stringoption->'alistt|List1:'at*stringoption->'alistt|Opt:'at->'aoptiont|Self:raw_tacexprt|Next:raw_tacexprt|Tokenof'aTok.p|Tokens:Procq.ty_patternlist->unitt|Seqof'aseqand_seq=|Nil:unitseq|Snoc:'aseq*'bt->('a*'b)seq(* We use snoc lists for seq because that works better when translating to Procq.Rule.t
(the same argument is on the outside of the tuple ['r] and of the function type ['f]) *)type_rec_=|NoRec:Gramlib.Grammar.norecrec_|MayRectype'asymbol=Symb:'mayrecrec_*(raw_tacexpr,'mayrec,'a)Procq.Symbol.t->'asymbol(* Procq.Rule.t contains the type ['fulla] parsed by the whole seq in it last argument.
We connect it to the type ['a] involved in the head of the seq using this GADT.
(and also handle mayrec) *)type('a,'fulla)rule=Rule:'mayrecrec_*(('a->Loc.t->'fulla)->'f)*(raw_tacexpr,'mayrec,'f,Loc.t->'fulla)Procq.Rule.t->('a,'fulla)ruleletnorecs=Symb(NoRec,s)letrecto_symbol:typea.at->asymbol=funs->letopenProcq.Symbolinmatchswith|NTerme->norec@@nterm(get_entrye)|NTerml(e,lev)->norec@@nterml(get_entrye)lev|List0(s,None)->letSymb(mayrec,s)=to_symbolsinSymb(mayrec,list0s)|List0(s,Somesep)->letSymb(mayrec,s)=to_symbolsinletsep=tokens[TPattern(CLexer.terminalsep)]inSymb(mayrec,list0sepssep)|List1(s,None)->letSymb(mayrec,s)=to_symbolsinSymb(mayrec,list1s)|List1(s,Somesep)->letSymb(mayrec,s)=to_symbolsinletsep=tokens[TPattern(CLexer.terminalsep)]inSymb(mayrec,list1sepssep)|Opts->letSymb(mayrec,s)=to_symbolsinSymb(mayrec,opts)|Self->Symb(MayRec,self)|Next->Symb(MayRec,next)|Tokenp->norec@@tokenp|Tokensl->norec@@tokensl|Seqs->seq_to_symbolsandseq_to_rule:typeafulla.aseq->(a,fulla)rule=funs->matchswith|Nil->Rule(NoRec,(funf(loc:Loc.t)->f()loc),Procq.Rule.stop)|Snoc(hd,x)->letRule(rechd,f,hd)=seq_to_rulehdinletSymb(recx,x)=to_symbolxinletf(g:a->Loc.t->fulla)x=f(funhdloc->g(hd,x)loc)inmatchrechd,recxwith|NoRec,NoRec->letrule=Procq.Rule.next_norechdxinRule(NoRec,f,rule)|MayRec,_|_,MayRec->letrule=Procq.Rule.nexthdxinRule(MayRec,f,rule)andseq_to_symbol:typea.aseq->asymbol=funs->letopenProcq.SymbolinletRule(mayrec,f,r)=seq_to_rulesinmatchmayrecwith|MayRec->CErrors.user_errPp.(str"Recursive symbols (self / next) are not allowed in local rules.")|NoRec->norec@@rules[Procq.Rules.maker(f(fun(x:a)(_:Loc.t)->x))]letconstr=register_entryProcq.Constr.constrletlconstr=register_entryProcq.Constr.lconstrletterm=register_entryProcq.Constr.termletcustom_constrc=CustomConstrcletcustom_ltac2c=CustomLtac2cletltac2_expr=register_entryinternal_ltac2_exprletnterme=NTermeletntermlelev=NTerml(e,lev)letlist0?seps=List0(s,sep)letlist1?seps=List1(s,sep)letopts=Optsletself=Selfletnext=Nextlettokenp=Tokenplettokensl=Tokenslletseqs=Seqsletnil=Nilletsnocab=Snoc(a,b)letrecequal:typeab.at->bt->(a,b)Util.eqoption=funab->matcha,bwith|NTerma,NTermb->entry_equalab|NTerml(a,leva),NTerml(b,levb)->lete=entry_equalabinifOption.has_somee&&String.equallevalevbtheneelseNone|List0(a,sepa),List0(b,sepb)->beginmatchequalabwith|None->None|SomeRefl->ifOption.equalString.equalsepasepbthenSomeReflelseNoneend|List1(a,sepa),List1(b,sepb)->beginmatchequalabwith|None->None|SomeRefl->ifOption.equalString.equalsepasepbthenSomeReflelseNoneend|Opta,Optb->beginmatchequalabwith|None->None|SomeRefl->SomeReflend|Self,Self->SomeRefl|Next,Next->SomeRefl|Tokena,Tokenb->Tok.equal_pab|Tokensa,Tokensb->leteq(Procq.TPatternp1)(Procq.TPatternp2)=Option.has_some(Tok.equal_pp1p2)inifCList.for_all2eqeqabthenSomeReflelseNone|Seqs1,Seqs2->equal_seqs1s2|(NTerm_|NTerml_|List0_|List1_|Opt_|Self|Next|Token_|Tokens_|Seq_),_->Noneandequal_seq:typeab.aseq->bseq->(a,b)Util.eqoption=funab->matcha,bwith|Nil,Nil->SomeRefl|Snoc(a1,a2),Snoc(b1,b2)->beginmatchequal_seqa1b1with|None->None|SomeRefl->matchequala2b2with|None->None|SomeRefl->SomeReflend|(Nil|Snoc_),_->Noneletreccompare:typeab.at->bt->int=funab->matcha,bwith|NTerma,NTermb->entry_compareab|NTerm_,_->-1|_,NTerm_->1|NTerml(a,leva),NTerml(b,levb)->lete=entry_compareabinife<>0theneelseString.comparelevalevb|NTerml_,_->-1|_,NTerml_->1|List0(a,sepa),List0(b,sepb)->beginmatchcompareabwith|0->Option.compareString.comparesepasepb|c->cend|List0_,_->-1|_,List0_->1|List1(a,sepa),List1(b,sepb)->beginmatchcompareabwith|0->Option.compareString.comparesepasepb|c->cend|List1_,_->-1|_,List1_->1|Opta,Optb->compareab|Opt_,_->-1|_,Opt_->1|Self,Self->0|Self,_->-1|_,Self->1|Next,Next->0|Next,_->-1|_,Next->1(* XXX treating [PIDENT (Some s)] and [PKEYWORD s] as equal may be
questionable, consider moving Tok.compare_p to this file (only
user at this time) and comparing them to be different
(AFAICT compare = 0 -> equal = Some Refl is the more important invariant,
we don't care as much about the other direction) *)|Tokena,Tokenb->Tok.compare_pab|Token_,_->-1|_,Token_->1|Tokensa,Tokensb->letcmp(Procq.TPatternp1)(Procq.TPatternp2)=Tok.compare_pp1p2inCList.comparecmpab|Tokens_,_->-1|_,Tokens_->1|Seqs1,Seqs2->compare_seqs1s2andcompare_seq:typeab.aseq->bseq->int=funab->matcha,bwith|Nil,Nil->0|Nil,_->-1|_,Nil->1|Snoc(a1,a2),Snoc(b1,b2)->beginmatchcompare_seqa1b1with|0->comparea2b2|c->cendendmoduleParsedNota=struct(* parsing rule + which entry it is in *)(* XXX also include level? *)type'at='aSyntax.seq*Tac2Custom.toptiontypeany=Any:_t->anyletcompare(a1,a2)(b1,b2)=letc=Option.compareTac2Custom.comparea2b2inifc<>0thencelseSyntax.compare_seqa1b1moduleAny=structtypet=anyletcompare(Anyx)(Anyy)=comparexyendmoduleAnyMap=CMap.Make(Any)endmoduleTacSyn=structtypet=WithArgs:'aParsedNota.t*'a->tletmake(x:t):tacsyn=Obj.magicxletget(x:tacsyn):t=Obj.magicxendtype'atoken=|TacTermofstring|TacNonTermofName.t*'atypesyntax_class_rule=|SyntaxRule:'aSyntax.t*('a->raw_tacexpr)->syntax_class_ruletypeused_levels=Int.Set.tTac2Custom.Map.tletno_used_levels=Tac2Custom.Map.emptyletunion_used_levelsab=Tac2Custom.Map.union(fun_ab->Some(Int.Set.unionab))ab(* hardcoded syntactic classes, from ltac2 or further plugins *)type'glbsyntax_class_decl={intern_synclass:sexprlist->used_levels*'glb;interp_synclass:'glb->syntax_class_rule;}moduleSynclassDyn=Dyn.Make()typesyntax_class=SynclassDyn.tmoduleSynclassInterpMap=SynclassDyn.Map(structtype'at='a->syntax_class_ruleend)letsyntax_class_interns:(sexprlist->used_levels*SynclassDyn.t)Id.Map.tref=refId.Map.emptyletsyntax_class_interps=refSynclassInterpMap.emptyletcheck_custom_entry_nameid=(* XXX allow it anyway? the name can be accessed by qualifying it *)ifId.Map.memid!syntax_class_internsthenCErrors.user_errPp.(str"Cannot declare "++Id.printid++str" as a ltac2 custom entry:"++spc()++str"that name is already used for a builtin syntactic class.")elseifCustomTab.exists(Lib.make_pathid)thenCErrors.user_errPp.(str"Ltac2 custom entry "++Id.printid++str" already exists.")letregister_custom_entryname=letname=name.CAst.vincheck_custom_entry_namename;(* not yet implemented: module local custom entries
NB: will need checks that exported notations don't rely on the local entries *)letlocal=falseinLib.add_leaf(inCustomEntrynamelocal)letregister_syntax_classid(s:_syntax_class_decl)=assert(not(Id.Map.memid!syntax_class_interns));lettag=SynclassDyn.create(Id.to_stringid)inletinternargs=letused,data=s.intern_synclassargsinused,SynclassDyn.Dyn(tag,data)insyntax_class_interns:=Id.Map.addidintern!syntax_class_interns;syntax_class_interps:=SynclassInterpMap.addtags.interp_synclass!syntax_class_interpsletlevel_namelev=string_of_intlevletterminal_synclass_tag:stringSynclassDyn.tag=SynclassDyn.create"<terminal>"letinterp_terminalstr:syntax_class_rule=letv_unit=CAst.make@@CTacCst(AbsKn(Tuple0))inSyntaxRule(Syntax.token(Tok.PIDENT(Somestr)),(fun_->v_unit))let()=syntax_class_interps:=SynclassInterpMap.addterminal_synclass_taginterp_terminal!syntax_class_interpstypecustom_synclass_data={custom_synclass_name:Tac2Custom.t;custom_synclass_level:intoption;}letinterp_custom_entrydata:syntax_class_rule=letename=data.custom_synclass_nameinletentry=Syntax.custom_ltac2enameinmatchdata.custom_synclass_levelwith|None->SyntaxRule(Syntax.ntermentry,(funexpr->expr))|Somelev->SyntaxRule(Syntax.ntermlentry(level_namelev),(funexpr->expr))letcustom_synclass_tag:custom_synclass_dataSynclassDyn.tag=SynclassDyn.create"<custom>"let()=syntax_class_interps:=SynclassInterpMap.addcustom_synclass_taginterp_custom_entry!syntax_class_interpsletintern_custom_entry?locqidenameargs:used_levels*custom_synclass_data=letlev=matchargswith|[]->None|[SexprInt{CAst.v=lev}]->Somelev|_::_->CErrors.user_err?locPp.(str"Invalid arguments for ltac2 custom entry "++pr_qualidqid++str".")inletused=matchlevwith|None->no_used_levels|Somelev->Tac2Custom.Map.singletonename(Int.Set.singletonlev)inused,{custom_synclass_name=ename;custom_synclass_level=lev;}letintern_syntactic_class?locqidargs=letis_class=ifqualid_is_identqidthenId.Map.find_opt(qualid_basenameqid)!syntax_class_internselseNoneinmatchis_classwith|Someintern->internargs|None->matchCustomTab.locateqidwith|kn->letused,data=intern_custom_entry?locqidknargsinused,SynclassDyn.Dyn(custom_synclass_tag,data)|exceptionNot_found->CErrors.user_err?locPp.(str"Unknown syntactic class"++spc()++pr_qualidqid)moduleParseToken=structletloc_of_token=function|SexprStr{loc}->loc|SexprInt{loc}->loc|SexprRec(loc,_,_)->Somelocletintern_syntax_class=function|SexprRec(_,{loc;v=Someid},toks)->intern_syntactic_classidtoks|SexprStr{v=str}->no_used_levels,SynclassDyn.Dyn(terminal_synclass_tag,str)|tok->letloc=loc_of_tokentokinCErrors.user_err?locPp.(str"Invalid parsing token")letcheck_namena=matchna.CAst.vwith|None->Anonymous|Someid->letid=ifqualid_is_identidthenqualid_basenameidelseCErrors.user_err?loc:id.locPp.(str"Must be an identifier.")inlet()=check_lowercase(CAst.make?loc:na.CAst.locid)inNameidletparse_token=function|SexprStr{v=s}->no_used_levels,TacTerms|SexprRec(_,na,[tok])->letna=check_namenainletused,syntax_class=intern_syntax_classtokinused,TacNonTerm(na,syntax_class)|tok->letloc=loc_of_tokentokinCErrors.user_err?locPp.(str"Invalid parsing token")letrecprint_syntax_class=letopenPpinfunction|SexprStrs->strs.CAst.v|SexprInti->inti.CAst.v|SexprRec(_,{v=na},[])->Option.catapr_qualid(str"_")na|SexprRec(_,{v=na},e)->Option.catapr_qualid(str"_")na++str"("++pr_sequenceprint_syntax_classe++str")"letprint_token=letopenPpinfunction|SexprStr{v=s}->quote(strs)|SexprRec(_,{v=na},[tok])->print_syntax_classtok|_->assertfalseendletintern_syntax_class=ParseToken.intern_syntax_classtypesynext={synext_used:used_levels;synext_tok:ParsedNota.any;synext_level:int;synext_local:bool;}letinterp_syntax_class(SynclassDyn.Dyn(tag,data))=letinterp=SynclassInterpMap.findtag!syntax_class_interpsininterpdatatypeany_seq=AnySeq:_Syntax.seq->any_seqletrecget_nota_parsing(tok:SynclassDyn.ttokenlist):any_seq=matchtokwith|[]->AnySeqNil|TacNonTerm(_,v)::tok->letSyntaxRule(syntax_class,_)=interp_syntax_classvinletAnySeqrest=get_nota_parsingtokinAnySeq(Snoc(rest,syntax_class))|TacTermt::tok->letAnySeqrest=get_nota_parsingtokinAnySeq(Snoc(rest,Syntax.token(CLexer.terminalt)))letdeprecated_ltac2_notation=Deprecation.create_warning~object_name:"Ltac2 notation"~warning_name_if_no_since:"deprecated-ltac2-notation"Pp.(fun(toks:sexprlist)->pr_sequenceParseToken.print_tokentoks)letltac2_levels=Procq.GramState.field"ltac2_levels"(* XXX optional lev and do reusefirst like in egramrocq? *)letfresh_levelstentrylev=matchentrywith|None->st,None|Someentry->letall_levels=Option.defaultTac2Custom.Map.empty@@Procq.GramState.getstltac2_levelsinletentry_levels=Option.defaultInt.Set.empty@@Tac2Custom.Map.find_optentryall_levelsinletlast_before=Int.Set.find_first_opt(funlev'->lev'>=lev)entry_levelsinifOption.equalInt.equallast_before(Somelev)thenst,Noneelseletpos=matchlast_beforewith|None->Gramlib.Gramext.First|Somelev'->Gramlib.Gramext.After(level_namelev')inletentry_levels=Int.Set.addleventry_levelsinletall_levels=Tac2Custom.Map.addentryentry_levelsall_levelsinletst=Procq.GramState.setstltac2_levelsall_levelsinst,Someposletcheck_levelsstused_levels=letall_levels=Option.defaultTac2Custom.Map.empty@@Procq.GramState.getstltac2_levelsinletiterknused=letknown=Option.defaultInt.Set.empty(Tac2Custom.Map.find_optknall_levels)inletmissing=Int.Set.diffusedknowninifnot(Int.Set.is_emptymissing)thenCErrors.user_errPp.(str"Unknown "++str(String.plural(Int.Set.cardinalmissing)"level")++str" for ltac2 custom entry "++CustomTab.prkn)inTac2Custom.Map.iteriterused_levelsletperform_notationsynst=letAnyparsing=syn.synext_tokinletused=syn.synext_usedinletrule,entry=parsinginletRule(_,f,rule)=Syntax.seq_to_ruleruleinletgargsloc=CAst.make~loc@@CTacSyn(TacSyn.make@@WithArgs(parsing,args))inletrule=Procq.Production.makerule(fg)inletlev=syn.synext_levelinletst,fresh=fresh_levelstentrylevinlet()=check_levelsstusedinletpos=Some(level_namelev)inletrule=matchfreshwith|None->Procq.Reuse(pos,[rule])|Somepos'->(* BothA means we can have SELF on both the left and right of a rule. *)Procq.Fresh(pos',[pos,SomeBothA,[rule]])inletentry=matchentrywith|None->internal_ltac2_expr|Someentry->find_custom_entryentryin[Procq.ExtendRule(entry,rule)],stletltac2_notation=Procq.create_grammar_command"ltac2-notation"{gext_fun=perform_notation;gext_eq=(==)(* FIXME *)}letcache_synextsyn=Procq.extend_grammar_command~ignore_kw:falseltac2_notationsyn(* XXX missing subst on custom entries, including recursively in SynclassDyn.t *)letsubst_synext(subst,syn)=synletltac2_notation_cat=Libobject.create_category"ltac2.notations"letinTac2Notation:synext->Libobject.obj=letopenLibobjectindeclare_object{(default_object"TAC2-NOTATION")withobject_stage=Summary.Stage.Synterp;cache_function=cache_synext;open_function=simple_open~cat:ltac2_notation_catcache_synext;subst_function=subst_synext;classify_function=(funo->ifo.synext_localthenDisposeelseSubstitute);}typenotation_data=|UntypedNotaofraw_tacexpr|TypedNotaof{nota_prms:int;nota_argtys:intglb_typexprId.Map.t;nota_ty:intglb_typexpr;nota_body:glb_tacexpr;}type('scope,'body)notation_interpretation={nota_local:bool;(* sexpr used for printing deprecation message, XXX print the internalized version? *)nota_raw:sexprlist;nota_depr:Deprecation.toption;nota_parsing:ParsedNota.any;nota_scope:'scope;nota_tok:SynclassDyn.ttokenlist;nota_body:'body;}letnotation_data:(Tac2Scope.t,notation_data)notation_interpretationTac2Scope.Map.tParsedNota.AnyMap.tref=Summary.ref~name:"tac2notation-data"ParsedNota.AnyMap.emptyletrecinterp_notation_args:typea.aSyntax.seq->_->a->_=funparsingtoksargs->matchparsing,toks,argswith|Nil,(_::_),()|Snoc_,[],(_,_)->assertfalse|Nil,[],()->[]|Snoc(hd,_),TacTerm_::toks,(args,_)->interp_notation_argshdtoksargs|Snoc(hd,x),TacNonTerm(na,tok)::toks,(args,arg)->letSyntaxRule(x',inj)=interp_syntax_classtokinletRefl=matchSyntax.equalxx'with|None->assertfalse|Somee->einletarg=injargin(* XXX loc (only used for untyped notations though) *)(CAst.makena,arg)::interp_notation_argshdtoksargs(* to have scoped notations: add a scope stack argument here,
per-scope notations in the notation_data map, and user syntax for
scopes *)letinterp_notation?locscopessyn:notation_data*(lname*raw_tacexpr)list=letWithArgs((rule,_asparsing),args)=TacSyn.getsyninletdata=(* NB no Reserve Notation for ltac2 so can't have a notation without interp data *)ParsedNota.AnyMap.get(Anyparsing)!notation_datainletdata=matchList.find_map(funsc->Tac2Scope.Map.find_optscdata)scopeswith|Somedata->data|None->CErrors.user_err?locPp.(str"Unknown interpretation for Ltac2 notation in currently open scopes"++spc()++str"(notation available in scopes: "++pr_enum(fun(sc,_)->ScopeTab.prsc)(Tac2Scope.Map.bindingsdata)++str").")inlet()=matchdata.nota_deprwith|None->()|Somedepr->deprecated_ltac2_notation?loc(data.nota_raw,depr)inletargs=interp_notation_argsruledata.nota_tokargsindata.nota_body,argsletcache_synext_interpdata=letadd_datam=letm=Option.defaultTac2Scope.Map.emptyminletm=Tac2Scope.Map.adddata.nota_scopedataminSomeminnotation_data:=ParsedNota.AnyMap.updatedata.nota_parsingadd_data!notation_dataletsubst_notation_datasubst=function|UntypedNotabodyasn->letbody'=subst_rawexprsubstbodyinifbody'==bodythennelseUntypedNotabody'|TypedNota{nota_prms=prms;nota_argtys=argtys;nota_ty=ty;nota_body=body}asn->letbody'=subst_exprsubstbodyinletargtys'=Id.Map.Smart.map(subst_typesubst)argtysinletty'=subst_typesubsttyinifbody'==body&&argtys'==argtys&&ty'==tythennelseTypedNota{nota_body=body';nota_argtys=argtys';nota_ty=ty';nota_prms=prms}(* XXX missing subst on custom entries, recursively in SynclassDyn.t *)letsubst_synext_interp(subst,data)=letbody'=subst_notation_datasubstdata.nota_bodyinifbody'==data.nota_bodythendataelse{datawithnota_body=body'}letinTac2NotationInterp:_->Libobject.obj=letopenLibobjectindeclare_object{(default_object"TAC2-NOTATION-INTERP")withcache_function=cache_synext_interp;open_function=simple_open~cat:ltac2_notation_catcache_synext_interp;subst_function=subst_synext_interp;classify_function=(fundata->ifdata.nota_localthenDisposeelseSubstitute);}typenotation_target={target_entry:qualidoption;target_level:intoption;target_scope:qualidoption;}letpr_register_notationtkntargetbody=letopenPpinletpptarget=matchtarget.target_entry,target.target_levelwith|None,None->mt()|None,Somelev->spc()++str": "++intlev|Someentry,None->spc()++str": "++pr_qualidentry|Someentry,Somelev->spc()++str": "++pr_qualidentry++str"("++intlev++str")"inprlist_with_sepspcTac2print.pr_syntax_classtkn++pptarget++spc()++hov2(str":= "++Tac2print.pr_rawexpr_genE5~avoid:Id.Set.emptybody)lettactic_qualid=qualid_of_ident(Id.of_string"tactic")letregister_notationattstkntargetbody=letdeprecation,local=Attributes.(parseNotations.(deprecation++locality))attsinletlocal=Option.defaultfalselocalinletentry=matchtarget.target_entrywith|Someentry->ifqualid_eqentrytactic_qualidthenNoneelsebegintrySome(CustomTab.locateentry)withNot_found->CErrors.user_errPp.(str"Unknown entry "++pr_qualidentry++str".")end|None->Nonein(* Globalize so that names are absolute *)letlev=ifOption.has_someentrythenletlev=matchtarget.target_levelwith|Somelev->lev|None->CErrors.user_errPp.(str"Custom entry level must be explicit.")inlet()=iflev<0thenCErrors.user_errPp.(str"Custom entry levels must be nonnegative.")inlevelsematchtarget.target_levelwith|Somen->let()=ifn<0||n>6thenCErrors.user_errPp.(str"Notation levels must range between 0 and 6")inn|None->(* autodetect level *)beginmatchtknwith|SexprStrs::_whenNames.Id.is_valids.CAst.v->1|_->5endinlettokens=List.rev_mapParseToken.parse_tokentkninletused,tokens=List.splittokensinletused=List.fold_leftunion_used_levelsno_used_levelsusedinletAnySeqparsing=get_nota_parsingtokensinletparsing=ParsedNota.Any(parsing,entry)inletext={synext_used=used;synext_tok=parsing;synext_level=lev;synext_local=local;}inLib.add_leaf(inTac2Notationext);{nota_local=local;nota_raw=tkn;nota_depr=deprecation;nota_parsing=parsing;nota_tok=tokens;nota_scope=target.target_scope;nota_body=body;}letintern_notation_interpretationintern_bodydata=letaccumulate_idsacc=function|TacTerm_->acc|TacNonTerm(Anonymous,_)->acc|TacNonTerm(Nameid,_)->Id.Set.addidaccinletids=List.fold_leftaccumulate_idsId.Set.emptydata.nota_tokinletbody=intern_bodyidsdata.nota_bodyinletscope=matchdata.nota_scopewith|None->default_scope()|Somesc->find_scopescin{datawithnota_body=body;nota_scope=scope}letregister_notation_interpretationdata=Lib.add_leaf(inTac2NotationInterpdata)moduleInternal=structletltac2_expr=internal_ltac2_exprend