123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899(* Windows path syntax. *)letis_drive_letter=function'A'..'Z'|'a'..'z'->true|_->falseletis_sepc=c='\\'||c='/'(* A recognizer matches at position [i] of [s] and returns the position
one past the match, or [None] if it doesn't match. *)typerecognizer=string->int->intoptionletcharpf:recognizer=funsi->ifi<String.lengths&&fs.[i]thenSome(i+1)elseNonelet(*>)(p:recognizer)(q:recognizer):recognizer=funsi->Option.bind(psi)(qs)let(<|>)(p:recognizer)(q:recognizer):recognizer=funsi->matchpsiwithSome_asr->r|None->qsiletoptp:recognizer=p<|>(fun_i->Somei)(* Zero or more matches of [p]. *)letrecmanyp:recognizer=funsi->matchpsiwithSomej->manypsj|None->Someiletchrc=charp(Char.equalc)letsep=charpis_sepletbslash=chr'\\'letqmark=chr'?'(* The (possibly empty) run of non-separator characters at [i]. *)letcomponent=many(charp(func->not(is_sepc)))(* A component whose text satisfies [f]. *)letcomponent_isf:recognizer=funsi->matchcomponentsiwith|Someewhenf(String.subsi(e-i))->Somee|_->None(* The component after this one. [opt sep] rather than [sep] so that a
malformed prefix is cut at whatever exists. *)letnext=optsep*>componentletdrive=charpis_drive_letter*>chr':'letq_or_dot=component_is(func->c="?"||c=".")letunc_kw=component_is(func->String.uppercase_asciic="UNC")(* The "C:", "\\server\share", "\\.\device", "\\?\..." or "\??\..."
volume prefix of a path. *)letvolume_prefix=drive(* C: *)<|>(sep*>sep*>q_or_dot*>optsep*>unc_kw*>next*>next)(* \\?\UNC\server\share *)<|>(sep*>sep*>component*>next)(* \\server\share, \\?\C: or \\.\device *)<|>(bslash*>qmark*>qmark*>component*>next)(* \??\C: - the NT object-manager form (backslash only) *)letvolume_ends=Option.value(volume_prefixs0)~default:0letvolumes=String.subs0(volume_ends)(* Win32 does no normalization in the verbatim and NT namespaces. *)letverbatim_prefix=bslash*>(bslash<|>qmark)*>qmarkletverbatims=Option.is_some(verbatim_prefixs0)(* [is_relative p] is [true] unless [p] begins with a volume or a separator. *)letis_relatives=Option.is_none((volume_prefix<|>sep)s0)letsplitp=letvend=volume_endpinletsep_at=ifverbatimpthenChar.equal'\\'elseis_sepinletsep_ati=sep_atp.[i]in(* Trailing separators are ignored; one is kept for a bare root. *)letrectrimi=ifi>vend+1&&sep_at(i-1)thentrim(i-1)elseiinletstop=trim(String.lengthp)inifstop<=vend||(stop=vend+1&&sep_atvend)thenNoneelseletrecrsepi=ifi<vendthenNoneelseifsep_atithenSomeielsersep(i-1)inmatchrsep(stop-1)with|None->Some(volumep,String.subpvend(stop-vend))|Someidx->letbasename=String.subp(idx+1)(stop-idx-1)inletdirname=(* keep the root separator itself *)String.subp0(ifidx=vendthenvend+1elsetrimidx)inSome(dirname,basename)letconcatab=letl=String.lengthainifl=0thenbelseif(ifverbatimathena.[l-1]='\\'elseis_sepa.[l-1])thena^belseifdrivea0=Somelthena^b(* a bare drive is drive-relative: adding a separator would change its meaning *)elsea^"\\"^bletjoinp1p2=matchp1,p2with|p1,""->concatp1p2|_,p2whennot(is_relativep2)->p2|".",p2->p2|p1,p2->concatp1p2