(** Utility functions for Coolaid *) module E = Errormsg module H = Hashtbl module IH = Inthash open Pretty exception GotSignal of int let withTimeout (secs: float) (* Seconds for timeout *) (handler: int -> 'b) (* What to do if we have a timeout. The * argument passed is the signal number * received. *) (f: 'a -> 'b) (* The function to run *) (arg: 'a) (* And its argument *) : 'b = let oldHandler = Sys.signal Sys.sigalrm (Sys.Signal_handle (fun i -> ignore (E.log "Got signal %d\n" i); raise (GotSignal i))) in let reset_sigalrm () = ignore (Unix.setitimer Unix.ITIMER_REAL { Unix.it_value = 0.0; Unix.it_interval = 0.0;}); Sys.set_signal Sys.sigalrm oldHandler; in ignore (Unix.setitimer Unix.ITIMER_REAL { Unix.it_value = secs; Unix.it_interval = 0.0;}); (* ignore (Unix.alarm 2); *) try let res = f arg in reset_sigalrm (); res with exc -> begin reset_sigalrm (); ignore (E.log "Got an exception\n"); match exc with GotSignal i -> handler i | _ -> raise exc end (** Print a hash table *) let docHash ?(sep=chr ',') (one: 'a -> 'b -> doc) () (h: ('a, 'b) H.t) = (H.fold (fun key data acc -> if acc == align then acc ++ one key data else acc ++ sep ++ one key data) h align) ++ unalign let hash_to_list (h: ('a, 'b) H.t) : ('a * 'b) list = H.fold (fun key data acc -> (key, data) :: acc) h [] let keys (h: ('a, 'b) H.t) : 'a list = H.fold (fun key data acc -> key :: acc) h [] let hash_copy_into (hfrom: ('a, 'b) H.t) (hto: ('a, 'b) H.t) : unit = H.clear hto; H.iter (H.add hto) hfrom let anticompare a b = compare b a ;; let rec list_drop (n : int) (xs : 'a list) : 'a list = if n < 0 then invalid_arg "Util.list_drop"; if n = 0 then xs else begin match xs with | [] -> invalid_arg "Util.list_drop" | y::ys -> list_drop (n-1) ys end let list_droptail (n : int) (xs : 'a list) : 'a list = if n < 0 then invalid_arg "Util.list_droptail"; let (ndrop,r) = List.fold_right (fun x (ndrop,acc) -> if ndrop = 0 then (ndrop, x :: acc) else (ndrop-1, acc)) xs (n,[]) in if ndrop > 0 then invalid_arg "Util.listdroptail" else r let rec list_span (p : 'a -> bool) (xs : 'a list) : 'a list * 'a list = begin match xs with | [] -> ([],[]) | x::xs' -> if p x then let (ys,zs) = list_span p xs' in (x::ys,zs) else ([],xs) end ;; let rec list_rev_append revxs ys = begin match revxs with | [] -> ys | x::xs -> list_rev_append xs (x::ys) end ;; let list_insert_by (cmp : 'a -> 'a -> int) (x : 'a) (xs : 'a list) : 'a list = let rec helper revhs ts = begin match ts with | [] -> List.rev (x::revhs) | t::ts' -> if cmp x t >= 0 then helper (t::revhs) ts' else list_rev_append (x::revhs) ts end in helper [] xs ;; let list_head_default (d : 'a) (xs : 'a list) : 'a = begin match xs with | [] -> d | x::_ -> x end ;; let rec list_iter3 f xs ys zs = begin match xs, ys, zs with | [], [], [] -> () | x::xs, y::ys, z::zs -> f x y z; list_iter3 f xs ys zs | _ -> invalid_arg "Util.list_iter3" end ;; let rec get_some_option_list (xs : 'a option list) : 'a list = begin match xs with | [] -> [] | None::xs -> get_some_option_list xs | Some x::xs -> x :: get_some_option_list xs end ;; (* tail-recursive append: reverses xs twice *) let list_append (xs: 'a list) (ys: 'a list): 'a list = match xs with (* optimize some common cases *) [] -> ys | [x] -> x::ys | _ -> list_rev_append (List.rev xs) ys let list_iteri (f: int -> 'a -> unit) (l: 'a list) : unit = let rec loop (i: int) (l: 'a list) : unit = match l with [] -> () | h :: t -> f i h; loop (i + 1) t in loop 0 l let list_mapi (f: int -> 'a -> 'b) (l: 'a list) : 'b list = let rec loop (i: int) (l: 'a list) : 'b list = match l with [] -> [] | h :: t -> let headres = f i h in headres :: loop (i + 1) t in loop 0 l let list_fold_lefti (f: 'acc -> int -> 'a -> 'acc) (start: 'acc) (l: 'a list) : 'acc = let rec loop (i, acc) l = match l with [] -> acc | h :: t -> loop (i + 1, f acc i h) t in loop (0, start) l let list_init (len : int) (init_fun : int -> 'a) : 'a list = let rec loop n acc = if n < 0 then acc else loop (n-1) ((init_fun n)::acc) in loop (len - 1) [] ;; let rec list_find_first (l: 'a list) (f: 'a -> 'b option) : 'b option = match l with [] -> None | h :: t -> begin match f h with None -> list_find_first t f | r -> r end (** Generates the range of integers starting with a and ending with b *) let int_range_list (a: int) (b: int) = list_init (b - a + 1) (fun i -> a + i) (** Some handling of registers *) type 'a growArrayFill = Elem of 'a | Susp of (int -> 'a) type 'a growArray = { gaFill: 'a growArrayFill; (** Stuff to use to fill in the array as it grows *) mutable gaMaxInitIndex: int; (** Maximum index that was written to. -1 if no writes have * been made. *) mutable gaData: 'a array; } let growTheArray (ga: 'a growArray) (len: int) (toidx: int) (why: string) : unit = if toidx >= len then begin (* Grow the array by 50% *) let newlen = toidx + 1 + len / 2 in (* ignore (E.log "growing an array to idx=%d (%s)\n" toidx why); *) let data' = begin match ga.gaFill with Elem x -> let data'' = Array.create newlen x in Array.blit ga.gaData 0 data'' 0 len; data'' | Susp f -> Array.init newlen (fun i -> if i < len then ga.gaData.(i) else f i) end in ga.gaData <- data' end let getReg (ga: 'a growArray) (r: int) : 'a = let len = Array.length ga.gaData in if r >= len then growTheArray ga len r "get"; ga.gaData.(r) let setReg (ga: 'a growArray) (r: int) (what: 'a) : unit = let len = Array.length ga.gaData in if r >= len then growTheArray ga len r "set"; if r > ga.gaMaxInitIndex then ga.gaMaxInitIndex <- r; ga.gaData.(r) <- what let newGrowArray (initsz: int) (fill: 'a growArrayFill) : 'a growArray = { gaFill = fill; gaMaxInitIndex = -1; gaData = begin match fill with Elem x -> Array.create initsz x | Susp f -> Array.init initsz f end; } let copyGrowArray (ga: 'a growArray) : 'a growArray = { ga with gaData = Array.copy ga.gaData } let deepCopyGrowArray (ga: 'a growArray) (copy: 'a -> 'a): 'a growArray = { ga with gaData = Array.map copy ga.gaData } (** Iterate over the initialized elements of the array *) let growArray_iteri (f: int -> 'a -> unit) (ga: 'a growArray) = for i = 0 to ga.gaMaxInitIndex do f i ga.gaData.(i) done (** Fold left over the initialized elements of the array *) let growArray_foldl (f: 'acc -> 'a -> 'acc) (acc: 'acc) (ga: 'a growArray) : 'acc = let rec loop (acc: 'acc) (idx: int) : 'acc = if idx > ga.gaMaxInitIndex then acc else loop (f acc ga.gaData.(idx)) (idx + 1) in loop acc 0 let hasPrefix (prefix: string) (what: string) : bool = let pl = String.length prefix in try String.sub what 0 pl = prefix with Invalid_argument _ -> false let restoreRef ?(deepCopy=(fun x -> x)) (r: 'a ref) : (unit -> unit) = let old = deepCopy !r in (fun () -> r := old) let restoreHash ?deepCopy (h: ('a, 'b) H.t) : (unit -> unit) = let old = match deepCopy with None -> H.copy h | Some f -> let old = H.create (H.length h) in H.iter (fun k d -> H.add old k (f d)) h; old in (fun () -> hash_copy_into old h) let restoreIntHash ?deepCopy (h: 'a IH.t) : (unit -> unit) = let old = match deepCopy with None -> IH.copy h | Some f -> let old = IH.create (IH.length h) in IH.iter (fun k d -> IH.add old k (f d)) h; old in (fun () -> IH.clear h; IH.iter (fun i k -> IH.add h i k) old) let restoreArray ?deepCopy (a: 'a array) : (unit -> unit) = let old = Array.copy a in (match deepCopy with None -> () | Some f -> Array.iteri (fun i v -> old.(i) <- f v) old); (fun () -> Array.blit old 0 a 0 (Array.length a)) let runThunks (l: (unit -> unit) list) : (unit -> unit) = fun () -> List.iter (fun f -> f ()) l (* Memoize *) let memoize (h: ('a, 'b) Hashtbl.t) (arg: 'a) (f: 'a -> 'b) : 'b = try Hashtbl.find h arg with Not_found -> begin let res = f arg in Hashtbl.add h arg res; res end (* Just another name for memoize *) let findOrAdd h arg f = memoize h arg f (* A tryFinally function *) let tryFinally (main: 'a -> 'b) (* The function to run *) (final: 'b option -> unit) (* Something to run at the end *) (arg: 'a) : 'b = try let res: 'b = main arg in final (Some res); res with e -> begin final None; raise e end let valOf : 'a option -> 'a = function None -> raise (Failure "Util.valOf") | Some x -> x (** * An accumulating for loop. * * Initialize the accumulator with init. The current index and accumulator * from the previous iteration is passed to f. *) let fold_for ~(init: 'a) ~(lo: int) ~(hi: int) (f: int -> 'a -> 'a) = let rec forloop i acc = if i > hi then acc else forloop (i+1) (f i acc) in forloop lo init (************************************************************************) module type STACK = sig type 'a t (** The type of stacks containing elements of type ['a]. *) exception Empty (** Raised when {!Stack.pop} or {!Stack.top} is applied to an empty stack. *) val create : unit -> 'a t (** Return a new stack, initially empty. *) val push : 'a -> 'a t -> unit (** [push x s] adds the element [x] at the top of stack [s]. *) val pop : 'a t -> 'a (** [pop s] removes and returns the topmost element in stack [s], or raises [Empty] if the stack is empty. *) val top : 'a t -> 'a (** [top s] returns the topmost element in stack [s], or raises [Empty] if the stack is empty. *) val clear : 'a t -> unit (** Discard all elements from a stack. *) val copy : 'a t -> 'a t (** Return a copy of the given stack. *) val is_empty : 'a t -> bool (** Return [true] if the given stack is empty, [false] otherwise. *) val length : 'a t -> int (** Return the number of elements in a stack. *) val iter : ('a -> unit) -> 'a t -> unit (** [iter f s] applies [f] in turn to all elements of [s], from the element at the top of the stack to the element at the bottom of the stack. The stack itself is unchanged. *) end module Stack = struct type 'a t = { mutable length : int; stack : 'a Stack.t; } exception Empty let create () = { length = 0; stack = Stack.create(); } let push x s = s.length <- s.length + 1; Stack.push x s.stack let pop s = s.length <- s.length - 1; Stack.pop s.stack let top s = Stack.top s.stack let clear s = s.length <- 0; Stack.clear s.stack let copy s = { length = s.length; stack = Stack.copy s.stack; } let is_empty s = Stack.is_empty s.stack let length s = s.length let iter f s = Stack.iter f s.stack end (************************************************************************) let absoluteFilename (fname: string) = if Filename.is_relative fname then Filename.concat (Sys.getcwd ()) fname else fname (* mapNoCopy is like map but avoid copying the list if the function does not * change the elements. *) let rec mapNoCopy (f: 'a -> 'a) = function [] -> [] | (i :: resti) as li -> let i' = f i in let resti' = mapNoCopy f resti in if i' != i || resti' != resti then i' :: resti' else li let rec mapNoCopyList (f: 'a -> 'a list) = function [] -> [] | (i :: resti) as li -> let il' = f i in let resti' = mapNoCopyList f resti in match il' with [i'] when i' == i && resti' == resti -> li | _ -> il' @ resti' (* Use a filter function that does not rewrite the list unless necessary *) let rec filterNoCopy (f: 'a -> bool) (l: 'a list) : 'a list = match l with [] -> [] | h :: rest when not (f h) -> filterNoCopy f rest | h :: rest -> let rest' = filterNoCopy f rest in if rest == rest' then l else h :: rest' (** Join a list of strings *) let rec joinStrings (sep: string) (sl: string list) = match sl with [] -> "" | [s1] -> s1 | s1 :: ((_ :: _) as rest) -> s1 ^ sep ^ joinStrings sep rest (************************************************************************ Configuration ************************************************************************) (** The configuration data can be of several types **) type configData = ConfInt of int | ConfBool of bool | ConfFloat of float | ConfString of string | ConfList of configData list (* Store here window configuration file *) let configurationData: (string, configData) H.t = H.create 13 let clearConfiguration () = H.clear configurationData let setConfiguration (key: string) (c: configData) = H.replace configurationData key c let findConfiguration (key: string) : configData = H.find configurationData key let findConfigurationInt (key: string) : int = match findConfiguration key with ConfInt i -> i | _ -> ignore (E.warn "Configuration %s is not an integer" key); raise Not_found let useConfigurationInt (key: string) (f: int -> unit) = try f (findConfigurationInt key) with Not_found -> () let findConfigurationString (key: string) : string = match findConfiguration key with ConfString s -> s | _ -> ignore (E.warn "Configuration %s is not a string" key); raise Not_found let useConfigurationString (key: string) (f: string -> unit) = try f (findConfigurationString key) with Not_found -> () let findConfigurationBool (key: string) : bool = match findConfiguration key with ConfBool b -> b | _ -> ignore (E.warn "Configuration %s is not a boolean" key); raise Not_found let useConfigurationBool (key: string) (f: bool -> unit) = try f (findConfigurationBool key) with Not_found -> () let findConfigurationList (key: string) : configData list = match findConfiguration key with ConfList l -> l | _ -> ignore (E.warn "Configuration %s is not a list" key); raise Not_found let useConfigurationList (key: string) (f: configData list -> unit) = try f (findConfigurationList key) with Not_found -> () let saveConfiguration (fname: string) = (** Convert configuration data to a string, for saving externally *) let configToString (c: configData) : string = let buff = Buffer.create 80 in let rec loop (c: configData) : unit = match c with ConfInt i -> Buffer.add_char buff 'i'; Buffer.add_string buff (string_of_int i); Buffer.add_char buff ';' | ConfBool b -> Buffer.add_char buff 'b'; Buffer.add_string buff (string_of_bool b); Buffer.add_char buff ';' | ConfFloat f -> Buffer.add_char buff 'f'; Buffer.add_string buff (string_of_float f); Buffer.add_char buff ';' | ConfString s -> if String.contains s '"' then E.s (E.unimp "Guilib: configuration string contains quotes"); Buffer.add_char buff '"'; Buffer.add_string buff s; Buffer.add_char buff '"'; (* '"' *) | ConfList l -> Buffer.add_char buff '['; List.iter loop l; Buffer.add_char buff ']' in loop c; Buffer.contents buff in try let oc = open_out fname in ignore (E.log "Saving configuration to %s\n" (absoluteFilename fname)); H.iter (fun k c -> output_string oc (k ^ "\n"); output_string oc ((configToString c) ^ "\n")) configurationData; close_out oc with _ -> ignore (E.warn "Cannot open configuration file %s\n" fname) (** Make some regular expressions early *) let intRegexp = Str.regexp "i\\([0-9]+\\);" let floatRegexp = Str.regexp "f\\([0-9]+\\.[0-9]+\\);" let boolRegexp = Str.regexp "b\\(\\(true\\)\\|\\(false\\)\\);" let stringRegexp = Str.regexp "\"\\([^\"]*\\)\"" let loadConfiguration (fname: string) : unit = H.clear configurationData; let stringToConfig (s: string) : configData = let idx = ref 0 in (** the current index *) let l = String.length s in let rec getOne () : configData = if !idx >= l then raise Not_found; if Str.string_match intRegexp s !idx then begin idx := Str.match_end (); ConfInt (int_of_string (Str.matched_group 1 s)) end else if Str.string_match floatRegexp s !idx then begin idx := Str.match_end (); ConfFloat (float_of_string (Str.matched_group 1 s)) end else if Str.string_match boolRegexp s !idx then begin idx := Str.match_end (); ConfBool (bool_of_string (Str.matched_group 1 s)) end else if Str.string_match stringRegexp s !idx then begin idx := Str.match_end (); ConfString (Str.matched_group 1 s) end else if String.get s !idx = '[' then begin (* We are starting a list *) incr idx; let rec loop (acc: configData list) : configData list = if !idx >= l then begin ignore (E.warn "Non-terminated list in configuration %s" s); raise Not_found end; if String.get s !idx = ']' then begin incr idx; List.rev acc end else loop (getOne () :: acc) in ConfList (loop []) end else begin ignore (E.warn "Bad configuration element in a list: %s\n" (String.sub s !idx (l - !idx))); raise Not_found end in getOne () in (try let ic = open_in fname in ignore (E.log "Loading configuration from %s\n" (absoluteFilename fname)); (try while true do let k = input_line ic in let s = input_line ic in try let c = stringToConfig s in setConfiguration k c with Not_found -> () done with End_of_file -> ()); close_in ic; with _ -> () (* no file, ignore *)); () (*********************************************************************) type symbol = int (**{ Registering symbol names} *) let registeredSymbolNames: (string, symbol) H.t = H.create 113 let symbolNames: string IH.t = IH.create 113 let nextSymbolId = ref 0 (* When we register symbol ranges, we store a naming function for use later * when we print the symbol *) let symbolRangeNaming: (int * int * (int -> string)) list ref = ref [] (* Reset the symbols. We want to allow the registration of symbols at the * top-level. This means that we cannot simply clear the hash tables. The * first time we call "reset" we actually remember the state. *) let resetThunk: (unit -> unit) option ref = ref None let snapshotSymbols () : unit -> unit = runThunks [ restoreIntHash symbolNames; restoreRef nextSymbolId; restoreHash registeredSymbolNames; restoreRef symbolRangeNaming ] let resetSymbols () = match !resetThunk with None -> resetThunk := Some (snapshotSymbols ()) | Some t -> t () let dumpSymbols () = ignore (E.log "Current symbols\n"); IH.iter (fun i k -> ignore (E.log " %s -> %d\n" k i)) symbolNames; () let newSymbol (n: string) : symbol = assert(not (H.mem registeredSymbolNames n)); let id = !nextSymbolId in incr nextSymbolId; H.add registeredSymbolNames n id; IH.add symbolNames id n; id let registerSymbolName (n: string) : symbol = try H.find registeredSymbolNames n with Not_found -> begin newSymbol n end (** Register a range of symbols. The mkname function will be invoked for * indices starting at 0 *) let registerSymbolRange (count: int) (mkname: int -> string) : symbol = if count < 0 then E.s (E.bug "registerSymbolRange: invalid counter"); let first = !nextSymbolId in nextSymbolId := !nextSymbolId + count; symbolRangeNaming := (first, !nextSymbolId - 1, mkname) :: !symbolRangeNaming; first let symbolName (id: symbol) : string = try IH.find symbolNames id with Not_found -> (* Perhaps it is one of the lazily named symbols *) try let (fst, _, mkname) = List.find (fun (fst,lst,_) -> fst <= id && id <= lst) !symbolRangeNaming in let n = mkname (id - fst) in IH.add symbolNames id n; n with Not_found -> ignore (E.warn "Cannot find the name of symbol %d" id); "symbol" ^ string_of_int id (************************************************************************) (** {1 Int32 Operators} *) module Int32Op = struct exception IntegerTooLarge let to_int (i: int32) = let i' = Int32.to_int i in (* Silently drop the 32nd bit *) if i = Int32.of_int i' then i' else raise IntegerTooLarge let (<%) = (fun x y -> (Int32.compare x y) < 0) let (<=%) = (fun x y -> (Int32.compare x y) <= 0) let (>%) = (fun x y -> (Int32.compare x y) > 0) let (>=%) = (fun x y -> (Int32.compare x y) >= 0) let (<>%) = (fun x y -> (Int32.compare x y) <> 0) let (+%) = Int32.add let (-%) = Int32.sub let ( *% ) = Int32.mul let (/%) = Int32.div let (~-%) = Int32.neg (* We cannot use the <<% because it trips camlp4 *) let sll = fun i j -> Int32.shift_left i (to_int j) let (>>%) = fun i j -> Int32.shift_right i (to_int j) let (>>>%) = fun i j -> Int32.shift_right_logical i (to_int j) end (*********************************************************************) let equals x1 x2 : bool = (compare x1 x2) = 0