(** A small library for manipulating bigstrings.

    A bigstring is a mutable data structure that contains a fixed-length
    sequence of bytes. Each byte can be indexed in constant time for reading and
    writing.

    Given a byte sequence [bstr] of length [len], we can access each of the
    [len] bytes of [bstr] via its index in the sequence. Indexes start at [0],
    and will call an index valid in [bstr] if it falls within the range
    [[0...len-1]] (inclusive). A position is the point between two bytes or at
    the beginning or end of the sequence. We call a position valid in [bstr] if
    it falls within the range [[0...len]] (inclusive). Note that byte at index
    [n] is between positions [n] and [n+1].

    Two parameters [off] and [len] are said to designate a valid range of [bstr]
    if [len >= 0] and [off] and [off+len] are valid positions in [bstr].

    Byte sequences can be modified in place, for instance via the {!val:set} and
    {!val:blit} functions described below.

    {1:bigarray Bigstrings & Bigarrays.}

    Bigstring is a specialised version of {!module:Bigarray} that not only
    handles bytes in the form of {!module:Char}acter but also imposes a "C-like"
    (see {!val:Bigarray.c_layout}) view as described above and allows common
    functions such as [memcpy(3)] or [memmove(3)] to be offered.

    For more details about Bigstrings and Bigarrays, we invite you to read the
    {!module:Bigarray} documentation, which offers more general functions that
    can be applied to Bigstrings.

    {1:bytes Bigstrings & Bytes.}

    Like bytes, a bigstring is a mutable data structure that contains a
    fixed-length sequence of bytes. However, a bigstring has a few special
    features that can make it more interesting to use than bytes.

    {2:location Bigstrings and the Garbage Collector.}

    A bigstring is not allocated in the same way as a standard OCaml value. In
    fact, the byte sequence that the bigstring refers to is found in the
    {i C heap} (rather than the OCaml heap). This means that the byte sequence
    can come from a [malloc(3)] or a function requesting a particular memory
    area from the system such as [Unix.map_file].

    This particularity has an implication with the GC: the byte sequence is
    {b not relocatable}. That is to say that during the cycle of the Garbage
    Collector, this byte sequence does not move — in contrast, a [bytes] can be
    moved by the GC (typically, from the minor heap to the major heap).

    Thus, bigstrings have advantages and disadvantages compared to bytes due to
    this particularity:
    - Creating a bigstring can be expensive. Whether it is with
      [malloc(3)]/{!val:create} or [Unix.map_file], creating a bigstring will
      always be more expensive than creating bytes with OCaml. For small byte
      sequences, it is therefore preferable to use bytes.
    - Since a bigstring cannot be moved, its position can be shared by [Thread]s
      and/or [Domain]s without interacting with the GC. An example is being able
      to perform a complex computation in parallel from the bytes of this
      sequence without {i blocking} the Garbage Collector during this
      computation.

    Depending on these characteristics, it may be more advantageous to use a
    bigstring rather than [bytes]. This basically depends on your usage, and the
    special features of bigstrings can unlock opportunities to outperform byte
    calculations or analysis.

    {2:sub Bigstring and slice.}

    Another advantage of bigstrings is that copying is avoided when extracting
    part of a larger bigstring. This is because the {!val:sub} function returns
    a "proxy" of the original bigstring.

    In this respect, and to be very precise, {!val:sub} avoids copying but the
    creation of this "proxy" {b remains} costly. In addition, this library is
    distributed with a new {!module:Slice_bstr} module. The latter offers a new
    type whose {!val:Slice_bstr.sub} function is much less costly than
    {!val:sub}.

    {1 Bigstrings.} *)

type t = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t

(** {2 Constructors.} *)

val empty : t
(** [empty] is an empty bigstring. *)

val create : int -> t
(** [create len] returns a new byte sequence of length [len]. The sequence
    {b is unitialized} and contains arbitrary bytes. *)

val make : int -> char -> t
(** [make len chr] is {!type:t} of length [len] with each index holding the
    character [chr]. *)

val copy : t -> t
(** [copy t] returns a new byte sequence that contains the same bytes as the
    argument. *)

val init : int -> (int -> char) -> t
(** [init len fn] returns a fresh byte sequence of length [len], with character
    [idx] initialized to the result of [fn idx] (in increasing index order). *)

(** {2 Memory-safe Operations.} *)

