diff --git a/Changes b/Changes index 251befd68..1b8253a59 100644 --- a/Changes +++ b/Changes @@ -111,6 +111,10 @@ Working version and more generally all other data that is not a well-formed OCaml value (Xavier Leroy, review by Damien Doligez and Gabriel Scherer) +- #9533: Added String.starts_with and String.ends_with. + (Bernhard Schommer, review by Daniel Bünzli, Gabriel Scherer and + Alain Frisch) + ### Other libraries: * #9206, #9419: update documentation of the threads library; diff --git a/stdlib/filename.ml b/stdlib/filename.ml index b0dd5c219..a16eb8217 100644 --- a/stdlib/filename.ml +++ b/stdlib/filename.ml @@ -100,9 +100,7 @@ module Unix : SYSDEPS = struct && (String.length n < 2 || String.sub n 0 2 <> "./") && (String.length n < 3 || String.sub n 0 3 <> "../") let check_suffix name suff = - String.length name >= String.length suff && - String.sub name (String.length name - String.length suff) - (String.length suff) = suff + String.ends_with ~suffix:suff name let chop_suffix_opt ~suffix filename = let len_s = String.length suffix and len_f = String.length filename in diff --git a/stdlib/string.ml b/stdlib/string.ml index 12a627f31..e3abf38c0 100644 --- a/stdlib/string.ml +++ b/stdlib/string.ml @@ -231,3 +231,22 @@ let to_seq s = bos s |> B.to_seq let to_seqi s = bos s |> B.to_seqi let of_seq g = B.of_seq g |> bts + +let 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 + +let ends_with ~suffix s = + let len_s = length s + and len_suf = length suffix in + let diff = len_s - len_suf in + let rec aux i = + if i = len_suf then true + else if unsafe_get s (diff + i) <> unsafe_get suffix i then false + else aux (i + 1) + in diff >= 0 && aux 0 diff --git a/stdlib/string.mli b/stdlib/string.mli index a38c8123b..7b94432bc 100644 --- a/stdlib/string.mli +++ b/stdlib/string.mli @@ -316,6 +316,14 @@ val equal: t -> t -> bool (** The equal function for strings. @since 4.03.0 *) +val starts_with : prefix:t -> t -> bool +(** [String.starts_with prefix s] tests if [s] starts with [prefix] + @since 4.12.0 *) + +val ends_with : suffix:t -> t -> bool +(** [String.ends_with suffix s] tests if [s] ends with [suffix] + @since 4.12.0 *) + val split_on_char: char -> string -> string list (** [String.split_on_char sep s] returns the list of all (possibly empty) substrings of [s] that are delimited by the [sep] character. diff --git a/stdlib/stringLabels.mli b/stdlib/stringLabels.mli index 29126b730..fa85ca346 100644 --- a/stdlib/stringLabels.mli +++ b/stdlib/stringLabels.mli @@ -282,6 +282,14 @@ val equal: t -> t -> bool (** The equal function for strings. @since 4.05.0 *) +val starts_with : prefix:t -> t -> bool +(** [String.starts_with prefix s] tests if [s] starts with [prefix] + @since 4.12.0 *) + +val ends_with : suffix:t -> t -> bool +(** [String.ends_with suffix s] tests if [s] ends with [suffix] + @since 4.12.0 *) + val split_on_char: sep:char -> string -> string list (** [String.split_on_char sep s] returns the list of all (possibly empty) substrings of [s] that are delimited by the [sep] character. diff --git a/testsuite/tests/lib-string/test_string.ml b/testsuite/tests/lib-string/test_string.ml index cd45af621..003236f46 100644 --- a/testsuite/tests/lib-string/test_string.ml +++ b/testsuite/tests/lib-string/test_string.ml @@ -51,5 +51,17 @@ let () = while !sz >= 0 do push big l; sz += Sys.max_string_length done; while !sz <= 0 do push big l; sz += Sys.max_string_length done; try ignore (String.concat "" !l); assert false - with Invalid_argument _ -> () + with Invalid_argument _ -> (); + assert(String.starts_with ~prefix:"foob" "foobarbaz"); + assert(String.starts_with ~prefix:"" "foobarbaz"); + assert(String.starts_with ~prefix:"" ""); + assert(not (String.starts_with ~prefix:"foobar" "bar")); + assert(not (String.starts_with ~prefix:"foo" "")); + assert(not (String.starts_with ~prefix:"fool" "foobar")); + assert(String.ends_with ~suffix:"baz" "foobarbaz"); + assert(String.ends_with ~suffix:"" "foobarbaz"); + assert(String.ends_with ~suffix:"" ""); + assert(not (String.ends_with ~suffix:"foobar" "bar")); + assert(not (String.ends_with ~suffix:"foo" "")); + assert(not (String.ends_with ~suffix:"obaz" "foobar")); end