12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123(************************************************************************)(* * The Coq Proof Assistant / The Coq 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) *)(************************************************************************)openPpopenCErrorsopenUtilopenNamesopenConstrexpropenConstrexpr_opsopenVernacexpropenNotation_termopenNotationexternopenNotation_gramopenNotation_opsopenPpextendopenExtendopenLibobjectopenConstrinternopenLibnamesopenNotationopenNameops(** **************************************************************** **)(** Printing grammar entries **)letentry_buf=Buffer.create64letpr_entrye=let()=Buffer.clearentry_bufinletft=Format.formatter_of_bufferentry_bufinlet()=Pcoq.Entry.printfteinstr(Buffer.contentsentry_buf)leterror_unknown_entry?locname=user_err?locPp.(str"Unknown or unprintable grammar entry "++strname++str".")letpr_registered_grammarname=letgram=Pcoq.find_grammars_by_namenameinmatchgramwith|[]->error_unknown_entryname|entries->letpr_one(Pcoq.Entry.Anye)=str"Entry "++str(Pcoq.Entry.namee)++str" is"++fnl()++pr_entryeinprlistpr_oneentriesletpr_grammar_subsetgrammar=letpp=String.Map.mapi(funnamel->matchlwith|[]->assertfalse|entries->str"Entry "++strname++str" is"++fnl()++prlist_with_sep(fun()->str"or"++fnl())(fun(Pcoq.Entry.Anye)->pr_entrye)entries)grammarinletpp=CString.Map.bindingsppinprlist_with_sepfnl(fun(_,pp)->pp)ppletis_known=letopenPcoq.Entryinfunction|"constr"|"term"|"binder_constr"->Some[AnyPcoq.Constr.constr;AnyPcoq.Constr.lconstr;AnyPcoq.Constr.binder_constr;AnyPcoq.Constr.term;]|"vernac"->SomePvernac.Vernac_.[Anyvernac_control;(* main_entry="vernac", included not because it's interesting but because otherwise it's shadowed by the "vernac" group defined here *)Anymain_entry;Anycommand;Anysyntax;Anygallina;Anygallina_ext;]|name->letgram=Pcoq.find_grammars_by_namenameinmatchgramwith|[]->None|entries->Someentriesletfull_grammar()=letopenPvernac.Vernac_inletopenPcoq.Entryinletproof_modes=List.map(fun(_,e)->Anye)(CString.Map.bindings(Pvernac.list_proof_modes()))inletentries=(Anyvernac_control)::(Anynoedit_mode)::proof_modesinPcoq.Entry.accumulate_inentriesletsame_entry(Pcoq.Entry.Anye)(Pcoq.Entry.Anye')=(Obj.magice)==(Obj.magice')letpr_grammar=function|[]->letgrammar=full_grammar()inpr_grammar_subsetgrammar|names->letknown,other=List.fold_left(fun(known,other)name->matchis_knownnamewith|Somev->v@known,other|None->known,name::other)([],[])namesinletgrammar=ifList.is_emptyotherthenString.Map.emptyelsefull_grammar()inlet()=List.iter(funname->ifnot(String.Map.memnamegrammar)thenerror_unknown_entryname)otherinletother=String.Set.of_listotherinletgrammar=String.Map.filter(funname_->String.Set.memnameother)grammarinletgrammar=List.fold_left(fungrammar(Pcoq.Entry.Anyeasany)->String.Map.update(Pcoq.Entry.namee)(function|None->Some[any]|Somevlasv->ifList.mem_fsame_entryanyvlthenvelseSome(any::vl))grammar)grammarknowninpr_grammar_subsetgrammarletpr_custom_grammarname=pr_registered_grammar("custom:"^name)letpr_keywords()=Pp.prlist_with_sepPp.fnlPp.str(CString.Set.elements(CLexer.keywords(Pcoq.get_keyword_state())))(** **************************************************************** **)(** Parse a format (every terminal starting with a letter or a single
quote (except a single quote alone) must be quoted) **)letparse_format({CAst.loc;v=str}:lstring)=letlen=String.lengthstrin(* TODO: update the line of the location when the string contains newlines *)letmake_locij=Option.map(Loc.shift_loc(i+1)(j-len))locinletpush_tokenloca=function|(i,cur)::l->(i,(loc,a)::cur)::l|[]->assertfalseinletpush_whiteinl=ifInt.equaln0thenlelsepush_token(make_loci(i+n))(UnpTerminal(String.maken' '))linletclose_boxstartstopb=function|(_,a)::(_::_asl)->push_token(make_locstartstop)(UnpBox(b,a))l|[a]->user_err?loc:(make_locstartstop)Pp.(str"Non terminated box in format.")|[]->assertfalseinletclose_quotationstarti=ifi<len&&str.[i]=='\''thenif(Int.equal(i+1)len||str.[i+1]==' ')theni+1elseuser_err?loc:(make_loc(i+1)(i+1))Pp.(str"Space expected after quoted expression.")elseuser_err?loc:(make_locstart(i-1))Pp.(str"Beginning of quoted expression expected to be ended by a quote.")inletrecspacesni=ifi<len&&str.[i]==' 'thenspaces(n+1)(i+1)elseninletrecnonspacesquotedni=ifi<len&&str.[i]!=' 'thenifstr.[i]=='\''&"ed&&(i+1>=len||str.[i+1]==' ')thenifInt.equaln0thenuser_err?loc:(make_loc(i-1)i)Pp.(str"Empty quoted token.")elsenelsenonspacesquoted(n+1)(i+1)elseifquotedthenuser_err?loc:(make_locii)Pp.(str"Spaces are not allowed in (quoted) symbols.")elseninletrecparse_non_formati=letn=nonspacesfalse0iinpush_token(make_loci(i+n-1))(UnpTerminal(String.substrin))(parse_token1(i+n))andparse_quotednki=ifi<lenthenmatchstr.[i]with(* Parse " // " *)|'/'wheni+1<len&&str.[i+1]=='/'->(* We discard the useless n spaces... *)push_token(make_loc(i-n)(i+1))(UnpCutPpFnl)(parse_token1(close_quotationi(i+2)))(* Parse " .. / .. " *)|'/'wheni+1<len->letp=spaces0(i+1)inpush_token(make_loc(i-n)(i+p))(UnpCut(PpBrk(n,p)))(parse_token1(close_quotationi(i+p+1)))|c->(* The spaces are real spaces *)push_white(i-n-1-k)n(matchcwith|'['->ifi+1<lenthenmatchstr.[i+1]with(* Parse " [h .. ", *)|'h'wheni+1<=len&&str.[i+2]=='v'->(parse_boxi(funn->PpHVBn)(i+3))(* Parse " [v .. ", *)|'v'->parse_boxi(funn->PpVBn)(i+2)(* Parse " [ .. ", *)|' '|'\''->parse_boxi(funn->PpHOVBn)(i+1)|_->user_err?loc:(make_locii)Pp.(str"\"v\", \"hv\", \" \" expected after \"[\" in format.")elseuser_err?loc:(make_locii)Pp.(str"\"v\", \"hv\" or \" \" expected after \"[\" in format.")(* Parse "]" *)|']'->((i,[])::parse_token1(close_quotationi(i+1)))(* Parse a non formatting token *)|c->letn=nonspacestrue0iinpush_token(make_loci(i+n-1))(UnpTerminal(String.substr(i-1)(n+2)))(parse_token1(close_quotationi(i+n))))elseifInt.equaln0then[]elseuser_err?loc:(make_loc(len-n)len)Pp.(str"Ending spaces non part of a format annotation.")andparse_boxstartboxi=letn=spaces0iinclose_boxstart(i+n-1)(boxn)(parse_token1(close_quotationi(i+n)))andparse_tokenki=letn=spaces0iinleti=i+ninifi<lenthenmatchstr.[i]with(* Parse a ' *)|'\''wheni+1>=len||str.[i+1]==' '->push_white(i-n)(n-k)(push_token(make_loci(i+1))(UnpTerminal"'")(parse_token1(i+1)))(* Parse the beginning of a quoted expression *)|'\''->parse_quoted(n-k)k(i+1)(* Otherwise *)|_->push_white(i-n)(n-k)(parse_non_formati)elsepush_white(i-n)n[(len,[])]inifnot(String.is_emptystr)thenmatchparse_token00with|[_,l]->l|(i,_)::_->user_err?loc:(make_locii)Pp.(str"Box closed without being opened.")|[]->assertfalseelse[](** **************************************************************** **)(** Analyzing notations **)(* Find non-terminal tokens of notation *)(* To protect alphabetic tokens and quotes from being seen as variables *)letquote_notation_tokenx=letn=String.lengthxinletnorm=CLexer.is_identxinif(n>0&&norm)||(n>2&&x.[0]=='\'')then"'"^x^"'"elsexletanalyze_notation_tokens~onlyprinting~infixentrydf=letdf=ifinfixthenquote_notation_tokendfelsedfinlet{recvars;mainvars;symbols}asres=decompose_raw_notationdfin(* don't check for nonlinearity if printing only, see Bug 5526 *)(ifnotonlyprintingthenmatchList.duplicatesId.equal(mainvars@List.mapsndrecvars)with|id::_->user_err(str"Variable "++Id.printid++str" occurs more than once.")|_->());letis_prim_token=is_prim_token_constant_in_constr(entry,symbols)inres,is_prim_tokenletadjust_symbolsvarsnotation_symbols=letx=Namegen.next_ident_away(Id.of_string"x")varsinlety=Namegen.next_ident_away(Id.of_string"y")(Id.Set.addxvars)inletnotation_symbols={recvars=notation_symbols.recvars;mainvars=x::notation_symbols.mainvars@[y];symbols=NonTerminalx::notation_symbols.symbols@[NonTerminaly];}inx,y,notation_symbolsletadjust_reserved_infix_notationnotation_symbols=letvars=Id.Set.of_list(List.map_filter(functionNonTerminalx->Somex|_->None)notation_symbols.symbols)inlet_,_,notation_symbols=adjust_symbolsvarsnotation_symbolsinnotation_symbolsletadjust_infix_notationdfnotation_symbolsc=letvars=names_of_constr_exprcinletx,y,notation_symbols=adjust_symbolsvarsnotation_symbolsinletdf=Id.to_stringx^" "^df^" "^Id.to_stringyinletinject_varx=CAst.make@@CRef(qualid_of_identx,None)inletmetas=[inject_varx;inject_vary]inletc=mkAppC(c,metas)indf,notation_symbols,cletwarn_unexpected_primitive_token_modifier=CWarnings.create~name:"primitive-token-modifier"~category:CWarnings.CoreCategories.parsing(fun()->str"Notations for numbers or strings are primitive; skipping this modifier.")letcheck_no_syntax_modifiers_for_numeral=function|[]->()|l->List.iter(function{CAst.loc}->warn_unexpected_primitive_token_modifier?loc())lleterror_not_same_scopexy=user_err(str"Variables "++Id.printx++str" and "++Id.printy++str" must be in the same scope.")(** **************************************************************** **)(** Build pretty-printing rules **)letpr_notation_entry=function|InConstrEntry->str"constr"|InCustomEntrys->str"custom "++strsletside=function|BorderProd(b,_)->Someb|_->Noneletprecedence_of_position_and_levelfrom_level=function|NumLeveln,BorderProd(b,Somea)->letprec=letopenGramlib.Gramextinmatcha,bwith|RightA,Left->LevelLtn|RightA,Right->LevelLen|LeftA,Left->LevelLen|LeftA,Right->LevelLtn|NonA,_->LevelLtnin{notation_subentry=InConstrEntry;notation_relative_level=prec;notation_position=Someb}|NumLeveln,b->{notation_subentry=InConstrEntry;notation_relative_level=LevelLen;notation_position=sideb}|NextLevel,b->{notation_subentry=InConstrEntry;notation_relative_level=LevelLtfrom_level;notation_position=sideb}|DefaultLevel,b->{notation_subentry=InConstrEntry;notation_relative_level=LevelSome;notation_position=sideb}(** Computing precedences of non-terminals for parsing *)letprecedence_of_entry_type{notation_entry=from_custom;notation_level=from_level}=function|ETConstr(custom,_,x)whennotation_entry_eqcustomfrom_custom->(precedence_of_position_and_levelfrom_levelx).notation_relative_level|ETConstr(custom,_,(NumLeveln,_))->LevelLen|ETConstr(custom,_,(NextLevel,_))->user_err(strbrk"\"next level\" is only for sub-expressions in the same entry as where the notation is ("++quote(pr_notation_entrycustom)++strbrk" is different from "++quote(pr_notation_entryfrom_custom)++str").")|ETPattern(_,n)->letn=matchnwithNone->0|Somen->ninLevelLen|_->LevelSome(* should not matter *)letpattern_entry_level=functionNone->0|Somen->n(** Computing precedences for future insertion of parentheses at
the time of printing using hard-wired constr levels *)letunparsing_precedence_of_entry_typefrom_level=function|ETConstr(InConstrEntry,_,x)->(* Possible insertion of parentheses at printing time to deal
with precedence in a constr entry is managed using [prec_less]
in [ppconstr.ml] *)precedence_of_position_and_levelfrom_levelx|ETConstr(custom,_,(_,b))->(* Precedence of printing for a custom entry is managed using
explicit insertion of entry coercions at the time of building
a [constr_expr] *){notation_subentry=custom;notation_relative_level=LevelSome;notation_position=sideb}|ETPattern(_,n)->(* in constr *){notation_subentry=InConstrEntry;notation_relative_level=LevelLe(pattern_entry_leveln);notation_position=None}|_->(* should not matter *){notation_subentry=InConstrEntry;notation_relative_level=LevelSome;notation_position=None}(** Utilities for building default printing rules *)(* Some breaking examples *)(* "x = y" : "x /1 = y" (breaks before any symbol) *)(* "x =S y" : "x /1 =S /1 y" (protect from confusion; each side for symmetry)*)(* "+ {" : "+ {" may breaks reversibility without space but oth. not elegant *)(* "x y" : "x spc y" *)(* "{ x } + { y }" : "{ x } / + { y }" *)(* "< x , y > { z , t }" : "< x , / y > / { z , / t }" *)letstarts_with_left_brackets=letl=String.lengthsinnot(Int.equall0)&&(s.[0]=='{'||s.[0]=='['||s.[0]=='(')letends_with_right_brackets=letl=String.lengthsinnot(Int.equall0)&&(s.[l-1]=='}'||s.[l-1]==']'||s.[l-1]==')')letis_left_brackets=starts_with_left_brackets&¬(ends_with_right_brackets)letis_right_brackets=not(starts_with_left_brackets)&&ends_with_right_bracketsletis_commas=letl=String.lengthsinnot(Int.equall0)&&(s.[0]==','||s.[0]==';')letis_operators=letl=String.lengthsinnot(Int.equall0)&&(s.[0]=='+'||s.[0]=='*'||s.[0]=='='||s.[0]=='-'||s.[0]=='/'||s.[0]=='<'||s.[0]=='>'||s.[0]=='@'||s.[0]=='\\'||s.[0]=='&'||s.[0]=='~'||s.[0]=='$')letis_non_terminal=function|NonTerminal_|SProdList_->true|_->falseletis_next_non_terminalb=function|[]->b|pr::_->is_non_terminalprletis_next_terminal=functionTerminal_::_->true|_->falseletis_next_break=functionBreak_::_->true|_->falseletadd_breaknl=(None,UnpCut(PpBrk(n,0)))::lletadd_break_if_nonenb=function|(_,UnpCut(PpBrk_))::_asl->l|[]whennotb->[]|l->(None,UnpCut(PpBrk(n,0)))::lletcheck_open_binderisopenslm=letpr_token=function|Terminals->strs|Breakn->str"␣"|_->assertfalseinifisopen&¬(List.is_emptysl)thenuser_err(str"as "++Id.printm++str" is a non-closed binder, no such \""++prlist_with_sepspcpr_tokensl++strbrk"\" is allowed to occur.")letunparsing_metavarifromtyps=letx=List.nthtyps(i-1)inletsubentry=unparsing_precedence_of_entry_typefromxinmatchxwith|ETConstr_|ETGlobal|ETBigint->UnpMetaVarsubentry|ETPattern_|ETName|ETIdent->UnpBinderMetaVar(subentry,NotQuotedPattern)|ETBinderisopen->UnpBinderMetaVar(subentry,QuotedPattern)(** Heuristics for building default printing rules *)letindex_ididl=List.indexId.equalidlletmake_hunksetypssymbolsfrom_level=letvars,typs=List.splitetypsinletrecmakeb=function|NonTerminalm::prods->leti=index_idmvarsinletu=unparsing_metavarifrom_leveltypsinifis_next_non_terminalbprodsthen(None,u)::add_break_if_none1b(makebprods)else(None,u)::make_with_spacebprods|Terminals::prodswhen(* true to simulate presence of non-terminal *)b||List.existsis_non_terminalprods->if(is_commas||is_operators)then(* Always a breakable space after comma or separator *)(None,UnpTerminals)::add_break_if_none1b(makebprods)elseifis_right_brackets&&is_next_terminalprodsthen(* Always no space after right bracked, but possibly a break *)(None,UnpTerminals)::add_break_if_none0b(makebprods)elseifis_left_brackets&&is_next_non_terminalbprodsthen(None,UnpTerminals)::makebprodselseifnot(is_next_breakprods)then(* Add rigid space, no break, unless user asked for something *)(None,UnpTerminal(s^" "))::makebprodselse(* Rely on user spaces *)(None,UnpTerminals)::makebprods|Terminals::prods->(* Separate but do not cut a trailing sequence of terminal *)(matchprodswith|Terminal_::_->(None,UnpTerminal(s^" "))::makebprods|_->(None,UnpTerminals)::makebprods)|Breakn::prods->add_breakn(makebprods)|SProdList(m,sl)::prods->leti=index_idmvarsinlettyp=List.nthtyps(i-1)inletsubentry=unparsing_precedence_of_entry_typefrom_leveltypinletsl'=(* If no separator: add a break *)ifList.is_emptyslthenadd_break1[](* We add NonTerminal for simulation but remove it afterwards *)elsemaketrueslinlethunk=matchtypwith|ETConstr_->UnpListMetaVar(subentry,List.mapsndsl')|ETBinderisopen->check_open_binderisopenslm;UnpBinderListMetaVar(isopen,true,List.mapsndsl')|ETName|ETIdent->UnpBinderListMetaVar(false,true,List.mapsndsl')|ETPattern_->UnpBinderListMetaVar(false,false,List.mapsndsl')|_->assertfalsein(None,hunk)::make_with_spacebprods|[]->[]andmake_with_spacebprods=matchprodswith|Terminals'::prods'->ifis_operators'then(* A rigid space before operator and a breakable after *)(None,UnpTerminal(" "^s'))::add_break_if_none1b(makebprods')elseifis_commas'then(* No space whatsoever before comma *)makebprodselseifis_right_brackets'thenmakebprodselse(* A breakable space between any other two terminals *)add_break_if_none1b(makebprods)|(NonTerminal_|SProdList_)::_->(* A breakable space before a non-terminal *)add_break_if_none1b(makebprods)|Break_::_->(* Rely on user wish *)makebprods|[]->[]inmakefalsesymbols(** Build default printing rules from explicit format *)leterror_format?loc()=user_err?locPp.(str"The format does not match the notation.")letwarn_format_break=CWarnings.create~name:"notation-both-format-and-spaces"~category:CWarnings.CoreCategories.parsing(fun()->strbrk"Discarding format implicitly indicated by multiple spaces in notation because an explicit format modifier is given.")lethas_ldotsl=List.exists(function(_,UnpTerminals)->String.equals(Id.to_stringNotation_ops.ldots_var)|_->false)lletrecsplit_format_at_ldotshd=function|(loc,UnpTerminals)::fmtwhenString.equals(Id.to_stringNotation_ops.ldots_var)->loc,List.revhd,fmt|u::fmt->check_no_ldots_in_boxu;split_format_at_ldots(u::hd)fmt|[]->raise_notraceExitandcheck_no_ldots_in_box=function|(_,UnpBox(_,fmt))->(tryletloc,_,_=split_format_at_ldots[]fmtinuser_err?locPp.(str("The special symbol \"..\" must occur at the same formatting depth than the variables of which it is the ellipse."))withExit->())|_->()leterror_not_same?loc()=user_err?locPp.(str"The format is not the same on the right- and left-hand sides of the special token \"..\".")letfind_prod_list_locsfmtfmt=(* [fmt] is some [UnpTerminal x :: sfmt @ UnpTerminal ".." :: sfmt @ UnpTerminal y :: rest] *)ifList.is_emptysfmtthen(* No separators; we highlight the sequence "x .." *)Loc.merge_opt(fst(List.hdfmt))(fst(List.hd(List.tlfmt)))else(* A separator; we highlight the separating sequence *)Loc.merge_opt(fst(List.hdsfmt))(fst(List.lastsfmt))letis_blanks=letn=String.lengthsinletrecauxis=i>=n||s.[i]=' '&&aux(i+1)sinaux0sletis_formatting=function|(_,UnpCut_)->true|(_,UnpTerminals)->is_blanks|_->falseletrecis_var_in_recursive_format=function|(_,UnpTerminals)whennot(is_blanks)->true|(loc,UnpBox(b,l))->(matchList.filter(funa->not(is_formattinga))lwith|[a]->is_var_in_recursive_formata|_->error_not_same?loc())|_->falseletreccheck_eq_var_upto_name=function|(_,UnpTerminals1),(_,UnpTerminals2)whennot(is_blanks1&&is_blanks2)||s1=s2->()|(_,UnpBox(b1,l1)),(_,UnpBox(b2,l2))whenb1=b2->List.itercheck_eq_var_upto_name(List.combinel1l2)|(_,UnpCutb1),(_,UnpCutb2)whenb1=b2->()|_,(loc,_)->error_not_same?loc()letskip_var_in_recursive_format=function|a::slwhenis_var_in_recursive_formata->a,sl|(loc,_)::_->error_not_same?loc()|[]->assertfalseletread_recursive_formatslfmt=(* Turn [[UnpTerminal s :: some-list @ UnpTerminal ".." :: same-some-list @ UnpTerminal s' :: rest] *)(* into [(some-list,rest)] *)letget_headfmt=letvar,sl=skip_var_in_recursive_formatfmtintryvar,split_format_at_ldots[]slwithExit->error_not_same?loc:(fst(List.last(ifsl=[]thenfmtelsesl)))()inletrecget_tail=function|(loc,a)::sepfmt,(_,b)::fmtwhen(=)ab->get_tail(sepfmt,fmt)(* FIXME *)|[],tail->skip_var_in_recursive_formattail|(loc,_)::_,([]|(_,UnpTerminal_)::_)->error_not_same?loc()|_,(loc,_)::_->error_not_same?loc()inletvar1,(loc,slfmt,fmt)=get_headfmtinletvar2,res=get_tail(slfmt,fmt)incheck_eq_var_upto_name(var1,var2);(* To do, though not so important: check that the names match
the names in the notation *)slfmt,reslethunks_of_format(from_level,(vars,typs))symfmt=letrecaux=function|symbs,(_,(UnpTerminals'asu))::fmtwhenString.equals'(String.make(String.lengths')' ')->letsymbs,l=aux(symbs,fmt)insymbs,u::l|Terminals::symbs,(_,UnpTerminals')::fmtwhenString.equals(String.drop_simple_quotess')->letsymbs,l=aux(symbs,fmt)insymbs,UnpTerminals::l|NonTerminals::symbs,(_,UnpTerminals')::fmtwhenId.equals(Id.of_strings')->leti=index_idsvarsinletsymbs,l=aux(symbs,fmt)insymbs,unparsing_metavarifrom_leveltyps::l|symbs,(_,(UnpCut_asu))::fmt->letsymbs,l=aux(symbs,fmt)insymbs,u::l|SProdList(m,sl)::symbs,fmtwhenhas_ldotsfmt->leti=index_idmvarsinlettyp=List.nthtyps(i-1)inletsubentry=unparsing_precedence_of_entry_typefrom_leveltypinletloc_slfmt,rfmt=read_recursive_formatslfmtinletsl,slfmt=aux(sl,loc_slfmt)inifnot(List.is_emptysl)thenerror_format?loc:(find_prod_list_locloc_slfmtfmt)();letsymbs,l=aux(symbs,rfmt)inlethunk=matchtypwith|ETConstr_->UnpListMetaVar(subentry,slfmt)|ETBinderisopen->check_open_binderisopenslm;UnpBinderListMetaVar(isopen,true,slfmt)|ETName|ETIdent->UnpBinderListMetaVar(false,true,slfmt)|ETPattern_->UnpBinderListMetaVar(false,false,slfmt)|_->assertfalseinsymbs,hunk::l|symbs,(_,UnpBox(a,b))::fmt->letsymbs',b'=aux(symbs,b)inletsymbs',l=aux(symbs',fmt)insymbs',UnpBox(a,List.map(funx->(None,x))b')::l|symbs,[]->symbs,[]|Break_::symbs,fmt->warn_format_break();aux(symbs,fmt)|_,fmt->error_format?loc:(fst(List.hdfmt))()inmatchauxsymfmtwith|[],l->l|_->error_format()(** **************************************************************** **)(** Build parsing rules **)letassoc_of_typefromn(_,typ)=precedence_of_entry_type{notation_entry=from;notation_level=n}typletis_not_small_constr=functionETProdConstr_->true|_->falseletdistributeall=List.map(funl->a@l)ll(* Expand LIST1(t,sep);sep;t;...;t (with the trailing pattern
occurring p times, possibly p=0) into the combination of
t;sep;t;...;t;sep;t (p+1 times)
t;sep;t;...;t;sep;t;sep;t (p+2 times)
...
t;sep;t;...;t;sep;t;...;t;sep;t (p+n times)
t;sep;t;...;t;sep;t;...;t;sep;t;LIST1(t,sep) *)letexpand_list_rulestyptklxnpll=letcamlp5_message_name=Some(add_suffixx("_"^string_of_intn))inletmain=GramConstrNonTerminal(ETProdConstr(s,typ),camlp5_message_name)inlettks=List.map(fun(kw,s)->GramConstrTerminal(kw,s))tklinletrecauxihdsll=ifi<pthenaux(i+1)(main::tks@hds)llelseifInt.equali(p+n)thenlethds=GramConstrListMark(p+n,true,p)::hds@[GramConstrNonTerminal(ETProdConstrList(s,typ,tkl),Somex)]indistributehdsllelsedistribute(GramConstrListMark(i+1,false,p)::hds@[main])ll@aux(i+1)(main::tks@hds)llinaux0[]llletis_constr_typ(s,lev)xetyps=matchList.assocxetypswith(* TODO: factorize these rules with the ones computing the effective
sublevel sent to camlp5, so as to include the case of
DefaultLevel which are valid *)|ETConstr(s',_,(lev',InternalProd|(NumLevel_|NextLevelaslev'),_))->notation_entry_eqss'&&production_level_eqlevlev'|_->falseletinclude_possible_similar_trailing_patterntypetypssll=letrecauxn=function|Terminals::sl,Terminals'::l'whens=s'->auxn(sl,l')|[],NonTerminalx::l'whenis_constr_typtypxetyps->try_auxnl'|Break_::sl,l->auxn(sl,l)|sl,Break_::l->auxn(sl,l)|_->raise_notraceExitandtry_auxnl=tryaux(n+1)(sl,l)withExit->n,lintry_aux0lletprod_entry_type=function|ETIdent->ETProdIdent|ETName->ETProdName|ETGlobal->ETProdGlobal|ETBigint->ETProdBigint|ETBindero->ETProdOneBindero|ETConstr(s,_,p)->ETProdConstr(s,p)|ETPattern(_,n)->ETProdPattern(pattern_entry_leveln)letkeyword_neededneeds=(* Ensure that IDENT articulation terminal symbols are keywords *)matchPcoq.terminalswith|Tok.PIDENT(Somek)->ifneedthenFlags.if_verboseFeedback.msg_info(str"Identifier '"++strk++str"' now a keyword");need|_->matchNumTok.Unsigned.parse_stringswith|Somen->ifneedthenFlags.if_verboseFeedback.msg_info(str"Number '"++NumTok.Unsigned.printn++str"' now a keyword");need|None->matchString.unquote_coq_stringswith|Some_->ifneedthenFlags.if_verboseFeedback.msg_info(str"String '"++strs++str"' now a keyword");need|_->trueletmake_production({notation_level=lev},_)etypssymbols=letrecauxneed=function|[]->[[]]|NonTerminalm::l->lettyp=prod_entry_type(List.assocmetyps)indistribute[GramConstrNonTerminal(typ,Somem)](aux(is_not_small_constrtyp)l)|Terminals::l->letkeyword=keyword_neededneedsindistribute[GramConstrTerminal(keyword,s)](auxfalsel)|Break_::l->auxneedl|SProdList(x,sl)::l->lettkl=List.flatten(List.map(functionTerminals->[s]|Break_->[]|_->anomaly(Pp.str"Found a non terminal token in recursive notation separator."))sl)inlettkl=List.map_i(funix->letneed=(i=0)in(keyword_neededneedx,x))0tklinmatchList.assocxetypswith|ETConstr(s,_,(lev,_astyp))->letp,l'=include_possible_similar_trailing_pattern(s,lev)etypssllinexpand_list_rulestyptklx1p(auxtruel')|ETBindero->check_open_binderoslx;lettyp=ifothen(assert(tkl=[]);ETBinderOpen)elseETBinderClosed(None,tkl)indistribute[GramConstrNonTerminal(ETProdBinderListtyp,Somex)](auxfalsel)|ETIdent->distribute[GramConstrNonTerminal(ETProdBinderList(ETBinderClosed(SomeETProdIdent,tkl)),Somex)](auxfalsel)|ETName->distribute[GramConstrNonTerminal(ETProdBinderList(ETBinderClosed(SomeETProdName,tkl)),Somex)](auxfalsel)|ETPattern(st,n)->distribute[GramConstrNonTerminal(ETProdBinderList(ETBinderClosed(Some(ETProdPattern(pattern_entry_leveln)),tkl)),Somex)](auxfalsel)|_->user_errPp.(str"Components of recursive patterns in notation must be terms or binders.")inletneed=(* a leading ident/number factorizes iff at level 0 *)lev<>0inauxneedsymbolsletrecfind_symbolsc_currentc_nextc_last=function|[]->[]|NonTerminalid::sl->letprec=ifnot(List.is_emptysl)thenc_currentelsec_lastin(id,prec)::(find_symbolsc_nextc_nextc_lastsl)|Terminals::sl->find_symbolsc_nextc_nextc_lastsl|Breakn::sl->find_symbolsc_currentc_nextc_lastsl|SProdList(x,_)::sl'->(x,c_next)::(find_symbolsc_nextc_nextc_lastsl')letborder=function|(_,(ETConstr(_,_,(_,BorderProd(_,a)))))::_->a|_->Noneletrecompute_assoctyps=letopenGramlib.Gramextinmatchbordertyps,border(List.revtyps)with|SomeLeftA,SomeRightA->assertfalse|SomeLeftA,_->SomeLeftA|_,SomeRightA->SomeRightA|_->None(** ******************************************************************** **)(** Registration of syntax extensions **)(** (parsing/printing, no interpretation) **)letpr_arg_levelfrom(lev,typ)=letpplev=function|LevelLtnwhenInt.equalnfrom->spc()++str"at next level"|LevelLen->spc()++str"at level "++intn|LevelLtn->spc()++str"at level below "++intn|LevelSome->mt()inPpvernac.pr_set_entry_type(fun_->(*TO CHECK*)mt())typ++pplevlevletpr_levelntn({notation_entry=from;notation_level=fromlevel},args)typs=(matchfromwithInConstrEntry->mt()|InCustomEntrys->str"in "++strs++spc())++str"at level "++intfromlevel++(matchargswith|[]->mt()|_::_->spc()++str"with arguments"++spc()++prlist_with_seppr_comma(pr_arg_levelfromlevel)(List.combineargstyps))leterror_incompatible_levelntnoldprecoldtypsprectyps=user_err(str"Notation "++pr_notationntn++str" is already defined"++spc()++pr_levelntnoldprecoldtyps++spc()++str"while it is now required to be"++spc()++pr_levelntnprectyps++str".")leterror_parsing_incompatible_levelntnntn'oldprecoldtypsprectyps=user_err(str"Notation "++pr_notationntn++str" relies on a parsing rule for "++pr_notationntn'++spc()++str" which is already defined"++spc()++pr_levelntnoldprecoldtyps++spc()++str"while it is now required to be"++spc()++pr_levelntnprectyps++str".")letwarn_incompatible_format=CWarnings.create~name:"notation-incompatible-format"~category:CWarnings.CoreCategories.parsing(fun(specific,ntn)->lethead,scope=matchspecificwith|None->str"Notation",mt()|SomeLastLonelyNotation->str"Lonely notation",mt()|Some(NotationInScopesc)->str"Notation",strbrk(" in scope "^sc)inhead++spc()++pr_notationntn++strbrk" was already defined with a different format"++scope++str".")typesyntax_extension={synext_level:level;synext_nottyps:constr_entry_keylist;synext_notgram:notation_grammaroption;synext_notprint:generic_notation_printing_rulesoption;}typesyntax_rules=|PrimTokenSyntax|SpecificSyntaxofsyntax_extensionletsyntax_rules_iterf=function|PrimTokenSyntax->()|SpecificSyntaxsynext->fsynextletcheck_reserved_formatntnrulesrules'=trylet{notation_printing_reserved=reserved;notation_printing_rules=generic_rules}=rulesinifreserved&&(not(List.for_all2equnparsing_eqrules'.notation_printing_unparsinggeneric_rules.notation_printing_unparsing))thenwarn_incompatible_format(None,ntn)withNot_found->()letspecific_format_to_declare(specific,ntnasspecific_ntn)rules=tryletspecific_rules=Ppextend.find_specific_notation_printing_rulespecific_ntninifnot(List.for_all2equnparsing_eqrules.notation_printing_unparsingspecific_rules.notation_printing_unparsing)then(warn_incompatible_format(Somespecific,ntn);true)elsefalsewithNot_found->truetypesyntax_extension_obj=locality_flag*(notation*syntax_extension)letcheck_and_extend_constr_grammarntnrule=tryletntn_for_grammar=rule.notgram_notationinifnotation_eqntnntn_for_grammarthenraiseNot_found;letprec=rule.notgram_levelinlettyps=rule.notgram_typsinletoldprec=Notation.level_of_notationntn_for_grammarinletoldparsing=trySome(Notgram_ops.grammar_of_notationntn_for_grammar)withNot_found->Noneinletoldtyps=Notgram_ops.non_terminals_of_notationntn_for_grammarinifnot(level_eqprecoldprec)&&oldparsing<>Nonethenerror_parsing_incompatible_levelntnntn_for_grammaroldprecoldtypsprectyps;ifoldparsing=NonethenraiseNot_foundwithNot_found->Egramcoq.extend_constr_grammarruleletwarn_prefix_incompatible_level=CWarnings.create~name:"notation-incompatible-prefix"~category:CWarnings.CoreCategories.parsing(fun(pref,ntn,pref_prec,pref_nottyps,prec,nottyps)->str"Notations "++pr_notationpref++spc()++str"defined "++pr_levelprefpref_precpref_nottyps++spc()++str"and "++pr_notationntn++spc()++str"defined "++pr_levelntnprecnottyps++spc()++str"have incompatible prefixes."++spc()++str"One of them will likely not work.")letlevel_firstnk(lvl,lvls)=lvl,tryCList.firstnklvlswithFailure_->[]letcheck_prefix_incompatible_levelntnprecnottyps=matchNotgram_ops.longest_common_prefixntnwith|None->()|Some(pref,k)->tryletpref_prec=Notation.level_of_notationprefinletpref_prec=level_firstnkpref_precinletprec=level_firstnkprecinletpref_nottyps=Notgram_ops.non_terminals_of_notationprefinletpref_nottyps=CList.firstnkpref_nottypsinletnottyps=CList.firstnknottypsinifnot(level_eqprecpref_prec&&List.for_all2Extend.constr_entry_key_eq_ignore_binder_kindnottypspref_nottyps)thenwarn_prefix_incompatible_level(pref,ntn,pref_prec,pref_nottyps,prec,nottyps);withNot_found|Failure_->()letcache_one_syntax_extension(ntn,synext)=letprec=synext.synext_levelin(* Check and ensure that the level and the precomputed parsing rule is declared *)letoldparsing=tryletoldprec=Notation.level_of_notationntninletoldparsing=trySome(Notgram_ops.grammar_of_notationntn)withNot_found->Noneinletoldtyps=Notgram_ops.non_terminals_of_notationntninifnot(level_eqprecoldprec&&List.for_all2Extend.constr_entry_key_eqsynext.synext_nottypsoldtyps)&&(oldparsing<>None||synext.synext_notgram=None)thenerror_incompatible_levelntnoldprecoldtypsprecsynext.synext_nottyps;oldparsingwithNot_found->check_prefix_incompatible_levelntnprecsynext.synext_nottyps;(* Declare the level and the precomputed parsing rule *)let()=Notation.declare_notation_levelntnprecinlet()=Notgram_ops.declare_notation_non_terminalsntnsynext.synext_nottypsinlet()=Option.iter(Notgram_ops.declare_notation_grammarntn)synext.synext_notgraminNonein(* Declare the parsing rule *)beginmatcholdparsing,synext.synext_notgramwith|None,Somegrams->List.iter(check_and_extend_constr_grammarntn)grams|_->(* The grammars rules are canonically derived from the string and the precedence*)()end;(* Printing *)Option.iter(declare_generic_notation_printing_rulesntn)synext.synext_notprintletcache_syntax_extension(_,sy)=cache_one_syntax_extensionsyletsubst_syntax_extension(subst,(local,(ntn,synext)))=(local,(ntn,synext))letclassify_syntax_definition(local,_)=iflocalthenDisposeelseSubstituteletopen_syntax_extensionio=ifInt.equali1thencache_syntax_extensionoletinSyntaxExtension:syntax_extension_obj->obj=declare_object{(default_object"SYNTAX-EXTENSION")withobject_stage=Summary.Stage.Synterp;open_function=simple_open~cat:notation_catopen_syntax_extension;cache_function=cache_syntax_extension;subst_function=subst_syntax_extension;classify_function=classify_syntax_definition}(** ******************************************************************** **)(** Precedences **)(* Interpreting user-provided modifiers *)(* XXX: We could move this to the parser itself *)moduleNotationMods=structtypenotation_modifier={assoc:Gramlib.Gramext.g_assocoption;level:intoption;etyps:(Id.t*simple_constr_prod_entry_key)list;(* common to syn_data below *)format:lstringoption;}letdefault={assoc=None;level=None;etyps=[];format=None;}endexceptionUnknownCustomEntryofstringlet()=CErrors.register_handler@@function|UnknownCustomEntryentry->SomePp.(str"Unknown custom entry: "++strentry++str".")|_->Noneletcheck_custom_entryentry=ifnot(Egramcoq.exists_custom_entryentry)thenraise@@UnknownCustomEntryentryletcheck_entry_type=function|ETConstr(InCustomEntryentry,_,_)->check_custom_entryentry|ETConstr(InConstrEntry,_,_)|ETPattern_|ETIdent|ETGlobal|ETBigint|ETName|ETBinder_->()letinterp_modifiersentrymodl=letopenNotationModsinletrecinterpacc=function|[]->acc|CAst.{loc;v}::l->matchvwith|SetEntryType(s,typ)->letid=Id.of_stringsincheck_entry_typetyp;ifId.List.mem_associdacc.etypsthenuser_err?loc(strs++str" is already assigned to an entry or constr level.");interp{accwithetyps=(id,typ)::acc.etyps;}l|SetItemLevel([],bko,n)->interpaccl|SetItemLevel(s::idl,bko,n)->letid=Id.of_stringsinifId.List.mem_associdacc.etypsthenuser_err?loc(strs++str" is already assigned to an entry or constr level.");interp{accwithetyps=(id,ETConstr(entry,bko,n))::acc.etyps}((CAst.make?loc@@SetItemLevel(idl,bko,n))::l)|SetLeveln->(matchentrywith|InCustomEntrys->ifacc.level<>Nonethenuser_err?loc(str("isolated \"at level "^string_of_intn^"\" unexpected."))elseuser_err?loc(str("use \"in custom "^s^" at level "^string_of_intn^"\"")++spc()++str"rather than"++spc()++str("\"at level "^string_of_intn^"\"")++spc()++str"isolated.")|InConstrEntry->ifacc.level<>Nonethenuser_err?loc(str"A level is already assigned.");interp{accwithlevel=Somen;}l)|SetCustomEntry(s,Somen)->(* Note: name of entry already registered in interp_non_syntax_modifiers *)ifacc.level<>Nonethenuser_err?loc(str("isolated \"at level "^string_of_int(Option.getacc.level)^"\" unexpected."));interp{accwithlevel=Somen}l|SetAssoca->ifnot(Option.is_emptyacc.assoc)thenuser_err?locPp.(str"An associativity is given more than once.");interp{accwithassoc=Somea;}l|SetOnlyParsing|SetOnlyPrinting|SetCustomEntry(_,None)|SetFormat_|SetItemScope_->(* interpreted in interp_non_syntax_modifiers *)assertfalseininterpdefaultmodlletcheck_useless_entry_typesrecvarsmainvarsetyps=letvars=let(l1,l2)=List.splitrecvarsinl1@l2@mainvarsinmatchList.filter(fun(x,etyp)->not(List.memxvars))etypswith|(x,_)::_->user_err(Id.printx++str" is unbound in the notation.")|_->()typenotation_main_data={onlyparsing:bool;onlyprinting:bool;user_warns:UserWarn.toption;entry:notation_entry;format:unparsingLoc.locatedlistoption;itemscopes:(Id.t*scope_name)list;}letwarn_only_parsing_reserved_notation=CWarnings.create~name:"irrelevant-reserved-notation-only-parsing"~category:CWarnings.CoreCategories.parsing(fun()->strbrk"The only parsing modifier has no effect in Reserved Notation.")letwarn_only_parsing_discarded_format=CWarnings.create~name:"discarded-format-only-parsing"~category:CWarnings.CoreCategories.parsing(fun()->strbrk"The format modifier has no effect for only-parsing notations.")leterror_onlyparsing_onlyprinting?loc=user_err?loc(str"A notation cannot be both \"only printing\" and \"only parsing\".")letset_onlyparsing?loc~reservedmain_data=ifreservedthen(warn_only_parsing_reserved_notation?loc();main_data)else(ifmain_data.onlyparsingthenuser_err?loc(str"\"only parsing\" is given more than once.");ifmain_data.onlyprintingthenerror_onlyparsing_onlyprinting?loc;{main_datawithonlyparsing=true})letset_onlyprinting?locmain_data=ifmain_data.onlyprintingthenuser_err?loc(str"\"only printing\" is given more than once.");ifmain_data.onlyparsingthenerror_onlyparsing_onlyprinting?loc;{main_datawithonlyprinting=true}letset_custom_entry?locmain_dataentry'=check_custom_entryentry';matchmain_data.entrywith|InConstrEntry->{main_datawithentry=InCustomEntryentry'}|_->user_err?loc(str"\"in custom\" is given more than once.")letwarn_irrelevant_format=CWarnings.create~name:"irrelevant-format-only-parsing"~category:CWarnings.CoreCategories.parsing(fun()->str"The format modifier is irrelevant for only-parsing rules.")letset_format?locmain_dataformat=ifnot(Option.is_emptymain_data.format)thenuser_err?locPp.(str"A format is given more than once.");letformat=ifmain_data.onlyparsingthen(warn_irrelevant_format?loc();None)elseSome(parse_formatformat)in{main_datawithformat}letset_item_scope?locmain_dataidssc=letitemscopes=List.map(funid->(Id.of_stringid,sc))ids@main_data.itemscopesinmatchList.duplicates(fun(id1,_)(id2,_)->Id.equalid1id2)itemscopeswith|(id,_)::_->user_err?loc(str"Notation scope for argument "++Id.printid++str" can be specified only once.")|[]->{main_datawithitemscopes}letinterp_non_syntax_modifiers~reserved~infix~abbrevuser_warnsmods=letset(main_data,rest)=CAst.with_loc_val(fun?loc->function|SetOnlyParsing->ifnot(Option.is_emptymain_data.format)then(warn_only_parsing_discarded_format?loc();(main_data,rest))else(set_onlyparsing?loc~reservedmain_data,rest)|SetOnlyPrintingwhennotabbrev->(set_onlyprinting?locmain_data,rest)|SetCustomEntry(entry,None)whennotabbrev->(set_custom_entry?locmain_dataentry,rest)|SetCustomEntry(entry,Some_)asxwhennotabbrev->(set_custom_entrymain_dataentry,CAst.make?locx::rest)|SetEntryType_wheninfix->user_err?locPp.(str"Unexpected entry type in infix notation.")|SetItemLevel_wheninfix->user_err?locPp.(str"Unexpected entry level in infix notation.")|SetFormat(TextFormats)whennotabbrev->(set_format?locmain_datas,rest)|SetItemScope(ids,sc)->(set_item_scope?locmain_dataidssc,rest)|modif->(main_data,(CAst.make?locmodif)::rest))inletmain_data={onlyparsing=false;onlyprinting=false;user_warns;entry=InConstrEntry;format=None;itemscopes=[]}inletmain_data,rest=List.fold_leftset(main_data,[])modsinmain_data,List.revrest(* Compute precedences from modifiers (or find default ones) *)letset_entry_typefromnetyps(x,typ)=letmake_levns=matchtypwith|BorderProd_->NumLeveln|InternalProd->DefaultLevelinlettyp=trymatchList.assocxetyps,typwith|ETConstr(s,bko,DefaultLevel),_->ifnotation_entry_eqfromsthenETConstr(s,bko,(make_levns,typ))elseETConstr(s,bko,(DefaultLevel,typ))|ETConstr(s,bko,n),BorderProd(left,_)->ETConstr(s,bko,(n,BorderProd(left,None)))|ETConstr(s,bko,n),InternalProd->ETConstr(s,bko,(n,InternalProd))|ETPattern(b,n),_->ETPattern(b,n)|(ETIdent|ETName|ETBigint|ETGlobal|ETBinder_asx),_->xwithNot_found->ETConstr(from,None,(make_levnfrom,typ))in(x,typ)letjoin_auxiliary_recursive_typesrecvarsetyps=List.fold_right(fun(x,y)typs->letxtyp=trySome(List.assocxetyps)withNot_found->Noneinletytyp=trySome(List.assocyetyps)withNot_found->Noneinmatchxtyp,ytypwith|None,None->typs|Some_,None->typs|None,Someytyp->(x,ytyp)::typs|Somextyp,Someytypwhen(=)xtypytyp->typs(* FIXME *)|Somextyp,Someytyp->user_err(strbrk"In "++Id.printx++str" .. "++Id.printy++strbrk", both ends have incompatible types."))recvarsetypsletinternalization_type_of_entry_type=function|ETBinder_|ETConstr(_,Some_,_)->NtnInternTypeOnlyBinder|ETConstr(_,None,_)|ETBigint|ETGlobal|ETIdent|ETName|ETPattern_->NtnInternTypeAnyNoneletmake_internalization_varsrecvarsmaintyps=letmaintyps=List.map(on_sndinternalization_type_of_entry_type)maintypsinletextratyps=List.map(fun(x,y)->(y,List.assocxmaintyps))recvarsinmaintyps@extratypsletmake_interpretation_typeisrecisbindingdefault_if_bindingtyp=matchtyp,isrecwith(* Parsed as constr, but interpreted as a specific kind of binder *)|ETConstr(_,Somebk,_),true->NtnTypeBinderList(NtnBinderParsedAsConstrbk)|ETConstr(_,Somebk,_),false->NtnTypeBinder(NtnBinderParsedAsConstrbk)(* Parsed as constr list but interpreted as the default kind of binder *)|ETConstr(_,None,_),truewhenisbinding->NtnTypeBinderList(NtnBinderParsedAsConstrdefault_if_binding)|ETConstr(_,None,_),falsewhenisbinding->NtnTypeBinder(NtnBinderParsedAsConstrdefault_if_binding)(* Parsed as constr, interpreted as constr *)|ETConstr(_,None,_),true->NtnTypeConstrList|ETConstr(_,None,_),false->NtnTypeConstr(* Different way of parsing binders, maybe interpreted also as
constr, but conventionally internally binders *)|ETIdent,true->NtnTypeBinderList(NtnBinderParsedAsSomeBinderKindAsIdent)|ETIdent,false->NtnTypeBinder(NtnBinderParsedAsSomeBinderKindAsIdent)|ETName,true->NtnTypeBinderList(NtnBinderParsedAsSomeBinderKindAsName)|ETName,false->NtnTypeBinder(NtnBinderParsedAsSomeBinderKindAsName)(* Parsed as ident/pattern, primarily interpreted as binder; maybe strict at printing *)|ETPattern(ppstrict,_),true->NtnTypeBinderList(NtnBinderParsedAsSomeBinderKind(ifppstrictthenAsStrictPatternelseAsAnyPattern))|ETPattern(ppstrict,_),false->NtnTypeBinder(NtnBinderParsedAsSomeBinderKind(ifppstrictthenAsStrictPatternelseAsAnyPattern))|ETBinder_,true->NtnTypeBinderListNtnBinderParsedAsBinder|ETBinder_,false->NtnTypeBinderNtnBinderParsedAsBinder(* Others *)|ETBigint,true|ETGlobal,true->NtnTypeConstrList|ETBigint,false|ETGlobal,false->NtnTypeConstrletentry_relative_level_of_constr_prod_entryfrom_level=function|ETConstr(entry,_,(_,y))asx->letside=matchywithBorderProd(side,_)->Someside|_->Nonein{notation_subentry=entry;notation_relative_level=precedence_of_entry_typefrom_levelx;notation_position=side}|_->constr_some_levelletmake_interpretation_vars(* For binders, default is to parse only as an ident *)?(default_if_binding=AsName)recvarsallvars(entry,_)typs=leteq_subscope(sc1,l1)(sc2,l2)=List.equalString.equalsc1sc2&&List.equalString.equall1l2inletcheck(x,y)=let(_,scope1,_ntn_binding_ids1)=Id.Map.findxallvarsinlet(_,scope2,_ntn_binding_ids2)=Id.Map.findyallvarsinifnot(eq_subscopescope1scope2)thenerror_not_same_scopexy(* Note: binding_ids should currently be the same, and even with
eventually more complex notations, such as e.g.
Notation "!! x .. y , P .. Q" := (fun x => (P, .. (fun y => (Q, True)) ..)).
each occurrence of the recursive notation variables may have its own binders *)inlet()=List.itercheckrecvarsinletuseless_recvars=List.mapsndrecvarsinletmainvars=Id.Map.filter(funx_->not(Id.List.memxuseless_recvars))allvarsinId.Map.mapi(funx(isonlybinding,sc,ntn_binding_ids)->lettyp=Id.List.assocxtypsin((entry_relative_level_of_constr_prod_entryentrytyp,sc),ntn_binding_ids,make_interpretation_type(Id.List.mem_assocxrecvars)isonlybindingdefault_if_bindingtyp))mainvarsletcheck_rule_productivityl=ifList.for_all(functionNonTerminal_|Break_->true|_->false)lthenuser_errPp.(str"A notation must include at least one symbol.");if(matchlwithSProdList_::_->true|_->false)thenuser_errPp.(str"A recursive notation must start with at least one symbol.")letwarn_notation_bound_to_variable=CWarnings.create~name:"notation-bound-to-variable"~category:CWarnings.CoreCategories.parsing(fun()->strbrk"This notation will not be used for printing as it is bound to a single variable.")letwarn_non_reversible_notation=CWarnings.create~name:"non-reversible-notation"~category:CWarnings.CoreCategories.parsing(function[@warning"+9"]|APrioriReversible->assertfalse|Forgetful{forget_ltac=ltac;forget_volatile_cast=cast;}->letwhat=(ifltacthen["Ltac expressions"]else[])@(ifcastthen["volatile casts"]else[])instrbrk"This notation contains "++prlist_with_sep(fun()->strbrk" and ")strwhat++str":"++spc()++str"it will not be used for printing."|NonInjectiveids->letn=List.lengthidsinstrbrk(String.pluraln"Variable")++spc()++pr_enumId.printids++spc()++strbrk(ifn>1then"do"else"does")++str" not occur in the right-hand side."++spc()++strbrk"The notation will not be used for printing as it is not reversible.")letis_coercionleveltyps=matchlevel,typswith|Some({notation_entry=custom;notation_level=n}asentry,_),[_,e]->(matche,customwith|ETConstr_,_->letentry_relative=entry_relative_level_of_constr_prod_entryentryeinifis_coercionentryentry_relativethenSome(IsEntryCoercion(entry,entry_relative))elseNone|ETGlobal,InCustomEntrys->Some(IsEntryGlobal(s,n))|ETIdent,InCustomEntrys->Some(IsEntryIdent(s,n))|_->None)|Some_,_->assertfalse|None,_->Noneletprintabilityleveltypsvarsonlyparsingreversibility=function|NVaridwhenreversibility=APrioriReversible&&List.mem_assoc_fId.equalidvars->letcoe=is_coercionleveltypsinletonlyparsing=ifnotonlyparsing&&Option.is_emptycoethen(warn_notation_bound_to_variable();true)elseonlyparsinginonlyparsing,coe|_->(ifnotonlyparsing&&reversibility<>APrioriReversiblethen(warn_non_reversible_notationreversibility;true)elseonlyparsing),Noneletwarn_closed_notation_not_level_0=CWarnings.create~name:"closed-notation-not-level-0"~category:CWarnings.CoreCategories.parsing(fun()->strbrk"Closed notations (i.e. starting and ending with a \
terminal symbol) should usually be at level 0 \
(default).")letwarn_postfix_notation_not_level_1=CWarnings.create~name:"postfix-notation-not-level-1"~category:CWarnings.CoreCategories.parsing(fun()->strbrk"Postfix notations (i.e. starting with a \
nonterminal symbol and ending with a terminal \
symbol) should usually be at level 1 (default).")letfind_precedencecustomlevetypssymbolsonlyprint=letfirst_symbol=letrecaux=function|Break_::t->auxt|h::t->Someh|[]->Noneinauxsymbolsinletlast_is_terminal()=letrecauxb=function|Break_::t->auxbt|Terminal_::t->auxtruet|_::t->auxfalset|[]->binauxfalsesymbolsinmatchfirst_symbolwith|None->[],0|Some(NonTerminalx)->letmsgs,lev=matchlast_is_terminal(),levwith|false,_->[],lev|true,None->[fun()->Flags.if_verbose(Feedback.msg_info?loc:None)(strbrk"Setting postfix notation at level 1.")],Some1|true,Some1->[],Some1|true,Somen->[fun()->warn_postfix_notation_not_level_1()],Someninlettest()=ifonlyprintthenifOption.is_emptylevthenuser_errPp.(str"Explicit level needed in only-printing mode when the level of the leftmost non-terminal is given.")elsemsgs,Option.getlevelseuser_errPp.(str"The level of the leftmost non-terminal cannot be changed.")in(trymatchList.assocxetyps,customwith|ETConstr(s,_,(NumLevel_|NextLevel)),s'whens=s'->test()|(ETIdent|ETName|ETBigint|ETGlobal),_->beginmatchlevwith|None->([fun()->Flags.if_verbose(Feedback.msg_info?loc:None)(strbrk"Setting notation at level 0.")],0)|Some0->(msgs,0)|_->user_errPp.(str"A notation starting with an atomic expression must be at level 0.")end|(ETPattern_|ETBinder_),InConstrEntrywhennotonlyprint->(* Don't know exactly if we can make sense of this case *)user_errPp.(str"Binders or patterns not supported in leftmost position.")|(ETPattern_|ETBinder_|ETConstr_),_->(* Give a default ? *)ifOption.is_emptylevthenuser_errPp.(str"Need an explicit level.")elsemsgs,Option.getlevwithNot_found->ifOption.is_emptylevthenuser_errPp.(str"A left-recursive notation must have an explicit level.")elsemsgs,Option.getlev)|Some(Terminal_)whenlast_is_terminal()->beginmatchlevwith|None->[fun()->Flags.if_verbose(Feedback.msg_info?loc:None)(strbrk"Setting notation at level 0.")],0|Some0->[],0|Somen->[fun()->warn_closed_notation_not_level_0()],nend|Some_->ifOption.is_emptylevthenuser_errPp.(str"Cannot determine the level.");[],Option.getlevletcheck_curly_brackets_notation_exists()=trylet_=Notation.level_of_notation(InConstrEntry,"{ _ }")in()withNot_found->user_errPp.(str"Notations involving patterns of the form \"{ _ }\" are treated \n\
specially and require that the notation \"{ _ }\" is already reserved.")(* Remove patterns of the form "{ _ }", unless it is the "{ _ }" notation *)letremove_curly_bracketsl=letrecskip_breakacc=function|Break_asbr::l->skip_break(br::acc)l|l->List.revacc,linletrecauxdeb=function|[]->[]|Terminal"{"ast1::l->letbr,next=skip_break[]lin(matchnextwith|NonTerminal_asx::l'->letbr',next'=skip_break[]l'in(matchnext'with|Terminal"}"ast2::l''->ifdeb&&List.is_emptyl''then[t1;x;t2]elsebegincheck_curly_brackets_notation_exists();x::auxfalsel''end|l1->t1::br@x::br'@auxfalsel1)|l0->t1::auxfalsel0)|x::l->x::auxfalselinauxtruellethas_implicit_formatsymbols=List.exists(functionBreak_->true|_->false)symbols(* Because of the special treatment for { }, the grammar rule sent
to the parser may be different than what the user sees; e.g. for
"{ A } + { B }", it is "A + B" which is sent to the parser *)typesyn_pa_data={ntn_for_grammar:notation;prec_for_grammar:level;typs_for_grammar:constr_entry_keylist;need_squash:bool;}moduleSynData=structtypesubentry_types=(Id.t*constr_entry_key)list(* XXX: Document *)typesyn_data={(* XXX: Callback to printing, must remove *)msgs:(unit->unit)list;(* Notation data for parsing *)level:level;subentries:subentry_types;pa_syntax_data:subentry_types*symbollist;pp_syntax_data:subentry_types*symbollist;not_data:syn_pa_data;}endletfind_subentry_typesfromnassocetypssymbols=lettyps=find_symbols(BorderProd(Left,assoc))(InternalProd)(BorderProd(Right,assoc))symbolsinletsy_typs=List.map(set_entry_typefromnetyps)typsinletprec=List.map(assoc_of_typefromn)sy_typsinsy_typs,precletcheck_locality_compatibilitylocalcustomi_typs=ifnotlocalthenletsubcustom=List.map_filter(function_,ETConstr(InCustomEntrys,_,_)->Somes|_->None)i_typsinletallcustoms=matchcustomwithInCustomEntrys->s::subcustom|_->subcustominList.iter(funs->ifEgramcoq.locality_of_custom_entrysthenuser_err(strbrk"Notation has to be declared local as it depends on custom entry "++strs++strbrk" which is local."))(List.uniquizeallcustoms)letlongest_common_prefix_levelntn=Notgram_ops.longest_common_prefixntn|>Option.map(fun(ntn,sz)->letlevel,levels=level_firstnsz(Notation.level_of_notationntn)inntn,level.notation_level,levels)letdefault_prefix_levelntn_prefix=letwith_prefixprefixlevel=Flags.if_verboseFeedback.msg_info(strbrk"Setting notation at level "++intlevel++spc()++str"to match previous notation with longest common prefix:"++spc()++str"\""++str(sndprefix)++str"\".");levelinfunctionSomen->Somen|None->Option.map(fun(prefix,level,_)->with_prefixprefixlevel)ntn_prefixletdefault_prefix_level_subentriesntnntn_prefixsymbolsetyps=letwith_prefixprefixfrom_levellevels=letdefault_entryetyps(x,l)=letl'=matchlwith|LevelLtnwhenInt.equalnfrom_level->NextLevel|LevelLen|LevelLtn->NumLeveln|LevelSome->DefaultLevelinlete=List.assoc_optxetyps|>Option.default(ETConstr(fstntn,None,DefaultLevel))inmatchl',ewith|(NumLevel_|NextLevel),ETConstr(n,b,DefaultLevel)->Flags.if_verboseFeedback.msg_info(strbrk"Setting "++Id.printx++str" "++pr_arg_levelfrom_level(l,e)++spc()++str"to match previous notation with longest common prefix:"++spc()++str"\""++str(sndprefix)++str"\".");(x,ETConstr(n,b,l'))::List.remove_assocxetyps|_->etypsinletlevels=letrecauxlevssymbs=matchlevs,symbswith|[],_|_,[]|_,SProdList_::_->[](* not handling recursive notations *)|_,(Terminal_|Break_)::symbs->auxlevssymbs|l::levs,NonTerminalx::symbs->(x,l)::auxlevssymbsinmatchlevels,symbolswith(* don't mess up with level of left border terminal *)|_::levs,NonTerminal_::symbs|levs,symbs->auxlevssymbsinList.fold_leftdefault_entryetypslevelsinmatchntn_prefixwith|None->etyps|Some(prefix,from_level,levels)->with_prefixprefixfrom_levellevelsletcompute_syntax_data~localmain_datanotation_symbolsntnmods=letopenSynDatainletopenNotationModsinifmain_data.itemscopes<>[]thenuser_err(str"General notations don't support 'in scope'.");let{recvars;mainvars;symbols}=notation_symbolsinletassoc=Option.appendmods.assoc(SomeGramlib.Gramext.NonA)inlet_=check_useless_entry_typesrecvarsmainvarsmods.etypsin(* Notations for interp and grammar *)letntn_prefix=longest_common_prefix_levelntninletlevel=default_prefix_levelntn_prefixmods.levelinletmsgs,n=find_precedencemain_data.entrylevelmods.etypssymbolsmain_data.onlyprintinginletsymbols_for_grammar=ifmain_data.entry=InConstrEntrythenremove_curly_bracketssymbolselsesymbolsinletneed_squash=not(List.equalNotation.symbol_eqsymbolssymbols_for_grammar)inletntn_for_grammar=ifneed_squashthenmake_notation_keymain_data.entrysymbols_for_grammarelsentninifmain_data.entry=InConstrEntry&¬main_data.onlyprintingthencheck_rule_productivitysymbols_for_grammar;(* To globalize... *)letetyps=default_prefix_level_subentriesntnntn_prefixsymbolsmods.etypsinletetyps=join_auxiliary_recursive_typesrecvarsetypsinletsy_typs,prec=find_subentry_typesmain_data.entrynassocetypssymbolsinletsy_typs_for_grammar,prec_for_grammar=ifneed_squashthenfind_subentry_typesmain_data.entrynassocetypssymbols_for_grammarelsesy_typs,precincheck_locality_compatibilitylocalmain_data.entrysy_typs;letpa_sy_data=(sy_typs_for_grammar,symbols_for_grammar)inletpp_sy_data=(sy_typs,symbols)inletsy_fulldata={ntn_for_grammar;prec_for_grammar=({notation_entry=main_data.entry;notation_level=n},prec_for_grammar);typs_for_grammar=List.mapsndsy_typs_for_grammar;need_squash}in(* Return relevant data for interpretation and for parsing/printing *){msgs;level=({notation_entry=main_data.entry;notation_level=n},prec);subentries=sy_typs;pa_syntax_data=pa_sy_data;pp_syntax_data=pp_sy_data;not_data=sy_fulldata;}(** **************************************************************** **)(** Registration of notation interpretation **)typenotation_obj={notobj_local:bool;notobj_scope:scope_nameoption;notobj_interp:interpretation;notobj_coercion:entry_coercion_kindoption;notobj_use:notation_useoption;notobj_user_warns:UserWarn.toption;notobj_notation:notation*notation_location;notobj_specific_pp_rules:notation_printing_rulesoption;}letload_notation_commonsilently_define_scope_if_undefined_nobj=(* When the default shall be to require that a scope already exists *)(* the call to ensure_scope will have to be removed *)ifsilently_define_scope_if_undefinedthen(* Don't warn if the scope is not defined: *)(* there was already a warning at "cache" time *)Option.iterNotation.declare_scopenobj.notobj_scopeelseOption.iterNotation.ensure_scopenobj.notobj_scopeletload_notation=load_notation_commontrueletopen_notationinobj=ifInt.equali1thenbeginletscope=nobj.notobj_scopeinlet(ntn,df)=nobj.notobj_notationinletpat=nobj.notobj_interpinletuser_warns=nobj.notobj_user_warnsinletscope=matchscopewithNone->LastLonelyNotation|Somesc->NotationInScopescin(* Declare the notation *)(matchnobj.notobj_usewith|Someuse->Notation.declare_notation(scope,ntn)patdf~usenobj.notobj_coercionuser_warns|None->());(* Declare specific format if any *)(matchnobj.notobj_specific_pp_ruleswith|Somepp_sy->ifspecific_format_to_declare(scope,ntn)pp_sythenPpextend.declare_specific_notation_printing_rules(scope,ntn)pp_sy|None->())endletcache_notationo=load_notation_commonfalse1o;open_notation1oletsubst_notation(subst,nobj)={nobjwithnotobj_interp=subst_interpretationsubstnobj.notobj_interp;}letclassify_notationnobj=ifnobj.notobj_localthenDisposeelseSubstituteletinNotation:notation_obj->obj=declare_object{(default_object"NOTATION")withopen_function=simple_open~cat:notation_catopen_notation;cache_function=cache_notation;subst_function=subst_notation;load_function=load_notation;classify_function=classify_notation}(**********************************************************************)(* Registration of interpretation scopes opening/closing *)letopen_scopei(local,op,sc)=ifInt.equali1thenifopthenNotation.open_scopescelseNotation.close_scopescletcache_scopeo=open_scope1oletsubst_scope(subst,sc)=scletdischarge_scope(local,_,_aso)=iflocalthenNoneelseSomeoletclassify_scope(local,_,_)=iflocalthenDisposeelseSubstituteletinScope:bool*bool*scope_name->obj=declare_object{(default_object"SCOPE")withcache_function=cache_scope;open_function=simple_open~cat:notation_catopen_scope;subst_function=subst_scope;discharge_function=discharge_scope;classify_function=classify_scope}letopen_close_scopelocal~to_opensc=Lib.add_leaf(inScope(local,to_open,normalize_scopesc))(**********************************************************************)letwith_lib_stk_protectionfx=letfs=Lib.Interp.freeze()intryleta=fxinLib.Interp.unfreezefs;awithreraise->letreraise=Exninfo.capturereraiseinlet()=Lib.Interp.unfreezefsinExninfo.iraisereraiseletwith_syntax_protectionfx=with_lib_stk_protection(Pcoq.with_grammar_rule_protection(with_notation_protectionf))x(** **************************************************************** **)(** Recovering existing syntax **)exceptionNoSyntaxRuleletrecover_notation_syntaxntn=tryletprec=Notation.level_of_notationntninletpa_typs=Notgram_ops.non_terminals_of_notationntninletpa_rule=trySome(Notgram_ops.grammar_of_notationntn)withNot_found->Noneinletpp_rule=trySome(find_generic_notation_printing_rulentn)withNot_found->Nonein{synext_level=prec;synext_nottyps=pa_typs;synext_notgram=pa_rule;synext_notprint=pp_rule;}withNot_found->raiseNoSyntaxRuleletrecover_squash_syntaxsy=letsq=recover_notation_syntax(InConstrEntry,"{ _ }")inmatchsq.synext_notgramwith|Somegram->sy::gram|None->raiseNoSyntaxRule(** **************************************************************** **)(** Main entry point for building parsing and printing rules **)letmake_pa_rule(typs,symbols)parsing_data=let{ntn_for_grammar;prec_for_grammar;typs_for_grammar;need_squash}=parsing_datainletassoc=recompute_assoctypsinletprod=make_productionprec_for_grammartypssymbolsinletsy={notgram_level=prec_for_grammar;notgram_assoc=assoc;notgram_notation=ntn_for_grammar;notgram_prods=prod;notgram_typs=typs_for_grammar;}in(* By construction, the rule for "{ _ }" is declared, but we need to
redeclare it because the file where it is declared needs not be open
when the current file opens (especially in presence of -nois) *)ifneed_squashthenrecover_squash_syntaxsyelse[sy]letmake_pp_rulelevel(typs,symbols)fmt=matchfmtwith|None->lethunks=make_hunkstypssymbolslevelinifList.exists(function_,(UnpCut(PpBrk_)|UnpListMetaVar_)->true|_->false)hunksthen[UnpBox(PpHOVB0,hunks)]else(* Optimization to work around what seems an ocaml Format bug (see Mantis #7804/#7807) *)List.mapsndhunks(* drop locations which are dummy *)|Somefmt->hunks_of_format(level,List.splittyps)(symbols,fmt)letmake_parsing_rulesmain_data(sd:SynData.syn_data)=letopenSynDatainifmain_data.onlyprintingthenNoneelseSome(make_pa_rulesd.pa_syntax_datasd.not_data)(** **************************************************************** **)(** Main functions about notations **)letmake_generic_printing_rulesreservedmain_datantnsd=letopenSynDatainlet{notation_entry=custom;notation_level=level},_=sd.levelinletmake_rulerule={notation_printing_reserved=reserved;notation_printing_rules={notation_printing_unparsing=rule;notation_printing_level=level;}}intryletrules=(Ppextend.find_generic_notation_printing_rulentn)inmatchmain_data.formatwith|Nonewhennot(has_implicit_format(sndsd.pp_syntax_data))->(* No intent to define a format, we reuse the existing generic rules *)Somerules|_->ifnotreserved&&main_data.onlyprintingthen(* No intent to define a generic format *)Someruleselseletrules'=make_rule(make_pp_rulelevelsd.pp_syntax_datamain_data.format)inlet()=check_reserved_formatntnrulesrules'.notation_printing_rulesinSomerules'withNot_found->Some(make_rule(make_pp_rulelevelsd.pp_syntax_datamain_data.format))letmake_syntax_rulesreservedmain_datantnsd=letopenSynDatain(* Prepare the parsing and printing rules *)letpa_rules=make_parsing_rulesmain_datasdinletpp_rules=make_generic_printing_rulesreservedmain_datantnsdin{synext_level=sd.level;synext_nottyps=List.mapsndsd.subentries;synext_notgram=pa_rules;synext_notprint=pp_rules;}(**********************************************************************)(* Main entry point for building specific printing rules *)letmake_specific_printing_rulesetypssymbolslevelpp_ruleformat=matchlevelwith|None->None|Some({notation_level=level},_)->matchformat,pp_rulewith|None,Some_whennot(has_implicit_formatsymbols)->None|_->Some{notation_printing_unparsing=make_pp_rulelevel(etyps,symbols)format;notation_printing_level=level;}(**********************************************************************)(* Miscellaneous *)letwarn_unused_interpretation=CWarnings.create~name:"unused-notation"~category:CWarnings.CoreCategories.parsing(funb->strbrk"interpretation is used neither for printing nor for parsing, "++(ifbthenstrbrk"the declaration could be replaced by \"Reserved Notation\"."elsestrbrk"the declaration could be removed."))letmake_usereservedonlyparseonlyprint=matchonlyparse,onlyprintwith|false,false->SomeParsingAndPrinting|true,false->SomeOnlyParsing|false,true->SomeOnlyPrinting|true,true->warn_unused_interpretationreserved;None(**********************************************************************)(* Main functions about notations *)letmake_notation_interpretation~localmain_datanotation_symbolsntnsyntax_rulesdfenv?(impls=empty_internalization_env)cscope=let{recvars;mainvars;symbols}=notation_symbolsin(* Recover types of variables and pa/pp rules; redeclare them if needed *)letlevel,i_typs,main_data,sy_pp_rules=matchsyntax_ruleswith|PrimTokenSyntax->None,[],main_data,None|SpecificSyntaxsy->(* If the only printing flag has been explicitly requested, put it back *)letmain_data={main_datawithonlyprinting=main_data.onlyprinting||(sy.synext_notgram=None&¬main_data.onlyparsing)}inSomesy.synext_level,List.combinemainvarssy.synext_nottyps,main_data,sy.synext_notprintin(* Declare interpretation *)letsy_pp_rules=make_specific_printing_rulesi_typssymbolslevelsy_pp_rulesmain_data.formatinletpath=(Lib.library_dp(),Lib.current_dirpathtrue)inletdf'=ntn,(path,df)inleti_vars=make_internalization_varsrecvarsi_typsinletnenv={ninterp_var_type=Id.Map.of_listi_vars;ninterp_rec_vars=Id.Map.of_listrecvars;}inlet(acvars,ac,reversibility)=interp_notation_constrenv~implsnenvcinletplevel=matchlevelwithSome(entry,l)->(entry,l)|None(* numeral: irrelevant )*)->(constr_lowest_level,[])inletinterp=make_interpretation_varsrecvarsacvarspleveli_typsinletmap(x,_)=trySome(x,Id.Map.findxinterp)withNot_found->Noneinletvars=List.map_filtermapi_varsin(* Order of elements is important here! *)letonlyparsing,coe=printabilityleveli_typsvarsmain_data.onlyparsingreversibilityacinletmain_data={main_datawithonlyparsing}inletuse=make_usefalseonlyparsingmain_data.onlyprintingin{notobj_local=local;notobj_scope=scope;notobj_use=use;notobj_interp=(vars,ac);notobj_coercion=coe;notobj_user_warns=main_data.user_warns;notobj_notation=df';notobj_specific_pp_rules=sy_pp_rules;}(* Notations without interpretation (Reserved Notation) *)letadd_reserved_notation~local~infix({CAst.loc;v=df},mods)=letopenSynDatainlet(main_data,mods)=interp_non_syntax_modifiers~reserved:true~infix~abbrev:falseNonemodsinletmods=interp_modifiersmain_data.entrymodsinletnotation_symbols,is_prim_token=analyze_notation_tokens~onlyprinting:main_data.onlyprinting~infixmain_data.entrydfinletnotation_symbols=ifinfixthenadjust_reserved_infix_notationnotation_symbolselsenotation_symbolsinletntn=make_notation_keymain_data.entrynotation_symbols.symbolsinifis_prim_tokenthenuser_err?loc(str"Notations for numbers or strings are primitive and need not be reserved.");letsd=compute_syntax_data~localmain_datanotation_symbolsntnmodsinletsynext=make_syntax_rulestruemain_datantnsdinList.iter(funf->f())sd.msgs;Lib.add_leaf(inSyntaxExtension(local,(ntn,synext)))typenotation_interpretation_decl=notation_declaration*notation_main_data*notation_symbols*notation*syntax_rules(* Notations associated to a where clause *)letprepare_where_notationntn_decl=let{ntn_decl_string={CAst.loc;v=df};ntn_decl_interp=c;ntn_decl_modifiers=modifiers;ntn_decl_scope=sc;}=ntn_declinlet(main_data,mods)=interp_non_syntax_modifiers~reserved:false~infix:false~abbrev:falseNonemodifiersinmatchmodswith|_::_->CErrors.user_err(str"Only modifiers not affecting parsing are supported here.")|[]->letnotation_symbols,is_prim_token=analyze_notation_tokens~onlyprinting:main_data.onlyprinting~infix:falsemain_data.entrydfinletntn=make_notation_keymain_data.entrynotation_symbols.symbolsinletsyntax_rules=ifis_prim_tokenthenPrimTokenSyntaxelsetrySpecificSyntax(recover_notation_syntaxntn)withNoSyntaxRule->user_errPp.(str"Parsing rule for this notation has to be previously declared.")in(ntn_decl,main_data,notation_symbols,ntn,syntax_rules)letadd_notation_interpretation~localenv(ntn_decl,main_data,notation_symbols,ntn,syntax_rules)=let{ntn_decl_string={CAst.loc;v=df};ntn_decl_interp=c;ntn_decl_scope=sc}=ntn_declinletnotation=make_notation_interpretation~localmain_datanotation_symbolsntnsyntax_rulesdfenvcscinLib.add_leaf(inNotationnotation);Dumpglob.dump_notation(CAst.make?locntn)sctrue(* interpreting a where clause *)letset_notation_for_interpretationenvimpls(ntn_decl,main_data,notation_symbols,ntn,syntax_rules)=let{ntn_decl_string={CAst.loc;v=df};ntn_decl_interp=c;ntn_decl_scope=sc}=ntn_declinletnotation=make_notation_interpretation~local:truemain_datanotation_symbolsntnsyntax_rulesdfenv~implscscinLib.add_leaf(inNotationnotation);Option.iter(funsc->Lib.add_leaf(inScope(false,true,sc)))scletbuild_notation_syntax~local~infixuser_warnsntn_decl=let{ntn_decl_string={CAst.loc;v=df};ntn_decl_modifiers=modifiers;ntn_decl_interp=c}=ntn_declin(* Extract the modifiers not affecting the parsing rule *)let(main_data,syntax_modifiers)=interp_non_syntax_modifiers~reserved:false~infix~abbrev:falseuser_warnsmodifiersin(* Extract the modifiers not affecting the parsing rule *)letnotation_symbols,is_prim_token=analyze_notation_tokens~onlyprinting:main_data.onlyprinting~infixmain_data.entrydfin(* Add variables on both sides if an infix notation *)letdf,notation_symbols,c=ifinfixthenadjust_infix_notationdfnotation_symbolscelsedf,notation_symbols,cin(* Build the canonical identifier of the syntactic part of the notation *)letntn=make_notation_keymain_data.entrynotation_symbols.symbolsinletsyntax_rules=ifis_prim_tokenthen(check_no_syntax_modifiers_for_numeralsyntax_modifiers;PrimTokenSyntax)elsematchsyntax_modifierswith|[]->(* No syntax data: try to rely on a previously declared rule *)(trySpecificSyntax(recover_notation_syntaxntn)withNoSyntaxRule->(* Try to determine a default syntax rule *)letsd=compute_syntax_data~localmain_datanotation_symbolsntnNotationMods.defaultinSpecificSyntax(make_syntax_rulesfalsemain_datantnsd))|_->letmods=interp_modifiersmain_data.entrysyntax_modifiersinletsd=compute_syntax_data~localmain_datanotation_symbolsntnmodsinSpecificSyntax(make_syntax_rulesfalsemain_datantnsd)inmain_data,notation_symbols,ntn,syntax_rules,c,dfletadd_notation_syntax~local~infixuser_warnsntn_decl=(* Build or rebuild the syntax rules *)letmain_data,notation_symbols,ntn,syntax_rules,c,df=build_notation_syntax~local~infixuser_warnsntn_declin(* Declare syntax *)syntax_rules_iter(funsy->Lib.add_leaf(inSyntaxExtension(local,(ntn,sy))))syntax_rules;letntn_decl_string=CAst.make?loc:ntn_decl.ntn_decl_string.CAst.locdfinletntn_decl={ntn_declwithntn_decl_interp=c;ntn_decl_string}inntn_decl,main_data,notation_symbols,ntn,syntax_rules(** **************************************************************** **)(** Scopes, delimiters and classes bound to scopes **)typescope_command=|ScopeDeclare|ScopeDelimAddofstring|ScopeDelimRemove|ScopeClassesofadd_scope_whereoption*scope_classlistletload_scope_command_commonsilently_define_scope_if_undefined_(local,scope,o)=letdeclare_scope_if_needed=ifsilently_define_scope_if_undefinedthenNotation.declare_scopeelseNotation.ensure_scopeinmatchowith|ScopeDeclare->Notation.declare_scopescope(* When the default shall be to require that a scope already exists *)(* the call to declare_scope_if_needed will have to be removed below *)|ScopeDelimAdddlm->declare_scope_if_neededscope|ScopeDelimRemove->declare_scope_if_neededscope|ScopeClasses_->declare_scope_if_neededscopeletload_scope_command=load_scope_command_commontrueletopen_scope_commandi(noexport,scope,o)=ifInt.equali1thenmatchowith|ScopeDeclare->()|ScopeDelimAdddlm->Notation.declare_delimitersscopedlm|ScopeDelimRemove->Notation.remove_delimitersscope|ScopeClasses(where,cl)->letlocal=Lib.sections_are_opened()inList.iter(Notation.declare_scope_classlocalscope?where)clletcache_scope_commando=load_scope_command_commonfalse1o;open_scope_command1oletsubst_scope_command(subst,(noexport,scope,oasx))=matchowith|ScopeClasses(where,cl)->letenv=Global.env()inletcl'=List.map_filter(subst_scope_classenvsubst)clinletcl'=ifList.for_all2eq(==)clcl'thenclelsecl'innoexport,scope,ScopeClasses(where,cl')|_->xletclassify_scope_command(noexport,_,_)=ifnoexportthenDisposeelseSubstituteletinScopeCommand:locality_flag*scope_name*scope_command->obj=declare_object{(default_object"DELIMITERS")withcache_function=cache_scope_command;open_function=simple_open~cat:notation_catopen_scope_command;load_function=load_scope_command;subst_function=subst_scope_command;classify_function=classify_scope_command}letdeclare_scopelocalscope=Lib.add_leaf(inScopeCommand(local,scope,ScopeDeclare))letadd_delimiterslocalscopekey=Lib.add_leaf(inScopeCommand(local,scope,ScopeDelimAddkey))letremove_delimiterslocalscope=Lib.add_leaf(inScopeCommand(local,scope,ScopeDelimRemove))letadd_class_scopelocalscopewherecl=Lib.add_leaf(inScopeCommand(local,scope,ScopeClasses(where,cl)))letinterp_abbreviation_modifiersuser_warnsmodl=letmods,skipped=interp_non_syntax_modifiers~reserved:false~infix:false~abbrev:trueuser_warnsmodlinifskipped<>[]then(letmodifier=List.hdskippedinuser_err?loc:modifier.CAst.loc(str"Abbreviations don't support "++Ppvernac.pr_syntax_modifiermodifier));(mods.onlyparsing,mods.itemscopes)letadd_abbreviation~localuser_warnsenvident(vars,c)modl=let(only_parsing,scopes)=interp_abbreviation_modifiersuser_warnsmodlinletvars=List.map(funv->v,List.assoc_optvscopes)varsinletacvars,pat,reversibility=matchvars,intern_name_aliascwith|[],Some(r,u)->(* Check if abbreviation to a name and avoid early insertion of
maximal implicit arguments *)Id.Map.empty,NRef(r,u),APrioriReversible|_->letfoldaccu(id,scope)=Id.Map.addid(NtnInternTypeAnyscope)accuinleti_vars=List.fold_leftfoldId.Map.emptyvarsinletnenv={ninterp_var_type=i_vars;ninterp_rec_vars=Id.Map.empty;}ininterp_notation_constrenvnenvcinletlevel_arg=NumLevel9(* level of arguments of an application *)inletin_pat(id,_)=(id,ETConstr(Constrexpr.InConstrEntry,None,(level_arg,InternalProd)))inletlevel=(* not relevant *)(constr_lowest_level,[])inletinterp=make_interpretation_vars~default_if_binding:AsAnyPattern[]acvarslevel(List.mapin_patvars)inletvars=List.map(fun(x,_)->(x,Id.Map.findxinterp))varsinletonlyparsing=only_parsing||fst(printabilityNone[]varsfalsereversibilitypat)inAbbreviation.declare_abbreviation~localuser_warnsident~onlyparsing(vars,pat)(**********************************************************************)(* Activating/deactivating notations *)letload_notation_toggle__=()letopen_notation_toggle_(local,(on,all,pat))=letenv=Global.env()inletsigma=Evd.from_envenvintoggle_notations~on~all~verbose:(not!Flags.quiet)(Constrextern.without_symbols(Printer.pr_glob_constr_envenvsigma))patletcache_notation_toggleo=load_notation_toggle1o;open_notation_toggle1oletsubst_notation_toggle(subst,(local,(on,all,pat)))=let{notation_entry_pattern;interp_rule_key_pattern;use_pattern;scope_pattern;interpretation_pattern}=patinletinterpretation_pattern=Option.map(subst_interpretationsubst)interpretation_patterninletinterp_rule_key_pattern=interp_rule_key_patternin(local,(on,all,{notation_entry_pattern;interp_rule_key_pattern;use_pattern;scope_pattern;interpretation_pattern}))letclassify_notation_toggle(local,_)=iflocalthenDisposeelseSubstituteletinNotationActivation:locality_flag*(bool*bool*notation_query_pattern)->obj=declare_object{(default_object"NOTATION-TOGGLE")withcache_function=cache_notation_toggle;open_function=simple_openopen_notation_toggle;load_function=load_notation_toggle;subst_function=subst_notation_toggle;classify_function=classify_notation_toggle}letdeclare_notation_togglelocal~on~alls=Lib.add_leaf(inNotationActivation(local,(on,all,s)))(** **************************************************************** **)(** Declaration of custom entries **)letwarn_custom_entry=CWarnings.create~name:"custom-entry-overridden"~category:CWarnings.CoreCategories.parsing(funs->strbrk"Custom entry "++strs++strbrk" has been overridden.")letload_custom_entry_(local,s)=ifEgramcoq.exists_custom_entrysthenwarn_custom_entryselseEgramcoq.create_custom_entry~localsletcache_custom_entryo=load_custom_entry1oletsubst_custom_entry(subst,x)=xletclassify_custom_entry(local,s)=iflocalthenDisposeelseSubstituteletinCustomEntry:locality_flag*string->obj=declare_object{(default_object"CUSTOM-ENTRIES")withobject_stage=Summary.Stage.Synterp;cache_function=cache_custom_entry;load_function=load_custom_entry;subst_function=subst_custom_entry;classify_function=classify_custom_entry}letdeclare_custom_entrylocals=ifEgramcoq.exists_custom_entrysthenuser_errPp.(str"Custom entry "++strs++str" already exists.")elseLib.add_leaf(inCustomEntry(local,s))