val of_string : string -> t
(** [of_string str] returns a new {!type:t} that contains the contents of the
    given string [str]. *)

val string : ?off:int -> ?len:int -> string -> t
(** [string ~off ~len str] is the sub-buffer of [str] that starts at position
    [off] (defaults to [0]) and stops at position [off + len] (defaults to
    [String.length str]). [str] is fully-replaced by a fresh allocated
    {!type:t}.

    @raise Invalid_argument
      if [off] and [len] do not designate a valid range of [str]. *)

val sub_string : t -> off:int -> len:int -> string
(** [sub_string bstr ~off ~len] returns a string of length [len] containing the
    bytes of [bstr] starting at [off].

    @raise Invalid_argument
      if [off] and [len] do not designate a valid range of [t]. *)

val to_string : t -> string
(** [to_string bstr] is equivalent to
    [sub_string bstr ~off:0 ~len:(length bstr)]. *)

val length : t -> int
(** [length bstr] is the number of bytes in [bstr]. *)

val get : t -> int -> char
(** [get bstr i] is the byte of [bstr]' at index [i]. This is equivalent to the
    [bstr.{i}] notation.

    @raise Invalid_argument if [i] is not an index of [bstr]. *)

val set : t -> int -> char -> unit
(** [set t i chr] modifies [t] in place, replacing the byte at index [i] with
    [chr].

    @raise Invalid_argument if [i] is not a valid index in [t]. *)

val unsafe_get : t -> int -> char
(** [unsafe_get t idx] is like {!val:get} except no bounds checking is
    performed. *)

val unsafe_set : t -> int -> char -> unit
(** [unsafe_set t idx chr] is like {!val:set} except no bounds checking is
    performed. *)

val chop : ?rev:bool -> t -> char option
(** [chop bstr] returns the first element of [bstr] or the last element if
    [rev = true]. If [bstr] is empty, it returns [None]. *)

val concat : string -> t list -> t
(** [concat sep ts] concatenates the list of bigstrings [ts], inserting the
    separator string [sep] between each. *)

val extend : t -> int -> int -> t
(** [extend bstr left right] returns a new bigstring that contains the bytes of
    [bstr], with [left] zero bytes prepended and [right] zero byte appended to
    it. If [left] or [right] is negative, then bytes are removed (instead of
    appended) from the corresponding side of [bstr].

    @raise Invalid_argument if the result length is negative *)

(** {2 Copy operation from one byte sequence to another.} *)

val blit : t -> src_off:int -> t -> dst_off:int -> len:int -> unit
(** [blit src ~src_off dst ~dst_off ~len] copies [len] bytes from byte sequence
    [src], starting at index [src_off], to byte sequence [dst], starting at
    index [dst_off]. It works correctly even if [src] and [dst] are (physically)
    the same byte sequence, and the source and destination intervals overlap.

    @raise Invalid_argument
      if [src_off] and [len] do not designate a valid range of [src], or if
      [dst_off] and [len] do not designate a valid range of [dst]. *)

val blit_from_string :
  string -> src_off:int -> t -> dst_off:int -> len:int -> unit
(** Just like {!val:blit}, but with a string as source one.

    {b Note}: since it is impossible for [src] to overlap [dst], {!val:memcpy}
    is used to do the copy.

    @raise Invalid_argument
      if [src_pos] and [len] do not designate a valid range of [src], or if
      [dst_off] and [len] do not designate a valid range of [dst]. *)

val blit_from_bytes :
  bytes -> src_off:int -> t -> dst_off:int -> len:int -> unit
(** Just like {!val:blit}, but with a bytes as source one.

    {b Note}: since it is impossible for [src] to overlap [dst], {!val:memcpy}
    is used to do the copy.

    @raise Invalid_argument
      if [src_pos] and [len] do not designate a valid range of [src], or if
      [dst_off] and [len] do not designate a valid range of [dst]. *)

val blit_to_bytes : t -> src_off:int -> bytes -> dst_off:int -> len:int -> unit
(** [blit_to_bytes src ~src_off dst ~dst_off ~len] copies [len] bytes from
    [src], starting at index [src_off], to byte sequence [dst], starting at
    index [dst_off].

    {b Note}: since it is impossible for [src] to overlap [dst], {!val:memcpy}
    is used to do the copy.

    @raise Invalid_argument
      if [src_off] and [len] do not designate a valid range of [src], or if
      [dst_off] and [len] do not designate a valid range of [dst]. *)

