123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985(* The package sedlex is released under the terms of an MIT-like license. *)(* See the attached LICENSE file. *)(* Copyright 2005, 2013 by Alain Frisch and LexiFi. *)openPpxlibopenAst_builder.DefaultopenAst_helperopenSedlex_compiler(* let ocaml_version = Versions.ocaml_408 *)(* Decision tree for partitions.
A partition maps Unicode code points to equivalence class indices. Rather
than generating a flat lookup table (which would be huge), we build a
binary decision tree that tests code points against split values. For
dense regions below [limit], a compact byte-string table is used instead.
[simplify] prunes unreachable branches given a known code-point range. *)letdefault_loc=Location.nonetypedecision_tree=|Lteofint*decision_tree*decision_tree|Tableofint*intarray|Returnofintletrecsimplify_decision_tree(x:decision_tree)=matchxwith|Table_|Return_->x|Lte(_,(Returnaasl),Returnb)whena=b->l|Lte(i,l,r)->(letl=simplify_decision_treelinletr=simplify_decision_treerinmatch(l,r)with|Returna,Returnbwhena=b->l|_->Lte(i,l,r))(* [decision segments] builds a balanced binary decision tree from a sorted
list of [(lo, hi, class_index)] segments. Pairs of adjacent segments are
merged bottom-up into [Lte] nodes. Gaps between segments return -1
(no match). *)letdecisionl=letl=List.map(fun(a,b,i)->(a,b,Returni))linletrecmerge2=function|(a1,b1,d1)::(a2,b2,d2)::rest->letx=ifb1+1=a2thend2elseLte(a2-1,Return(-1),d2)in(a1,b2,Lte(b1,d1,x))::merge2rest|rest->restinletrecaux=function|[(a,b,d)]->Lte(a-1,Return(-1),Lte(b,d,Return(-1)))|[]->Return(-1)|l->aux(merge2l)inauxl(* Code points below [limit] with class index < 255 are eligible for
compact byte-string table lookup instead of a decision tree. *)letlimit=8192(* [decision_table segments] partitions segments into a table-eligible
prefix (dense, low code points) and a tree-handled suffix. The prefix
becomes a [Table] node for O(1) lookup; the suffix uses [decision]. *)letdecision_tablel=letrecauxmaccu=function|((a,b,i)asx)::remwhenb<limit&&i<255->aux(minam)(x::accu)rem|rem->(m,accu,rem)inletmin,table,rest=auxmax_int[]linmatchtablewith|[]->decisionl|[(min,max,i)]->Lte(min-1,Return(-1),Lte(max,Returni,decisionrest))|(_,max,_)::_->letarr=Array.make(max-min+1)0inletset(a,b,i)=forj=atobdoarr.(j-min)<-i+1doneinList.itersettable;Lte(min-1,Return(-1),Lte(max,Table(min,arr),decisionrest))letrecsimplifyminmax=function|Lte(i,yes,no)->ifi>=maxthensimplifyminmaxyeselseifi<minthensimplifyminmaxnoelseLte(i,simplifyminiyes,simplify(i+1)maxno)|x->x(* [segments_of_partition p] flattens a partition (array of char sets, one
per equivalence class) into a sorted list of [(lo, hi, class_index)]
segments suitable for [decision_table]. *)letsegments_of_partitionp=letseg=ref[]inArray.iteri(funic->List.iter(fun(a,b)->seg:=(a,b,i)::!seg)(c:Cset.t:>(int*int)list))p;List.sort(fun(a1,_,_)(a2,_,_)->comparea1a2)!seg(* [decision_table partition] builds a complete decision tree for a
partition: extracts segments, builds the hybrid table/tree, then
simplifies by pruning branches outside the valid code-point range. *)letdecision_tablep=simplify(-1)Cset.max_code(decision_table(segments_of_partitionp))(* Helpers to build AST *)letappfunsl=letloc=default_locineapply~loc(evar~locs)lletglb_valuenamedef=letloc=default_locinpstr_value~locNonrecursive[value_binding~loc~pat:(pvar~locname)~expr:def](* Named regexps *)moduleStringMap=Map.Make(structtypet=stringletcompare=compareend)letbuiltin_regexps=List.fold_left(funacc(n,c)->StringMap.addn(Ir.charsc)acc)StringMap.empty([("any",Cset.any);("eof",Cset.eof);("xml_letter",Xml.letter);("xml_digit",Xml.digit);("xml_extender",Xml.extender);("xml_base_char",Xml.base_char);("xml_ideographic",Xml.ideographic);("xml_combining_char",Xml.combining_char);("xml_blank",Xml.blank);("tr8876_ident_char",Iso.tr8876_ident_char);]@Unicode.Categories.list@Unicode.Properties.list)(* Tables (indexed mapping: codepoint -> next state) *)lettables=Hashtbl.create31letget_tables()=Hashtbl.fold(funkeyxaccu->(x,key)::accu)tables[]lettable_namex=tryHashtbl.findtablesxwithNot_found->lets=Printf.sprintf"__sedlex_table_%i"(Hashtbl.lengthtables+1)inHashtbl.addtablesxs;s(* [table (name, v)] generates a top-level [let __sedlex_table_N = "..."]
binding where the string encodes the byte array [v] (one byte per entry,
used for compact partition lookup). *)lettable(name,v)=letn=Array.lengthvinlets=Bytes.createninfori=0ton-1doBytes.setsi(Char.chrv.(i))done;glb_valuename(estring~loc:default_loc(Bytes.to_strings))(* Partition (function: codepoint -> next state) *)letpartitions=Hashtbl.create31letget_partitions()=Hashtbl.fold(funkeyxaccu->(x,key)::accu)partitions[]letpartition_namex=tryHashtbl.findpartitionsxwithNot_found->lets=Printf.sprintf"__sedlex_partition_%i"(Hashtbl.lengthpartitions+1)inHashtbl.addpartitionsxs;sletreset_state()=Hashtbl.cleartables;Hashtbl.clearpartitions(* [partition (name, p)] generates a top-level [let __sedlex_partition_N c = ...]
function that maps a code point [c] to its equivalence class index using
the decision tree built from partition [p]. The EOF case (-1) is handled
naturally by the decision tree since [simplify] uses -1 as the lower bound. *)letpartition(name,p)=letloc=default_locinletrecgen_tree=function|Lte(i,yes,no)->[%exprifc<=[%eeint~loci]then[%egen_treeyes]else[%egen_treeno]]|Returni->eint~loc:default_loci|Table(offset,t)->letc=ifoffset=0then[%exprc]else[%exprc-[%eeint~locoffset]]in[%exprChar.code(String.unsafe_get[%eevar~loc(table_namet)][%ec])-1]inletbody=gen_tree(simplify_decision_tree(decision_tablep))inglb_valuename[%exprfunc->letopen!Stdlibin[%ebody]](* Code generation for the automata *)letstate_funstate=Printf.sprintf"__sedlex_state_%i"state(* What [Set_position] records in the generated code. Transition operations
execute before their character is consumed, but the code runs them after
reading it. *)typeposition=|Current(** final operations, and end-of-input transitions: reading end of input
does not advance *)|Previous(** character transitions: one code point back *)(* [gen_tag_ops ~position lexbuf ops cont] wraps [cont] in the code performing
the tag operations [ops], which form a parallel move: every [Copy] must
read its source as it was before any operation of the list executed. Sources that
the list also writes are first saved in let-bound locals; everything else
compiles to direct [__private__set_mem_pos] / [__private__set_mem_value] /
[__private__copy_mem] calls in list order. *)letgen_tag_ops~positionlexbuf(ops:Sedlex.tag_oplist)cont=letloc=default_locinletdests=List.mapSedlex.op_destopsinletclobbered=List.sort_uniqcompare(List.filter_map(fun(op:Sedlex.tag_op)->matchopwith|Copy{src;_}whenList.memsrcdests->Somesrc|_->None)ops)inletlocals=Printf.sprintf"__sedlex_mem_%d"sinletwrites=List.fold_right(fun(op:Sedlex.tag_op)acc->matchopwith|Set_position{dst}->(letdst=eint~locdstinmatchpositionwith|Current->[%exprSedlexing.__private__set_mem_pos[%elexbuf][%edst];[%eacc]]|Previous->[%exprSedlexing.__private__set_mem_prev_pos[%elexbuf][%edst];[%eacc]])|Set_value{dst;value}->[%exprSedlexing.__private__set_mem_value[%elexbuf][%eeint~locdst][%eeint~locvalue];[%eacc]]|Copy{dst;src}whenList.memsrcclobbered->[%exprSedlexing.__private__mem_set[%elexbuf][%eeint~locdst][%eevar~loc(localsrc)];[%eacc]]|Copy{dst;src}->[%exprSedlexing.__private__copy_mem[%elexbuf][%eeint~locdst][%eeint~locsrc];[%eacc]])opscontinList.fold_right(funsacc->[%exprlet[%ppvar~loc(locals)]=Sedlexing.__private__mem_get[%elexbuf][%eeint~locs]in[%eacc]])clobberedwrites(* [call_state lexbuf auto state] generates the expression that transitions
into DFA [state]. If the state has no outgoing transitions (a sink), it
executes the state's final tag operations and returns the accepting rule
index directly, or backtracks if the state accepts nothing; otherwise it
emits a function call to the generated state function. *)letcall_statelexbuf(auto:Sedlex.dfa)state=letloc=default_locinlet{Sedlex.trans;accept}=auto.(state)inifArray.lengthtrans=0then(matchacceptwith|Some{Sedlex.rule;final_ops}->gen_tag_ops~position:Currentlexbuffinal_ops(eint~locrule)|None->(* Nothing can match from here, e.g. after [eof] in [eof, eof]. *)[%exprSedlexing.backtrack[%elexbuf]])elseappfun(state_funstate)[lexbuf](* [gen_state (lexbuf_name, lexbuf) auto i {trans; accept}] generates the
function [__sedlex_state_N] for DFA state [i]. The function:
1. If the state is accepting ([accept = Some { rule; final_ops }]), executes
[final_ops] then calls [mark] to save the current position. The cells
read by the rule's action are only written by [final_ops].
2. Reads the next code point, maps it through the partition function to
get an equivalence class index, then pattern-matches on that index.
3. Each transition arm executes its tag operations then calls the target
state function (or returns the rule index for sink states).
4. The default arm calls [backtrack] to return the last accepted rule.
Returns [] for states with no outgoing transitions (sinks), which
[call_state] inlines. *)letgen_state(lexbuf_name,lexbuf)(auto:Sedlex.dfa)i{Sedlex.trans;accept}=letloc=default_locinletpartition=Array.map(fun(cs,_,_)->cs)transinletcases=Array.mapi(funi(cs,j,tags)->letposition=ifCset.mem(-1)csthen((* End of input never shares a transition with characters. *)assert(Cset.is_empty(Cset.differencecsCset.eof));Current)elsePreviousinletrhs=gen_tag_ops~positionlexbuftags(call_statelexbufautoj)incase~lhs:(pint~loci)~guard:None~rhs)transinletcases=Array.to_listcasesinletbody()=pexp_match~loc(appfun(partition_namepartition)[[%exprSedlexing.__private__next_int[%elexbuf]]])(cases@[case~lhs:[%pat?_]~guard:None~rhs:[%exprSedlexing.backtrack[%elexbuf]];])inletretbody=letlhs=pvar~loc:lexbuf.pexp_loclexbuf_namein[value_binding~loc~pat:(pvar~loc(state_funi))~expr:(Exp.fun_~locNolabelNonelhsbody);]inmatchacceptwith|_whenArray.lengthtrans=0->[]|None->ret(body())|Some{Sedlex.rule;final_ops}->ret(gen_tag_ops~position:Currentlexbuffinal_ops[%exprSedlexing.mark[%elexbuf][%eeint~locrule];[%ebody()]])(* [gen_recflag auto] determines whether the generated state functions need
[let rec]. If every transition leads to a sink state (no further
transitions), the functions are non-recursive; otherwise they are
mutually recursive. *)letgen_recflag(auto:Sedlex.dfa)=(* The generated function is not recursive if the transitions end
in states with no further transitions. *)tryArray.iter(fun{Sedlex.trans;_}->Array.iter(fun(_,j,_)->ifArray.lengthauto.(j).Sedlex.trans>0thenraiseExit)trans)auto;NonrecursivewithExit->Recursive(* [gen_definition lexbuf_with_name compiled cases error] generates the
complete lexer expression for one [match%sedlex] block:
- Defines all [__sedlex_state_N] functions via [let rec ... in].
- Emits a start sequence: [start lexbuf], then (if the pattern has [as]
bindings) [ensure_mem], then calls state 0.
- Wraps the result in a [match] on the returned rule index, dispatching
to user-provided right-hand-side expressions, with [error] as default. *)letgen_definition((_,lexbuf)aslexbuf_with_name)(compiled:Sedlex.compiled)lerror=letloc=default_locinletauto=compiled.dfainletcases=List.mapi(funi(_,e)->case~lhs:(pint~loci)~guard:None~rhs:e)linletstates=Array.mapi(gen_statelexbuf_with_nameauto)autoinletstates=List.flatten(Array.to_liststates)inletstart_expr=ifcompiled.num_tags>0then(letensure_mem=[%exprSedlexing.__private__ensure_mem[%elexbuf][%eeint~loccompiled.num_tags]]inpexp_sequence~loc[%exprSedlexing.start[%elexbuf]](pexp_sequence~locensure_mem(call_statelexbufauto0)))elsepexp_sequence~loc[%exprSedlexing.start[%elexbuf]](call_statelexbufauto0)inletmatch_expr=pexp_match~locstart_expr(cases@[case~lhs:(ppat_any~loc)~guard:None~rhs:error])in(* [states] is empty when state 0 is an accepting sink (e.g. the pattern
[""]): [call_state] inlines its result above, so there are no state
functions to bind and [pexp_let] with no bindings would be invalid. *)matchstateswith|[]->match_expr|_->pexp_let~loc(gen_recflagauto)statesmatch_expr(* Lexer specification parser *)letcodepointi=ifi<0||i>Cset.max_codethenfailwith(Printf.sprintf"Invalid Unicode code point: %i"i);iletcharc=Cset.singleton(Char.codec)letucharc=Cset.singleton(Uchar.to_intc)leterrlocfmt=Printf.ksprintf(funs->raise(Location.Error(Location.Error.createf~loc"Sedlex: %s"s)))fmttypeencoding=Utf8|Latin1|Asciiletstring_of_encoding=function|Utf8->"UTF-8"|Latin1->"Latin-1"|Ascii->"ASCII"(* [rev_csets_of_string ~loc ~encoding s] decodes string [s] under
[encoding] and returns a list of singleton char sets in reverse order
(one per character). Used to build sequence regexps from string literals. *)letrev_csets_of_string~loc~encodings=matchencodingwith|Utf8->Utf8.fold~f:(funacc_x->matchxwith|`Malformed_->errloc"Malformed %s string"(string_of_encodingencoding)|`Ucharc->ucharc::acc)[]s|Latin1->letl=ref[]infori=0toString.lengths-1dol:=chars.[i]::!ldone;!l|Ascii->letl=ref[]infori=0toString.lengths-1domatchs.[i]with|'\x00'..'\x7F'asc->l:=charc::!l|_->errloc"Malformed %s string"(string_of_encodingencoding)done;!l(* Code generation for `as` bindings.
[gen_binding_code] turns the [compiled_binding list] (from the compiler)
into [let] bindings that extract sub-matches from the lexbuf's memory cells
(or computed offsets). For or-patterns with discriminators it emits a chain
of if/else checks on the discriminator cell to select the correct positions. *)(* [gen_pos_expr lexbuf pe] generates code that evaluates a [pos_expr]
to an integer position (offset from token start, in code points). *)letgen_pos_exprlexbuf(pe:Sedlex.pos_expr)=letloc=default_locinmatchpewith|Tag{tag;offset=0}->[%exprSedlexing.__private__mem_pos[%elexbuf][%eeint~loctag]]|Tag{tag;offset}->[%exprSedlexing.__private__mem_pos[%elexbuf][%eeint~loctag]+[%eeint~locoffset]]|Start_plusn->eint~locn|End_minus0->[%exprSedlexing.lexeme_length[%elexbuf]]|End_minusn->[%exprSedlexing.lexeme_length[%elexbuf]-[%eeint~locn]](* [gen_sub_lexeme lexbuf st et] generates an expression that evaluates
position expressions [st] and [et] and constructs a [Sedlexing.submatch]
record. *)letgen_sub_lexemelexbufstet=letloc=default_locin[%exprlet__s=[%egen_pos_exprlexbufst]inlet__e=[%egen_pos_exprlexbufet]in{Sedlexing.lexbuf=[%elexbuf];pos=__s;len=__e-__s}](* [gen_binding_code lexbuf bindings action] wraps [action] with [let]
bindings that extract sub-match values from the lexbuf's memory cells.
For a single binding (no or-pattern), emits a direct sub_lexeme call.
For or-patterns with multiple tag pairs, emits a chain of
[if disc_cell = value then ...] to select the correct positions at runtime. *)letgen_binding_codelexbuf(bindings:Sedlex.compiled_bindinglist)action=letloc=default_locinignoreloc;ifbindings=[]thenactionelse((* Group bindings by variable name *)letby_name=lettbl=Hashtbl.create8inletorder=ref[]inList.iter(fun({name;start_pos;end_pos;disc}:Sedlex.compiled_binding)->ifnot(Hashtbl.memtblname)thenorder:=name::!order;letexisting=tryHashtbl.findtblnamewithNot_found->[]inHashtbl.replacetblname(existing@[(start_pos,end_pos,disc)]))bindings;List.rev_map(funname->(name,Hashtbl.findtblname))!orderinList.fold_right(fun(name,entries)acc->matchentrieswith|[(st,et,_)]->[%exprlet[%ppvar~locname]=[%egen_sub_lexemelexbufstet]in[%eacc]]|_->(* All the discriminators on the way to an alternative are
tested, the outermost included: the cells are not cleared
between tokens, so an inner one may hold the value of an
earlier token, and only counts once the outer ones agree. *)letgen_disc_conddiscs=letcheck(cell,value)=[%exprSedlexing.__private__mem_value[%elexbuf][%eeint~loccell]=[%eeint~locvalue]]inmatchdiscswith|[]->assertfalse|[d]->checkd|d::ds->List.fold_left(funaccd->[%expr[%eacc]&&[%echeckd]])(checkd)dsinletrecgen_checks=function|[(st,et,_)]->gen_sub_lexemelexbufstet|(st,et,discs)::rest->ifdiscs=[]thenfailwith"discriminator required for or-pattern with multiple \
bindings";[%exprif[%egen_disc_conddiscs]then[%egen_sub_lexemelexbufstet]else[%egen_checksrest]]|[]->assertfalsein[%exprlet[%ppvar~locname]=[%egen_checksentries]in[%eacc]])by_nameaction)(* [ir_of_pattern env] returns a function [pattern -> Ir.t] that parses an
OCaml pattern AST into an IR node. [env] maps names to previously defined
IR patterns. All tag allocation, discriminator handling, and fixed-length
optimization are deferred to the compiler's [compile_ir]. *)letir_of_patternenv=letunwraploc=functionOkir->ir|Errormsg->errloc"%s"msgin(* An empty character set can never match; it would also leave the DFA
with a dead state the code generator cannot express. Reject it where it
is formed ([Chars ""], [Compl any], [Sub]/[Intersect] of coinciding
classes) so the mistake is reported at its source. *)letnonemptylocr=matchrwith|Ir.CharscwhenCset.is_emptyc->errloc"empty character set: this pattern can never match"|r->rinletrecchar_pair_opfuncname~encoding~loctuple=(* Construct something like Sub(a,b) *)matchtuplewith|Some{ppat_desc=Ppat_tuple[p0;p1];_}->(letr0=unwrapp0.ppat_loc(Ir.reject_capturesname(aux~encodingp0))inletr1=unwrapp1.ppat_loc(Ir.reject_capturesname(aux~encodingp1))inmatchfuncr0r1with|Somer->nonemptylocr|None->errloc"the %s operator can only applied to single-character length \
regexps"name)|_->errloc"the %s operator requires two arguments, like %s(a,b)"namenameandir_complr=matchrwith|Ir.Charsc->Some(Ir.Chars(Cset.differenceCset.anyc))|_->Noneandir_subtractr0r1=match(r0,r1)with|Ir.Charsc0,Ir.Charsc1->Some(Ir.Chars(Cset.differencec0c1))|_->Noneandir_intersectionr0r1=match(r0,r1)with|Ir.Charsc0,Ir.Charsc1->Some(Ir.Chars(Cset.intersectionc0c1))|_->Noneandaux~encodingp=matchp.ppat_descwith(* name as x — named sub-match binding *)|Ppat_alias(inner,{txt=name;loc=name_loc})->unwrapname_loc(Ir.capturename(aux~encodinginner))(* p1 | p2 — alternation *)|Ppat_or(p1,p2)->unwrapp.ppat_loc(Ir.alt(aux~encodingp1)(aux~encodingp2))(* (p1, p2, ...) — sequence *)|Ppat_tuple(p::pl)->List.fold_left(funaccp->unwrapp.ppat_loc(Ir.seqacc(aux~encodingp)))(aux~encodingp)pl(* Star p — zero-or-more repetition *)|Ppat_construct({txt=Lident"Star";_},Some(_,p))->unwrapp.ppat_loc(Ir.star(aux~encodingp))(* Plus p — one-or-more repetition *)|Ppat_construct({txt=Lident"Plus";_},Some(_,p))->unwrapp.ppat_loc(Ir.plus(aux~encodingp))(* Utf8 p — switch to UTF-8 encoding *)|Ppat_construct({txt=Lident"Utf8";_},Some(_,p))->aux~encoding:Utf8p(* Latin1 p — switch to Latin-1 encoding *)|Ppat_construct({txt=Lident"Latin1";_},Some(_,p))->aux~encoding:Latin1p(* Ascii p — switch to ASCII encoding *)|Ppat_construct({txt=Lident"Ascii";_},Some(_,p))->aux~encoding:Asciip(* Rep (p, n..m) — bounded repetition *)|Ppat_construct({txt=Lident"Rep";_},Some(_,{ppat_desc=Ppat_tuple[p0;{ppat_desc=Ppat_constant(i1asi2)|Ppat_interval(i1,i2);_;};];_;}))->(letr=aux~encodingp0inmatch(i1,i2)with|Pconst_integer(i1,_),Pconst_integer(i2,_)->leti1=int_of_stringi1inleti2=int_of_stringi2in(* Check bounds explicitly here for better error locations *)if0<=i1&&i1<=i2thenunwrapp0.ppat_loc(Ir.repri1i2)elseerrp.ppat_loc"Invalid range for Rep operator"|_->errp.ppat_loc"Rep must take an integer constant or interval")(* Rep _ — malformed *)|Ppat_construct({txt=Lident"Rep";_},_)->errp.ppat_loc"the Rep operator takes 2 arguments"(* Opt p — optional (zero or one). [alt r eps], consume first: greedy
like Star/Plus/Rep, so [Opt p] and [Rep (p, 0 .. 1)] agree. *)|Ppat_construct({txt=Lident"Opt";_},Some(_,p))->letr=unwrapp.ppat_loc(Ir.reject_captures"Opt"(aux~encodingp))inunwrapp.ppat_loc(Ir.altrIr.eps)(* Compl p — complement of a character class *)|Ppat_construct({txt=Lident"Compl";_},arg)->(matchargwith|Some(_,p0)->(letr=unwrapp0.ppat_loc(Ir.reject_captures"Compl"(aux~encodingp0))inmatchir_complrwith|Somer->nonemptyp.ppat_locr|None->errp.ppat_loc"the Compl operator can only applied to a \
single-character length regexp")|_->errp.ppat_loc"the Compl operator requires an argument")(* Sub (a, b) — character class subtraction *)|Ppat_construct({txt=Lident"Sub";_},arg)->char_pair_op~encodingir_subtract"Sub"~loc:p.ppat_loc(Option.map(fun(_,arg)->arg)arg)(* Intersect (a, b) — character class intersection *)|Ppat_construct({txt=Lident"Intersect";_},arg)->char_pair_op~encodingir_intersection"Intersect"~loc:p.ppat_loc(Option.map(fun(_,arg)->arg)arg)(* Chars "..." — character set from string literal *)|Ppat_construct({txt=Lident"Chars";_},arg)->(letconst=matchargwith|Some(_,{ppat_desc=Ppat_constantconst;_})->Someconst|_->Noneinmatchconstwith|Some(Pconst_string(s,_,_))->letl=rev_csets_of_string~loc:p.ppat_loc~encodingsinletchars=List.fold_leftCset.unionCset.emptylinnonemptyp.ppat_loc(Ir.charschars)|_->errp.ppat_loc"the Chars operator requires a string argument")(* 'a' .. 'z' or 0x41 .. 0x5a — character/codepoint range *)|Ppat_interval(i_start,i_end)->(match(i_start,i_end)with|Pconst_charc1,Pconst_charc2->letvalid=matchencodingwith(* utf8 char interval can only match ascii because
of the OCaml lexer. *)|Ascii|Utf8->(function'\x00'..'\x7f'->true|_->false)|Latin1->(function_->true)inifnot(validc1&&validc2)thenerrp.ppat_loc"this pattern is not a valid %s interval regexp"(string_of_encodingencoding);Ir.chars(Cset.interval(Char.codec1)(Char.codec2))|Pconst_integer(i1,_),Pconst_integer(i2,_)->Ir.chars(Cset.interval(codepoint(int_of_stringi1))(codepoint(int_of_stringi2)))|_->errp.ppat_loc"this pattern is not a valid interval regexp")(* "string" or 'c' or 0x42 — literal string, char, or codepoint *)|Ppat_constantconst->(matchconstwith|Pconst_string(s,_,_)->letrev_l=rev_csets_of_strings~loc:p.ppat_loc~encodinginList.fold_left(funacccset->(* chars have no captures, so seq cannot fail *)Result.get_ok(Ir.seq(Ir.charscset)acc))Ir.epsrev_l|Pconst_charc->Ir.chars(charc)|Pconst_integer(i,_)->Ir.chars(Cset.singleton(codepoint(int_of_stringi)))|_->errp.ppat_loc"this pattern is not a valid regexp")(* Bare operator without argument *)|Ppat_construct({txt=Lident(("Star"|"Plus"|"Opt"|"Utf8"|"Latin1"|"Ascii")asname);_;},None)->errp.ppat_loc"the %s operator requires an argument"name(* Unknown constructor *)|Ppat_construct({txt=Lidentname;_},_)->errp.ppat_loc"unknown sedlex operator %s"name(* name — reference to a previously defined regexp *)|Ppat_var{txt=x;_}->(matchStringMap.find_optxenvwith|Someir->ir|None->errp.ppat_loc"unbound regexp %s"x)|_->errp.ppat_loc"this pattern is not a valid regexp"inaux~encoding:Ascii(* [handle_sedlex_match_ ~env ~map_rhs match_expr] is the main entry point
for compiling a [match%sedlex lexbuf with ...] expression. It:
1. Extracts the lexbuf identifier and match cases.
2. Parses each case's pattern into [Ir.t] via [ir_of_pattern].
3. Compiles all patterns via [Sedlex.compile_ir] (tag allocation, DFA).
4. Applies [map_rhs] to each case's right-hand side.
5. Wraps each RHS with [gen_binding_code] for [as] binding extraction.
6. Generates the full lexer code via [gen_definition].
Returns [(generated_expr, dfa)] for use by the main mapper and tests. *)lethandle_sedlex_match_~env~map_rhsmatch_expr=letlexbuf=matchmatch_exprwith|{pexp_desc=Pexp_match(lexbuf,_);_}->(matchlexbufwith|{pexp_desc=Pexp_ident{txt=Lidenttxt;_};_}->(txt,lexbuf)|_->errlexbuf.pexp_loc"the matched expression must be a single identifier")|_->errmatch_expr.pexp_loc"the %%sedlex extension is only recognized on match expressions"inletcases=matchmatch_exprwith|{pexp_desc=Pexp_match(_,cases);_}->cases|_->assertfalseinletcases=List.revcasesinleterror=matchList.hdcaseswith|{pc_lhs=[%pat?_];pc_rhs=e;pc_guard=None}->map_rhse|{pc_lhs=p;_}->errp.ppat_loc"the last branch must be a catch-all error case"inletcases=List.rev(List.tlcases)inletcases_with_ir=List.map(function|{pc_lhs=p;pc_rhs=e;pc_guard=None}->letir=ir_of_patternenvpin(ir,p.ppat_loc,e)|{pc_guard=Somee;_}->erre.pexp_loc"'when' guards are not supported")casesinletcompiled=Sedlex.compile_ir(Array.of_list(List.map(fun(ir,_,_)->ir)cases_with_ir))in(* map_rhs is called after compile_ir so that nested match%sedlex blocks
(which call compile_ir, resetting tags) cannot corrupt the outer
tag counter. *)letcases=List.mapi(funi(_,_,e)->letaction=gen_binding_code(sndlexbuf)(Array.getcompiled.bindingsi)(map_rhse)in((),action))cases_with_irinletcompiled_basic:Sedlex.compiled={dfa=compiled.dfa;num_tags=compiled.num_tags}in(gen_definitionlexbufcompiled_basiccaseserror,compiled.dfa)lethandle_sedlex_matchmatch_expr=handle_sedlex_match_~env:builtin_regexps~map_rhs:Fun.idmatch_exprletprevious=ref[]letregexps=ref[]letshould_set_cookies=reffalse(* The ppxlib AST mapper. It carries an [env] of named regexps (built-in
Unicode categories + user [let%sedlex.regexp] definitions). The [toplevel]
flag distinguishes the outermost structure (where partition/table
definitions are emitted) from nested modules. *)letmapper=object(this)inheritAst_traverse.mapassupervalenv=builtin_regexpsmethoddefine_regexpnameir={<env=StringMap.addnameirenv>}methodeval_regexp_expre=matchewith(* [%sedlex.regexp? <pattern>] *)|[%expr[%sedlex.regexp?[%p?p]]]->letir=ir_of_patternenvpinifnot(Ir.SSet.is_empty(Ir.capture_namesir))thenerrp.ppat_loc"'as' bindings are not allowed in regexp definitions";Someir(* let <name> = [%sedlex.regexp? <pattern>] in <body> *)|[%exprlet[%p?{ppat_desc=Ppat_var{txt=name;_};_}]=[%sedlex.regexp?[%p?p]]in[%e?body]]->letir=ir_of_patternenvpinifnot(Ir.SSet.is_empty(Ir.capture_namesir))thenerrp.ppat_loc"'as' bindings are not allowed in regexp definitions";(this#define_regexpnameir)#eval_regexp_exprbody|_->Nonemethod!expressione=matchewith(* match%sedlex <lexbuf> with ... *)|[%expr[%sedlex[%e?{pexp_desc=Pexp_match_;_}asmatch_expr]]]->fst(handle_sedlex_match_~env~map_rhs:this#expressionmatch_expr)(* let <name> = <rhs> in <body> — intercept when <rhs> is a regexp *)|[%exprlet[%p?{ppat_desc=Ppat_var{txt=name;_};_}]=[%e?rhs]in[%e?_body]]->(matchthis#eval_regexp_exprrhswith|Somer->(this#define_regexpnamer)#expression_body|None->super#expressione)(* [%sedlex <not-a-match>] — error *)|[%expr[%sedlex[%e?_]]]->erre.pexp_loc"the %%sedlex extension is only recognized on match expressions"|_->super#expressionevaltoplevel=truemethodstructure_with_regexpsl=letmapper=refthisinletregexps=ref[]inletl=List.concat(List.map(function(* let <name> = <e> — intercept top-level regexp definitions *)|[%strilet[%p?{ppat_desc=Ppat_var{txt=name;_};_}]=[%e?e]]asi->(match!mapper#eval_regexp_exprewith|Somer->regexps:=i::!regexps;mapper:=!mapper#define_regexpnamer;[]|None->[!mapper#structure_itemi])|i->[!mapper#structure_itemi])l)in(l,List.rev!regexps)method!structurel=iftoplevelthen(letsub={<toplevel=false>}inletl,regexps'=sub#structure_with_regexps(!previous@l)inletparts=List.mappartition(get_partitions())inlettables=List.maptable(get_tables())inregexps:=regexps';should_set_cookies:=true;tables@parts@l)elsefst(this#structure_with_regexpsl)endletmap_expressionexpr=mapper#expressionexpr(* ppxlib cookie handlers: regexp definitions survive across compilation
units by round-tripping through a ppxlib cookie named "sedlex.regexps". *)letpre_handlercookies=previous:=matchDriver.Cookies.getcookies"sedlex.regexps"Ast_pattern.__with|Some{pexp_desc=Pexp_extension(_,PStrl);_}->l|Some_->assertfalse|None->[]letpost_handlercookies=if!should_set_cookiesthen(letloc=default_locinDriver.Cookies.setcookies"sedlex.regexps"(pexp_extension~loc({loc;txt="regexps"},PStr!regexps)))(* We register via ~impl:mapper#structure rather than ~extensions so that
partition and table definitions can be inserted at the top level. *)(* let extensions =
[
Extension.declare "sedlex" Extension.Context.expression
Ast_pattern.(single_expr_payload __)
(fun ~loc:_ ~path:_ expr -> mapper#expression expr);
] *)let()=Driver.Cookies.add_handlerpre_handler;Driver.Cookies.add_post_handlerpost_handler;Driver.register_transformation"sedlex"~impl:mapper#structure