123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337openStringletstring_aftersn=String.subsn(String.lengths-n)letquotes=letlen=String.lengthsinletbuf=Buffer.create(2*len)infori=0tolen-1domatchs.[i]with'['|']'|'*'|'.'|'\\'|'?'|'+'|'^'|'$'asc->Buffer.add_charbuf'\\';Buffer.add_charbufc|c->Buffer.add_charbufcdone;Buffer.contentsbuf(* Not tail recursive for "performance", please choose low values for
[max]. The idea is that max is always small because it's hard
code *)letsplit_char_boundedstr~on~max=ifstr=""then[]elseifmax=1then[str]elseletrecloopoffsettokens=iftokens=max-1then[substroffset(lengthstr-offset)]elsetryletindex=index_fromstroffsetoninifindex=offsetthen""::(loop(offset+1)(tokens+1))elselettoken=String.substroffset(index-offset)intoken::(loop(index+1)(tokens+1))withNot_found->[substroffset(lengthstr-offset)]inloop00letsplit_char_unboundedstr~on=ifstr=""then[]elseletrecloopaccoffset=trybeginletindex=rindex_fromstroffsetoninifindex=offsetthenloop(""::acc)(index-1)elselettoken=substr(index+1)(offset-index)inloop(token::acc)(index-1)endwithNot_found->(substr0(offset+1))::accinloop[](lengthstr-1)letof_char=String.make1letfull_splitstr~on=ifstr=""then[]elseletsep=of_charoninletrecloopaccoffset=trybeginletindex=rindex_fromstroffsetoninifindex=offsetthenloop(sep::acc)(index-1)elselettoken=substr(index+1)(offset-index)inloop(sep::token::acc)(index-1)endwithNot_found->ifoffset>=0then(substr0(offset+1))::accelseaccinloop[](lengthstr-1)(* copying core's convention for String.split but with an optional max
argument *)letsplit?maxs~on=matchmaxwith|None->split_char_unboundeds~on|Somemax->(* assert (max < 100); *)split_char_boundeds~on~maxletrindex_from_ons~offset~on=letrecloopi=ifi<0thenraiseNot_foundelseifString.containsons.[i]thenielseloop(i-1)inloopoffsetlettrim_left_subs~pos~len~chars=letstart_pos=letfinal=pos+leninletreclooplast_chari=ifi=finalthenlast_charelseifString.containscharss.[i]thenloop(i+1)(i+1)elselast_charinloopposposinletnew_len=len-(start_pos-pos)inString.subsstart_posnew_lenletsplit_trim_leftstr~on~trim=ifstr=""then[]elseletrecloopaccoffset=trybeginletindex=rindex_from_onstr~offset~oninifindex=offsetthenloop(""::acc)(index-1)elselettoken=trim_left_substr~pos:(index+1)~len:(offset-index)~chars:triminloop(token::acc)(index-1)endwithNot_found->(trim_left_substr~pos:0~len:(offset+1)~chars:trim)::accinloop[](lengthstr-1)exceptionFound_intofintletfirst_char_nesc=String.lengths>0&&s.[0]<>clettrim_lefts=iffirst_char_nes' 'thenselseletlen=String.lengthsintryfori=0tolen-1doifs.[i]<>' 'thenraise(Found_inti)done;""withFound_intnon_space->subsnon_space(len-non_space)letsubstr_eq?(start=0)s~pattern=tryfori=0toString.lengthpattern-1doifs.[i+start]<>pattern.[i]thenraiseExitdone;truewith_->falseletfind_from?(start=0)str~pattern=tryfori=startto(String.lengthstr)-(String.lengthpattern)doifsubstr_eq~start:istr~patternthenraise(Found_inti)done;Nonewith|Found_inti->Somei|_->Noneletfind_minl~f=letrecloopxfx=function|[]->Some(x,fx)|x'::xs->letfx'=fx'iniffx'<fxthenloopx'fx'xselseloopxfxxsinmatchlwith|[]->None|x::xs->loopx(fx)xsletreplace_allstr~pattern~with_=let(slen,plen)=String.(lengthstr,lengthpattern)inletbuf=Buffer.createsleninletrecloopi=matchfind_from~start:istr~patternwith|None->Buffer.add_substringbufstri(slen-i);Buffer.contentsbuf|Somej->Buffer.add_substringbufstri(j-i);Buffer.add_stringbufwith_;loop(j+plen)inloop0exceptionFound_replaceofint*string*stringletreplace_all_assocstrtbl=letslen=String.lengthstrinletbuf=Buffer.createsleninletrecloopi=ifi>=slenthenBuffer.contentsbufelseletr=tryletfound=reffalseinlete=find_mintbl~f:(fun(pattern,with_)->matchfind_from~start:istr~patternwith|None->max_int|Somejwhenj=i->raise(Found_replace(j,pattern,with_))|Somej->found:=true;j)inmatchewith|None->None|Some((pattern,with_),j)when!found->Some(j,pattern,with_)|Some_->NonewithFound_replace(j,pattern,with_)->Some(j,pattern,with_)inmatchrwith|None->Buffer.add_substringbufstri(slen-i);Buffer.contentsbuf|Some(j,pattern,with_)->Buffer.add_substringbufstri(j-i);Buffer.add_stringbufwith_;loop(j+String.lengthpattern)inloop0letiterifl=letrecloopi=function|[]->()|x::xs->(fix);loop(succi)xsinloop0lletof_listxs=letl=List.lengthxsinlets=Bytes.createliniteri(funic->Bytes.setsic)xs;Bytes.unsafe_to_stringsletto_lists=letrecloopacci=ifi=-1thenaccelseloop(s.[i]::acc)(predi)inloop[](String.lengths-1)letof_arraya=letlen=Array.lengthainletbytes=Bytes.createleninfori=0tolen-1doBytes.setbytesia.(i)done;Bytes.unsafe_to_stringbytesletto_arrays=Array.init(String.lengths)(String.gets)(* ripped off from one of dbuenzli's libs *)letcuts~on=letsep_max=lengthon-1inifsep_max<0theninvalid_arg"Stringext.cut: empty separator"elselets_max=lengths-1inifs_max<0thenNoneelseletk=ref0inleti=ref0in(* We run from the start of [s] to end with [i] trying to match the
first character of [on] in [s]. If this matches, we verify that
the whole [on] is matched using [k]. If it doesn't match we
continue to look for [on] with [i]. If it matches we exit the
loop and extract a substring from the start of [s] to the
position before the [on] we found and another from the position
after the [on] we found to end of string. If [i] is such that no
separator can be found we exit the loop and return the no match
case. *)trywhile(!i+sep_max<=s_max)do(* Check remaining [on] chars match, access to unsafe s (!i + !k) is
guaranteed by loop invariant. *)ifunsafe_gets!i<>unsafe_geton0thenincrielsebegink:=1;while(!k<=sep_max&&unsafe_gets(!i+!k)=unsafe_geton!k)doincrkdone;if!k<=sep_maxthen(* no match *)incrielseraiseExitenddone;None(* no match in the whole string. *)with|Exit->(* i is at the beginning of the separator *)letleft_end=!i-1inletright_start=!i+sep_max+1inSome(subs0(left_end+1),subsright_start(s_max-right_start+1))letrcuts~on=letsep_max=lengthon-1inifsep_max<0theninvalid_arg"Stringext.rcut: empty separator"elselets_max=lengths-1inifs_max<0thenNoneelseletk=ref0inleti=refs_maxin(* We run from the end of [s] to the beginning with [i] trying to
match the last character of [on] in [s]. If this matches, we
verify that the whole [on] is matched using [k] (we do that
backwards). If it doesn't match we continue to look for [on]
with [i]. If it matches we exit the loop and extract a
substring from the start of [s] to the position before the
[on] we found and another from the position after the [on] we
found to end of string. If [i] is such that no separator can
be found we exit the loop and return the no match case. *)trywhile(!i>=sep_max)doifunsafe_gets!i<>unsafe_getonsep_maxthendecrielsebegin(* Check remaining [on] chars match, access to unsafe_get
s (sep_start + !k) is guaranteed by loop invariant. *)letsep_start=!i-sep_maxink:=sep_max-1;while(!k>=0&&unsafe_gets(sep_start+!k)=unsafe_geton!k)dodecrkdone;if!k>=0then(* no match *)decrielseraiseExitenddone;None(* no match in the whole string. *)with|Exit->(* i is at the end of the separator *)letleft_end=!i-sep_max-1inletright_start=!i+1inSome(subs0(left_end+1),subsright_start(s_max-right_start+1))letchop_prefixs~prefix=letprefix_l=String.lengthprefixinletstring_l=String.lengthsinifprefix_l>string_lthenNoneelsetryfori=0toprefix_l-1doifs.[i]<>prefix.[i]thenraiseExit;done;Some(String.subsprefix_l(string_l-prefix_l))with_->Noneletdropsn=letl=String.lengthsinifn>=lthen""elseString.subsn(l-n)lettakesn=ifn>=String.lengthsthenselseString.subs0n