val memcpy : t -> src_off:int -> t -> dst_off:int -> len:int -> unit
(** [memcpy src ~src_off dst ~dst_off ~len] copies [len] bytes from [src] to
    [dst]. [src] {b must not} overlap [dst]. Use {!val:memmove} if [src] & [dst]
    do overlap.

    You can check whether two buffers overlap using {!val:overlap}. If this
    returns [None], the two values do not refer to a common memory area — and it
    is safe to use memcpy.

    @raise Invalid_argument
      if [src_off] and [len] do not designate a valid range of [src], or if
      [dst_off] and [len] do not designate a valid range of [dst]. *)

val memcpy_mmaped : t -> src_off:int -> t -> dst_off:int -> len:int -> unit
(** [memcpy_mmaped] is like {!val:memcpy} but [src] and [dst] can be a
    {i mmaped} bigarray (from [Unix.map_file]). In this specific case, copying
    from one to the other can take some time because it involves reading/writing
    to disk. The operation can take longer than if the two bigarrays were
    allocated via [malloc()]/{!val:Bigarray.Array1.create}.

    It may therefore be worthwhile to release the GC lock so that this specific
    operation can be carried out in parallel (in a [Thread]) without
    interruption by the GC.

    Note that the bigarrays do not necessarily need to be {i mmaped}. This
    function also applies to "normal" bigarrays. It may also be worthwhile to
    use this function if you know that you are copying a large area and would
    like to do it in parallel (in a [Thread]). *)

val memmove : t -> src_off:int -> t -> dst_off:int -> len:int -> unit
(** [memmove src ~src_off dst ~dst_off ~len] copies [len] bytes from [src] to
    [dst]. [src] and [dst] may overlap: copying takes place as though the bytes
    in [src] are first copied into a temporary array that does not overlap [src]
    or [dst], and the bytes are then copied from the temporary array to [dst].

    @raise Invalid_argument
      if [src_off] and [len] do not designate a valid range of [src], or if
      [dst_off] and [len] do not designate a valid range of [dst]. *)

val memmove_mmaped : t -> src_off:int -> t -> dst_off:int -> len:int -> unit
(** [memmove_mmaped] is like {!val:memmove} but [src] and [dst] can be a
    {i mmaped} bigarray (from [Unix.map_file]). In this specific case, copying
    from one to the other can take some time because it involves reading/writing
    to disk. The operation can take longer than if the two bigarrays were
    allocated via [malloc()]/{!val:Bigarray.Array1.create}.

    It may therefore be worthwhile to release the GC lock so that this specific
    operation can be carried out in parallel (in a [Thread]) without
    interruption by the GC.

    Note that the bigarrays do not necessarily need to be {i mmaped}. This
    function also applies to "normal" bigarrays. It may also be worthwhile to
    use this function if you know that you are copying a large area and would
    like to do it in parallel (in a [Thread]). *)

val memcmp : t -> src_off:int -> t -> dst_off:int -> len:int -> int
(** [memcmp s1 ~src_off s2 ~dst_off ~len] compares the first [len] bytes of the
    memory areas [s1] (starting at [src_off]) and [s2] (starting at [dst_off]).

    [memcmp] returns [0] is [s1] and [s2] don't match.

    @raise Invalid_argument
      if [src_off] and [len] do not designate a valid range of [src], or if
      [dst_off] and [len] do not designate a valid range of [dst]. *)

val memset : t -> off:int -> len:int -> char -> unit
(** [memset t ~off ~len chr] fills [len] bytes (starting at [off]) into [t] with
    the constant byte [chr].

    @raise Invalid_argument
      if [off] and [len] do not designate a valid range of [t]. *)

val fill : t -> ?off:int -> ?len:int -> char -> unit
(** [fill t off len chr] modifies [t] in place, replacing [len] characters with
    [chr], starting at [off].

    @raise Invalid_argument
      if [off] and [len] do not designate a valid range of [t]. *)

(** {2 Decode integers from a byte sequence.} *)

val get_int8 : t -> int -> int
(** [get_int8 bstr i] is [bstr]'s signed 8-bit integer starting at byte index
    [i]. *)

val get_uint8 : t -> int -> int
(** [get_uint8 bstr i] is [bstr]'s unsigned 8-bit integer starting at byte index
    [i]. *)

