diff --git a/bin/subst.ml b/bin/subst.ml index 6057c5c581a..3fca1e330b4 100644 --- a/bin/subst.ml +++ b/bin/subst.ml @@ -66,12 +66,9 @@ let subst_string s path ~map = loop 1 0 0 in let rec loop i acc = - if i = len - then acc - else ( - match s.[i] with - | '%' -> after_percent (i + 1) acc - | _ -> loop (i + 1) acc) + match String.index_from_unchecked s i '%' with + | -1 -> acc + | percent -> after_percent (percent + 1) acc and after_percent i acc = if i = len then acc diff --git a/otherlibs/stdune/src/bytes.ml b/otherlibs/stdune/src/bytes.ml index 4da63ff1275..2cea44ba9cc 100644 --- a/otherlibs/stdune/src/bytes.ml +++ b/otherlibs/stdune/src/bytes.ml @@ -1 +1,10 @@ include StdLabels.Bytes + +external index_in_range_unchecked + : t + -> pos:int + -> len:int + -> char + -> int + = "dune_bytes_index_in_range" +[@@noalloc] diff --git a/otherlibs/stdune/src/bytes.mli b/otherlibs/stdune/src/bytes.mli index 389d42eef48..3ff0912c793 100644 --- a/otherlibs/stdune/src/bytes.mli +++ b/otherlibs/stdune/src/bytes.mli @@ -1,3 +1,8 @@ include module type of struct include StdLabels.Bytes end + +(** [index_in_range_unchecked bytes ~pos ~len char] returns the first index of + [char] between [pos] and [pos + len], excluding the latter, or [-1] if it is + absent. The range must be within [bytes]. *) +val index_in_range_unchecked : t -> pos:int -> len:int -> char -> int diff --git a/otherlibs/stdune/src/dune b/otherlibs/stdune/src/dune index 27e4e3a0a5c..10f3badb26e 100644 --- a/otherlibs/stdune/src/dune +++ b/otherlibs/stdune/src/dune @@ -31,6 +31,7 @@ platform_stubs copyfile_stubs signal_stubs + string_stubs time_stubs))) (rule diff --git a/otherlibs/stdune/src/filename.ml b/otherlibs/stdune/src/filename.ml index 6e9a62f7bbf..25db8eb74fe 100644 --- a/otherlibs/stdune/src/filename.ml +++ b/otherlibs/stdune/src/filename.ml @@ -18,18 +18,19 @@ let concat dirname filename = else String.append_with_char dirname ~sep:(String.unsafe_get dir_sep 0) filename ;; -let rec contains_slash s i = - i >= 0 && (Char.equal (String.unsafe_get s i) '/' || contains_slash s (i - 1)) -;; - -let rec contains_dir_sep s i = +let rec contains_windows_dir_sep s i = if i < 0 then false else ( match String.unsafe_get s i with - | '/' -> true - | ('\\' | ':') when Sys.win32 || Sys.cygwin -> true - | _ -> contains_dir_sep s (i - 1)) + | '/' | '\\' | ':' -> true + | _ -> contains_windows_dir_sep s (i - 1)) +;; + +let contains_dir_sep s = + if Sys.win32 || Sys.cygwin + then contains_windows_dir_sep s (String.length s - 1) + else String.contains s '/' ;; let is_valid s = @@ -40,7 +41,7 @@ let is_valid s = || not (Char.equal (String.unsafe_get s 0) '.' && Char.equal (String.unsafe_get s 1) '.')) - && not (contains_dir_sep s (len - 1)) + && not (contains_dir_sep s) ;; let of_string s = Option.some_if (is_valid s) s @@ -111,9 +112,7 @@ module Extension = struct let is_valid s = let len = String.length s in - len > 0 - && Char.equal (String.unsafe_get s 0) '.' - && not (contains_dir_sep s (len - 1)) + len > 0 && Char.equal (String.unsafe_get s 0) '.' && not (contains_dir_sep s) ;; let of_string s = Option.some_if (is_valid s) s @@ -245,9 +244,7 @@ type program_name_kind = let analyze_program_name fn = if not (is_relative fn) then Absolute - else if - contains_slash fn (String.length fn - 1) - || (Stdlib.Sys.win32 && String.contains fn '\\') + else if String.contains fn '/' || (Stdlib.Sys.win32 && String.contains fn '\\') then Relative_to_current_dir else In_path ;; diff --git a/otherlibs/stdune/src/io.ml b/otherlibs/stdune/src/io.ml index fa635592550..50e24e823b0 100644 --- a/otherlibs/stdune/src/io.ml +++ b/otherlibs/stdune/src/io.ml @@ -19,7 +19,7 @@ let input_lines = ;; let input_zero_from_buffer from buf = - match String.index_from_opt buf from '\x00' with + match String.index_from buf from '\x00' with | None -> None | Some eos -> Some (String.sub buf ~pos:from ~len:(eos - from), eos + 1) ;; diff --git a/otherlibs/stdune/src/path0.ml b/otherlibs/stdune/src/path0.ml index 249ccfe012b..1f4d3831e40 100644 --- a/otherlibs/stdune/src/path0.ml +++ b/otherlibs/stdune/src/path0.ml @@ -11,26 +11,47 @@ let is_dir_sep = let basename_opt ~is_root ~basename t = if is_root t then None else Some (basename t) let explode_path = - let rec start acc path i = - if i < 0 - then acc - else if is_dir_sep (String.unsafe_get path i) - then start acc path (i - 1) - else component acc path i (i - 1) - and component acc path end_ i = - if i < 0 - then String.take path (end_ + 1) :: acc - else if is_dir_sep (String.unsafe_get path i) - then start (String.sub path ~pos:(i + 1) ~len:(end_ - i) :: acc) path (i - 1) - else component acc path end_ (i - 1) - in - fun path -> - if path = Filename.current_dir_name - then [ path ] - else ( - match start [] path (String.length path - 1) with - | "." :: xs -> xs - | xs -> xs) + if Sys.win32 || Sys.cygwin + then ( + let rec start acc path i = + if i < 0 + then acc + else if is_dir_sep (String.unsafe_get path i) + then start acc path (i - 1) + else component acc path i (i - 1) + and component acc path end_ i = + if i < 0 + then String.take path (end_ + 1) :: acc + else if is_dir_sep (String.unsafe_get path i) + then start (String.sub path ~pos:(i + 1) ~len:(end_ - i) :: acc) path (i - 1) + else component acc path end_ (i - 1) + in + fun path -> + if path = Filename.current_dir_name + then [ path ] + else ( + match start [] path (String.length path - 1) with + | "." :: xs -> xs + | xs -> xs)) + else ( + let rec components path length start = + if start = length + then [] + else ( + match String.index_from_unchecked path start '/' with + | -1 -> [ String.sub path ~pos:start ~len:(length - start) ] + | separator when separator = start -> components path length (start + 1) + | separator -> + String.sub path ~pos:start ~len:(separator - start) + :: components path length (separator + 1)) + in + fun path -> + if path = Filename.current_dir_name + then [ path ] + else ( + match components path (String.length path) 0 with + | "." :: xs -> xs + | xs -> xs)) ;; module Local_gen = struct diff --git a/otherlibs/stdune/src/spawn.ml b/otherlibs/stdune/src/spawn.ml index 60c1bbf5006..148a0557d13 100644 --- a/otherlibs/stdune/src/spawn.ml +++ b/otherlibs/stdune/src/spawn.ml @@ -1,5 +1,3 @@ -external contains_null : string -> bool = "dune_spawn_contains_null" [@@noalloc] - module Working_dir = struct type 'a gen = | Path of string @@ -224,7 +222,7 @@ let spawn_windows ;; let no_null s = - if contains_null s + if String.contains s '\000' then Printf.ksprintf invalid_arg diff --git a/otherlibs/stdune/src/spawn_stubs.c b/otherlibs/stdune/src/spawn_stubs.c index 7bac87f6417..9d5fc21b9b9 100644 --- a/otherlibs/stdune/src/spawn_stubs.c +++ b/otherlibs/stdune/src/spawn_stubs.c @@ -209,11 +209,6 @@ static int __pthread_fchdir(int fd) { #endif -CAMLprim value dune_spawn_contains_null(value v_string) -{ - return Val_bool(memchr(String_val(v_string), '\0', caml_string_length(v_string)) != NULL); -} - #if !defined(_WIN32) # if defined(USE_POSIX_SPAWN) diff --git a/otherlibs/stdune/src/string.ml b/otherlibs/stdune/src/string.ml index 33cf78f4c57..d897269b13e 100644 --- a/otherlibs/stdune/src/string.ml +++ b/otherlibs/stdune/src/string.ml @@ -76,13 +76,8 @@ module Caseless = Cased_functions (struct include Stdlib.StringLabels -let index_from_unchecked s i c = - let length = length s in - let rec loop i = - if i = length then -1 else if Char.equal (unsafe_get s i) c then i else loop (i + 1) - in - loop i -;; +external index_from_unchecked : t -> int -> char -> int = "dune_string_index_from" +[@@noalloc] let rindex_from_unchecked s i c = let rec loop i = @@ -91,6 +86,30 @@ let rindex_from_unchecked s i c = loop i ;; +let index_from s i c = + let length = length s in + if i < 0 || i > length then invalid_arg "String.index_from_opt / Bytes.index_from_opt"; + if i = length + then None + else ( + match index_from_unchecked s i c with + | -1 -> None + | index -> Some index) +;; + +let index s c = index_from s 0 c + +let contains_from s i c = + let length = length s in + if i < 0 || i > length then invalid_arg "String.contains_from / Bytes.contains_from"; + i < length && index_from_unchecked s i c <> -1 +;; + +let contains s c = + let length = length s in + length > 0 && index_from_unchecked s 0 c <> -1 +;; + (* [StringLabels] shadows these implementations with versions that allocate a local recursive closure on each call. *) let starts_with = Case_sensitive.starts_with @@ -116,8 +135,6 @@ let capitalize = capitalize_ascii let uncapitalize = uncapitalize_ascii let uppercase = uppercase_ascii let lowercase = lowercase_ascii -let index = index_opt -let index_from s i c = index_from_opt s i c let rindex = rindex_opt let rindex_from s i c = rindex_from_opt s i c let break s ~pos = sub s ~pos:0 ~len:pos, sub s ~pos ~len:(length s - pos) @@ -182,15 +199,17 @@ let rsplit2 s ~on = include String_split +let split_on_char ~sep s = split s ~on:sep + let escape_only c s = - let n = ref 0 in - let len = length s in - for i = 0 to len - 1 do - if unsafe_get s i = c then incr n - done; - if !n = 0 + if not (contains s c) then s else ( + let n = ref 0 in + let len = length s in + for i = 0 to len - 1 do + if unsafe_get s i = c then incr n + done; let b = Bytes.create (len + !n) in n := 0; for i = 0 to len - 1 do @@ -226,7 +245,7 @@ let quoted = Printf.sprintf "%S" let maybe_quoted s = let escaped = escaped s in - if (s == escaped || s = escaped) && not (String.contains s ' ') then s else quoted s + if (s == escaped || s = escaped) && not (contains s ' ') then s else quoted s ;; include Comparable.Make (T) diff --git a/otherlibs/stdune/src/string.mli b/otherlibs/stdune/src/string.mli index 8324a35b462..9544528c8a5 100644 --- a/otherlibs/stdune/src/string.mli +++ b/otherlibs/stdune/src/string.mli @@ -48,6 +48,8 @@ val capitalize : t -> t val uncapitalize : t -> t val uppercase : t -> t val lowercase : t -> t +val contains : t -> char -> bool +val contains_from : t -> int -> char -> bool val index : t -> char -> int option val index_from : t -> int -> char -> int option val rindex : t -> char -> int option @@ -84,6 +86,7 @@ val rsplit2 : t -> on:char -> (t * t) option the original string [s]. *) val split : t -> on:char -> t list +val split_on_char : sep:char -> t -> t list val split_lines : t -> t list (** Escape ONLY one character. {!escape} also escapes '\n',... and transforms diff --git a/otherlibs/stdune/src/string_split.ml b/otherlibs/stdune/src/string_split.ml index 37dc46b1c03..e04d888d059 100644 --- a/otherlibs/stdune/src/string_split.ml +++ b/otherlibs/stdune/src/string_split.ml @@ -2,39 +2,36 @@ module List = Stdlib.ListLabels module String = Stdlib.StringLabels open String +external index_from : string -> int -> char -> int = "dune_string_index_from" [@@noalloc] + let split s ~on = let len = length s in - let rec loop i j = - if j = len - then [ sub s ~pos:i ~len:(j - i) ] - else if String.unsafe_get s j = on - then sub s ~pos:i ~len:(j - i) :: loop (j + 1) (j + 1) - else loop i (j + 1) + let rec loop i = + let separator = if i = len then -1 else index_from s i on in + match separator with + | -1 -> [ sub s ~pos:i ~len:(len - i) ] + | j -> sub s ~pos:i ~len:(j - i) :: loop (j + 1) in - loop 0 0 + loop 0 ;; let split_lines s = let len = length s in - let rec loop ~last_is_cr ~acc i j = - if j = len - then ( + let rec loop acc i = + let newline = if i = len then -1 else index_from s i '\n' in + match newline with + | -1 -> let acc = - if j = i || (j = i + 1 && last_is_cr) + if i = len || (i + 1 = len && String.unsafe_get s i = '\r') then acc - else sub s ~pos:i ~len:(j - i) :: acc + else sub s ~pos:i ~len:(len - i) :: acc + in + List.rev acc + | j -> + let line_len = + if j > i && String.unsafe_get s (j - 1) = '\r' then j - i - 1 else j - i in - List.rev acc) - else ( - match String.unsafe_get s j with - | '\r' -> loop ~last_is_cr:true ~acc i (j + 1) - | '\n' -> - let line = - let len = if last_is_cr then j - i - 1 else j - i in - sub s ~pos:i ~len - in - loop ~acc:(line :: acc) (j + 1) (j + 1) ~last_is_cr:false - | _ -> loop ~acc i (j + 1) ~last_is_cr:false) + loop (sub s ~pos:i ~len:line_len :: acc) (j + 1) in - loop ~acc:[] 0 0 ~last_is_cr:false + loop [] 0 ;; diff --git a/otherlibs/stdune/src/string_stubs.c b/otherlibs/stdune/src/string_stubs.c new file mode 100644 index 00000000000..460d99b7b2c --- /dev/null +++ b/otherlibs/stdune/src/string_stubs.c @@ -0,0 +1,25 @@ +#include + +#include +#include + +CAMLprim value dune_string_index_from(value v_string, value v_position, value v_char) +{ + const unsigned char *string = (const unsigned char *)String_val(v_string); + size_t length = caml_string_length(v_string); + size_t position = Long_val(v_position); + const unsigned char *result = + (const unsigned char *)memchr(string + position, Int_val(v_char), length - position); + return Val_long(result == NULL ? -1 : result - string); +} + +CAMLprim value dune_bytes_index_in_range( + value v_bytes, value v_position, value v_length, value v_char) +{ + const unsigned char *bytes = (const unsigned char *)String_val(v_bytes); + size_t position = Long_val(v_position); + size_t length = Long_val(v_length); + const unsigned char *result = + (const unsigned char *)memchr(bytes + position, Int_val(v_char), length); + return Val_long(result == NULL ? -1 : result - bytes); +} diff --git a/otherlibs/stdune/test/path_tests.ml b/otherlibs/stdune/test/path_tests.ml index 99076674dcc..43888d6c3e6 100644 --- a/otherlibs/stdune/test/path_tests.ml +++ b/otherlibs/stdune/test/path_tests.ml @@ -122,6 +122,20 @@ let%expect_test "canonical local component boundaries" = |}] ;; +let%expect_test "normalize relative path separators" = + List.iter + [ "foo//bar/"; "./foo"; "foo///bar//baz"; "foo/./bar"; "foo/../bar" ] + ~f:(fun path -> Path.relative Path.root path |> Path.to_dyn |> print_dyn); + [%expect + {| + In_source_tree "foo/bar" + In_source_tree "foo" + In_source_tree "foo/bar/baz" + In_source_tree "foo/bar" + In_source_tree "bar" + |}] +;; + let%expect_test _ = let p = Path.(relative root) "foo" in descendant p ~of_:p; diff --git a/otherlibs/stdune/test/string_tests.ml b/otherlibs/stdune/test/string_tests.ml index dc4c0815194..2d2c1f0ba50 100644 --- a/otherlibs/stdune/test/string_tests.ml +++ b/otherlibs/stdune/test/string_tests.ml @@ -208,6 +208,14 @@ let%expect_test _ = [%expect {| [ ""; ""; ""; "" ] |}] ;; +let%expect_test "split_on_char agrees with split" = + List.iter [ ""; "a"; ":"; "a:b"; "::a::b::" ] ~f:(fun s -> + assert ( + List.equal String.equal (String.split_on_char ~sep:':' s) (String.split s ~on:':'))); + print_endline "all splits agree"; + [%expect {| all splits agree |}] +;; + let%expect_test "extract blank separated words" = List.iter [ ""; " \t "; "one"; " one\ttwo three " ] ~f:(fun s -> String.extract_blank_separated_words s |> list string |> print_dyn); @@ -262,6 +270,23 @@ let%expect_test "forward searches agree with the standard library" = [%expect {| all searches agree |}] ;; +let%expect_test "bytes search is restricted to the requested range" = + let bytes = Bytes.of_string "a%b%c" in + List.iter + [ 0, 0; 0, 1; 0, 2; 2, 2; 4, 1; 5, 0 ] + ~f:(fun (pos, len) -> + Bytes.index_in_range_unchecked bytes ~pos ~len '%' |> printfn "%d"); + [%expect + {| + -1 + -1 + 1 + 3 + -1 + -1 + |}] +;; + let%expect_test "reverse searches agree with the standard library" = List.iter search_test_strings ~f:(fun s -> for pos = -1 to String.length s - 1 do diff --git a/src/dune_rules/artifact_substitution.ml b/src/dune_rules/artifact_substitution.ml index 727840e9a63..bde9fc05077 100644 --- a/src/dune_rules/artifact_substitution.ml +++ b/src/dune_rules/artifact_substitution.ml @@ -405,14 +405,9 @@ module Scanner = struct buffer is the less likely case. *) let rec scan0 ~buf ~pos ~end_of_data = - if pos < end_of_data - then ( - let c = Bytes.unsafe_get buf pos in - let pos = pos + 1 in - match c with - | '%' -> scan1 ~buf ~pos ~end_of_data - | _ -> scan0 ~buf ~pos ~end_of_data) - else Scan0 + match Bytes.index_in_range_unchecked buf ~pos ~len:(end_of_data - pos) '%' with + | -1 -> Scan0 + | percent -> scan1 ~buf ~pos:(percent + 1) ~end_of_data and scan1 ~buf ~pos ~end_of_data = if pos < end_of_data