mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-08 12:15:32 -05:00
244 lines
6.4 KiB
OCaml
244 lines
6.4 KiB
OCaml
type 'a iter = ('a -> unit) -> unit
|
|
|
|
type t = {
|
|
mutable bytes: bytes;
|
|
mutable sz: int;
|
|
}
|
|
|
|
let create ?(cap=0) () : t =
|
|
let bytes =
|
|
if cap=0 then Bytes.unsafe_of_string "" else Bytes.create cap in
|
|
{ sz=0; bytes }
|
|
|
|
let[@inline] capacity self : int = Bytes.length self.bytes
|
|
let[@inline] bytes self = self.bytes
|
|
let[@inline] length self = self.sz
|
|
|
|
let[@inline] is_empty self = self.sz = 0
|
|
let[@inline] clear self = self.sz <- 0
|
|
|
|
(*$T
|
|
(let b = create() in is_empty b)
|
|
(let b = create ~cap:32 () in is_empty b)
|
|
(let b = create() in length b = 0)
|
|
(let b = create ~cap:32 () in length b = 0)
|
|
*)
|
|
|
|
let grow_cap_ self =
|
|
min Sys.max_string_length
|
|
(let n = capacity self in n + n lsl 1 + 5)
|
|
|
|
let grow_to_ self newcap =
|
|
if newcap = capacity self then (
|
|
invalid_arg "byte_buf: cannot grow further";
|
|
);
|
|
let newbytes = Bytes.create newcap in
|
|
Bytes.blit self.bytes 0 newbytes 0 self.sz;
|
|
self.bytes <- newbytes
|
|
|
|
let[@inline never] grow_ self =
|
|
let newcap = grow_cap_ self in
|
|
grow_to_ self newcap
|
|
|
|
let ensure_cap self n =
|
|
if n>capacity self then (
|
|
let newcap = max n (grow_cap_ self) in
|
|
grow_to_ self newcap
|
|
)
|
|
|
|
let shrink_to self n =
|
|
if self.sz > n then self.sz <- n
|
|
|
|
let append_buf (self:t) buf : unit =
|
|
let n = Buffer.length buf in
|
|
ensure_cap self (length self + n);
|
|
Buffer.blit buf 0 self.bytes self.sz n;
|
|
self.sz <- self.sz + n
|
|
|
|
let append_subbytes self b off len =
|
|
ensure_cap self (length self + len);
|
|
Bytes.blit b off self.bytes self.sz len;
|
|
self.sz <- self.sz + len
|
|
|
|
let append_bytes self b = append_subbytes self b 0 (Bytes.length b)
|
|
let append_string self s = append_bytes self (Bytes.unsafe_of_string s)
|
|
let append_substring self s off len = append_subbytes self (Bytes.unsafe_of_string s) off len
|
|
|
|
let[@inline] add_char_unsafe_ self c =
|
|
Bytes.unsafe_set self.bytes self.sz c;
|
|
self.sz <- self.sz + 1
|
|
|
|
let[@inline] add_char self c =
|
|
if self.sz = capacity self then grow_ self;
|
|
add_char_unsafe_ self c
|
|
|
|
let[@inline] unsafe_get self i = Bytes.unsafe_get self.bytes i
|
|
let[@inline] unsafe_set self i c = Bytes.unsafe_set self.bytes i c
|
|
|
|
let[@inline] get self i =
|
|
if i < 0 || i >= self.sz then invalid_arg "Byte_buf.get";
|
|
unsafe_get self i
|
|
|
|
let[@inline] set self i c =
|
|
if i < 0 || i >= self.sz then invalid_arg "Byte_buf.set";
|
|
unsafe_set self i c
|
|
|
|
let[@inline] contents self = Bytes.sub_string self.bytes 0 self.sz
|
|
let[@inline] contents_bytes self = Bytes.sub self.bytes 0 self.sz
|
|
|
|
let[@inline] append_iter self i = i (add_char self)
|
|
let[@inline] append_seq self seq = Seq.iter (add_char self) seq
|
|
|
|
let fold_left f acc self =
|
|
let {bytes; sz} = self in (* capture current content *)
|
|
|
|
let acc = ref acc in
|
|
for i=0 to sz do
|
|
acc := f !acc (Bytes.unsafe_get bytes i)
|
|
done;
|
|
!acc
|
|
|
|
let iter f self =
|
|
let {bytes; sz} = self in (* capture current content *)
|
|
for i=0 to sz do
|
|
f (Bytes.unsafe_get bytes i)
|
|
done
|
|
|
|
let of_seq seq =
|
|
let self = create ~cap:32 () in
|
|
append_seq self seq;
|
|
self
|
|
|
|
let of_iter iter =
|
|
let self = create ~cap:32 () in
|
|
append_iter self iter;
|
|
self
|
|
|
|
let to_iter self yield = iter yield self
|
|
let to_seq self =
|
|
let {bytes;sz} = self in
|
|
let rec s i () =
|
|
if i= sz then Seq.Nil
|
|
else Seq.Cons (Bytes.unsafe_get bytes i, s (i+1))
|
|
in
|
|
s 0
|
|
|
|
(* TODO: unicode operators.*)
|
|
|
|
(*$inject
|
|
let test_count = 2_500
|
|
|
|
open QCheck
|
|
|
|
type op =
|
|
| Add_char of char
|
|
| Add_string of string
|
|
| Get_contents
|
|
| Get of int
|
|
| Clear
|
|
| Shrink_to of int
|
|
| Set of int * char
|
|
|
|
let spf = Printf.sprintf
|
|
|
|
let str_op = function
|
|
| Add_char c -> spf "add_char %C" c
|
|
| Add_string s -> spf "add_string %S" s
|
|
| Get_contents -> "contents"
|
|
| Get i -> spf "get %d" i
|
|
| Clear -> "clear"
|
|
| Shrink_to n -> spf "shrink %d" n
|
|
| Set (i,c) -> spf "set %d %C" i c
|
|
|
|
let gen_op size : (_*_) Gen.t =
|
|
let open Gen in
|
|
let base = if size>0 then
|
|
[1, ((0--size) >|= fun x -> Get x, size);
|
|
1, ((0--size) >>= fun x -> printable >|= fun c -> Set (x,c), size);
|
|
1, ((0--size) >|= fun x -> Shrink_to x, x);
|
|
]
|
|
else []
|
|
in
|
|
frequency (base @ [
|
|
1, return (Get_contents, size);
|
|
1, return (Clear, 0);
|
|
3, (printable >|= fun c -> Add_char c, size+1);
|
|
1, (string_size (0 -- 100) ~gen:printable >|= fun s ->
|
|
Add_string s, size+String.length s);
|
|
])
|
|
|
|
let rec gen_l acc sz n =
|
|
let open Gen in
|
|
if n=0 then return (List.rev acc)
|
|
else (
|
|
gen_op sz >>= fun (op, sz) ->
|
|
gen_l (op::acc) sz (n-1)
|
|
)
|
|
|
|
let gen : op list Gen.t = Gen.sized (gen_l [] 0)
|
|
|
|
let is_valid ops =
|
|
let rec loop sz = function
|
|
| [] -> true
|
|
| Add_char _ :: tl -> loop (sz+1) tl
|
|
| Clear :: tl -> loop 0 tl
|
|
| Add_string s :: tl -> loop (sz+String.length s) tl
|
|
| (Get n | Set (n,_)) :: tl -> n < sz && loop sz tl
|
|
| Get_contents :: tl -> loop sz tl
|
|
| Shrink_to x :: tl -> x <= sz && loop x tl
|
|
in loop 0 ops
|
|
|
|
let shrink_op = Iter.(function
|
|
| Get_contents | Clear -> empty
|
|
| Get n -> Shrink.int n >|= fun n->Get n
|
|
| Add_char c -> Shrink.char c >|= fun c -> Add_char c
|
|
| Add_string s -> Shrink.string s >|= fun s -> Add_string s
|
|
| Shrink_to n -> Shrink.int n >|= fun n -> Shrink_to n
|
|
| Set (n,c) ->
|
|
(Shrink.int n >|= fun n-> Set(n,c)) <+>
|
|
(Shrink.char c >|= fun c-> Set(n,c))
|
|
)
|
|
|
|
let arb = make gen ~print:(Print.list str_op)
|
|
~shrink:Shrink.(filter is_valid @@ list ~shrink:shrink_op)
|
|
|
|
exception Nope of string
|
|
let prop_consistent ops =
|
|
let buf = ref "" in
|
|
let b = create ~cap:32 () in
|
|
|
|
let run_op op =
|
|
match op with
|
|
| Get i ->
|
|
assert (String.length !buf = length b);
|
|
let c1 = (!buf).[i] in
|
|
let c2 = get b i in
|
|
if c1<>c2 then raise (Nope (spf "c1=%C, c2=%C" c1 c2))
|
|
|
|
| Get_contents ->
|
|
let s1 = !buf in
|
|
let s2 = contents b in
|
|
if s1<>s2 then raise (Nope (spf "s1=%S, s2=%S" s1 s2))
|
|
|
|
| Add_char c -> buf := !buf ^ String.make 1 c; add_char b c
|
|
| Add_string s -> buf := !buf ^ s; append_string b s
|
|
| Clear -> buf := ""; clear b
|
|
| Shrink_to n -> buf := String.sub !buf 0 n; shrink_to b n
|
|
| Set (n,c) ->
|
|
(
|
|
let b' = Bytes.of_string !buf in
|
|
Bytes.set b' n c;
|
|
buf := Bytes.unsafe_to_string b';
|
|
);
|
|
set b n c
|
|
in
|
|
|
|
assume (is_valid ops);
|
|
try List.iter run_op ops; true
|
|
with Nope str ->
|
|
Test.fail_reportf "consistent ops failed:\n%s" str
|
|
*)
|
|
|
|
(*$Q
|
|
arb (fun ops -> prop_consistent ops)
|
|
*)
|