val get_uint16_ne : t -> int -> int
(** [get_int16_ne bstr i] is [bstr]'s native-endian unsigned 16-bit integer
    starting at byte index [i]. *)

val get_uint16_le : t -> int -> int
(** [get_int16_le bstr i] is [bstr]'s little-endian unsigned 16-bit integer
    starting at byte index [i]. *)

val get_uint16_be : t -> int -> int
(** [get_int16_be bstr i] is [bstr]'s big-endian unsigned 16-bit integer
    starting at byte index [i]. *)

val get_int16_ne : t -> int -> int
(** [get_int16_ne bstr i] is [bstr]'s native-endian signed 16-bit integer
    starting at byte index [i]. *)

val get_int16_le : t -> int -> int
(** [get_int16_le bstr i] is [bstr]'s little-endian signed 16-bit integer
    starting at byte index [i]. *)

val get_int16_be : t -> int -> int
(** [get_int16_be bstr i] is [bstr]'s big-endian signed 16-bit integer starting
    at byte index [i]. *)

val get_int32_ne : t -> int -> int32
(** [get_int32_ne bstr i] is [bstr]'s native-endian 32-bit integer starting at
    byte index [i]. *)

val get_int32_le : t -> int -> int32
(** [get_int32_le bstr i] is [bstr]'s little-endian 32-bit integer starting at
    byte index [i]. *)

val get_int32_be : t -> int -> int32
(** [get_int32_be bstr i] is [bstr]'s big-endian 32-bit integer starting at byte
    index [i]. *)

val get_int64_ne : t -> int -> int64
(** [get_int64_ne bstr i] is [bstr]'s native-endian 64-bit integer starting at
    byte index [i]. *)

val get_int64_le : t -> int -> int64
(** [get_int64_le bstr i] is [bstr]'s little-endian 64-bit integer starting at
    byte index [i]. *)

val get_int64_be : t -> int -> int64
(** [get_int64_be bstr i] is [bstr]'s big-endian 64-bit integer starting at byte
    index [i]. *)

val set_int8 : t -> int -> int -> unit
(** [set_int8 t i v] sets [t]'s signed 8-bit integer starting at byte index [i]
    to [v]. *)

val set_uint8 : t -> int -> int -> unit
(** [set_uint8 t i v] sets [t]'s unsigned 8-bit integer starting at byte index
    [i] to [v]. *)

val set_uint16_ne : t -> int -> int -> unit
(** [set_uint16_ne t i v] sets [t]'s native-endian unsigned 16-bit integer
    starting at byte index [i] to [v]. *)

val set_uint16_le : t -> int -> int -> unit
(** [set_uint16_le t i v] sets [t]'s little-endian unsigned 16-bit integer
    starting at byte index [i] to [v]. *)

val set_uint16_be : t -> int -> int -> unit
(** [set_uint16_le t i v] sets [t]'s big-endian unsigned 16-bit integer starting
    at byte index [i] to [v]. *)

val set_int16_ne : t -> int -> int -> unit
(** [set_uint16_ne t i v] sets [t]'s native-endian signed 16-bit integer
    starting at byte index [i] to [v]. *)

val set_int16_le : t -> int -> int -> unit
(** [set_uint16_le t i v] sets [t]'s little-endian signed 16-bit integer
    starting at byte index [i] to [v]. *)

val set_int16_be : t -> int -> int -> unit
(** [set_uint16_le t i v] sets [t]'s big-endian signed 16-bit integer starting
    at byte index [i] to [v]. *)

val set_int32_ne : t -> int -> int32 -> unit
(** [set_int32_ne t i v] sets [t]'s native-endian 32-bit integer starting at
    byte index [i] to [v]. *)

val set_int32_le : t -> int -> int32 -> unit
(** [set_int32_ne t i v] sets [t]'s little-endian 32-bit integer starting at
    byte index [i] to [v]. *)

val set_int32_be : t -> int -> int32 -> unit
(** [set_int32_ne t i v] sets [t]'s big-endian 32-bit integer starting at byte
    index [i] to [v]. *)

val set_int64_ne : t -> int -> int64 -> unit
(** [set_int32_ne t i v] sets [t]'s native-endian 64-bit integer starting at
    byte index [i] to [v]. *)

val set_int64_le : t -> int -> int64 -> unit
(** [set_int32_ne t i v] sets [t]'s little-endian 64-bit integer starting at
    byte index [i] to [v]. *)

