123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887(* The parser of the engine. This is the port of parse.go of the ecmaregex package of the Go module. *)moduleUcd=Corvus_json_schema_ucdtypekind=|Empty|Char|Set|Cat|Alt|Group|Repeat|Bol|Eol|Line_start(** [^] inside a group with the m modifier. *)|Line_end(** [$] inside a group with the m modifier. *)|Word_boundary|Not_word_boundary|Look|Backref(** The max of a quantifier with no upper bound. *)letunbounded=-1(** Caps the bounds of a counted quantifier. No text that fits in memory reaches a larger count. It is the largest
32-bit integer, as in the Go source, and an [int] holds it. *)letmax_count=0x7FFFFFFF(* The limits on the size of one pattern. A pattern may come from outside, so reading and compiling one must take
bounded time and memory whatever it holds. A pattern over a limit is refused with the error Too_large, which is not the error
of a pattern that is not valid. They are the limits of the Go package, counted the same way, so that the same
patterns are refused. *)(** The longest pattern that is read, in bytes of UTF-8. *)letmax_pattern_length=1lsl20(** Caps the depth of groups, which bounds the recursion of the parser and of the compilers. *)letmax_nesting=1000(** The largest size a pattern may have, in the bytes the parser counts for it: [node_size] for each node of its syntax
tree, and [range_size] for each range of a class as its members are read, for each range a case-insensitive group or
a negated property adds, for each alternative that encloses a named group and for each group that shares its name.
The tree, the program compiled from it and the time to make both are proportional to that size, so it bounds them
all. *)letmax_size=1lsl20letnode_size=32letrange_size=8(* The names of the limits. *)letlimit_length="length"letlimit_nesting="nesting"letlimit_size="size"exceptionOver_limitofstring*int(** A pattern over one of the limits of size: the name of the limit and its value. The pattern was not read to its end,
so this says nothing of whether it is valid. *)typeerror=|Invalidofstring(** The pattern is not valid, with the reason. *)|Too_largeofstring*int(** The pattern is over a limit of size: the name of the limit and its value. *)(** What the engine says of an error. For a pattern that is too large it is the wording of the Go package. *)leterror_message=function|Invalidmessage->message|Too_large(limit,value)->iflimit=limit_lengththenPrintf.sprintf"the pattern is longer than %d bytes"valueelseiflimit=limit_nestingthenPrintf.sprintf"the groups of the pattern nest more than %d deep"valueelsePrintf.sprintf"the pattern has a size of more than %d bytes when it is read"valuetypenode={kind:kind;r:int;(** The code point of a [Char]. *)set:Charset.t;(** The class of a [Set]. *)subs:nodearray;(** The children of [Cat] and [Alt], and the one child of [Group], [Repeat] and [Look]. *)min:int;(** [min], [max] and [lazy_] describe a [Repeat]. *)max:int;lazy_:bool;group:int;(** The number of the capturing group of a [Group], counted from 1. *)mutablegroups:intarray;(** The groups a [Backref] names. A name can belong to several groups in separate alternatives, of which at most
one has taken part at any time. *)name:string;(** The group name of a named backreference until it is resolved. *)fold:bool;(** Marks a [Backref], [Word_boundary] or [Not_word_boundary] inside a case-insensitive group. *)behind:bool;(** [behind] and [negative] describe a [Look]. *)negative:bool;}(** One element of the syntax tree of a pattern. *)letempty_node={kind=Empty;r=0;set=Charset.none;subs=[||];min=0;max=0;lazy_=false;group=0;groups=[||];name="";fold=false;behind=false;negative=false;}typebranch={disjunction:int;mutablealternative:int}(** One alternative of one disjunction. *)typenamed_group={number:int;group_path:(int*int)array}(** A capturing group with a name, and the alternatives that enclose it, outermost first. *)typeparser={p:intarray;(** The pattern as code points with the u flag, and as UTF-16 code units without it. *)mutablei:int;unicode:bool;mutabledepth:int;mutablegroup_count:int;(** [group_count] and [has_names] come from a scan of the whole pattern before parsing, because the meaning of
[\1] and [\k] depends on groups that may follow them. *)mutablehas_names:bool;mutablegroups:int;names:(string,named_grouplist)Hashtbl.t;mutablenamed_refs:nodelist;mutablepath:branchlist;(** The alternatives that enclose the current position, innermost first. *)mutabledisjunctions:int;mutableicase:bool;(** [icase], [multiline] and [dot_all] are the modifiers in force, which only a group such as [(?i:...)] changes.
*)mutablemultiline:bool;mutabledot_all:bool;mutableneeds_backtracking:bool;(** Records that the pattern has a construct only the backtracking matcher can run. *)mutablesize:int;(** The size of what has been read so far (see {!max_size}). *)properties:(string,Charset.t)Hashtbl.t;(** The set of each property expression the pattern has named, so that a property written many times is one set.
The Go package keeps these for the process. This library keeps no table for the process, so the table is the
parser's. *)}(** A parser reads a pattern by recursive descent. With [unicode] set it applies the grammar of the u flag. Without it,
it applies the Annex B grammar of patterns with no flag. *)exceptionParse_errorofstring*int(** The reason a pattern is not valid, and the offset in characters at which it was found. *)letfailpmessage=raise(Parse_error(message,p.i))(* Adds to the size of the pattern, and stops the parser when the size is over its limit. *)letchargepbytes=p.size<-p.size+bytes;ifp.size>max_sizethenraise(Over_limit(limit_size,max_size))(* Returns the set with every character equivalent to one of its members, and charges the pattern for the work and
for the set: a byte for each character with a case variant that the set holds, and its ranges. *)letfoldpset=letclosed,variants=Charset.fold_closure_countedsetp.unicodeinchargep(variants+(range_size*Array.lengthclosed.ranges/2));closed(* Counts the capturing groups of the pattern and notes whether any is named. *)letscan_groupsp=letin_class=reffalseinlets=p.pinletn=Array.lengthsinletk=ref0inwhile!k<ndoletc=s.(!k)inifc=0x5C(* \ *)thenincrkelseifc=0x5B(* [ *)thenin_class:=trueelseifc=0x5D(* ] *)thenin_class:=falseelseifc=0x28(* ( *)&¬!in_classthenif!k+1>=n||s.(!k+1)<>0x3F(* ? *)thenp.group_count<-p.group_count+1elseif!k+3<n&&s.(!k+2)=0x3C(* < *)&&s.(!k+3)<>0x3D(* = *)&&s.(!k+3)<>0x21(* ! *)thenbeginp.group_count<-p.group_count+1;p.has_names<-trueend;incrkdoneletmorep=p.i<Array.lengthp.pletpeekp=ifp.i<Array.lengthp.pthenp.p.(p.i)else-1leteatpc=ifp.i<Array.lengthp.p&&p.p.(p.i)=Char.codecthenbeginp.i<-p.i+1;trueendelsefalse(* The character as a [char] for a match over the ASCII characters of the syntax, or NUL for any other. *)letasciic=ifc>0&&c<128thenChar.chrcelse'\000'(* Returns the node for one literal character. Inside a case-insensitive group that is the class of the characters
equivalent to it. *)letchar_nodepc=letplain={empty_nodewithkind=Char;r=c}inifp.icasethenletset=Charset.fold_closure(Charset.make[|c;c|]false)p.unicodeinifArray.lengthset.ranges<>2||set.ranges.(0)<>set.ranges.(1)then{empty_nodewithkind=Set;set}elseplainelseplain(* Returns the node for a class. Inside a case-insensitive group the class also takes every character equivalent to
one of its members. *)letset_nodepset={empty_nodewithkind=Set;set=(ifp.icasethenfoldpsetelseset)}letis_digitc=c>=0x30&&c<=0x39(* Reports whether the text at the current '{' reads as {n}, {n,} or {n,m}. *)letlooks_like_quantifierp=lets=p.pinletn=Array.lengthsinletk=ref(p.i+1)inletstart=!kinwhile!k<n&&is_digits.(!k)doincrkdone;if!k=start||!k>=nthenfalseelseifs.(!k)=0x7D(* } *)thentrueelseifs.(!k)<>0x2C(* , *)thenfalseelsebeginincrk;while!k<n&&is_digits.(!k)doincrkdone;!k<n&&s.(!k)=0x7Dend(* Reads a run of decimal digits, saturating at max_count. *)letnumberp=letstart=p.iinletv=ref0inwhilep.i<Array.lengthp.p&&is_digitp.p.(p.i)doif!v<max_countthenbeginv:=(!v*10)+(p.p.(p.i)-0x30);if!v>max_countthenv:=max_countend;p.i<-p.i+1done;ifstart=p.ithenfailp"expected a number";!v(* Reports whether the first of two runs of decimal digits of the pattern, from a up to a_end and from b up to b_end,
is the greater number. The runs are compared as written, because a count has no limit of size in the grammar and
number stops at max_count. *)letgreater_as_writtenpaa_endbb_end=letrecskip_zerosklimit=ifk<limit&&p.p.(k)=0x30thenskip_zeros(k+1)limitelsekinleta=skip_zerosaa_endandb=skip_zerosbb_endinifa_end-a<>b_end-bthena_end-a>b_end-belseletreccomparek=k<a_end-a&&(p.p.(a+k)>p.p.(b+k)||(p.p.(a+k)=p.p.(b+k)&&compare(k+1)))incompare0(* Reads a quantifier, if one is there, as its min, its max and whether it is lazy. *)letquantifierp=letbounds=matchascii(peekp)with|'*'->p.i<-p.i+1;Some(0,unbounded)|'+'->p.i<-p.i+1;Some(1,unbounded)|'?'->p.i<-p.i+1;Some(0,1)|'{'whenlooks_like_quantifierp->p.i<-p.i+1;letfirst=p.iinletmin=numberpinletfirst_end=p.iinletmax=ifnot(eatp',')thenminelseifpeekp=0x7Dthenunboundedelsebeginletsecond=p.iinletmax=numberpin(* Both counts stop at max_count, so the numbers are compared as they are written. *)ifgreater_as_writtenpfirstfirst_endsecondp.ithenfailp"numbers out of order in quantifier";maxendinifnot(eatp'}')thenfailp"malformed quantifier";Some(min,max)|_->Noneinmatchboundswith|None->None|Some(min,max)->letlazy_=eatp'?'inSome(min,max,lazy_)letclosep=ifnot(eatp')')thenfailp"unterminated group"(* Reports whether two positions lie in different alternatives of some disjunction. Each is the alternatives that
enclose it, outermost first. *)letseparate_alternativesab=letrecwalki=ifi<Array.lengtha&&i<Array.lengthb&&fsta.(i)=fstb.(i)thensnda.(i)<>sndb.(i)||walk(i+1)elsefalseinwalk0(* The alternatives that enclose the current position, outermost first. *)letcurrent_pathp=Array.of_list(List.rev_map(funbranch->(branch.disjunction,branch.alternative))p.path)lethex_valuec=ifc>=0x30&&c<=0x39thenc-0x30elseifc>=0x61&&c<=0x66thenc-0x61+10elseifc>=0x41&&c<=0x46thenc-0x41+10else-1(* Reads exactly n hexadecimal digits. It returns -1, consuming nothing, when they are not there. *)lethexpn=ifp.i+n>Array.lengthp.pthen-1elseletrecreadkv=ifk=nthenbeginp.i<-p.i+n;vendelseletd=hex_valuep.p.(p.i+k)inifd<0then-1elseread(k+1)((v*16)+d)inread00(* Reads what follows a \u and returns the code point, or -1, consuming nothing, when it is not a well-formed escape.
With unicode_mode set, as it is with the u flag and always in a group name, it accepts the \u{...} form and reads a
surrogate pair written as two escapes as one code point. Without it each escape is one code unit. *)letunicode_escapepunicode_mode=letstart=p.iinifunicode_mode&&eatp'{'thenbeginletdigits=p.iinletv=ref0inwhilemorep&&hex_value(peekp)>=0doif!v<=Charset.max_runethenv:=(!v*16)+hex_value(peekp);p.i<-p.i+1done;ifdigits=p.i||(not(eatp'}'))||!v>Charset.max_runethenbeginp.i<-start;-1endelse!vendelseletu=hexp4inifu<0then-1elseifunicode_mode&&u>=0xD800&&u<=0xDBFF&&p.i+6<=Array.lengthp.p&&p.p.(p.i)=0x5C(* \ *)&&p.p.(p.i+1)=0x75(* u *)thenbeginletsave=p.iinp.i<-p.i+2;letlow=hexp4iniflow>=0xDC00&&low<=0xDFFFthen0x10000+((u-0xD800)lsl10)+(low-0xDC00)elsebeginp.i<-save;uendendelseuletid_start=matchUcd.binary"ID_Start"withSometable->table|None->assertfalseletid_continue=matchUcd.binary"ID_Continue"withSometable->table|None->assertfalse(* Reads a group name up to and including its '>'. A character of the name may be written as a \u escape. *)letgroup_namep=letname=Buffer.create16inletcount=ref0inwhilenot(eatp'>')doifnot(morep)thenfailp"invalid group name";letc=peekpinp.i<-p.i+1;letc=ifc=0x5C(* \ *)thenbeginifnot(eatp'u')thenfailp"invalid group name";letc=unicode_escapeptrueinifc<0thenfailp"invalid group name";cendelseifc>=0xD800&&c<=0xDBFF&&peekp>=0xDC00&&peekp<=0xDFFFthenbegin(* Without the u flag the pattern is code units, and a name still takes a surrogate pair as one character. *)letlow=peekpinp.i<-p.i+1;0x10000+((c-0xD800)lsl10)+(low-0xDC00)endelsecinif!count=0thenbeginifnot(c=0x24(* $ *)||c=0x5F(* _ *)||Ucd.containsid_startc)thenfailp"invalid group name"endelseifnot(c=0x24||c=0x200C||c=0x200D||Ucd.containsid_continuec)thenfailp"invalid group name";Utf8.add_runenamec;incrcountdone;if!count=0thenfailp"invalid group name";Buffer.contentsname(* Reads an Annex B octal escape of up to three digits with a value below 256. A digit that cannot start one (8 or 9)
stands for itself. *)letlegacy_octalp=letv=ref0andk=ref0inwhile!k<3&&morep&&peekp>=0x30&&peekp<=0x37&&(!v*8)+(peekp-0x30)<=0o377dov:=(!v*8)+(peekp-0x30);p.i<-p.i+1;incrkdone;if!k=0thenbeginletd=peekpinp.i<-p.i+1;dendelse!v(* Reads a class escape such as \d or \p{...} whose letter is c. It returns [None], consuming nothing, when c does not
start one. *)letclass_escapepc=lettakeset=p.i<-p.i+1;Somesetinmatchasciicwith|'d'->takeCharset.digit_set|'D'->takeCharset.not_digit_set|'w'->take(ifp.unicode&&p.icasethenCharset.fold_word_setelseCharset.word_set)|'W'->take(ifp.unicode&&p.icasethenCharset.not_fold_word_setelseCharset.not_word_set)|'s'->takeCharset.space_set|'S'->takeCharset.not_space_set|('p'|'P')whenp.unicode->(* Without the u flag \p is the letter p. *)p.i<-p.i+1;ifnot(eatp'{')thenfailp"invalid property name";letstart=p.iinwhilemorep&&peekp<>0x7D(* } *)dop.i<-p.i+1done;letexpr=Buffer.create32infork=starttop.i-1doUtf8.add_runeexprp.p.(k)done;ifnot(eatp'}')thenfailp"invalid property name";letexpr=Buffer.contentsexprinletset=matchHashtbl.find_optp.propertiesexprwith|Someset->set|None->(matchCharset.property_setexprwith|None->failp"invalid property name"|Someset->Hashtbl.replacep.propertiesexprset;set)inifc=0x50(* P *)thenbeginchargep(range_size*Array.lengthset.ranges/2);Some(Charset.makeset.rangestrue)endelseSomeset|_->Noneletis_ascii_letterc=(c>=0x61&&c<=0x7A)||(c>=0x41&&c<=0x5A)(* Reports whether c is one of the characters the u flag lets an identity escape name. *)letsyntax_characterc=matchasciicwith|'^'|'$'|'\\'|'.'|'*'|'+'|'?'|'('|')'|'['|']'|'{'|'}'|'|'|'/'->true|_->false(* Reads the escape after a backslash, in or out of a class, and returns the code point it names. *)letcharacter_escapepin_class=letc=peekpinp.i<-p.i+1;matchasciicwith|'f'->0x0C|'n'->0x0A|'r'->0x0D|'t'->0x09|'v'->0x0B|'c'->letl=peekpinifis_ascii_letterlthenbeginp.i<-p.i+1;lmod32endelseif(notp.unicode)&&in_class&&(is_digitl||l=0x5F(* _ *))thenbeginp.i<-p.i+1;lmod32endelsebeginifp.unicodethenfailp"invalid control escape";(* Annex B reads a \c that is not a control escape as a backslash followed by the letter c. *)p.i<-p.i-1;0x5Cend|'0'->ifmorep&&is_digit(peekp)thenbeginifp.unicodethenfailp"invalid decimal escape";p.i<-p.i-1;legacy_octalpendelse0|'x'->leth=hexp2inifh<0thenbeginifp.unicodethenfailp"invalid \\x escape";0x78endelseh|'u'->letu=unicode_escapepp.unicodeinifu<0thenbeginifp.unicodethenfailp"invalid \\u escape";0x75endelseu|_->ifc>=0x31&&c<=0x39&&(notp.unicode)&&in_classthenbeginp.i<-p.i-1;legacy_octalpendelsebegin(* Identity escapes. The u flag allows only the syntax characters and '/', and '-' in a class. Annex B allows
any character, except k in a pattern with a named group. Outside a class atom_escape reads \k before this
is reached, so here the \k of such a pattern is in a class, where it is not a reference either. *)ifp.unicode&&(not(syntax_characterc))&¬(in_class&&c=0x2D(* - *))thenfailp"invalid escape";ifc=0x6B(* k *)&&p.has_namesthenfailp"invalid escape";cend(* One member of a class: a code point, or a set for a class escape. *)typeclass_atom=Pointofint|ClassofCharset.t(* Reads one member of a class. *)letclass_atomp=letc=peekpinp.i<-p.i+1;ifc<>0x5C(* \ *)thenPointcelsebeginifnot(morep)thenfailp"\\ at end of pattern";lete=peekpinife=0x62(* b *)thenbeginp.i<-p.i+1;Point0x08endelsematchclass_escapepewithSomeset->Classset|None->Point(character_escapeptrue)endletclass_atom_pairs=functionPointc->[|c;c|]|Classset->set.Charset.ranges(* Reads a character class from its '[' to its ']'. *)letclass_p=p.i<-p.i+1;letnegated=eatp'^'inletpairs=ref[]in(* The ranges read so far, and those of them the size of the pattern has been charged for. *)letcount=ref0andcharged=ref0inletaddmore_pairs=pairs:=more_pairs::!pairs;count:=!count+(Array.lengthmore_pairs/2)inletcharge_read()=chargep(range_size*(!count-!charged));charged:=!countinletfinished=reffalseinwhilenot!finisheddoifnot(morep)thenfailp"unterminated character class";ifeatp']'thenfinished:=trueelsebegincharge_read();letfirst=class_atompinifpeekp=0x2D(* - *)&&p.i+1<Array.lengthp.p&&p.p.(p.i+1)<>0x5D(* ] *)thenbeginp.i<-p.i+1;letsecond=class_atompinmatch(first,second)with|Pointfirst,Pointsecond->ifsecond<firstthenfailp"range out of order in character class";add[|first;second|]|_->ifp.unicodethenfailp"invalid character class range";(* Annex B reads the '-' as itself when a class escape is at either end. *)add(class_atom_pairsfirst);add[|0x2D;0x2D|];add(class_atom_pairssecond)endelseadd(class_atom_pairsfirst)enddone;(* Inside a case-insensitive group a character matches the class when it is equivalent to a member, and a negated
class excludes exactly those characters. *)charge_read();letset=Charset.make(Array.concat(List.rev!pairs))falseinletset=ifp.icasethenfoldpsetelsesetinletset=ifnegatedthenCharset.makeset.rangestrueelsesetin{empty_nodewithkind=Set;set}letrecdisjunctionp=p.depth<-p.depth+1;ifp.depth>max_nestingthenraise(Over_limit(limit_nesting,max_nesting));p.disjunctions<-p.disjunctions+1;letbranch={disjunction=p.disjunctions;alternative=0}inletouter=p.pathinp.path<-branch::outer;letfirst=alternativepinletresult=ifpeekp<>0x7C(* | *)thenfirstelsebeginletsubs=ref[first]inwhileeatp'|'dochargepnode_size;branch.alternative<-branch.alternative+1;subs:=alternativep::!subsdone;{empty_nodewithkind=Alt;subs=Array.of_list(List.rev!subs)}endinp.path<-outer;p.depth<-p.depth-1;resultandalternativep=letterms=ref[]inwhilemorep&&peekp<>0x7C(* | *)&&peekp<>0x29(* ) *)doterms:=termp::!termsdone;match!termswith|[]->{empty_nodewithkind=Empty}|[one]->one|terms->{empty_nodewithkind=Cat;subs=Array.of_list(List.revterms)}andtermp=(* The atom, and the repetition or the group around it. *)chargep(2*node_size);letc=peekpinletatom,quantifiable=matchasciicwith|'^'->p.i<-p.i+1;ifp.multilinethenbeginp.needs_backtracking<-true;({empty_nodewithkind=Line_start},false)endelse({empty_nodewithkind=Bol},false)|'$'->p.i<-p.i+1;ifp.multilinethenbeginp.needs_backtracking<-true;({empty_nodewithkind=Line_end},false)endelse({empty_nodewithkind=Eol},false)|'('->groupp|'.'->p.i<-p.i+1;({empty_nodewithkind=Set;set=(ifp.dot_allthenCharset.any_setelseCharset.dot_set)},true)|'['->(class_p,true)|'\\'->atom_escapep|'*'|'+'|'?'->failp"nothing to repeat"|'{'->ifp.unicode||looks_like_quantifierpthenfailp"nothing to repeat";p.i<-p.i+1;(char_nodep0x7B,true)|']'|'}'->ifp.unicodethenfailp"lone bracket";p.i<-p.i+1;(char_nodepc,true)|_->p.i<-p.i+1;(char_nodepc,true)inmatchquantifierpwith|None->atom|Some(min,max,lazy_)->ifnotquantifiablethenfailp"nothing to repeat";{empty_nodewithkind=Repeat;subs=[|atom|];min;max;lazy_}(* Reads a group or a lookaround. It also reports whether a quantifier may follow. *)andgroupp=p.i<-p.i+1;ifnot(eatp'?')then(capturep"",true)elseifeatp':'thenbeginletbody=disjunctionpinclosep;({empty_nodewithkind=Group;subs=[|body|]},true)endelseletc=peekpinifc=0x69(* i *)||c=0x6D(* m *)||c=0x73(* s *)||c=0x2D(* - *)then(modifier_groupp,true)elseifeatp'='||eatp'!'thenbeginletnegative=p.p.(p.i-1)=0x21(* ! *)inletbody=disjunctionpinclosep;p.needs_backtracking<-true;(* Annex B lets a quantifier follow a lookahead. The u flag does not. *)({empty_nodewithkind=Look;subs=[|body|];negative},notp.unicode)endelseifeatp'<'thenifeatp'='||eatp'!'thenbeginletnegative=p.p.(p.i-1)=0x21inletbody=disjunctionpinclosep;p.needs_backtracking<-true;({empty_nodewithkind=Look;subs=[|body|];negative;behind=true},false)endelsebeginletname=group_namepin(* Two groups may share a name only when they are in separate alternatives of one disjunction, so that no
match can go through both. *)lethere=current_pathpinList.iter(funother->ifnot(separate_alternativesother.group_pathhere)thenfailp"duplicate group name")(matchHashtbl.find_optp.namesnamewithSomenamed->named|None->[]);(capturepname,true)endelsefailp"invalid group"(* Reads a group that turns modifiers on or off for its body, such as (?i:...) or (?s-i:...). The position is after
the "(?". *)andmodifier_groupp=leticase=p.icaseandmultiline=p.multilineanddot_all=p.dot_allinletseen=[|false;false;false|]inletremoving=reffalseandcount=ref0inwhilenot(eatp':')doletc=peekpinp.i<-p.i+1;ifc=0x2D(* - *)thenbeginif!removingthenfailp"invalid group modifier";removing:=trueendelsebeginletflag=matchasciicwith|'i'->p.icase<-not!removing;0|'m'->p.multiline<-not!removing;1|'s'->p.dot_all<-not!removing;2|_->failp"invalid group modifier"inifseen.(flag)thenfailp"repeated group modifier";seen.(flag)<-true;incrcountenddone;if!count=0thenfailp"invalid group modifier";letbody=disjunctionpinclosep;p.icase<-icase;p.multiline<-multiline;p.dot_all<-dot_all;{empty_nodewithkind=Group;subs=[|body|]}andcapturepname=p.groups<-p.groups+1;letnumber=p.groupsinifname<>""thenbeginletnamed=matchHashtbl.find_optp.namesnamewithSomenamed->named|None->[]in(* The path is kept, and every group of the same name is compared with it. *)chargep(range_size*(List.lengthp.path+List.lengthnamed));Hashtbl.replacep.namesname(named@[{number;group_path=current_pathp}])end;letbody=disjunctionpinclosep;{empty_nodewithkind=Group;group=number;subs=[|body|]}(* Reads an escape outside a class. It also reports whether a quantifier may follow. *)andatom_escapep=p.i<-p.i+1;ifnot(morep)thenfailp"\\ at end of pattern";letc=peekpinifc=0x62(* b *)||c=0x42(* B *)thenbeginp.i<-p.i+1;(* With the u flag, a case-insensitive word boundary counts two more characters as word characters, which the
automaton does not. *)letfold=p.unicode&&p.icaseiniffoldthenp.needs_backtracking<-true;({empty_nodewithkind=(ifc=0x42thenNot_word_boundaryelseWord_boundary);fold},false)endelseifc=0x6B(* k *)then(* Annex B reads \k as the letter k in a pattern with no named group. *)ifp.unicode||p.has_namesthenbeginp.i<-p.i+1;ifnot(eatp'<')thenfailp"invalid named reference";letreference={empty_nodewithkind=Backref;name=group_namep;fold=p.icase}inp.named_refs<-reference::p.named_refs;p.needs_backtracking<-true;(reference,true)endelsebeginp.i<-p.i+1;(char_nodep0x6B,true)endelseifc>=0x31&&c<=0x39thenbeginletstart=p.iinletn=numberpinifn<=p.group_countthenbeginp.needs_backtracking<-true;({empty_nodewithkind=Backref;groups=[|n|];fold=p.icase},true)endelsebeginifp.unicodethenfailp"reference to a group that does not exist";(* Annex B reads it as an octal escape, or as the digit itself. *)p.i<-start;(char_nodep(legacy_octalp),true)endendelsematchclass_escapepcwith|Someset->(set_nodepset,true)|None->(char_nodep(character_escapepfalse),true)(* Writes the pattern for a message: in quotes, with the characters that would break a line or the quotes escaped. *)letquotepattern=letout=Buffer.create(String.lengthpattern+2)inBuffer.add_charout'"';String.iter(func->ifc='"'||c='\\'thenbeginBuffer.add_charout'\\';Buffer.add_charoutcendelseifChar.codec<0x20||Char.codec=0x7FthenBuffer.add_stringout(Printf.sprintf"\\x%02x"(Char.codec))elseBuffer.add_charoutc)pattern;Buffer.add_charout'"';Buffer.contentsout(** [parse pattern unicode] reads the pattern with the grammar of the u flag, or with the Annex B grammar. It returns
the root of the tree and the parser, or [Invalid] with the reason the pattern is not valid under that grammar, or
[Too_large] for a pattern over a limit of size, which it stops reading at the limit.
Without the u flag ECMA-262 reads a pattern, and matches a text, a UTF-16 code unit at a time, so the pattern is
read as code units. A character beyond the Basic Multilingual Plane is then two atoms, a quantifier after it repeats
the second, and in a class it is two members, so a range between two such characters runs from a low surrogate to a
high one and is an error. *)letparse_withinpatternunicode=letp={p=Utf8.runespattern(notunicode);i=0;unicode;depth=0;group_count=0;has_names=false;groups=0;names=Hashtbl.create8;named_refs=[];path=[];disjunctions=0;icase=false;multiline=false;dot_all=false;needs_backtracking=false;size=0;properties=Hashtbl.create8;}intryscan_groupsp;letroot=disjunctionpinifp.i<Array.lengthp.pthenifp.p.(p.i)=0x29(* ) *)thenfailp"unmatched ')'"elsefailp"unexpected character";List.iter(funreference->matchHashtbl.find_optp.namesreference.namewith|None->failp"reference to an unknown group name"|Somenamed->reference.groups<-Array.of_list(List.map(fung->g.number)named))p.named_refs;Ok(root,p)with|Parse_error(message,position)->Error(Invalid(Printf.sprintf"ecmaregex: %s at offset %d in %s"messageposition(quotepattern)))|Over_limit(limit,value)->Error(Too_large(limit,value))letparsepatternunicode=ifString.lengthpattern>max_pattern_lengththenError(Too_large(limit_length,max_pattern_length))elseparse_withinpatternunicode