(* * Copyright (c) 2012 Anil Madhavapeddy * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) type buffer = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t (* Note: * * We try to maintain the property that no constructed [t] can ever point out of * its underlying buffer. This property is guarded by all of the constructing * functions and the fact that the type is private, and used by various * functions that would otherwise be completely unsafe. * * Furthermore, no operation on [t] is allowed to extend the view on the * underlying Bigarray structure, only narrowing is allowed. * * All well-intended souls are kindly invited to cross-check that the code * indeed maintains this invariant. *) type t = { buffer: buffer; off : int; len : int; } let pp_t ppf t = Format.fprintf ppf "[%d,%d](%d)" t.off t.len (Bigarray.Array1.dim t.buffer) let string_t ppf str = Format.fprintf ppf "[%d]" (String.length str) let bytes_t ppf str = Format.fprintf ppf "[%d]" (Bytes.length str) let err fmt = let b = Buffer.create 20 in (* for thread safety. *) let ppf = Format.formatter_of_buffer b in let k ppf = Format.pp_print_flush ppf (); invalid_arg (Buffer.contents b) in Format.kfprintf k ppf fmt let err_of_bigarray t = err "Cstruct.of_bigarray off=%d len=%d" t let err_sub t = err "Cstruct.sub: %a off=%d len=%d" pp_t t let err_shift t = err "Cstruct.shift %a %d" pp_t t let err_shiftv n = err "Cstruct.shiftv short by %d" n let err_copy_to_string caller t = err "Cstruct.%s %a off=%d len=%d" caller pp_t t let err_to_hex_string t = err "Cstruct.to_hex_string %a off=%d len=%d" pp_t t let err_blit_src src dst = err "Cstruct.blit src=%a dst=%a src-off=%d len=%d" pp_t src pp_t dst let err_blit_dst src dst = err "Cstruct.blit src=%a dst=%a dst-off=%d len=%d" pp_t src pp_t dst let err_blit_from_string_src src dst = err "Cstruct.blit_from_string src=%a dst=%a src-off=%d len=%d" string_t src pp_t dst let err_blit_from_string_dst src dst = err "Cstruct.blit_from_string src=%a dst=%a dst-off=%d len=%d" string_t src pp_t dst let err_blit_from_bytes_src src dst = err "Cstruct.blit_from_bytes src=%a dst=%a src-off=%d len=%d" bytes_t src pp_t dst let err_blit_from_bytes_dst src dst = err "Cstruct.blit_from_bytes src=%a dst=%a dst-off=%d len=%d" bytes_t src pp_t dst let err_blit_to_bytes_src src dst = err "Cstruct.blit_to_bytes src=%a dst=%a src-off=%d len=%d" pp_t src bytes_t dst let err_blit_to_bytes_dst src dst= err "Cstruct.blit_to_bytes src=%a dst=%a dst-off=%d len=%d" pp_t src bytes_t dst let err_invalid_bounds f = err "invalid bounds in Cstruct.%s %a off=%d len=%d" f pp_t [@@inline never] let err_split t = err "Cstruct.split %a start=%d off=%d" pp_t t let err_iter t = err "Cstruct.iter %a i=%d len=%d" pp_t t let of_bigarray ?(off=0) ?len buffer = let dim = Bigarray.Array1.dim buffer in let len = match len with | None -> dim - off | Some len -> len in if off < 0 || len < 0 || off + len < 0 || off + len > dim then err_of_bigarray off len else { buffer; off; len } let to_bigarray buffer = Bigarray.Array1.sub buffer.buffer buffer.off buffer.len let create_unsafe len = let buffer = Bigarray.(Array1.create char c_layout len) in { buffer ; len ; off = 0 } let check_bounds t len = len >= 0 && Bigarray.Array1.dim t.buffer >= len let empty = create_unsafe 0 external check_alignment_bigstring : buffer -> int -> int -> bool = "caml_check_alignment_bigstring" let check_alignment t alignment = if alignment > 0 then check_alignment_bigstring t.buffer t.off alignment else invalid_arg "check_alignment must be positive integer" type byte = char let byte (i:int) : byte = Char.chr i let byte_to_int (b:byte) = int_of_char b type uint8 = int type uint16 = int type uint32 = int32 type uint64 = int64 let debug t = let max_len = Bigarray.Array1.dim t.buffer in if t.off+t.len > max_len || t.len < 0 || t.off < 0 then ( Format.printf "ERROR: t.off+t.len=%d %a\n%!" (t.off+t.len) pp_t t; assert false; ) else Format.asprintf "%a" pp_t t let sub t off len = (* from https://github.com/mirage/ocaml-cstruct/pull/245 Cstruct.sub should select what a programmer intuitively expects a sub-cstruct to be. I imagine holding out my hands, with the left representing the start offset and the right the end. I think of a sub-cstruct as any span within this range. If I move my left hand only to the right (new_start >= t.off), and my right hand only to the left (new_end <= old_end), and they don't cross (new_start <= new_end), then I feel sure the result will be a valid sub-cstruct. And if I violate any one of these constraints (e.g. moving my left hand further left), then I feel sure that the result wouldn't be something I'd consider to be a sub-cstruct. Wrapping considerations in modular arithmetic: Note that if x is non-negative, and x + y wraps, then x + y must be negative. This is easy to see with modular arithmetic because if y is negative then the two arguments will cancel to some degree the result cannot be further from zero than one of the arguments. If y is positive then x + y can wrap, but even max_int + max_int doesn't wrap all the way to zero. The three possibly-wrapping operations are: new_start = t.off + off. t.off is non-negative so if this wraps then new_start will be negative and will fail the new_start >= t.off test. new_end = new_start + len. The above test ensures that new_start is non-negative in any successful return. So if this wraps then new_end will be negative and will fail the new_start <= new_end test. old_end = t.off + t.len. This uses only the existing trusted values. It could only wrap if the underlying bigarray had a negative length! *) let new_start = t.off + off in let new_end = new_start + len in let old_end = t.off + t.len in if new_start >= t.off && new_end <= old_end && new_start <= new_end then { t with off = new_start ; len } else err_sub t off len let shift t amount = let off = t.off + amount in let len = t.len - amount in if amount < 0 || amount > t.len || not (check_bounds t (off+len)) then err_shift t amount else { t with off; len } let rec skip_empty = function | t :: ts when t.len = 0 -> skip_empty ts | x -> x let rec shiftv ts = function | 0 -> skip_empty ts | n -> match ts with | [] -> err_shiftv n | t :: ts when n >= t.len -> shiftv ts (n - t.len) | t :: ts -> shift t n :: ts external unsafe_blit_bigstring_to_bigstring : buffer -> int -> buffer -> int -> int -> unit = "caml_blit_bigstring_to_bigstring" [@@noalloc] external unsafe_blit_string_to_bigstring : string -> int -> buffer -> int -> int -> unit = "caml_blit_string_to_bigstring" [@@noalloc] external unsafe_blit_bytes_to_bigstring : Bytes.t -> int -> buffer -> int -> int -> unit = "caml_blit_string_to_bigstring" [@@noalloc] external unsafe_blit_bigstring_to_bytes : buffer -> int -> Bytes.t -> int -> int -> unit = "caml_blit_bigstring_to_string" [@@noalloc] external unsafe_compare_bigstring : buffer -> int -> buffer -> int -> int -> int = "caml_compare_bigstring" [@@noalloc] external unsafe_fill_bigstring : buffer -> int -> int -> int -> unit = "caml_fill_bigstring" [@@noalloc] let copy_to_string caller src srcoff len = if len < 0 || srcoff < 0 || src.len - srcoff < len then err_copy_to_string caller src srcoff len else let b = Bytes.create len in unsafe_blit_bigstring_to_bytes src.buffer (src.off+srcoff) b 0 len; (* The following call is safe, since b is not visible elsewhere. *) Bytes.unsafe_to_string b let copy = copy_to_string "copy" let blit src srcoff dst dstoff len = if len < 0 || srcoff < 0 || src.len - srcoff < len then err_blit_src src dst srcoff len else if dstoff < 0 || dst.len - dstoff < len then err_blit_dst src dst dstoff len else unsafe_blit_bigstring_to_bigstring src.buffer (src.off+srcoff) dst.buffer (dst.off+dstoff) len let sub_copy cstr off len : t = let cstr2 = create_unsafe len in blit cstr off cstr2 0 len; cstr2 let blit_from_string src srcoff dst dstoff len = if len < 0 || srcoff < 0 || dstoff < 0 || String.length src - srcoff < len then err_blit_from_string_src src dst srcoff len else if dst.len - dstoff < len then err_blit_from_string_dst src dst dstoff len else unsafe_blit_string_to_bigstring src srcoff dst.buffer (dst.off+dstoff) len let blit_from_bytes src srcoff dst dstoff len = if len < 0 || srcoff < 0 || dstoff < 0 || Bytes.length src - srcoff < len then err_blit_from_bytes_src src dst srcoff len else if dst.len - dstoff < len then err_blit_from_bytes_dst src dst dstoff len else unsafe_blit_bytes_to_bigstring src srcoff dst.buffer (dst.off+dstoff) len let blit_to_bytes src srcoff dst dstoff len = if len < 0 || srcoff < 0 || dstoff < 0 || src.len - srcoff < len then err_blit_to_bytes_src src dst srcoff len else if Bytes.length dst - dstoff < len then err_blit_to_bytes_dst src dst dstoff len else unsafe_blit_bigstring_to_bytes src.buffer (src.off+srcoff) dst dstoff len let compare t1 t2 = let l1 = t1.len and l2 = t2.len in match compare l1 l2 with | 0 -> ( match unsafe_compare_bigstring t1.buffer t1.off t2.buffer t2.off l1 with | 0 -> 0 | r -> if r < 0 then -1 else 1 ) | r -> r let equal t1 t2 = compare t1 t2 = 0 (* Note that this is only safe as long as all [t]s are coherent. *) let memset t x = unsafe_fill_bigstring t.buffer t.off t.len x let create len = let t = create_unsafe len in memset t 0; t let set_uint8 t i c = if i >= t.len || i < 0 then err_invalid_bounds "set_uint8" t i 1 else Bigarray.Array1.set t.buffer (t.off+i) (Char.unsafe_chr c) let set_char t i c = if i >= t.len || i < 0 then err_invalid_bounds "set_char" t i 1 else Bigarray.Array1.set t.buffer (t.off+i) c let get_uint8 t i = if i >= t.len || i < 0 then err_invalid_bounds "get_uint8" t i 1 else Char.code (Bigarray.Array1.get t.buffer (t.off+i)) let get_char t i = if i >= t.len || i < 0 then err_invalid_bounds "get_char" t i 1 else Bigarray.Array1.get t.buffer (t.off+i) external ba_set_int16 : buffer -> int -> uint16 -> unit = "%caml_bigstring_set16u" external ba_set_int32 : buffer -> int -> uint32 -> unit = "%caml_bigstring_set32u" external ba_set_int64 : buffer -> int -> uint64 -> unit = "%caml_bigstring_set64u" external ba_get_int16 : buffer -> int -> uint16 = "%caml_bigstring_get16u" external ba_get_int32 : buffer -> int -> uint32 = "%caml_bigstring_get32u" external ba_get_int64 : buffer -> int -> uint64 = "%caml_bigstring_get64u" external swap16 : int -> int = "%bswap16" external swap32 : int32 -> int32 = "%bswap_int32" external swap64 : int64 -> int64 = "%bswap_int64" let set_uint16 swap p t i c = if i > t.len - 2 || i < 0 then err_invalid_bounds (p ^ ".set_uint16") t i 2 else ba_set_int16 t.buffer (t.off+i) (if swap then swap16 c else c) [@@inline] let set_uint32 swap p t i c = if i > t.len - 4 || i < 0 then err_invalid_bounds (p ^ ".set_uint32") t i 4 else ba_set_int32 t.buffer (t.off+i) (if swap then swap32 c else c) [@@inline] let set_uint64 swap p t i c = if i > t.len - 8 || i < 0 then err_invalid_bounds (p ^ ".set_uint64") t i 8 else ba_set_int64 t.buffer (t.off+i) (if swap then swap64 c else c) [@@inline] let get_uint16 swap p t i = if i > t.len - 2 || i < 0 then err_invalid_bounds (p ^ ".get_uint16") t i 2 else let r = ba_get_int16 t.buffer (t.off+i) in if swap then swap16 r else r [@@inline] let get_uint32 swap p t i = if i > t.len - 4 || i < 0 then err_invalid_bounds (p ^ ".get_uint32") t i 4 else let r = ba_get_int32 t.buffer (t.off+i) in if swap then swap32 r else r [@@inline] let get_uint64 swap p t i = if i > t.len - 8 || i < 0 then err_invalid_bounds (p ^ ".get_uint64") t i 8 else let r = ba_get_int64 t.buffer (t.off+i) in if swap then swap64 r else r [@@inline] module BE = struct let set_uint16 t i c = set_uint16 (not Sys.big_endian) "BE" t i c [@@inline] let set_uint32 t i c = set_uint32 (not Sys.big_endian) "BE" t i c [@@inline] let set_uint64 t i c = set_uint64 (not Sys.big_endian) "BE" t i c [@@inline] let get_uint16 t i = get_uint16 (not Sys.big_endian) "BE" t i [@@inline] let get_uint32 t i = get_uint32 (not Sys.big_endian) "BE" t i [@@inline] let get_uint64 t i = get_uint64 (not Sys.big_endian) "BE" t i [@@inline] end module LE = struct let set_uint16 t i c = set_uint16 Sys.big_endian "LE" t i c [@@inline] let set_uint32 t i c = set_uint32 Sys.big_endian "LE" t i c [@@inline] let set_uint64 t i c = set_uint64 Sys.big_endian "LE" t i c [@@inline] let get_uint16 t i = get_uint16 Sys.big_endian "LE" t i [@@inline] let get_uint32 t i = get_uint32 Sys.big_endian "LE" t i [@@inline] let get_uint64 t i = get_uint64 Sys.big_endian "LE" t i [@@inline] end module HE = struct let set_uint16 t i c = set_uint16 false "HE" t i c [@@inline] let set_uint32 t i c = set_uint32 false "HE" t i c [@@inline] let set_uint64 t i c = set_uint64 false "HE" t i c [@@inline] let get_uint16 t i = get_uint16 false "HE" t i [@@inline] let get_uint32 t i = get_uint32 false "HE" t i [@@inline] let get_uint64 t i = get_uint64 false "HE" t i [@@inline] end let length { len ; _ } = len (** [sum_lengths ~caller acc l] is [acc] plus the sum of the lengths of the elements of [l]. Raises [Invalid_argument caller] if arithmetic overflows. *) let rec sum_lengths_aux ~caller acc = function | [] -> acc | h :: t -> let sum = length h + acc in if sum < acc then invalid_arg caller else sum_lengths_aux ~caller sum t let sum_lengths ~caller l = sum_lengths_aux ~caller 0 l let lenv l = sum_lengths ~caller:"Cstruct.lenv" l let copyv ts = let sz = sum_lengths ~caller:"Cstruct.copyv" ts in let dst = Bytes.create sz in let _ = List.fold_left (fun off src -> let x = length src in unsafe_blit_bigstring_to_bytes src.buffer src.off dst off x; off + x ) 0 ts in (* The following call is safe, since dst is not visible elsewhere. *) Bytes.unsafe_to_string dst let fillv ~src ~dst = let rec aux dst n = function | [] -> n, [] | hd::tl -> let avail = length dst in let first = length hd in if first <= avail then ( blit hd 0 dst 0 first; aux (shift dst first) (n + first) tl ) else ( blit hd 0 dst 0 avail; let rest_hd = shift hd avail in (n + avail, rest_hd :: tl) ) in aux dst 0 src let to_string ?(off=0) ?len:sz t = let len = match sz with None -> length t - off | Some l -> l in copy_to_string "to_string" t off len let to_hex_string ?(off=0) ?len:sz t : string = let[@inline] nibble_to_char (i:int) : char = if i < 10 then Char.chr (i + Char.code '0') else Char.chr (i - 10 + Char.code 'a') in let len = match sz with None -> length t - off | Some l -> l in if len < 0 || off < 0 || t.len - off < len then err_to_hex_string t off len else ( let out = Bytes.create (2 * len) in for i=0 to len-1 do let c = Char.code @@ Bigarray.Array1.get t.buffer (i+t.off+off) in Bytes.set out (2*i) (nibble_to_char (c lsr 4)); Bytes.set out (2*i+1) (nibble_to_char (c land 0xf)); done; Bytes.unsafe_to_string out ) let to_bytes ?off ?len t = Bytes.unsafe_of_string (to_string ?off ?len t) let [@inline always] of_data_abstract blitfun lenfun ?allocator ?(off=0) ?len buf = let buflen = match len with | None -> lenfun buf - off | Some len -> len in match allocator with | None -> let c = create_unsafe buflen in blitfun buf off c 0 buflen; c | Some fn -> let c = fn buflen in blitfun buf off c 0 buflen; { c with len = buflen } let of_string ?allocator ?off ?len buf = of_data_abstract blit_from_string String.length ?allocator ?off ?len buf let of_bytes ?allocator ?off ?len buf = of_data_abstract blit_from_bytes Bytes.length ?allocator ?off ?len buf let of_hex ?(off=0) ?len str = let str = let l = match len with None -> String.length str - off | Some l -> l in String.sub str off l in let string_fold ~f ~z str = let st = ref z in ( String.iter (fun c -> st := f !st c) str ; !st ) in let hexdigit p = function | 'a' .. 'f' as x -> int_of_char x - 87 | 'A' .. 'F' as x -> int_of_char x - 55 | '0' .. '9' as x -> int_of_char x - 48 | x -> Format.ksprintf invalid_arg "of_hex: invalid character at pos %d: %C" p x in let whitespace = function | ' ' | '\t' | '\r' | '\n' -> true | _ -> false in match string_fold ~f:(fun (cs, i, p, acc) -> let p' = succ p in function | char when whitespace char -> (cs, i, p', acc) | char -> match acc, hexdigit p char with | (None , x) -> (cs, i, p', Some (x lsl 4)) | (Some y, x) -> set_uint8 cs i (x lor y) ; (cs, succ i, p', None)) ~z:(create_unsafe (String.length str lsr 1), 0, 0, None) str with | _ , _, _, Some _ -> Format.ksprintf invalid_arg "of_hex: odd numbers of characters" | cs, i, _, _ -> sub cs 0 i let hexdump_pp fmt t = let before fmt = function | 0 -> () | 8 -> Format.fprintf fmt " "; | _ -> Format.fprintf fmt " " in let after fmt = function | 15 -> Format.fprintf fmt "@;" | _ -> () in Format.pp_open_vbox fmt 0 ; for i = 0 to length t - 1 do let column = i mod 16 in let c = Char.code (Bigarray.Array1.get t.buffer (t.off+i)) in Format.fprintf fmt "%a%.2x%a" before column c after column done ; Format.pp_close_box fmt () let hexdump = Format.printf "@\n%a@." hexdump_pp let hexdump_to_buffer buf t = let f = Format.formatter_of_buffer buf in Format.fprintf f "@\n%a@." hexdump_pp t let split ?(start=0) t off = try let header =sub t start off in let body = sub t (start+off) (length t - off - start) in header, body with Invalid_argument _ -> err_split t start off type 'a iter = unit -> 'a option let iter lenfn pfn t = let body = ref (Some t) in let i = ref 0 in fun () -> match !body with |Some buf when length buf = 0 -> body := None; None |Some buf -> begin match lenfn buf with |None -> body := None; None |Some plen -> incr i; let p,rest = try split buf plen with Invalid_argument _ -> err_iter buf !i plen in body := Some rest; Some (pfn p) end |None -> None let rec fold f next acc = match next () with | None -> acc | Some v -> fold f next (f acc v) let append cs1 cs2 = let l1 = length cs1 and l2 = length cs2 in let cs = create_unsafe (l1 + l2) in blit cs1 0 cs 0 l1 ; blit cs2 0 cs l1 l2 ; cs let concat = function | [] -> create_unsafe 0 | [cs] -> cs | css -> let result = create_unsafe (sum_lengths ~caller:"Cstruct.concat" css) in let aux off cs = let n = length cs in blit cs 0 result off n ; off + n in ignore @@ List.fold_left aux 0 css ; result let rev t = let n = length t in let out = create_unsafe n in for i_src = 0 to n - 1 do let byte = get_uint8 t i_src in let i_dst = n - 1 - i_src in set_uint8 out i_dst byte done; out (* Convenience function. *) external unsafe_blit_string_to_bigstring : string -> int -> buffer -> int -> int -> unit = "caml_blit_string_to_bigstring" [@@noalloc] let get { buffer; off; len; } zidx = if zidx < 0 || zidx >= len then invalid_arg "index out of bounds" ; Bigarray.Array1.get buffer (off + zidx) let get_byte { buffer; off; len; } zidx = if zidx < 0 || zidx >= len then invalid_arg "index out of bounds" ; Char.code (Bigarray.Array1.get buffer (off + zidx)) let string ?(off= 0) ?len str = let str_len = String.length str in let len = match len with None -> str_len | Some len -> len in if off < 0 || len < 0 || off + len > str_len then invalid_arg "index out of bounds" ; let buffer = Bigarray.(Array1.create char c_layout str_len) in unsafe_blit_string_to_bigstring str 0 buffer 0 str_len ; of_bigarray ~off ~len buffer let buffer ?(off= 0) ?len buffer = let buffer_len = Bigarray.Array1.dim buffer in let len = match len with None -> buffer_len - off | Some len -> len in if off < 0 || len < 0 || off + len > buffer_len then invalid_arg "index out of bounds" ; of_bigarray ~off ~len buffer let start_pos { off; _ } = off let stop_pos { off; len; _ } = off + len let head ?(rev= false) ({ len; _ } as cs) = if len = 0 then None else Some (get_char cs (if rev then len - 1 else 0)) let tail ?(rev= false) ({ buffer; off; len; } as cs) = if len = 0 then cs else if rev then of_bigarray ~off ~len:(len - 2) buffer else of_bigarray ~off:(off + 1) ~len:(len - 1) buffer let is_empty { len; _ } = len = 0 let is_prefix ~affix:({ len= alen; _ } as affix) ({ len; _ } as cs) = if alen > len then false else let max_zidx = alen - 1 in let rec loop i = if i > max_zidx then true else if get_char affix i <> get_char cs i then false else loop (succ i) in loop 0 let is_infix ~affix:({ len= alen; _ } as affix) ({ len; _ } as cs) = if alen > len then false else let max_zidx_a = alen - 1 in let max_zidx_s = len - alen in let rec loop i k = if i > max_zidx_s then false else if k > max_zidx_a then true else if k > 0 then if get_char affix k = get_char cs (i + k) then loop i (succ k) else loop (succ i) 0 else if get_char affix 0 = get_char cs i then loop i 1 else loop (succ i) 0 in loop 0 0 let is_suffix ~affix:({ len= alen; _ } as affix) ({ len; _ } as cs) = if alen > len then false else let max_zidx = alen - 1 in let max_zidx_a = alen - 1 in let max_zidx_s = len - 1 in let rec loop i = if i > max_zidx then true else if get_char affix (max_zidx_a - i) <> get_char cs (max_zidx_s - i) then false else loop (succ i) in loop 0 let for_all sat cs = let rec go acc i = if i < length cs then go (sat (get_char cs i) && acc) (succ i) else acc in go true 0 let exists sat cs = let rec go acc i = if i < length cs then go (sat (get_char cs i) || acc) (succ i) else acc in go false 0 let start { buffer; off; _ } = of_bigarray buffer ~off ~len:0 let stop { buffer; off; len; } = of_bigarray buffer ~off:(off + len) ~len:0 let is_white = function ' ' | '\t' .. '\r' -> true | _ -> false let trim ?(drop = is_white) ({ buffer; off; len; } as cs) = if len = 0 then cs else let max_zpos = len in let max_zidx = len - 1 in let rec left_pos i = if i > max_zidx then max_zpos else if drop (get_char cs i) then left_pos (succ i) else i in let rec right_pos i = if i < 0 then 0 else if drop (get_char cs i) then right_pos (pred i) else succ i in let left = left_pos 0 in if left = max_zpos then of_bigarray buffer ~off:((off * 2 + len) / 2) ~len:0 else let right = right_pos max_zidx in if left = 0 && right = max_zpos then cs else of_bigarray buffer ~off:(off + left) ~len:(right - left) let fspan ~min ~max ~sat ({ buffer= v; off; len; } as cs) = if min < 0 then invalid_arg "span: negative min" ; if max < 0 then invalid_arg "span: negative max" ; if min > max || max = 0 then (buffer ~off:off ~len:0 v, cs) else let max_zidx = len - 1 in let max_zidx = let k = max - 1 in if k > max_zidx || k < 0 then max_zidx else k in let need_zidx = min in let rec loop i = if i <= max_zidx && sat (get_char cs i) then loop (i + 1) else if i < need_zidx || i = 0 then buffer ~off:off ~len:0 v, cs else if i = len then (cs, buffer ~off:(off + len) ~len:0 v) else buffer ~off:off ~len:i v, buffer ~off:(off + i) ~len:(len - i) v in loop 0 let rspan ~min ~max ~sat ({ buffer= v; off; len; } as cs) = if min < 0 then invalid_arg "span: negative min" ; if max < 0 then invalid_arg "span: negative max" ; if min > max || max = 0 then (cs, buffer ~off:(off + len) ~len:0 v) else let max_zidx = len - 1 in let min_zidx = let k = len - max in if k < 0 then 0 else k in let need_zidx = len - min - 1 in let rec loop i = if i >= min_zidx && sat (get_char cs i) then loop (i - 1) else if i > need_zidx || i = max_zidx then (cs, buffer ~off:(off + len) ~len:0 v) else if i < 0 then (buffer ~off:off ~len:0 v, cs) else (buffer ~off:off ~len:(i + 1) v, buffer ~off:(off + i + 1) ~len:(len - (i + 1)) v) in loop max_zidx let span ?(rev= false) ?(min= 0) ?(max= max_int) ?(sat= fun _ -> true) cs = match rev with | true -> rspan ~min ~max ~sat cs | false -> fspan ~min ~max ~sat cs let take ?(rev= false) ?min ?max ?sat cs = (if rev then snd else fst) @@ span ~rev ?min ?max ?sat cs let drop ?(rev= false) ?min ?max ?sat cs = (if rev then fst else snd) @@ span ~rev ?min ?max ?sat cs let fcut ~sep:({ len= sep_len; _ } as sep) ({ buffer= v; off; len; } as cs) = if sep_len = 0 then invalid_arg "cut: empty separator" ; let max_sep_zidx = sep_len - 1 in let max_s_zidx = len - sep_len in let rec check_sep i k = if k > max_sep_zidx then Some (buffer ~off:off ~len:i v, buffer ~off:(off + i + sep_len) ~len:(len - i - sep_len) v) else if get_char cs (i + k) = get_char sep k then check_sep i (k + 1) else scan (i + 1) and scan i = if i > max_s_zidx then None else if get_char cs i = get_char sep 0 then check_sep i 1 else scan (i + 1) in scan 0 let rcut ~sep:({ len= sep_len; _ } as sep) ({ buffer= v; off; len; } as cs) = if sep_len = 0 then invalid_arg "cut: empty separator" ; let max_sep_zidx = sep_len - 1 in let max_s_zidx = len - 1 in let rec check_sep i k = if k > max_sep_zidx then Some (buffer ~off:off ~len:i v, buffer ~off:(off + i + sep_len) ~len:(len - i - sep_len) v) else if get_char cs (i + k) = get_char sep k then check_sep i (k + 1) else rscan (i - 1) and rscan i = if i < 0 then None else if get_char cs i = get_char sep 0 then check_sep i 1 else rscan (i - 1) in rscan (max_s_zidx - max_sep_zidx) let cut ?(rev= false) ~sep cs = match rev with | true -> rcut ~sep cs | false -> fcut ~sep cs let add_sub ~no_empty buf ~off ~len acc = if len = 0 then ( if no_empty then acc else buffer ~off ~len buf :: acc ) else buffer ~off ~len buf :: acc let fcuts ~no_empty ~sep:({ len= sep_len; _ } as sep) ({ buffer; off; len; } as cs) = if sep_len = 0 then invalid_arg "cuts: empty separator" ; let max_sep_zidx = sep_len - 1 in let max_s_zidx = len - sep_len in let rec check_sep zanchor i k acc = if k > max_sep_zidx then let new_start = i + sep_len in scan new_start new_start (add_sub ~no_empty buffer ~off:(off + zanchor) ~len:(i - zanchor) acc) else if get_char cs (i + k) = get_char sep k then check_sep zanchor i (k + 1) acc else scan zanchor (i + 1) acc and scan zanchor i acc = if i > max_s_zidx then if zanchor = 0 then (if no_empty && len = 0 then [] else [ cs ]) else List.rev (add_sub ~no_empty buffer ~off:(off + zanchor) ~len:(len - zanchor) acc) else if get_char cs i = get_char sep 0 then check_sep zanchor i 1 acc else scan zanchor (i + 1) acc in scan 0 0 [] let rcuts ~no_empty ~sep:({ len= sep_len; _ } as sep) ({ buffer; len; _ } as cs) = if sep_len = 0 then invalid_arg "cuts: empty separator" ; let s_len = len in let max_sep_zidx = sep_len - 1 in let max_s_zidx = len - 1 in let rec check_sep zanchor i k acc = if k > max_sep_zidx then let off = i + sep_len in rscan i (i - sep_len) (add_sub ~no_empty buffer ~off ~len:(zanchor - off) acc) else if get_char cs (i + k) = get_char cs k then check_sep zanchor i (k + 1) acc else rscan zanchor (i - 1) acc and rscan zanchor i acc = if i < 0 then if zanchor = s_len then ( if no_empty && s_len = 0 then [] else [ cs ]) else add_sub ~no_empty buffer ~off:0 ~len:zanchor acc else if get_char cs i = get_char sep 0 then check_sep zanchor i 1 acc else rscan zanchor (i - 1) acc in rscan s_len (max_s_zidx - max_sep_zidx) [] let cuts ?(rev= false) ?(empty= true) ~sep cs = match rev with | true -> rcuts ~no_empty:(not empty) ~sep cs | false -> fcuts ~no_empty:(not empty) ~sep cs let fields ?(empty= false) ?(is_sep= is_white) ({ buffer; off; len; } as cs) = let no_empty = not empty in let max_pos = len in let rec loop i end_pos acc = if i < 0 then begin if end_pos = len then ( if no_empty && len = 0 then [] else [ cs ]) else add_sub ~no_empty buffer ~off:off ~len:(end_pos - (i + 1)) acc end else begin if not (is_sep (get_char cs i)) then loop (i - 1) end_pos acc else loop (i - 1) i (add_sub ~no_empty buffer ~off:(off + i + 1) ~len:(end_pos - (i + 1)) acc) end in loop (max_pos - 1) max_pos [] let ffind sat ({ buffer= v; len; _ } as cs) = let max_idx = len - 1 in let rec loop i = if i > max_idx then None else if sat (get_char cs i) then Some (buffer ~off:i ~len:1 v) else loop (i + 1) in loop 0 let rfind sat ({ buffer= v; len; _ } as cs) = let rec loop i = if i < 0 then None else if sat (get_char cs i) then Some (buffer ~off:i ~len:1 v) else loop (i - 1) in loop (len - 1) let find ?(rev= false) sat cs = match rev with | true -> rfind sat cs | false -> ffind sat cs let ffind_sub ~sub:({ len= sub_len; _ } as sub) ({ buffer= v; off; len; } as cs) = if sub_len > len then None else let max_zidx_sub = sub_len - 1 in let max_zidx_s = len - sub_len in let rec loop i k = if i > max_zidx_s then None else if k > max_zidx_sub then Some (buffer v ~off:(off + i) ~len:sub_len) else if k > 0 then ( if get_char sub k = get_char cs (i + k) then loop i (k + 1) else loop (i + 1) 0 ) else if get_char sub 0 = get_char cs i then loop i 1 else loop (i + 1) 0 in loop 0 0 let rfind_sub ~sub:({ len= sub_len; _ } as sub) ({ buffer= v; len; _ } as cs) = if sub_len > len then None else let max_zidx_sub = sub_len - 1 in let rec loop i k = if i < 0 then None else if k > max_zidx_sub then Some (buffer v ~off:i ~len:sub_len) else if k > 0 then ( if get_char sub k = get_char cs (i + k) then loop i (k + 1) else loop (i - 1) 0 ) else if get_char sub 0 = get_char cs i then loop i 1 else loop (i - 1) 0 in loop (len - sub_len) 0 let find_sub ?(rev= false) ~sub cs = match rev with | true -> rfind_sub ~sub cs | false -> ffind_sub ~sub cs let filter sat ({ len; _ } as cs) = if len = 0 then empty else let b = create len in let max_zidx = len - 1 in let rec loop b k i = if i > max_zidx then (if k = len then b else sub b 0 k) else let chr = get_char cs i in if sat chr then ( set_char b k chr ; loop b (k + 1) (i + 1)) else loop b k (i + 1) in loop b 0 0 let filter_map f ({ len; _ } as cs) = if len = 0 then empty else let b = create len in let max_zidx = len - 1 in let rec loop b k i = if i > max_zidx then (if k = len then b else sub b 0 k) else match f (get_char cs i) with | Some chr -> set_char b i chr ; loop b (k + 1) (i + 1) | None -> loop b k (i + 1) in loop b 0 0 let map f ({ len; _ } as cs) = if len = 0 then empty else let b = create len in for i = 0 to len - 1 do set_char b i (f (get_char cs i)) done ; b let mapi f ({ len; _ } as cs) = if len = 0 then empty else let b = create len in for i = 0 to len - 1 do set_char b i (f i (get_char cs i)) done ; b