mte/unikernel/duniverse/stringext/lib/stringext.ml

338 lines
10 KiB
OCaml
Raw Normal View History

2025-11-11 02:07:51 +01:00
open String
let string_after s n = String.sub s n (String.length s - n)
let quote s =
let len = String.length s in
let buf = Buffer.create (2 * len) in
for i = 0 to len - 1 do
match s.[i] with
'[' | ']' | '*' | '.' | '\\' | '?' | '+' | '^' | '$' as c ->
Buffer.add_char buf '\\';
Buffer.add_char buf c
| c -> Buffer.add_char buf c
done;
Buffer.contents buf
(* Not tail recursive for "performance", please choose low values for
[max]. The idea is that max is always small because it's hard
code *)
let split_char_bounded str ~on ~max =
if str = "" then []
else if max = 1 then [str]
else
let rec loop offset tokens =
if tokens = max - 1
then [sub str offset (length str - offset)]
else
try
let index = index_from str offset on in
if index = offset then
""::(loop (offset + 1) (tokens + 1))
else
let token = String.sub str offset (index - offset) in
token::(loop (index + 1) (tokens + 1))
with Not_found -> [sub str offset (length str - offset)]
in loop 0 0
let split_char_unbounded str ~on =
if str = "" then []
else
let rec loop acc offset =
try begin
let index = rindex_from str offset on in
if index = offset then
loop (""::acc) (index - 1)
else
let token = sub str (index + 1) (offset - index) in
loop (token::acc) (index - 1)
end
with Not_found -> (sub str 0 (offset + 1))::acc
in loop [] (length str - 1)
let of_char = String.make 1
let full_split str ~on =
if str = "" then []
else
let sep = of_char on in
let rec loop acc offset =
try begin
let index = rindex_from str offset on in
if index = offset then
loop (sep::acc) (index - 1)
else
let token = sub str (index + 1) (offset - index) in
loop (sep::token::acc) (index - 1)
end
with Not_found ->
if offset >= 0
then (sub str 0 (offset + 1))::acc
else acc
in loop [] (length str - 1)
(* copying core's convention for String.split but with an optional max
argument *)
let split ?max s ~on =
match max with
| None -> split_char_unbounded s ~on
| Some max -> (* assert (max < 100); *)
split_char_bounded s ~on ~max
let rindex_from_on s ~offset ~on =
let rec loop i =
if i < 0 then raise Not_found
else if String.contains on s.[i] then i
else loop (i - 1)
in loop offset
let trim_left_sub s ~pos ~len ~chars =
let start_pos =
let final = pos + len in
let rec loop last_char i =
if i = final then last_char
else if String.contains chars s.[i] then loop (i + 1) (i + 1)
else last_char
in loop pos pos
in
let new_len = len - (start_pos - pos) in
String.sub s start_pos new_len
let split_trim_left str ~on ~trim =
if str = "" then []
else
let rec loop acc offset =
try begin
let index = rindex_from_on str ~offset ~on in
if index = offset then
loop (""::acc) (index - 1)
else
let token = trim_left_sub str ~pos:(index + 1)
~len:(offset - index) ~chars:trim in
loop (token::acc) (index - 1)
end
with Not_found ->
(trim_left_sub str ~pos:0 ~len:(offset + 1) ~chars:trim)::acc
in loop [] (length str - 1)
exception Found_int of int
let first_char_ne s c =
String.length s > 0 && s.[0] <> c
let trim_left s =
if first_char_ne s ' ' then s
else
let len = String.length s in
try
for i=0 to len - 1 do
if s.[i] <> ' ' then raise (Found_int i)
done;
""
with Found_int non_space ->
sub s non_space (len - non_space)
let substr_eq ?(start=0) s ~pattern =
try
for i = 0 to String.length pattern - 1 do
if s.[i + start] <> pattern.[i] then raise Exit
done;
true
with _ -> false
let find_from ?(start=0) str ~pattern =
try
for i = start to (String.length str) - (String.length pattern) do
if substr_eq ~start:i str ~pattern then
raise (Found_int i)
done;
None
with
| Found_int i -> Some i
| _ -> None
let find_min l ~f =
let rec loop x fx = function
| [] -> Some (x, fx)
| x'::xs ->
let fx' = f x' in
if fx' < fx then loop x' fx' xs
else loop x fx xs
in
match l with
| [] -> None
| x::xs -> loop x (f x) xs
let replace_all str ~pattern ~with_ =
let (slen, plen) = String.(length str, length pattern) in
let buf = Buffer.create slen in
let rec loop i =
match find_from ~start:i str ~pattern with
| None ->
Buffer.add_substring buf str i (slen - i);
Buffer.contents buf
| Some j ->
Buffer.add_substring buf str i (j - i);
Buffer.add_string buf with_;
loop (j + plen)
in loop 0
exception Found_replace of int * string * string
let replace_all_assoc str tbl =
let slen = String.length str in
let buf = Buffer.create slen in
let rec loop i =
if i >= slen then Buffer.contents buf
else
let r =
try
let found = ref false in
let e =
find_min tbl ~f:(fun (pattern, with_) ->
match find_from ~start:i str ~pattern with
| None -> max_int
| Some j when j = i -> raise (Found_replace (j, pattern, with_))
| Some j -> found := true; j)
in
match e with
| None -> None
| Some ((pattern, with_), j) when !found -> Some (j, pattern, with_)
| Some _ -> None
with Found_replace (j, pattern, with_) -> Some (j, pattern, with_)
in
match r with
| None ->
Buffer.add_substring buf str i (slen - i);
Buffer.contents buf
| Some (j, pattern, with_) ->
Buffer.add_substring buf str i (j - i);
Buffer.add_string buf with_;
loop (j + String.length pattern)
in loop 0
let iteri f l =
let rec loop i = function
| [] -> ()
| x::xs -> (f i x); loop (succ i) xs
in loop 0 l
let of_list xs =
let l = List.length xs in
let s = Bytes.create l in
iteri (fun i c -> Bytes.set s i c) xs;
Bytes.unsafe_to_string s
let to_list s =
let rec loop acc i =
if i = -1 then acc
else
loop (s.[i] :: acc) (pred i)
in loop [] (String.length s - 1)
let of_array a =
let len = Array.length a in
let bytes = Bytes.create len in
for i = 0 to len - 1 do
Bytes.set bytes i a.(i)
done;
Bytes.unsafe_to_string bytes
let to_array s = Array.init (String.length s) (String.get s)
(* ripped off from one of dbuenzli's libs *)
let cut s ~on =
let sep_max = length on - 1 in
if sep_max < 0 then invalid_arg "Stringext.cut: empty separator" else
let s_max = length s - 1 in
if s_max < 0 then None else
let k = ref 0 in
let i = ref 0 in
(* We run from the start of [s] to end with [i] trying to match the
first character of [on] in [s]. If this matches, we verify that
the whole [on] is matched using [k]. If it doesn't match we
continue to look for [on] with [i]. If it matches we exit the
loop and extract a substring from the start of [s] to the
position before the [on] we found and another from the position
after the [on] we found to end of string. If [i] is such that no
separator can be found we exit the loop and return the no match
case. *)
try
while (!i + sep_max <= s_max) do
(* Check remaining [on] chars match, access to unsafe s (!i + !k) is
guaranteed by loop invariant. *)
if unsafe_get s !i <> unsafe_get on 0 then incr i else begin
k := 1;
while (!k <= sep_max && unsafe_get s (!i + !k) = unsafe_get on !k)
do incr k done;
if !k <= sep_max then (* no match *) incr i else raise Exit
end
done;
None (* no match in the whole string. *)
with
| Exit -> (* i is at the beginning of the separator *)
let left_end = !i - 1 in
let right_start = !i + sep_max + 1 in
Some (sub s 0 (left_end + 1),
sub s right_start (s_max - right_start + 1))
let rcut s ~on =
let sep_max = length on - 1 in
if sep_max < 0 then invalid_arg "Stringext.rcut: empty separator" else
let s_max = length s - 1 in
if s_max < 0 then None else
let k = ref 0 in
let i = ref s_max in
(* We run from the end of [s] to the beginning with [i] trying to
match the last character of [on] in [s]. If this matches, we
verify that the whole [on] is matched using [k] (we do that
backwards). If it doesn't match we continue to look for [on]
with [i]. If it matches we exit the loop and extract a
substring from the start of [s] to the position before the
[on] we found and another from the position after the [on] we
found to end of string. If [i] is such that no separator can
be found we exit the loop and return the no match case. *)
try
while (!i >= sep_max) do
if unsafe_get s !i <> unsafe_get on sep_max then decr i else begin
(* Check remaining [on] chars match, access to unsafe_get
s (sep_start + !k) is guaranteed by loop invariant. *)
let sep_start = !i - sep_max in
k := sep_max - 1;
while (!k >= 0 && unsafe_get s (sep_start + !k) = unsafe_get on !k)
do decr k done;
if !k >= 0 then (* no match *) decr i else raise Exit
end
done;
None (* no match in the whole string. *)
with
| Exit -> (* i is at the end of the separator *)
let left_end = !i - sep_max - 1 in
let right_start = !i + 1 in
Some (sub s 0 (left_end + 1),
sub s right_start (s_max - right_start + 1))
let chop_prefix s ~prefix =
let prefix_l = String.length prefix in
let string_l = String.length s in
if prefix_l > string_l then None
else
try
for i = 0 to prefix_l - 1 do
if s.[i] <> prefix.[i] then raise Exit;
done;
Some (String.sub s prefix_l (string_l - prefix_l))
with _ -> None
let drop s n =
let l = String.length s in
if n >= l
then ""
else String.sub s n (l - n)
let take s n =
if n >= String.length s
then s
else String.sub s 0 n