1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084(**************************************************************************)(* *)(* OCaml *)(* *)(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)(* *)(* Copyright 1996 Institut National de Recherche en Informatique et *)(* en Automatique. *)(* *)(* All rights reserved. This file is distributed under the terms of *)(* the GNU Lesser General Public License version 2.1, with the *)(* special exception on linking described in the file LICENSE. *)(* *)(**************************************************************************)moduleCamlString=StringopenStd(* Errors *)exceptionFatal_errorofstring*Printexc.raw_backtracelet()=Printexc.register_printer(function|Fatal_error(msg,bt)->Some(Printf.sprintf"Fatal error: %s\n%s"msg(Printexc.raw_backtrace_to_stringbt))|_->None)letfatal_errormsg=raise(Fatal_error(msg,Printexc.get_callstack50))letfatal_errorffmt=(*Format.kasprintf is not available in 4.02.3 *)(*Format.kasprintf fatal_error fmt*)ignore(Format.flush_str_formatter());Format.kfprintf(fun_ppf->fatal_error(Format.flush_str_formatter()))Format.str_formatterfmt(* Exceptions *)lettry_finally?(always=(fun()->()))?(exceptionally=(fun()->()))work=matchwork()with|result->beginmatchalways()with|()->result|exceptionalways_exn->(* raise_with_backtrace is not available before OCaml 4.05 *)(*let always_bt = Printexc.get_raw_backtrace () in*)exceptionally();(*Printexc.raise_with_backtrace always_exn always_bt*)raisealways_exnend|exceptionwork_exn->(*let work_bt = Printexc.get_raw_backtrace () in*)beginmatchalways()with|()->exceptionally();(*Printexc.raise_with_backtrace work_exn work_bt*)raisework_exn|exceptionalways_exn->(*let always_bt = Printexc.get_raw_backtrace () in*)exceptionally();(*Printexc.raise_with_backtrace always_exn always_bt*)raisealways_exnendletreraise_preserving_backtraceef=letbt=Printexc.get_raw_backtrace()inf();Printexc.raise_with_backtraceebttyperef_and_value=R:'aref*'a->ref_and_valueletprotect_refs=letset_refsl=List.iter~f:(fun(R(r,v))->r:=v)linfunrefsf->letbackup=List.map~f:(fun(R(r,_))->R(r,!r))refsinset_refsrefs;matchf()with|x->set_refsbackup;x|exceptione->set_refsbackup;raisee(** {1 Minimal support for Unicode characters in identifiers} *)moduleUtf8_lexeme=structtypet=string(* Non-ASCII letters that are allowed in identifiers (currently: Latin-9) *)typecase=UpperofUchar.t|LowerofUchar.tletknown_chars:(Uchar.t,case)Hashtbl.t=Hashtbl.create32let_=List.iter~f:(fun(upper,lower)->letupper=Uchar.of_intupperandlower=Uchar.of_intlowerinHashtbl.addknown_charsupper(Upperlower);Hashtbl.addknown_charslower(Lowerupper))[(0xc0,0xe0);(* À, à *)(0xc1,0xe1);(* Á, á *)(0xc2,0xe2);(* Â, â *)(0xc3,0xe3);(* Ã, ã *)(0xc4,0xe4);(* Ä, ä *)(0xc5,0xe5);(* Å, å *)(0xc6,0xe6);(* Æ, æ *)(0xc7,0xe7);(* Ç, ç *)(0xc8,0xe8);(* È, è *)(0xc9,0xe9);(* É, é *)(0xca,0xea);(* Ê, ê *)(0xcb,0xeb);(* Ë, ë *)(0xcc,0xec);(* Ì, ì *)(0xcd,0xed);(* Í, í *)(0xce,0xee);(* Î, î *)(0xcf,0xef);(* Ï, ï *)(0xd0,0xf0);(* Ð, ð *)(0xd1,0xf1);(* Ñ, ñ *)(0xd2,0xf2);(* Ò, ò *)(0xd3,0xf3);(* Ó, ó *)(0xd4,0xf4);(* Ô, ô *)(0xd5,0xf5);(* Õ, õ *)(0xd6,0xf6);(* Ö, ö *)(0xd8,0xf8);(* Ø, ø *)(0xd9,0xf9);(* Ù, ù *)(0xda,0xfa);(* Ú, ú *)(0xdb,0xfb);(* Û, û *)(0xdc,0xfc);(* Ü, ü *)(0xdd,0xfd);(* Ý, ý *)(0xde,0xfe);(* Þ, þ *)(0x160,0x161);(* Š, š *)(0x17d,0x17e);(* Ž, ž *)(0x152,0x153);(* Œ, œ *)(0x178,0xff);(* Ÿ, ÿ *)(0x1e9e,0xdf);(* ẞ, ß *)](* NFD to NFC conversion table for the letters above *)letknown_pairs:(Uchar.t*Uchar.t,Uchar.t)Hashtbl.t=Hashtbl.create32let_=List.iter~f:(fun(c1,n2,n)->Hashtbl.addknown_pairs(Uchar.of_charc1,Uchar.of_intn2)(Uchar.of_intn))[('A',0x300,0xc0);(* À *)('A',0x301,0xc1);(* Á *)('A',0x302,0xc2);(*  *)('A',0x303,0xc3);(* à *)('A',0x308,0xc4);(* Ä *)('A',0x30a,0xc5);(* Å *)('C',0x327,0xc7);(* Ç *)('E',0x300,0xc8);(* È *)('E',0x301,0xc9);(* É *)('E',0x302,0xca);(* Ê *)('E',0x308,0xcb);(* Ë *)('I',0x300,0xcc);(* Ì *)('I',0x301,0xcd);(* Í *)('I',0x302,0xce);(* Î *)('I',0x308,0xcf);(* Ï *)('N',0x303,0xd1);(* Ñ *)('O',0x300,0xd2);(* Ò *)('O',0x301,0xd3);(* Ó *)('O',0x302,0xd4);(* Ô *)('O',0x303,0xd5);(* Õ *)('O',0x308,0xd6);(* Ö *)('U',0x300,0xd9);(* Ù *)('U',0x301,0xda);(* Ú *)('U',0x302,0xdb);(* Û *)('U',0x308,0xdc);(* Ü *)('Y',0x301,0xdd);(* Ý *)('Y',0x308,0x178);(* Ÿ *)('S',0x30c,0x160);(* Š *)('Z',0x30c,0x17d);(* Ž *)('a',0x300,0xe0);(* à *)('a',0x301,0xe1);(* á *)('a',0x302,0xe2);(* â *)('a',0x303,0xe3);(* ã *)('a',0x308,0xe4);(* ä *)('a',0x30a,0xe5);(* å *)('c',0x327,0xe7);(* ç *)('e',0x300,0xe8);(* è *)('e',0x301,0xe9);(* é *)('e',0x302,0xea);(* ê *)('e',0x308,0xeb);(* ë *)('i',0x300,0xec);(* ì *)('i',0x301,0xed);(* í *)('i',0x302,0xee);(* î *)('i',0x308,0xef);(* ï *)('n',0x303,0xf1);(* ñ *)('o',0x300,0xf2);(* ò *)('o',0x301,0xf3);(* ó *)('o',0x302,0xf4);(* ô *)('o',0x303,0xf5);(* õ *)('o',0x308,0xf6);(* ö *)('u',0x300,0xf9);(* ù *)('u',0x301,0xfa);(* ú *)('u',0x302,0xfb);(* û *)('u',0x308,0xfc);(* ü *)('y',0x301,0xfd);(* ý *)('y',0x308,0xff);(* ÿ *)('s',0x30c,0x161);(* š *)('z',0x30c,0x17e);(* ž *)]letnormalize_generic~keep_asciitransforms=letrecnormcheckbufprevi=ifi>=String.lengthsthenbeginBuffer.add_utf_8_ucharbuf(transformprev)endelsebeginletd=String.get_utf_8_ucharsiinletu=Uchar.utf_decode_uchardincheckdu;leti'=i+Uchar.utf_decode_lengthdinmatchHashtbl.find_optknown_pairs(prev,u)with|Someu'->normcheckbufu'i'|None->Buffer.add_utf_8_ucharbuf(transformprev);normcheckbufui'endinletascii_limit=128inifs=""||keep_ascii&&String.for_all(funx->Char.codex<ascii_limit)sthenOkselseletbuf=Buffer.create(String.lengths)inletvalid=reftrueinletcheckdu=valid:=!valid&&Uchar.utf_decode_is_validd&&u<>Uchar.repinletd=String.get_utf_8_uchars0inletu=Uchar.utf_decode_uchardincheckdu;normcheckbufu(Uchar.utf_decode_lengthd);letcontents=Buffer.contentsbufinif!validthenOkcontentselseErrorcontentsletnormalizes=normalize_generic~keep_ascii:true(funu->u)s(* Capitalization *)letuchar_is_uppercaseu=letc=Uchar.to_intuinifc<0x80thenc>=65&&c<=90elsematchHashtbl.find_optknown_charsuwith|Some(Upper_)->true|_->falseletuchar_lowercaseu=letc=Uchar.to_intuinifc<0x80thenifc>=65&&c<=90thenUchar.of_int(c+32)elseuelsematchHashtbl.find_optknown_charsuwith|Some(Upperu')->u'|_->uletuchar_uppercaseu=letc=Uchar.to_intuinifc<0x80thenifc>=97&&c<=122thenUchar.of_int(c-32)elseuelsematchHashtbl.find_optknown_charsuwith|Some(Loweru')->u'|_->uletcapitalizes=letfirst=reftrueinnormalize_generic~keep_ascii:false(funu->if!firstthen(first:=false;uchar_uppercaseu)elseu)sletuncapitalizes=letfirst=reftrueinnormalize_generic~keep_ascii:false(funu->if!firstthen(first:=false;uchar_lowercaseu)elseu)sletis_capitalizeds=s<>""&&uchar_is_uppercase(Uchar.utf_decode_uchar(String.get_utf_8_uchars0))(* Characters allowed in identifiers after normalization is applied.
Currently:
- ASCII letters, underscore
- Latin-9 letters, represented in NFC
- ASCII digits, single quote (but not as first character)
- dot if [with_dot] = true
*)letuchar_valid_in_identifier~with_dotu=letc=Uchar.to_intuinifc<0x80thenc>=97(* a *)&&c<=122(* z *)||c>=65(* A *)&&c<=90(* Z *)||c>=48(* 0 *)&&c<=57(* 9 *)||c=95(* underscore *)||c=39(* single quote *)||(with_dot&&c=46)(* dot *)elseHashtbl.memknown_charsuletuchar_not_identifier_startu=letc=Uchar.to_intuinc>=48(* 0 *)&&c<=57(* 9 *)||c=39(* single quote *)(* Check whether a normalized string is a valid OCaml identifier. *)typevalidation_result=|Valid|Invalid_characterofUchar.t(** Character not allowed *)|Invalid_beginningofUchar.t(** Character not allowed as first char *)letvalidate_identifier?(with_dot=false)s=letrecchecki=ifi>=String.lengthsthenValidelsebeginletd=String.get_utf_8_ucharsiinletu=Uchar.utf_decode_uchardinleti'=i+Uchar.utf_decode_lengthdinifnot(uchar_valid_in_identifier~with_dotu)thenInvalid_characteruelseifi=0&&uchar_not_identifier_startuthenInvalid_beginninguelsechecki'endincheck0letis_valid_identifiers=validate_identifiers=Validletstarts_like_a_valid_identifiers=s<>""&&(letu=Uchar.utf_decode_uchar(String.get_utf_8_uchars0)inuchar_valid_in_identifier~with_dot:falseu&¬(uchar_not_identifier_startu))letis_lowercases=letrecis_lowercase_atlensn=ifn>=lenthentrueelseletd=String.get_utf_8_ucharsninletu=Uchar.utf_decode_uchardin(uchar_valid_in_identifier~with_dot:falseu)&¬(uchar_is_uppercaseu)&&is_lowercase_atlens(n+Uchar.utf_decode_lengthd)inis_lowercase_at(String.lengths)s0end(* List functions *)letmap_endfl1l2=List.map_end~fl1l2letrev_map_endfl1l2=letrecrmap_faccu=function|[]->accu|hd::tl->rmap_f(fhd::accu)tlinrmap_fl2l1letrecmap_left_rightf=function[]->[]|hd::tl->letres=fhdinres::map_left_rightftlletfor_all2predl1l2=List.for_all2~f:predl1l2letreplicate_list=List.replicateletlist_removex=List.remove~phys:falsexletrecsplit_last=function[]->assertfalse|[x]->([],x)|hd::tl->let(lst,last)=split_lasttlin(hd::lst,last)(* Options *)letmayfx=Option.iter~fxletmay_mapfx=Option.map~fx(* File functions *)letremove_filefilename=tryifSys.is_regular_filefilenamethenSys.removefilenamewithSys_error_msg->()letrecsplit_path_and_prependpathacc=matchFilename.dirnamepathwith|dirwhendir=path->letis_letterc=(c>='a'&&c<='z')||(c>='A'&&c<='Z')inletdir=ifnotSys.unix&&String.lengthdir>2&&is_letterdir.[0]&&dir.[1]=':'then(* We do two things here:
- We use an uppercase letter to match Dune's behavior
- We also add the separator ousrselves because [Filename.concat]
does not if its first argument is of the form ["C:"] *)Printf.sprintf"%c:%s"(Char.uppercase_asciidir.[0])Filename.dir_sepelsedirindir::acc|dir->split_path_and_prependdir(Filename.basenamepath::acc)letsplit_pathpath=split_path_and_prependpath[](* Deal with case insensitive FS *)externalfs_exact_case:string->string="ml_merlin_fs_exact_case"externalfs_exact_case_basename:string->stringoption="ml_merlin_fs_exact_case_basename"(* A replacement for sys_file_exists that makes use of stat_cache *)moduleExists_in_directory=File_cache.Make(structletcache_name="Exists_in_directory"typet=string->boolletreaddir=ifSys.file_existsdir&&Sys.is_directorydirthenletcache=Hashtbl.create4in(funfilename->matchHashtbl.findcachefilenamewith|x->x|exceptionNot_found->letexists=Sys.file_exists(Filename.concatdirfilename)inHashtbl.addcachefilenameexists;exists)else(fun_->false)end)letexact_file_exists~dirname~basename=Exists_in_directory.readdirnamebasename&&letpath=Filename.concatdirnamebasenameinmatchfs_exact_case_basenamepathwith|None->letpath'=fs_exact_casepathinpath==path'||(* only on macos *)basename=Filename.basenamepath'|Somebn->(* only on windows *)basename=bnletcanonicalize_filename?cwdpath=letparts=matchsplit_pathpathwith|dot::restwhendot=Filename.current_dir_name->split_path_and_prepend(matchcwdwithNone->Sys.getcwd()|Somec->c)rest|parts->partsinletgouppath=function|dirwhendir=Filename.parent_dir_name->(matchpathwith_::t->t|[]->[])|dirwhendir=Filename.current_dir_name->path|dir->dir::pathinletparts=List.rev(List.fold_left~f:goup~init:[]parts)inletfilename_concats=function|[]->""|root::subs->List.fold_left~f:Filename.concat~init:rootsubsinfs_exact_case(filename_concatsparts)letrecexpand_glob~filteraccroot=function|[]->root::acc|Glob.Wildwild::_tl->(* FIXME: why is tl not used? *)letrecappendaccroot=letitems=trySys.readdirrootwithSys_error_->[||]inletprocessaccdir=letfilename=Filename.concatrootdiriniffilterfilenamethenappend(filename::acc)filenameelseaccinArray.fold_leftprocess(root::acc)itemsinappendaccroot|Glob.Exactcomponent::tl->letfilename=Filename.concatrootcomponentinexpand_glob~filteraccfilenametl|pattern::tl->letitems=trySys.readdirrootwithSys_error_->[||]inletprocessaccdir=ifGlob.match_patternpatterndirthenletroot'=Filename.concatrootdiriniffilterroot'thenexpand_glob~filteraccroot'tlelseaccelseaccinArray.fold_leftprocessaccitemsletexpand_glob?(filter=fun_->true)pathacc=matchsplit_pathpathwith|[]->acc|root::subs->letpatterns=List.map~f:Glob.compile_patternsubsinexpand_glob~filteraccrootpatternsletfind_in_pathpathname=canonicalize_filenamebeginifnot(Filename.is_implicitname)thenifexact_file_exists~dirname:(Filename.dirnamename)~basename:(Filename.basenamename)thennameelseraiseNot_foundelseList.find_mappath~f:(fundirname->ifexact_file_exists~dirname~basename:namethenSome(Filename.concatdirnamename)elseNone)endletfind_in_path_relpathname=letrecsimplifys=letopenFilenameinletbase=basenamesinletdir=dirnamesinifdir=sthendirelseifbase=current_dir_namethensimplifydirelseconcat(simplifydir)baseinletrectry_dir=function|[]->raiseNot_found|dir::rem->letdir=simplifydirinifExists_in_directory.readdirnamethenFilename.concatdirnameelsetry_dirremintry_dirpathletnormalized_unit_filename=String.uncapitalize_asciiletfind_in_path_normalized?(fallback="")pathname=lethas_fallback=fallback<>""incanonicalize_filenamebeginletuname=normalized_unit_filenamenameinletufallback=normalized_unit_filenamefallbackinList.find_mappath~f:(fundirname->ifexact_file_exists~dirname~basename:unamethenSome(Filename.concatdirnameuname)elseifexact_file_exists~dirname~basename:namethenSome(Filename.concatdirnamename)elselet()=Logger.log~section:"locate"~title:"find_in_path_uncap""Failed to load %s/%s"dirnamenameinifhas_fallback&&exact_file_exists~dirname~basename:ufallbackthenSome(Filename.concatdirnameufallback)elseifhas_fallback&&exact_file_exists~dirname~basename:fallbackthenSome(Filename.concatdirnamefallback)elseNone)end(* Expand a -I option: if it starts with +, make it relative to the standard
library directory *)letexpand_directoryalts=ifString.lengths>0&&s.[0]='+'thenFilename.concatalt(String.subs~pos:1~len:(String.lengths-1))elses(* Hashtable functions *)letcreate_hashtablesizeinit=lettbl=Hashtbl.createsizeinList.iter~f:(fun(key,data)->Hashtbl.addtblkeydata)init;tbl(* File copy *)letcopy_fileicoc=letbuff=Bytes.create0x1000inletreccopy()=letn=inputicbuff00x1000inifn=0then()else(outputocbuff0n;copy())incopy()letcopy_file_chunkicoclen=letbuff=Bytes.create0x1000inletreccopyn=ifn<=0then()elsebeginletr=inputicbuff0(minn0x1000)inifr=0thenraiseEnd_of_fileelse(outputocbuff0r;copy(n-r))endincopylenletstring_of_fileic=letb=Buffer.create0x10000inletbuff=Bytes.create0x1000inletreccopy()=letn=inputicbuff00x1000inifn=0thenBuffer.contentsbelse(Buffer.add_subbytesbbuff0n;copy())incopy()letoutput_to_file_via_temporary?(mode=[Open_text])filenamefn=let(temp_filename,oc)=Filename.open_temp_file~mode(*~perms:0o666*)~temp_dir:(Filename.dirnamefilename)(Filename.basenamefilename)".tmp"in(* The 0o666 permissions will be modified by the umask. It's just
like what [open_out] and [open_out_bin] do.
With temp_dir = dirname filename, we ensure that the returned
temp file is in the same directory as filename itself, making
it safe to rename temp_filename to filename later.
With prefix = basename filename, we are almost certain that
the first generated name will be unique. A fixed prefix
would work too but might generate more collisions if many
files are being produced simultaneously in the same directory. *)matchfntemp_filenameocwith|res->close_outoc;begintrySys.renametemp_filenamefilename;reswithexn->remove_filetemp_filename;raiseexnend|exceptionexn->close_outoc;remove_filetemp_filename;raiseexn(* Reading from a channel *)letinput_bytesicn=letresult=Bytes.createninreally_inputicresult0n;result(* Integer operations *)letreclog2n=ifn<=1then0else1+log2(nasr1)letalignna=ifn>=0then(n+a-1)land(-a)elsenland(-a)letno_overflow_addab=(alxorb)lor(alxor(lnot(a+b)))<0letno_overflow_subab=(alxor(lnotb))lor(blxor(a-b))<0(* Taken from Hacker's Delight, chapter "Overflow Detection" *)letno_overflow_mulab=not((a=min_int&&b<0)||(b<>0&&(a*b)/b<>a))letno_overflow_lslak=0<=k&&k<Sys.word_size-1&&min_intasrk<=a&&a<=max_intasrkletletter_of_intn=letletter=String.make1(Char.chr(Char.code'a'+nmod26))inletnum=n/26inifnum=0thenletterelseletter^Int.to_stringnummoduleInt_literal_converter=struct(* To convert integer literals, allowing max_int + 1 (PR#4210) *)letcvt_int_auxstrnegof_string=ifString.lengthstr=0||str.[0]='-'thenof_stringstrelseneg(of_string("-"^str))letints=cvt_int_auxs(~-)int_of_stringletint32s=cvt_int_auxsInt32.negInt32.of_stringletint64s=cvt_int_auxsInt64.negInt64.of_stringletnativeints=cvt_int_auxsNativeint.negNativeint.of_stringend(* [find_first_mono p] assumes that there exists a natural number
N such that [p] is false on [0; N[ and true on [N; max_int], and
returns this N. (See misc.mli for the detailed specification.) *)letfind_first_mono=letrecfindp~low~jump~high=(* Invariants:
[low, jump, high] are non-negative with [low < high],
[p low = false],
[p high = true]. *)iflow+1=highthenhigh(* ensure that [low + jump] is in ]low; high[ *)elseifjump<1thenfindp~low~jump:1~highelseifjump>=high-lowthenfindp~low~jump:((high-low)/2)~highelseifp(low+jump)then(* We jumped too high: continue with a smaller jump and lower limit *)findp~low:low~jump:(jump/2)~high:(low+jump)else(* we jumped too low:
continue from [low + jump] with a larger jump *)letnext_jump=maxjump(2*jump)(* avoid overflows *)infindp~low:(low+jump)~jump:next_jump~highinfunp->ifp0then0elsefindp~low:0~jump:1~high:max_int(* String operations *)(* let split_null_terminated s =
let[@tail_mod_cons] rec discard_last_sep = function
| [] | [""] -> []
| x :: xs -> x :: discard_last_sep xs
in
discard_last_sep (String.split_on_char ~sep:'' s) *)(* let concat_null_terminated = function
| [] -> ""
| l -> String.concat ~sep:"" (l @ [""]) *)letchop_extension_if_anyfname=tryFilename.chop_extensionfnamewithInvalid_argument_->fnameletchop_extensionsfile=letdirname=Filename.dirnamefileandbasename=Filename.basenamefileintryletpos=String.indexbasename'.'inletbasename=String.subbasename~pos:0~len:posinifFilename.is_implicitfile&&dirname=Filename.current_dir_namethenbasenameelseFilename.concatdirnamebasenamewithNot_found->fileletsearch_substringpatstrstart=letrecsearchij=ifj>=String.lengthpatthenielseifi+j>=String.lengthstrthenraiseNot_foundelseifstr.[i+j]=pat.[j]thensearchi(j+1)elsesearch(i+1)0insearchstart0letreplace_substring~before~afterstr=letrecsearchacccurr=matchsearch_substringbeforestrcurrwith|next->letprefix=String.substr~pos:curr~len:(next-curr)insearch(prefix::acc)(next+String.lengthbefore)|exceptionNot_found->letsuffix=String.substr~pos:curr~len:(String.lengthstr-curr)inList.rev(suffix::acc)inString.concat~sep:after(search[]0)letrev_split_stringconds=letrecsplit1resi=ifi>=String.lengthsthenreselsebeginifconds.[i]thensplit1res(i+1)elsesplit2resi(i+1)endandsplit2resij=ifj>=String.lengthsthenString.subs~pos:i~len:(j-i)::reselsebeginifconds.[j]thensplit1(String.subs~pos:i~len:(j-i)::res)(j+1)elsesplit2resi(j+1)endinsplit1[]0letrev_split_wordss=lethelper=function|' '|'\t'|'\r'|'\n'->true|_->falseinrev_split_stringhelpersletrev_string_split~ons=rev_split_string((=)on)sletget_refr=letv=!rinr:=[];vletset_or_ignorefoptx=matchfxwith|None->()|Somey->opt:=Someyletfst3(x,_,_)=xletsnd3(_,x,_)=xletthd3(_,_,x)=xletfst4(x,_,_,_)=xletsnd4(_,x,_,_)=xletthd4(_,_,x,_)=xletfor4(_,_,_,x)=xletcut_atsc=letpos=String.indexscinString.subs~pos:0~len:pos,String.subs~pos:(pos+1)~len:(String.lengths-pos-1)letordinal_suffixn=letteen=(nmod100)/10=1inmatchnmod10with|1whennotteen->"st"|2whennotteen->"nd"|3whennotteen->"rd"|_->"th"(* Color support handling *)moduleColor=structexternalisatty:out_channel->bool="caml_sys_isatty"(* reasonable heuristic on whether colors should be enabled *)letshould_enable_color()=letterm=trySys.getenv"TERM"withNot_found->""interm<>"dumb"&&term<>""&&isattystderrtypesetting=Auto|Always|Neverletdefault_setting=Autoletenabled=reftrueend(* Terminal styling handling *)moduleStyle=struct(* use ANSI color codes, see https://en.wikipedia.org/wiki/ANSI_escape_code *)typecolor=|Black|Red|Green|Yellow|Blue|Magenta|Cyan|Whitetypestyle=|FGofcolor(* foreground *)|BGofcolor(* background *)|Bold|Resetletansi_of_color=function|Black->"0"|Red->"1"|Green->"2"|Yellow->"3"|Blue->"4"|Magenta->"5"|Cyan->"6"|White->"7"letcode_of_style=function|FGc->"3"^ansi_of_colorc|BGc->"4"^ansi_of_colorc|Bold->"1"|Reset->"0"letansi_of_style_ll=lets=matchlwith|[]->code_of_styleReset|[s]->code_of_styles|_->String.concat~sep:";"(List.map~f:code_of_stylel)in"\x1b["^s^"m"typeFormat.stag+=Styleofstylelisttypetag_style={ansi:stylelist;text_open:string;text_close:string}typestyles={error:tag_style;warning:tag_style;loc:tag_style;hint:tag_style;inline_code:tag_style;}letno_markupstl={ansi=stl;text_close="";text_open=""}letdefault_styles={warning=no_markup[Bold;FGMagenta];error=no_markup[Bold;FGRed];loc=no_markup[Bold];hint=no_markup[Bold;FGBlue];inline_code={ansi=[Bold];text_open={|"|};text_close={|"|}}}letcur_styles=refdefault_stylesletget_styles()=!cur_stylesletset_styless=cur_styles:=s(* map a tag to a style, if the tag is known.
@raise Not_found otherwise *)letstyle_of_tags=matchswith|Format.String_tag"error"->(!cur_styles).error|Format.String_tag"warning"->(!cur_styles).warning|Format.String_tag"loc"->(!cur_styles).loc|Format.String_tag"hint"->(!cur_styles).hint|Format.String_tag"inline_code"->(!cur_styles).inline_code|Styles->no_markups|_->raiseNot_foundletas_inline_codeprinterppfx=letopenFormat_docinpp_open_stagppf(Format.String_tag"inline_code");printerppfx;pp_close_stagppf()letinline_codeppfs=as_inline_codeFormat_doc.pp_print_stringppfs(* either prints the tag of [s] or delegates to [or_else] *)letmark_open_tag~or_elses=tryletstyle=style_of_tagsinif!Color.enabledthenansi_of_style_lstyle.ansielsestyle.text_openwithNot_found->or_elsesletmark_close_tag~or_elses=tryletstyle=style_of_tagsinif!Color.enabledthenansi_of_style_l[Reset]elsestyle.text_closewithNot_found->or_elses(* add tag handling to formatter [ppf] *)letset_tag_handlingppf=letopenFormatinletfunctions=pp_get_formatter_stag_functionsppf()inletfunctions'={functionswithmark_open_stag=(mark_open_tag~or_else:functions.mark_open_stag);mark_close_stag=(mark_close_tag~or_else:functions.mark_close_stag);}inpp_set_mark_tagsppftrue;(* enable tags *)pp_set_formatter_stag_functionsppffunctions';()letsetup=letfirst=reftruein(* initialize only once *)letformatter_l=[Format.std_formatter;Format.err_formatter;Format.str_formatter]inletenable_color=function|Color.Auto->Color.should_enable_color()|Color.Always->true|Color.Never->falseinfuno->if!firstthen(first:=false;Format.set_mark_tagstrue;List.iter~f:set_tag_handlingformatter_l;Color.enabled:=(matchowith|Somes->enable_colors|None->enable_colorColor.default_setting));()endletedit_distanceabcutoff=letla,lb=String.lengtha,String.lengthbinletcutoff=(* using max_int for cutoff would cause overflows in (i + cutoff + 1);
we bring it back to the (max la lb) worstcase *)Int.min(Int.maxlalb)cutoffinifabs(la-lb)>cutoffthenNoneelsebegin(* initialize with 'cutoff + 1' so that not-yet-written-to cases have
the worst possible cost; this is useful when computing the cost of
a case just at the boundary of the cutoff diagonal. *)letm=Array.make_matrix(la+1)(lb+1)(cutoff+1)inm.(0).(0)<-0;fori=1toladom.(i).(0)<-i;done;forj=1tolbdom.(0).(j)<-j;done;fori=1toladoforj=Int.max1(i-cutoff-1)toInt.minlb(i+cutoff+1)doletcost=ifa.[i-1]=b.[j-1]then0else1inletbest=(* insert, delete or substitute *)Int.min(1+Int.minm.(i-1).(j)m.(i).(j-1))(m.(i-1).(j-1)+cost)inletbest=(* swap two adjacent letters; we use "cost" again in case of
a swap between two identical letters; this is slightly
redundant as this is a double-substitution case, but it
was done this way in most online implementations and
imitation has its virtues *)ifnot(i>1&&j>1&&a.[i-1]=b.[j-2]&&a.[i-2]=b.[j-1])thenbestelseInt.minbest(m.(i-2).(j-2)+cost)inm.(i).(j)<-bestdone;done;letresult=m.(la).(lb)inifresult>cutoffthenNoneelseSomeresultendletspellcheckenvname=letcutoff=matchString.lengthnamewith|1|2->0|3|4->1|5|6->2|_->3inletcomparetargetacchead=matchedit_distancetargetheadcutoffwith|None->acc|Somedist->let(best_choice,best_dist)=accinifdist<best_distthen([head],dist)elseifdist=best_distthen(head::best_choice,dist)elseaccinletenv=List.sort_uniq~cmp:(funs1s2->String.compares2s1)envinfst(List.fold_left~f:(comparename)~init:([],max_int)env)letdid_you_meanppfget_choices=letopenFormat_docin(* flush now to get the error report early, in the (unheard of) case
where the search in the get_choices function would take a bit of
time; in the worst case, the user has seen the error, she can
interrupt the process before the spell-checking terminates. *)fprintfppf"@?";matchget_choices()with|[]->()|choices->letrest,last=split_lastchoicesinfprintfppf"@\n@[@{<hint>Hint@}: Did you mean %a%s%a?@]"(pp_print_list~pp_sep:commaStyle.inline_code)rest(ifrest=[]then""else" or ")Style.inline_codelastletprint_see_manualppfmanual_section=letopenFormat_docinfprintfppf"(see manual section %a)"(pp_print_list~pp_sep:(funf()->pp_print_charf'.')pp_print_int)manual_sectionlettime_spent()=letopenUnixinlett=times()in((t.tms_utime+.t.tms_stime+.t.tms_cutime+.t.tms_cstime)*.1000.0)letnormalise_eols=letb=Buffer.create80infori=0toString.lengths-1doifs.[i]<>'\r'thenBuffer.add_charbs.[i]done;Buffer.contentsbletunitnamefilename=letunitname=tryString.subfilename~pos:0~len:(String.indexfilename'.')withNot_found->filenameinString.capitalizeunitname(* [modules_in_path ~ext path] lists ocaml modules corresponding to
* filenames with extension [ext] in given [path]es.
* For instance, if there is file "a.ml","a.mli","b.ml" in ".":
* - modules_in_path ~ext:".ml" ["."] returns ["A";"B"],
* - modules_in_path ~ext:".mli" ["."] returns ["A"] *)letmodules_in_path~extpath=letseen=Hashtbl.create7inList.fold_left~init:[]path~f:beginfunresultsdir->tryArray.fold_leftbeginfunresultsfile->ifFilename.check_suffixfileextthenletname=Filename.chop_extensionfilein(ifHashtbl.memseennamethenresultselse(Hashtbl.addseenname();String.capitalizename::results))elseresultsendresults(Sys.readdirdir)withSys_error_->resultsendmoduleString=structincludeCamlStringmoduleOrd=structtypet=stringletcompare=String.compareendmoduleSet=Set.Make(Ord)moduleMap=Map.Make(Ord)moduleTbl=Hashtbl.Make(structtypet=stringletequal(x:string)(y:string):bool=(x=y)lethash=Hashtbl.hashend)endtypefilepath=stringtypemodname=stringtypecrcs=(modname*Digest.toption)listtypealerts=stringString.Map.t