diff --git a/src/core/CCString.cppo.ml b/src/core/CCString.cppo.ml index 246811bf..6a06e672 100644 --- a/src/core/CCString.cppo.ml +++ b/src/core/CCString.cppo.ml @@ -87,6 +87,30 @@ let is_sub ~sub i s j ~len = if i+len > String.length sub then invalid_arg "String.is_sub"; _is_sub ~sub i s j ~len +(* note: inefficient *) +let find ?(start=0) ~sub s = + let n = String.length sub in + let i = ref start in + try + while !i + n < String.length s do + if _is_sub ~sub 0 s !i ~len:n then raise Exit; + incr i + done; + -1 + with Exit -> + !i + +let rfind ~sub s = + let n = String.length sub in + let i = ref (String.length s - n) in + try + while !i >= 0 do + if _is_sub ~sub 0 s !i ~len:n then raise Exit; + decr i + done; + ~-1 + with Exit -> + !i module Split = struct type split_state = @@ -158,20 +182,17 @@ module Split = struct let seq ~by s = _mkseq ~by s _tuple3 let seq_cpy ~by s = _mkseq ~by s String.sub -end -(* note: inefficient *) -let find ?(start=0) ~sub s = - let n = String.length sub in - let i = ref start in - try - while !i + n < String.length s do - if _is_sub ~sub 0 s !i ~len:n then raise Exit; - incr i - done; - -1 - with Exit -> - !i + let left ~by s = + let i = find ~sub:by s in + if i = ~-1 then None + else Some (String.sub s 0 i, String.sub s (i+1) (String.length s - i - 1)) + + let right ~by s = + let i = rfind ~sub:by s in + if i = ~-1 then None + else Some (String.sub s 0 i, String.sub s (i+1) (String.length s - i - 1)) +end let repeat s n = assert (n>=0); @@ -252,11 +273,6 @@ let of_list l = List.iter (Buffer.add_char buf) l; Buffer.contents buf -(*$T - of_list ['a'; 'b'; 'c'] = "abc" - of_list [] = "" -*) - let of_array a = init (Array.length a) (fun i -> a.(i)) @@ -285,11 +301,80 @@ let set s i c = if i<0 || i>= String.length s then invalid_arg "CCString.set"; init (String.length s) (fun j -> if i=j then c else s.[j]) -(*$T - set "abcd" 1 '_' = "a_cd" - set "abcd" 0 '-' = "-bcd" - (try set "abc" 5 '_'; false with Invalid_argument _ -> true) -*) +let iter = String.iter + +#if OCAML_MAJOR >= 4 + +let map = String.map +let mapi = String.mapi +let iteri = String.iteri + +#else + +let map f s = init (length s) (fun i -> f s.[i]) +let mapi f s = init (length s) (fun i -> f i s.[i]) + +let iteri f s = + for i = 0 to String.length s - 1 do + f i s.[i] + done + +#endif + +let flat_map ?sep f s = + let buf = Buffer.create (String.length s) in + iteri + (fun i c -> + begin match sep with + | Some _ when i=0 -> () + | None -> () + | Some sep -> Buffer.add_string buf sep + end; + Buffer.add_string buf (f c) + ) s; + Buffer.contents buf + +exception MyExit + +let for_all p s = + try iter (fun c -> if not (p c) then raise MyExit) s; true + with MyExit -> false + +let exists p s = + try iter (fun c -> if p c then raise MyExit) s; false + with MyExit -> true + +let map2 f s1 s2 = + if length s1 <> length s2 then invalid_arg "String.map2"; + init (String.length s1) (fun i -> f s1.[i] s2.[i]) + +let iter2 f s1 s2 = + if length s1 <> length s2 then invalid_arg "String.iter2"; + for i = 0 to String.length s1 - 1 do + f s1.[i] s2.[i] + done + +let iteri2 f s1 s2 = + if length s1 <> length s2 then invalid_arg "String.iteri2"; + for i = 0 to String.length s1 - 1 do + f i s1.[i] s2.[i] + done + +let fold2 f acc s1 s2 = + if length s1 <> length s2 then invalid_arg "String.fold2"; + let rec fold' acc s1 s2 i = + if i = String.length s1 then acc + else fold' (f acc s1.[i] s2.[i]) s1 s2 (i+1) + in + fold' acc s1 s2 0 + +let for_all2 p s1 s2 = + try iter2 (fun c1 c2 -> if not (p c1 c2) then raise MyExit) s1 s2; true + with MyExit -> false + +let exists2 p s1 s2 = + try iter2 (fun c1 c2 -> if p c1 c2 then raise MyExit) s1 s2; false + with MyExit -> true let pp buf s = Buffer.add_char buf '"'; diff --git a/src/core/CCString.mli b/src/core/CCString.mli index 50c7f417..65de38d7 100644 --- a/src/core/CCString.mli +++ b/src/core/CCString.mli @@ -81,12 +81,35 @@ val of_klist : char klist -> string val of_list : char list -> string val of_array : char array -> string +(*$T + of_list ['a'; 'b'; 'c'] = "abc" + of_list [] = "" +*) + val to_array : string -> char array val find : ?start:int -> sub:string -> string -> int (** Find [sub] in string, returns its first index or [-1]. Should only be used with very small [sub] *) +(*$T + find ~sub:"bc" "abcd" = 1 + find ~sub:"bc" "abd" = ~-1 + find ~sub:"a" "_a_a_a_" = 1 +*) + +val rfind : sub:string -> string -> int +(** Find [sub] in string from the right, returns its first index or [-1]. + Should only be used with very small [sub] + @since NEXT_RELEASE *) + +(*$T + rfind ~sub:"bc" "abcd" = 1 + rfind ~sub:"bc" "abd" = ~-1 + rfind ~sub:"a" "_a_a_a_" = 5 + rfind ~sub:"bc" "abcdbcd" = 4 +*) + val is_sub : sub:string -> int -> string -> int -> len:int -> bool (** [is_sub ~sub i s j ~len] returns [true] iff the substring of [sub] starting at position [i] and of length [len] *) @@ -143,8 +166,75 @@ val set : string -> int -> char -> string @raise Invalid_argument if [i] is an invalid index @since NEXT_RELEASE *) +(*$T + set "abcd" 1 '_' = "a_cd" + set "abcd" 0 '-' = "-bcd" + (try ignore (set "abc" 5 '_'); false with Invalid_argument _ -> true) +*) + +val iter : (char -> unit) -> string -> unit +(** Alias to {!String.iter} + @since NEXT_RELEASE *) + +val iteri : (int -> char -> unit) -> string -> unit +(** iter on chars with their index + @since NEXT_RELEASE *) + +val map : (char -> char) -> string -> string +(** map chars + @since NEXT_RELEASE *) + +val mapi : (int -> char -> char) -> string -> string +(** map chars with their index + @since NEXT_RELEASE *) + +val flat_map : ?sep:string -> (char -> string) -> string -> string +(** map each chars to a string, then concatenates them all + @param sep optional separator between each generated string + @since NEXT_RELEASE *) + +val for_all : (char -> bool) -> string -> bool +(** true for all chars? + @since NEXT_RELEASE *) + +val exists : (char -> bool) -> string -> bool +(** true for some char? + @since NEXT_RELEASE *) + include S with type t := string +(** {2 Operations on 2 strings} *) + +val map2 : (char -> char -> char) -> string -> string -> string +(** map pairs of chars + @raises Invalid_argument if the strings have not the same length + @since NEXT_RELEASE *) + +val iter2: (char -> char -> unit) -> string -> string -> unit +(** iterate on pairs of chars + @raises Invalid_argument if the strings have not the same length + @since NEXT_RELEASE *) + +val iteri2: (int -> char -> char -> unit) -> string -> string -> unit +(** iterate on pairs of chars with their index + @raises Invalid_argument if the strings have not the same length + @since NEXT_RELEASE *) + +val fold2: ('a -> char -> char -> 'a) -> 'a -> string -> string -> 'a +(** fold on pairs of chars + @raises Invalid_argument if the strings have not the same length + @since NEXT_RELEASE *) + +val for_all2 : (char -> char -> bool) -> string -> string -> bool +(** all pair of chars respect the predicate? + @raises Invalid_argument if the strings have not the same length + @since NEXT_RELEASE *) + +val exists2 : (char -> char -> bool) -> string -> string -> bool +(** exists a pair of chars? + @raises Invalid_argument if the strings have not the same length + @since NEXT_RELEASE *) + (** {2 Splitting} *) module Split : sig @@ -181,6 +271,26 @@ module Split : sig val seq_cpy : by:string -> string -> string sequence val klist_cpy : by:string -> string -> string klist + + val left : by:string -> string -> (string * string) option + (** Split on the first occurrence of [by] from the left-most part of + the string + @since NEXT_RELEASE *) + + (*$T + Split.left ~by:" " "ab cde f g " = Some ("ab", "cde f g ") + Split.left ~by:"_" "abcde" = None + *) + + val right : by:string -> string -> (string * string) option + (** Split on the first occurrence of [by] from the rightmost part of + the string + @since NEXT_RELEASE *) + + (*$T + Split.right ~by:" " "ab cde f g" = Some ("ab cde f", "g") + Split.right ~by:"_" "abcde" = None + *) end (** {2 Slices} A contiguous part of a string *)