val set_int64_be : t -> int -> int64 -> unit
(** [set_int32_ne t i v] sets [t]'s big-endian 64-bit integer starting at byte
    index [i] to [v]. *)

val sub : t -> off:int -> len:int -> t
(** [sub bstr ~off ~len] does not allocate a bigstring, but instead returns a
    new view into [bstr] starting at [off], and with length [len].

    {b Note} [sub] does not allocate a new buffer, but instead shares the memory
    area of [bstr] with the newly-returned bigstring. This means that the
    changes ([set{,_*}] functions) made to the returned bigstring will also be
    reflected in the [bstr] bigstring given.

    {b Note} [sub] is more expensive than a [Slice.sub] (about 8 times slower).
    If you want to focus on performance while avoiding copying, it's best to use
    a [Slice]. *)

val shift : t -> int -> t
(** [shift bstr n] is [sub bstr n (length bstr - n)] (see {!val:sub} for more
    details). *)

val overlap : t -> t -> (int * int * int) option
(** [overlap x y] returns the size (in bytes) of what is physically common
    between [x] and [y], as well as the position of [y] in [x] and the position
    of [x] in [y]. *)

(** {2 Predicates and comparaisons.} *)

val is_empty : t -> bool
(** [is_empty bstr] is [length bstr = 0]. *)

val is_prefix : affix:string -> t -> bool
(** [is_prefix ~affix bstr] is [true] iff [affix.[idx] = bstr.{idx}] for all
    indices [idx] of [affix]. *)

val starts_with : prefix:t -> t -> bool
(** [starts_with ~prefix t] is like {!val:is_prefix} but the prefix is a
    {!type:t} (instead of a [string]). *)

val is_infix : affix:string -> t -> bool
(** [is_infix ~affix bstr] is [true] iff there exists an index [j] in [bstr]
    such that for all indices [i] of [affix] we have [affix.[i] = bstr.{j + i}].
*)

val is_suffix : affix:string -> t -> bool
(** [is_suffix ~affix bstr] is [true] iff [affix.[n - idx] = bstr.{m - idx}] for
    all indices [idx] of [affix] with [n = String.length affix - 1] and
    [m = length bstr - 1]. *)

