123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357(* The sets of code points of the engine, and case-insensitive matching. This is the port of charset.go and fold.go of
the ecmaregex package of the Go module.
Two things differ from the Go source, as they do in the Object Pascal port.
Go keeps the sets already built for property expressions in a sync.Map, so that a property many patterns name is
built once. Here each pattern builds the property sets it names, so there is no shared state to guard. That costs a
pattern some microseconds when it is compiled and nothing when it matches.
Go builds each of the two tables of case-insensitive matching on first use behind a sync.Once. Here a thread that
finds no table builds one and publishes it through an [Atomic]. Two threads or domains that race both build the
table, which is the same either way, and each uses the one it reads. *)moduleUcd=Corvus_json_schema_ucd(** The last Unicode code point. *)letmax_rune=Ucd.max_runetypet={ranges:intarray;ascii:string}(** An immutable set of code points. The ranges are sorted, inclusive pairs that neither overlap nor touch. [ascii]
repeats the membership of the code points below 128 as 128 bits in 16 bytes, so that the common case is one bit
test. The Go source holds them in two words of 64 bits. An [int] has 63, so they are bytes here. *)(** Reports whether the 128 bits hold the code point [c], which is below 128. *)letascii_hasasciic=(Char.code(String.getascii(clsr3))lsr(cland7))land1<>0letcompare_int(a:int)(b:int)=compareab(** Builds a set from inclusive pairs in any order, which may overlap. With [negate] it builds the complement. *)letmakepairsnegate=letn=Array.lengthpairs/2in(* A range is one number here, its first code point above its last, so that sorting the numbers sorts the ranges. A
code point has 21 bits, so the number fits an int of 63 bits. *)letorder=Array.initn(funi->(pairs.(2*i)lsl32)lorpairs.((2*i)+1))inArray.sortcompare_intorder;letmerged=Array.make(2*n)0inletm=ref0inArray.iter(funpair->letlo=pairlsr32andhi=pairland0xFFFFFFFFinif!m>0&&lo<=merged.(!m-1)+1thenbeginifhi>merged.(!m-1)thenmerged.(!m-1)<-hiendelsebeginmerged.(!m)<-lo;merged.(!m+1)<-hi;m:=!m+2end)order;letmerged=Array.submerged0!minletranges=ifnotnegatethenmergedelsebeginletinverse=Array.make(Array.lengthmerged+2)0inletk=ref0andnext=ref0andi=ref0inwhile!i<Array.lengthmergeddoifmerged.(!i)>!nextthenbegininverse.(!k)<-!next;inverse.(!k+1)<-merged.(!i)-1;k:=!k+2end;next:=merged.(!i+1)+1;i:=!i+2done;if!next<=max_runethenbegininverse.(!k)<-!next;inverse.(!k+1)<-max_rune;k:=!k+2end;Array.subinverse0!kendinletascii=Bytes.make16'\000'inleti=ref0inwhile!i<Array.lengthranges&&ranges.(!i)<128doletc=refranges.(!i)inwhile!c<=ranges.(!i+1)&&!c<128doBytes.setascii(!clsr3)(Char.chr(Char.code(Bytes.getascii(!clsr3))lor(1lsl(!cland7))));incrcdone;i:=!i+2done;{ranges;ascii=Bytes.to_stringascii}letrecsearchrangesrlohi=iflo>=hithenfalseelseletmid=(lo+hi)lsr1inifr>ranges.((2*mid)+1)thensearchrangesr(mid+1)hielseifr<ranges.(2*mid)thensearchrangesrlomidelsetrue(** Reports whether the set holds the code point. *)letcontainssr=ifr<128thenr>=0&&ascii_hass.asciirelsesearchs.rangesr0(Array.lengths.ranges/2)(** Reports whether the set holds no code point. *)letis_emptys=Array.lengths.ranges=0(** The set of no code point. It also stands where a set is wanted and none applies. *)letnone=make[||]false(* The classes ECMA-262 defines. \d and \w are ASCII only, \s is WhiteSpace and LineTerminator, and . is everything
but the four line terminators. *)letdigit_pairs=[|0x30;0x39|]letword_pairs=[|0x30;0x39;0x41;0x5A;0x5F;0x5F;0x61;0x7A|]letspace_pairs=[|0x9;0xD;0x20;0x20;0xA0;0xA0;0x1680;0x1680;0x2000;0x200A;0x2028;0x2029;0x202F;0x202F;0x205F;0x205F;0x3000;0x3000;0xFEFF;0xFEFF;|][@@ocamlformat"disable"]letline_terminator_pairs=[|0x0A;0x0A;0x0D;0x0D;0x2028;0x2029|]letdot_set=makeline_terminator_pairstrueletany_set=make[|0;max_rune|]falseletdigit_set=makedigit_pairsfalseletnot_digit_set=makedigit_pairstrueletword_set=makeword_pairsfalseletnot_word_set=makeword_pairstrueletspace_set=makespace_pairsfalseletnot_space_set=makespace_pairstrue(** Reports whether a byte is an ECMA-262 word character. Every word character is ASCII, so a word boundary can be
decided from the bytes on either side of a position. *)letis_word_byteb=(b>=0x61&&b<=0x7A)||(b>=0x41&&b<=0x5A)||(b>=0x30&&b<=0x39)||b=0x5F(* The long names and the aliases of the General_Category values, each with its short name. *)letcategory_names=[|("Letter","L");("Lowercase_Letter","Ll");("Uppercase_Letter","Lu");("Titlecase_Letter","Lt");("Cased_Letter","LC");("Modifier_Letter","Lm");("Other_Letter","Lo");("Mark","M");("Combining_Mark","M");("Nonspacing_Mark","Mn");("Spacing_Mark","Mc");("Enclosing_Mark","Me");("Number","N");("Decimal_Number","Nd");("digit","Nd");("Letter_Number","Nl");("Other_Number","No");("Punctuation","P");("punct","P");("Connector_Punctuation","Pc");("Dash_Punctuation","Pd");("Open_Punctuation","Ps");("Close_Punctuation","Pe");("Initial_Punctuation","Pi");("Final_Punctuation","Pf");("Other_Punctuation","Po");("Symbol","S");("Math_Symbol","Sm");("Currency_Symbol","Sc");("Modifier_Symbol","Sk");("Other_Symbol","So");("Separator","Z");("Space_Separator","Zs");("Line_Separator","Zl");("Paragraph_Separator","Zp");("Other","C");("Control","Cc");("cntrl","Cc");("Format","Cf");("Surrogate","Cs");("Private_Use","Co");("Unassigned","Cn");("L","L");("Ll","Ll");("Lu","Lu");("Lt","Lt");("LC","LC");("Lm","Lm");("Lo","Lo");("M","M");("Mn","Mn");("Mc","Mc");("Me","Me");("N","N");("Nd","Nd");("Nl","Nl");("No","No");("P","P");("Pc","Pc");("Pd","Pd");("Ps","Ps");("Pe","Pe");("Pi","Pi");("Pf","Pf");("Po","Po");("S","S");("Sm","Sm");("Sc","Sc");("Sk","Sk");("So","So");("Z","Z");("Zs","Zs");("Zl","Zl");("Zp","Zp");("C","C");("Cc","Cc");("Cf","Cf");("Cs","Cs");("Co","Co");("Cn","Cn");|][@@ocamlformat"disable"](* The names and the aliases of the binary properties ECMA-262 lists, each with its canonical name. *)letbinary_names=[|("ASCII","ASCII");("ASCII_Hex_Digit","ASCII_Hex_Digit");("AHex","ASCII_Hex_Digit");("Alphabetic","Alphabetic");("Alpha","Alphabetic");("Any","Any");("Assigned","Assigned");("Bidi_Control","Bidi_Control");("Bidi_C","Bidi_Control");("Bidi_Mirrored","Bidi_Mirrored");("Bidi_M","Bidi_Mirrored");("Case_Ignorable","Case_Ignorable");("CI","Case_Ignorable");("Cased","Cased");("Changes_When_Casefolded","Changes_When_Casefolded");("CWCF","Changes_When_Casefolded");("Changes_When_Casemapped","Changes_When_Casemapped");("CWCM","Changes_When_Casemapped");("Changes_When_Lowercased","Changes_When_Lowercased");("CWL","Changes_When_Lowercased");("Changes_When_NFKC_Casefolded","Changes_When_NFKC_Casefolded");("CWKCF","Changes_When_NFKC_Casefolded");("Changes_When_Titlecased","Changes_When_Titlecased");("CWT","Changes_When_Titlecased");("Changes_When_Uppercased","Changes_When_Uppercased");("CWU","Changes_When_Uppercased");("Dash","Dash");("Default_Ignorable_Code_Point","Default_Ignorable_Code_Point");("DI","Default_Ignorable_Code_Point");("Deprecated","Deprecated");("Dep","Deprecated");("Diacritic","Diacritic");("Dia","Diacritic");("Emoji","Emoji");("Emoji_Component","Emoji_Component");("EComp","Emoji_Component");("Emoji_Modifier","Emoji_Modifier");("EMod","Emoji_Modifier");("Emoji_Modifier_Base","Emoji_Modifier_Base");("EBase","Emoji_Modifier_Base");("Emoji_Presentation","Emoji_Presentation");("EPres","Emoji_Presentation");("Extended_Pictographic","Extended_Pictographic");("ExtPict","Extended_Pictographic");("Extender","Extender");("Ext","Extender");("Grapheme_Base","Grapheme_Base");("Gr_Base","Grapheme_Base");("Grapheme_Extend","Grapheme_Extend");("Gr_Ext","Grapheme_Extend");("Hex_Digit","Hex_Digit");("Hex","Hex_Digit");("IDS_Binary_Operator","IDS_Binary_Operator");("IDSB","IDS_Binary_Operator");("IDS_Trinary_Operator","IDS_Trinary_Operator");("IDST","IDS_Trinary_Operator");("ID_Continue","ID_Continue");("IDC","ID_Continue");("ID_Start","ID_Start");("IDS","ID_Start");("Ideographic","Ideographic");("Ideo","Ideographic");("Join_Control","Join_Control");("Join_C","Join_Control");("Logical_Order_Exception","Logical_Order_Exception");("LOE","Logical_Order_Exception");("Lowercase","Lowercase");("Lower","Lowercase");("Math","Math");("Noncharacter_Code_Point","Noncharacter_Code_Point");("NChar","Noncharacter_Code_Point");("Pattern_Syntax","Pattern_Syntax");("Pat_Syn","Pattern_Syntax");("Pattern_White_Space","Pattern_White_Space");("Pat_WS","Pattern_White_Space");("Quotation_Mark","Quotation_Mark");("QMark","Quotation_Mark");("Radical","Radical");("Regional_Indicator","Regional_Indicator");("RI","Regional_Indicator");("Sentence_Terminal","Sentence_Terminal");("STerm","Sentence_Terminal");("Soft_Dotted","Soft_Dotted");("SD","Soft_Dotted");("Terminal_Punctuation","Terminal_Punctuation");("Term","Terminal_Punctuation");("Unified_Ideograph","Unified_Ideograph");("UIdeo","Unified_Ideograph");("Uppercase","Uppercase");("Upper","Uppercase");("Variation_Selector","Variation_Selector");("VS","Variation_Selector");("White_Space","White_Space");("space","White_Space");("XID_Continue","XID_Continue");("XIDC","XID_Continue");("XID_Start","XID_Start");("XIDS","XID_Start");|][@@ocamlformat"disable"](* The two lists as tables. Nothing writes to them once the module is initialised, so reading them needs no lock. *)lettable_ofpairs=lettable=Hashtbl.create(2*Array.lengthpairs)inArray.iter(fun(name,canonical)->Hashtbl.replacetablenamecanonical)pairs;tableletcategory_table:(string,string)Hashtbl.t=table_ofcategory_namesletbinary_table:(string,string)Hashtbl.t=table_ofbinary_names(* Returns the ranges of a table as inclusive pairs, or [None] for no table. *)lettable_pairs=functionNone->None|Sometable->Some(Ucd.rangestable)letcategory_pairsvalue=matchHashtbl.find_optcategory_tablevaluewithNone->None|Someshort->table_pairs(Ucd.categoryshort)(** Returns the ranges of a property expression, or [None] for a name or value that ECMA-262 does not define. Every
range comes from the tables of the ucd library, which hold one fixed version of Unicode. *)letproperty_pairsexpr=matchString.index_optexpr'='with|Somei->(letname=String.subexpr0iandvalue=String.subexpr(i+1)(String.lengthexpr-i-1)inmatchnamewith|"General_Category"|"gc"->category_pairsvalue|"Script"|"sc"->table_pairs(Ucd.scriptvalue)|"Script_Extensions"|"scx"->table_pairs(Ucd.script_extensionsvalue)|_->None)|None->(matchcategory_pairsexprwith|Somepairs->Somepairs|None->(matchHashtbl.find_optbinary_tableexprwith|None->None|Some"ASCII"->Some[|0;0x7F|]|Some"Any"->Some[|0;max_rune|]|Somecanonical->table_pairs(Ucd.binarycanonical)))(** Resolves the expression between the braces of [\p{...}] to its set, or [None] for a name or value that ECMA-262 does
not define. *)letproperty_setexpr=matchproperty_pairsexprwithNone->None|Somepairs->Some(makepairsfalse)(* Case-insensitive matching exists only inside a modifier group such as (?i:...). ECMA-262 defines it through a
Canonicalize function. Two characters match when they canonicalize to the same character. The function differs
between the two grammars. With the u flag it is Unicode simple case folding. With no flag it is the single
character uppercase mapping of a UTF-16 code unit, which never maps a character outside ASCII into ASCII.
The parser applies the equivalence to every literal and class as it reads them, so both back ends match a
case-insensitive group with plain classes. Only a backreference needs the function when matching.
The folding and the uppercase mapping come from the tables of the ucd library. *)(* The characters of the Basic Multilingual Plane whose uppercase form is more than one character. canonicalize
leaves them alone. *)letmulti_upper=make[|0xDF;0xDF;0x149;0x149;0x1F0;0x1F0;0x390;0x390;0x3B0;0x3B0;0x587;0x587;0x1E96;0x1E9A;0x1F50;0x1F50;0x1F52;0x1F52;0x1F54;0x1F54;0x1F56;0x1F56;0x1F80;0x1FAF;0x1FB2;0x1FB4;0x1FB6;0x1FB7;0x1FBC;0x1FBC;0x1FC2;0x1FC4;0x1FC6;0x1FC7;0x1FCC;0x1FCC;0x1FD2;0x1FD3;0x1FD6;0x1FD7;0x1FE2;0x1FE4;0x1FE6;0x1FE7;0x1FF2;0x1FF4;0x1FF6;0x1FF7;0x1FFC;0x1FFC;0xFB00;0xFB06;0xFB13;0xFB17;|]false[@@ocamlformat"disable"](** The Canonicalize function of ECMA-262 for case-insensitive matching. It does not allocate. *)letcanonicalizerunicode_mode=ifunicode_modethen(* Every character of a simple case folding class folds to the same character. *)Ucd.foldrelseif(* A character beyond the Basic Multilingual Plane is two code units, and neither has an uppercase form. *)r>0xFFFF||containsmulti_upperrthenrelseletupper=Ucd.to_upperrinif(r>=128&&upper<128)||upper>0xFFFFthenrelseuppertypefold_table={points:intarray;classes:intarrayarray}(** The characters that are equivalent to some other character, in ascending order, each with all the members of its
equivalence class. *)(* Builds the table from the classes, each the list of its members by the character they canonicalize to. *)letnew_fold_table(classes:(int,intlist)Hashtbl.t)=letby_member=Hashtbl.create4096inletpoints=ref[]inHashtbl.iter(fun_members->matchmemberswith|[]|[_]->()|_->letclass_=Array.of_list(List.revmembers)inArray.iter(funmember->points:=member::!points;Hashtbl.replaceby_membermemberclass_)class_)classes;letpoints=Array.of_list!pointsinArray.sortcompare_intpoints;{points;classes=Array.map(Hashtbl.findby_member)points}letadd_memberclasseskeymember=Hashtbl.replaceclasseskey(member::(matchHashtbl.find_optclasseskeywithSomemembers->members|None->[]))letbuild_fold_tableunicode_mode=letclasses=Hashtbl.create4096inifunicode_modethen(* A class is a character that others fold to, with those others. *)Array.iter(funr->letkey=Ucd.foldrinifnot(Hashtbl.memclasseskey)thenHashtbl.replaceclasseskey[key];add_memberclasseskeyr)(Ucd.folding())elseforr=0to0xFFFFdoadd_memberclasses(canonicalizerfalse)rdone;new_fold_tableclassesletunicode_fold_table:fold_tableoptionAtomic.t=Atomic.makeNoneletlegacy_fold_table:fold_tableoptionAtomic.t=Atomic.makeNone(** Returns the table of the grammar in use. It is built on first use, which only a pattern with a case-insensitive
group reaches. *)letfold_classesunicode_mode=letslot=ifunicode_modethenunicode_fold_tableelselegacy_fold_tableinmatchAtomic.getslotwith|Sometable->table|None->lettable=build_fold_tableunicode_modeinAtomic.setslot(Sometable);table(* The first index of [points] whose element is at least [lo]. *)letrecfirst_at_leastpointslofromuntil=iffrom>=untilthenfromelseletmid=(from+until)lsr1inifpoints.(mid)>=lothenfirst_at_leastpointslofrommidelsefirst_at_leastpointslo(mid+1)until(** Returns the set of every character equivalent to some member of the set. *)letfold_closure_countedsetunicode_mode=lettable=fold_classesunicode_modeinletextra=ref[]inletvariants=ref0inleti=ref0inwhile!i<Array.lengthset.rangesdoletlo=set.ranges.(!i)andhi=set.ranges.(!i+1)inletk=ref(first_at_leasttable.pointslo0(Array.lengthtable.points))inwhile!k<Array.lengthtable.points&&table.points.(!k)<=hidoincrvariants;Array.iter(funmember->ifnot(containssetmember)thenextra:=member::member::!extra)table.classes.(!k);incrkdone;i:=!i+2done;((if!extra=[]thensetelsemake(Array.append(Array.of_list!extra)set.ranges)false),!variants)letfold_closuresetunicode_mode=fst(fold_closure_countedsetunicode_mode)(* With the u flag and case-insensitive matching, \w and \b also count the two characters that fold to an ASCII
letter (U+017F, the long s, and U+212A, the Kelvin sign) as word characters. *)letfold_word_pairs=[|0x30;0x39;0x41;0x5A;0x5F;0x5F;0x61;0x7A;0x17F;0x17F;0x212A;0x212A|]letfold_word_set=makefold_word_pairsfalseletnot_fold_word_set=makefold_word_pairstrueletis_fold_word_runer=(r<128&&is_word_byter)||r=0x17F||r=0x212A