123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081typetoken=|Empty|DotDot|Stringofstringletrectokenise=function|[]->[]|["."]->[Empty](* "path/." is the same as "path/" *)|"."::xs->tokenisexs(* Skip dot if not at end *)|""::xs->Empty::tokenisexs|".."::xs->DotDot::tokenisexs|x::xs->Stringx::tokenisexsmoduleRel=structtypet=|Leafof{basename:string;trailing_slash:bool}|Self(* A final "." *)|Childofstring*t|Parentoftletrecparse=function|[]->Self|[Stringbasename;Empty]->Leaf{basename;trailing_slash=true}|[Stringbasename]->Leaf{basename;trailing_slash=false}|[DotDot]->ParentSelf|DotDot::xs->Parent(parsexs)|Strings::xs->Child(s,parsexs)|Empty::xs->parsexsletparses=parse(tokenises)letrecconcatab=matchawith|Leaf{basename;trailing_slash=_}->Child(basename,b)|Child(name,xs)->Child(name,concatxsb)|Parentxs->Parent(concatxsb)|Self->bletrecdumpf=function|Child(x,xs)->Fmt.pff"%S / %a"xdumpxs|Parentxs->Fmt.pff".. / %a"dumpxs|Self->Fmt.pff"."|Leaf{basename;trailing_slash}->Fmt.pff"%S"basename;iftrailing_slashthenFmt.pff" /"letrecsegs=function|Leaf{basename;trailing_slash}->[iftrailing_slashthenbasename^"/"elsebasename]|Self->[""]|Child(x,xs)->x::segsxs|ParentSelf->[".."]|Parentxs->".."::segsxsletto_string=function|Self->"."|t->String.concat"/"(segst)endtypet=|RelativeofRel.t|AbsoluteofRel.tletrecparse_abs=function|""::[]->AbsoluteSelf|""::xs->parse_absxs|xs->Absolute(Rel.parsexs)letparse=function|""->RelativeSelf|s->matchString.split_on_char'/'swith|""::path->parse_abspath|path->Relative(Rel.parsepath)letdumpf=function|Relativer->Rel.dumpfr|Absoluter->Fmt.pff"/ %a"Rel.dumprletto_string=function|Relativer->Rel.to_stringr|Absoluter->String.concat"/"(""::Rel.segsr)