val ends_with : suffix:t -> t -> bool
(** [ends_with ~suffix t] is like {!val:is_suffix} but the suffix is a {!type:t}
    (instead of a [string]. *)

val for_all : (char -> bool) -> t -> bool
(** [for_all p bstr] is [true] iff for all indices [idx] of [bstr],
    [p bstr.{idx} = true]. *)

val contains : t -> ?off:int -> ?len:int -> char -> bool
(** [contains bstr ?off ?len chr] is [true] if and only if [chr] appears in
    [len] byte(s)'s [bstr] after position [off] (defaults to [0]). *)

val equal : t -> t -> bool
(** [equal a b] is [a = b]. *)

val constant_equal : t -> t -> bool
(** [constant_equal] gives the same result as {!val:equal} but the execution
    time of the function, whether or not the two values are equivalent (as long
    as they have the {b same} size) is the same.

    Indeed, the {!val:equal} function ends as soon as a difference exists. This
    function continues even if a difference exists. This function is useful when
    comparing passwords — and avoiding an {i timing attack}. *)

val compare : t -> t -> int
(** [compare bstr0 bstr1] sorts [bstr0] and [bstr1] in lexicographical order. *)

val index : t -> ?off:int -> ?len:int -> char -> int option
(** [index bstr ?off ?len chr] is the index of the first occurrence of [chr] in
    [len] byte(s)'s [bstr] after position [off] (defaults to [0]). If [chr] does
    not occur in given range of [bstr], we return [None].

    @raise Invalid_argument
      if [off] and [len] do not designate a valid range of [bstr]. *)

val memchr : t -> off:int -> len:int -> char -> int
(** [memchr t ~off ~len chr] scans [len] bytes (starting at [off]) of [t] for
    the first instance of [chr]. It returns the position in [t] where the first
    occurrence of [chr] is found. Otherwise, it returns [-1].

    @raise Invalid_argument
      if [off] and [len] do not designate a valid range of [t]. *)

(** {2 Extracting substrings.} *)

val with_range : ?first:int -> ?len:int -> t -> t
(** [with_range ~first ~len bstr] are the consecutive bytes of [bstr] whose
    indices exist in the range \[[first];[first + len - 1]\].

    [first] defaults to [0] and [len] to [max_int]. Note that [first] can be any
    integer and [len] any positive integer. *)

val with_index_range : ?first:int -> ?last:int -> t -> t
(** [with_index_range ~first ~last bstr] are the consecutive bytes of [bstr]
    whose indices exists in the range \[[first];[last]\].

    [first] defaults to [0] and [last] to [length bstr - 1].

    Note that both [first] and [last] can be any integer. If [first > last] the
    interval is empty and the empty bigstring is returned. *)

val trim : ?drop:(char -> bool) -> t -> t
(** [trim ~drop bstr] is [bstr] with prefix and suffix bytes satisfying [drop]
    in [bstr] removed. [drop] defaults to [fun chr -> chr = ' ']. *)

val span :
  ?rev:bool -> ?min:int -> ?max:int -> ?sat:(char -> bool) -> t -> t * t
(** [span ~rev ~min ~max ~sat bstr] is [(l, r)] where:
    - if [rev] is [false] (default), [l] is at least [min] and at most [max]
      consecutive [sat] satisfying initial bytes of [bstr] or {!empty} if there
      are no such bytes. [r] are the remaining bytes of [bstr].
    - if [rev] is [true], [r] is at least [min] and at most [max] consecutive
      [sat] satisfying final bytes of [bstr] or {!empty} if there are no such
      bytes. [l] are the remaining bytes of [bstr].

    If [max] is unspecified the span is unlimited. If [min] is unspecified it
    defaults to [0]. If [min > max] the condition can't be satisfied and the
    left or right span, depending on [rev], is always empty. [sat] defaults to
    [Fun.const true].

    @raise Invalid_argument if [max] or [min] is negative. *)

val take : ?rev:bool -> ?min:int -> ?max:int -> ?sat:(char -> bool) -> t -> t
(** [take ~rev ~min ~max ~sat bstr] is the matching span of {!span} without the
    remaining one. In other words:

    {[
      (if rev then snd else fst) (span ~rev ~min ~max ~sat bstr)
    ]} *)

val drop : ?rev:bool -> ?min:int -> ?max:int -> ?sat:(char -> bool) -> t -> t
(** [drop ~rev ~min ~max ~sat bstr] is the remaining span of {!span} without the
    matching span. In other words:

    {[
      (if rev then fst else snd) (span ~rev ~min ~max ~sat bstr)
    ]} *)

val cut : ?rev:bool -> sep:string -> t -> (t * t) option
(** [cut ~sep bstr] is either the pair [Some (l, r)] of the two (possibly empty)
    sub-buffers of [bstr] that are delimited by the first match of the non empty
    separator string [sep] or [None] if [sep] can't be matched in [bstr].
    Matching starts from the beginning of [bstr] ([rev] is [false], default) or
    the end ([rev] is [true]).

    The invariant [l ^ sep ^ r = s] holds.

    For instance, the {i ABNF} expression:

    {v
    field_name := *PRINT
    field_value := *ASCII
    field := field_name ":" field_value
    v}

    can be translated to:

    {[
      match Bstr.cut ~sep:":" value with
      | Some (field_name, field_value) -> ...
      | None -> invalid_arg "Invalid field"
    ]}

    @raise Invalid_argument if [sep] is the empty buffer. *)

val split_on_char : char -> t -> t list
(** [split_on_char sep t] is the list of all (possibly empty)
    {!val:sub}-bigstrings of [t] that are delimited by the character [sep]. If
    [t] is empty, the result is the singleton list [[empty]].

    The function's result is specified by the following invariant:
    - the list is not empty.
    - concatenating its elements using [sep] as a separator returns a bigstring
      equal to the input.
    - no bigstring in the result contains the [sep] character. *)

(** {2 Traversing strings.} *)

val iter : (char -> unit) -> t -> unit
(** [iter fn t] applies function [fn] in turn to all the characters of [t]. It
    is equivalent to [fn t.{0}; fn t.{1}; ...; fn t.{length t - 1}; ()]. *)

val to_seq : t -> char Seq.t
(** Iterate on the bigstring, in increasing index order. Modifications of the
    bigstring during iteration will be reflected in the sequence. *)

val to_seqi : t -> (int * char) Seq.t
(** Iterate on the bigstring, in increasing order, yielding indices along chars.
*)

val of_seq : char Seq.t -> t
(** Create a bigstring from the generator. *)
