Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file b_utils.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505(** Utilities *)(* Function names should be unambiguous enough to make it safe to open this
module anywhere. *)openTsdlexceptionSdl_errorofstringletnop_=()letdebug=letd=trymatchSys.getenv"BOGUE_DEBUG"|>String.capitalize_asciiwith|"YES"|"1"|"TRUE"->true|_->falsewithNot_found->false(* set to false for production *)|e->raiseeinrefdletlog_channel=refstdoutletclose_log()=close_out!log_channelletflush_log()=flush!log_channelletdebug_thread=1letdebug_warning=2letdebug_graphics=4letdebug_error=8letdebug_io=16letdebug_memory=32letdebug_board=64letdebug_event=128letdebug_custom=256letdebug_user=512(* messages that can be of interest to the (non developer)
user *)letdebug_disable=1024(* use this to disable the debug message *)letdebug_code=ref(debug_error+debug_warning+debug_graphics+debug_thread+debug_io+debug_board+debug_memory+debug_event+debug_custom+debug_user)(* debug_code := !debug_code lor debug_thread;; *)(* let debug_code = ref 511;; *)(* everything *)letdebug_vars=["Thread",debug_thread;"Warning",debug_warning;"Graphics",debug_graphics;"ERROR",debug_error;"I/O",debug_io;"Memory",debug_memory;"Board",debug_board;"Event",debug_event;"Custom",debug_custom;"User",debug_user]letdebug_to_string=letdebug_array=Array.of_listdebug_varsinfunc->letrecloopinlist=ifi=0||n=16thenlistelseletcode=iland1inifcode=0thenloop(ilsr1)(n+1)listelselets=ifn>0&&n<11thenfstdebug_array.(n-1)else"Unknown"inloop(ilsr1)(n+1)(s::list)inString.concat"; "(loopc1[])(* Should we put this in a Var/Atomic? *)(* TODO: use this to reduce the number of lock if there is no thread *)letthreads_created=ref0(* couleurs xterm, cf : http://linuxgazette.net/issue65/padala.html *)letxterm_red="\027[0;31m"letxterm_blue="\027[0;94m"letxterm_light_grey="\027[1;30m"letxterm_nc="\027[0m"letprints=Printf.ksprintfprint_endlinesletinvalid_args=Printf.ksprintfinvalid_argsletprint_debug_olds=Printf.ksprintf(funs->if!debugthenprint_endline(xterm_blue^"["^(string_of_int(Int32.to_int(Sdl.get_ticks())mod60000))^"] : "^xterm_nc^s))sletdebug_select_oldcodes=if!debug&&(codeland!debug_code<>0)thenprint_endline(xterm_red^(debug_to_stringcode)^xterm_nc^": "^s)letiksprintf_f=Printf.ikfprintf(fun()->())()(* [printd] is used extensively to trace the execution of Bogue; the syntax is
[printd some_debug_code "My sprintf style string, like %s=%d." (name_of x)
(compute x)] It will output log messages only if [!debug] is true. However,
all arguments are evaluated (like [name_of x] and [compute x] in the example
above), no matter the value of !debug. If their evaluation is costly, one
should rather write [if !debug then printd .... ] *)letprintdcode=letdebug=!debug&&(codeland!debug_code<>0)inletprintf=Printf.(ifdebugthenksprintfelseiksprintf)inprintf(funs->output_string!log_channel(xterm_blue^"["^(string_of_int(Int32.to_int(Sdl.get_ticks())mod60000))^"]"^xterm_light_grey^"["^(string_of_int(Thread.id(Thread.self())))^"]"^xterm_nc^" :\t "^xterm_nc^xterm_red^(debug_to_stringcode)^xterm_nc^": "^s^"\n");if!log_channel=stdoutthenflush!log_channel)(* check if string s starts with string sub *)letstartswithssub=String.lengthsub=0||beginString.lengths>=String.lengthsub&&String.subs0(String.lengthsub)=subend(* create function for generating integers, starting from 1 *)letfresh_int()=letid=ref0infun()->if!id<max_intthen(incrid;!id)elsefailwith"Too many ids created!"(* round float to nearest integer: *)letroundx=int_of_float(Float.roundx)letpi=Float.piletsquarex=x*.xletrecpwr_oldkx=assert(k>=0);ifk=0then1.elsex*.(pwr_old(k-1)x)letpwrkx=Float.powx(floatk)(* Use Int.max and Int.min for ocaml >= 4.13 *)letimax(x:int)(y:int)=ifx>ythenxelseyletimin(x:int)(y:int)=ifx<ythenxelseyletfmax=Float.maxletfmin=Float.minletrecgcdab=ifb=0thenaelsegcdb(amodb)letgo:'aTsdl.Sdl.result->'a=function|Error_->raise(Sdl_error("SDL ERROR: "^(Sdl.get_error())))|Okr->r(* List utilities *)(******************)letlist_iterlistf=List.iterflist(* Return an option containing the first element of the list for which the
function f does not return None *)letreclist_checkfl=matchlwith|[]->None|x::rest->beginmatchfxwith|None->list_checkfrest|s->send(* Idem where the function f returns true *)(* let list_check_ok f l = *)(* list_check (fun x -> if f x then Some x else None) l *)(* This is now List.find_opt *)letlist_check_ok=List.find_opt(* Return the first element of the list satisfying p, and its index *)letlist_findipl=letrecloopi=function|[]->None|a::rest->ifpathenSome(a,i)elseloop(i+1)restinloop0l(* Split l into two lists l1rev,l2, where the first element of l2 is the first
element of l for which f is true (l2 can be empty). We always have: l =
List.rev_append l1rev l2. *)letlist_split_first_revfl=letrecloopl1rev=function|[]->l1rev,[]|x::restasl2->iffxthenl1rev,l2elseloop(x::l1rev)restinloop[]lletlist_split_beforelequalx=letl1rev,l2=list_split_first_rev(equalx)linList.revl1rev,l2(* Return l1rev, x, l2, where x is the first element for which f x = true and l
is the concatenation of List.rev l1, x and l2 *)letlist_split_at_revfl=matchlist_split_first_revflwith|_,[]->raiseNot_found|l1,x::l2->l1,x,l2(* Replace the first element for which f returns true by x *)letlist_replaceflx=letl1,_,l2=list_split_at_revflinList.rev_appendl1(x::l2)(* returns the list where the first element for which f is true is removed *)letlist_remove_firstfl=matchlist_split_first_revflwith|_,[]->raiseNot_found|l1,_::l2->List.rev_appendl1l2(* Splits a list atfer the xth element. (x=0 for first element; the first
returned list has length x.) *)letsplit_list_revlistx=letrecloopheadtaili=ifi>=xthen(head,tail)elsematchtailwith|[]->printddebug_error"Error: position too far in list";raiseNot_found|a::rest->loop(a::head)rest(i+1)inloop[]list0letsplit_listlistx=letdaeh,tail=split_list_revlistxinList.revdaeh,tail(* checks if 'a' contained in the list, with 'equal' function *)letrecmemequalalist=matchlistwith|[]->false|b::rest->equalab||memequalarest(* checks if all elements are different (using the 'equal' function) *)(* not used, use "repeated" below instead *)letrecinjectiveequallist=matchlistwith|[]->true|a::rest->ifmemequalarestthenfalseelseinjectiveequalrest(* Check if some element is repeated and return the first one. Note this is
O(n²) in the worse case. One could use sort_uniq instead which should be O(n
log n). *)letrecrepeatedequallist=matchlistwith|[]->None|a::rest->ifmemequalarestthenSomeaelserepeatedequalrest(* max of a list *)(* in case of equal elements, the *first* one is selected *)letlist_maxcomparelist=matchlistwith|[]->None|a::rest->Some(List.fold_left(funmaxx->(* printd debug_warning "Compare=%d" (compare x min); *)ifcomparexmax>0thenxelsemax)arest)letlist_mincomparelist=matchlistwith|[]->None|a::rest->Some(List.fold_left(funminx->ifcomparexmin<0thenxelsemin)arest)letreclist_last=function|[]->printddebug_error"[list_last]: empty list";raiseNot_found|[x]->x|_::rest->list_lastrestletreclist_last_opt=function|[]->None|[x]->Somex|_::rest->list_last_optrest(* Return the element following the first occurence of x, or None if x is the
last element. It can be equal to x if x is repeated. *)letreclist_nextequalx=function|[]->printddebug_error"[list_next]: empty list";raiseNot_found|[a]whenequalax->None|[_]->printddebug_error"[list_next] does not contain x";raiseNot_found|a::b::rest->ifequalaxthenSomebelselist_nextequalx(b::rest)(* Return the element preceeding the first occurence of x, or None if x is the
first element. It cannot be equal to x. *)letreclist_prevequalx=function|[]->printddebug_error"[list_prev]: empty list";raiseNot_found|a::_whenequalax->None|[_]->printddebug_error"[list_prev] does not contain x";raiseNot_found|a::b::rest->ifequalbxthenSomeaelselist_prevequalx(b::rest)lettest_list_prev()=assert(list_prev(=)2[2;1;2;3]=None);let()=tryignore(list_prev(=)1[2])with|Not_found->()|_->assert(false)inassert(list_prev(=)1[2;1]=Some2);assert(list_prev(=)3[1;2;3]=Some2)letlist_hd_opt=function[]->None|a::_->Somealetrunf=f()letapplyxf=fx(* Monadic operations. Starting with ocaml 4.08 we can use the Option module.
Warning, all arguments being evaluated even if not used, for costly argments
it is better to use the original pattern matching | Some | None *)exceptionNone_option(* used when the option should not be None. *)(* let map_option o f = match o with
* | Some x -> Some (f x)
* | None -> None *)letmap_optionof=Option.mapfo(* let do_option o f = match o with
* | Some x -> f x
* | None -> () *)letdo_optionof=Option.iterfo(* let check_option o f = match o with
* | Some x -> f x
* | None -> None *)letcheck_option=Option.bind(* Warning the "d" is always evaluated, so it's not always a good idea to use
this...use the lazy or fn version instead. *)letdefaultod=matchowith|Somex->x|None->dletdefault_lazyod=matchowith|Somex->x|None->Lazy.forcedletdefault_fnof=matchowith|Somex->x|None->f()letdefault_optionood=matchowith|None->od|o->oletmap2_optiono1o2f=matcho1,o2with|Somex1,Somex2->Some(fx1x2)|_->Noneletone_of_twoo1o2=matcho1,o2with|None,None->None|_,None->o1|None,_->o2|_->printddebug_warning"one_of_two takes first of two options";o1letremove_option=function|Somex->x|None->raiseNone_optionletstring_of_optionf=function|Somex->fx|None->"None"let(let@)fx=fx(** This can be used to write, for instance,
[let@ x = Var.with_protect v in foo] instead of
[Var.with_protect v (fun x -> foo)],
where [foo] is any expression using [x].
{b Warning:} the whole sequence of expressions is used. For instance
[let@ x = Var.with_protect v in foo; bar]
will use the function
[x -> foo; bar]
and hence is not the same as
[Var.with_protect v (fun x -> foo); bar].
It can be wise to write [begin let@ ... in .. end]
See also https://github.com/ocaml/ocaml/pull/9887 *)(* memo *)(* standard memo fns. Don't use when the variable is mutable, it would store the
old value for ever when the variable changes. *)letmemo~namef=letstore=Hashtbl.create100infunx->matchHashtbl.find_optstorexwith|Somey->y|None->letresult=fxinHashtbl.addstorexresult;printddebug_memory"##Size of %s Hashtbl : %u"name(Hashtbl.lengthstore);resultletmemo2f=letstore=Hashtbl.create100infunxy->matchHashtbl.find_optstore(x,y)with|Somey->y|None->letresult=fxyinHashtbl.addstore(x,y)result;printddebug_memory"##Size of Hashtbl2 : %u"(Hashtbl.lengthstore);resultletmemo3f=letstore3=Hashtbl.create100in(funxyz->matchHashtbl.find_optstore3(x,y,z)with|Somey->y|None->letresult=fxyzinHashtbl.addstore3(x,y,z)result;printddebug_memory"###Size of Hashtbl3 : %u"(Hashtbl.lengthstore3);result),store3(* inutile ? *)letlist_sumlist=List.fold_left(+)0list(* let find_file list_list = *)letone_line_command_ouputcommand=trylets=Unix.open_process_in(command)inletres=trySome(input_lines)with_->NoneinbeginmatchUnix.close_process_inswith|Unix.WEXITED0->res|Unix.WEXITED1->None(* in principle this is redundant since `res`
is already None at this point *)|_->printd(debug_error+debug_io)"The `%s` command exited with error."command;Noneendwith|_->printd(debug_error+debug_io)"Cannot use the `%s` command."command;None(* Ocaml >= 4.13 *)(* let string_starts_with ~prefix s = *)(* let len_s = length s *)(* and len_pre = length prefix in *)(* let rec aux i = *)(* if i = len_pre then true *)(* else if unsafe_get s i <> unsafe_get prefix i then false *)(* else aux (i + 1) *)(* in len_s >= len_pre && aux 0 *)(* Result of [uname -s] for Unixes. Can be:
"FreeBSD"
"Linux"
"Darwin"
"NetBSD"
"OpenBSD"
"SunOS"
"AIX"
"HP-UX"
"CYGWIN_NT-*" ?
"MSYS_NT-**" ?
"Windows_NT" (si uname est utilisé via un environnement Unix sur un système Windows).
"Minix"
"DragonFly"
etc.
*)letos_type=letres=refNoneinfun()->match!reswith|Somes->s|None->lets=matchSys.os_typewith|"Unix"->beginmatchone_line_command_ouput"uname -s"with|None->"Unix_unknown"|Somes->send|s->sinres:=Somes;sletwhichcommand=(* BETTER: (specially for portability to WIN/MAC) use
https://opam.ocaml.org/packages/fileutils/ *)letcmdlinecommand=ifSys.win32then"where "^command^" 2> NUL"else"which "^command^" 2>/dev/null"inone_line_command_ouput(cmdlinecommand)