Skip to content

Commit

Permalink
Merge pull request #2403 from BuckleScript/more_tests_and_consistent_api
Browse files Browse the repository at this point in the history
more tests and idiomatic api
  • Loading branch information
bobzhang committed Dec 26, 2017
2 parents c878a86 + f796255 commit 5a36672
Show file tree
Hide file tree
Showing 27 changed files with 345 additions and 1,160 deletions.
6 changes: 3 additions & 3 deletions jscomp/others/.depend
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ bs_Queue.cmj : bs_Array.cmj bs_Queue.cmi
bs_List.cmj : js_json.cmj bs_Array.cmj bs_List.cmi
bs_internalBucketsType.cmj : bs_Array.cmj
bs_internalSetBuckets.cmj : bs_internalBucketsType.cmj bs_Array.cmj bs.cmj
bs_internalBuckets.cmj : bs_internalBucketsType.cmj bs_Array.cmj
bs_internalBuckets.cmj : bs_internalBucketsType.cmj bs_Array.cmj bs.cmj
bs_HashMap.cmj : bs_internalBucketsType.cmj bs_internalBuckets.cmj \
bs_Hash.cmj bs_Bag.cmj bs_Array.cmj bs_HashMap.cmi
bs_HashSet.cmj : bs_internalSetBuckets.cmj bs_internalBucketsType.cmj \
Expand Down Expand Up @@ -53,9 +53,9 @@ js_global.cmj :
js_cast.cmj : js_cast.cmi
js_promise.cmj :
bs_HashMapInt.cmj : bs_internalBucketsType.cmj bs_internalBuckets.cmj \
bs_Array.cmj bs_HashMapInt.cmi
bs_Array.cmj bs.cmj bs_HashMapInt.cmi
bs_HashMapString.cmj : bs_internalBucketsType.cmj bs_internalBuckets.cmj \
bs_Array.cmj bs_HashMapString.cmi
bs_Array.cmj bs.cmj bs_HashMapString.cmi
node_process.cmi : js_dict.cmi
js_re.cmi :
js_null_undefined.cmi :
Expand Down
8 changes: 4 additions & 4 deletions jscomp/others/bs_HashMap.ml
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ let create dict initialize_size =
let clear h = clear0 (B.data h)
let reset h = reset0 (B.data h)
let length h = length0 (B.data h)
let iter f h = iter0 f (B.data h)
let fold f h init = fold0 f (B.data h) init
let iter h f = iter0 (B.data h) f
let fold h init f = fold0 (B.data h) init f
let logStats h = logStats0 (B.data h)

let add (type a) (type b ) (type id) (h : (a,b,id) t) (key:a) (info:b) =
Expand Down Expand Up @@ -261,5 +261,5 @@ let mem (type a) (type b) (type id) (h : (a,b,id) t) (key : a) =
let module M = (val dict) in
mem0 ~hash:M.hash ~eq:M.eq data key

let filterMapInplace f h =
filterMapInplace0 f (B.data h)
let filterMapInplace h f =
filterMapInplace0 (B.data h) f
41 changes: 6 additions & 35 deletions jscomp/others/bs_HashMap.mli
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ val replace:
This is functionally equivalent to {!Hashtbl.remove}[ tbl x]
followed by {!Hashtbl.add}[ tbl x y]. *)

val iter0 : ('a -> 'b -> unit [@bs]) -> ('a, 'b, 'id) t0 -> unit
val iter : ('a -> 'b -> unit [@bs]) -> ('a, 'b, 'id) t -> unit
val iter0 : ('a, 'b, 'id) t0 -> ('a -> 'b -> unit [@bs]) -> unit
val iter : ('a, 'b, 'id) t -> ('a -> 'b -> unit [@bs]) -> unit
(** [Hashtbl.iter f tbl] applies [f] to all bindings in table [tbl].
[f] receives the key as first argument, and the associated value
as second argument. Each binding is presented exactly once to [f].
Expand All @@ -144,8 +144,8 @@ val iter : ('a -> 'b -> unit [@bs]) -> ('a, 'b, 'id) t -> unit
of OCaml. For randomized hash tables, the order of enumeration
is entirely random. *)

val fold0 : ('a -> 'b -> 'c -> 'c [@bs]) -> ('a, 'b, 'id) t0 -> 'c -> 'c
val fold : ('a -> 'b -> 'c -> 'c [@bs]) -> ('a, 'b, 'id) t -> 'c -> 'c
val fold0 : ('a, 'b, 'id) t0 -> 'c -> ('a -> 'b -> 'c -> 'c [@bs]) -> 'c
val fold : ('a, 'b, 'id) t -> 'c -> ('a -> 'b -> 'c -> 'c [@bs]) -> 'c
(** [Hashtbl.fold f tbl init] computes
[(f kN dN ... (f k1 d1 init)...)],
where [k1 ... kN] are the keys of all bindings in [tbl],
Expand All @@ -163,8 +163,8 @@ val fold : ('a -> 'b -> 'c -> 'c [@bs]) -> ('a, 'b, 'id) t -> 'c -> 'c
of OCaml. For randomized hash tables, the order of enumeration
is entirely random. *)

val filterMapInplace0 : ('a -> 'b -> 'b option [@bs]) -> ('a, 'b, 'id) t0 -> unit
val filterMapInplace : ('a -> 'b -> 'b option [@bs]) -> ('a, 'b, 'id) t -> unit
val filterMapInplace0 : ('a, 'b, 'id) t0 -> ('a -> 'b -> 'b option [@bs]) -> unit
val filterMapInplace : ('a, 'b, 'id) t -> ('a -> 'b -> 'b option [@bs]) -> unit

val length0 : ('a, 'b, 'id) t0 -> int
val length : ('a, 'b, 'id) t -> int
Expand Down Expand Up @@ -196,36 +196,7 @@ val logStats : _ t -> unit
buckets by size.
@since 4.00.0 *)

(** {6 Functorial interface} *)

(** The functorial interface allows the use of specific comparison
and hash functions, either for performance/security concerns,
or because keys are not hashable/comparable with the polymorphic builtins.
For instance, one might want to specialize a table for integer keys:
{[
module IntHash =
struct
type t = int
let equal i j = i=j
let hash i = i land max_int
end
module IntHashtbl = Hashtbl.Make(IntHash)
let h = IntHashtbl.create 17 in
IntHashtbl.add h 12 "hello";;
]}
This creates a new module [IntHashtbl], with a new type ['a
IntHashtbl.t] of tables from [int] to ['a]. In this example, [h]
contains [string] values so its type is [string IntHashtbl.t].
Note that the new type ['a IntHashtbl.t] is not compatible with
the type [('a,'b) Hashtbl.t] of the generic interface. For
example, [Hashtbl.length h] would not type-check, you must use
[IntHashtbl.length].
*)



19 changes: 18 additions & 1 deletion jscomp/others/bs_HashMapInt.ml
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,21 @@ let iter = N.iter0
let fold = N.fold0
let logStats = N.logStats0
let filterMapInplace = N.filterMapInplace0

let toArray = N.toArray0

let ofArray arr =
let len = Bs.Array.length arr in
let v = create len in
for i = 0 to len - 1 do
let k,value = (Bs.Array.unsafe_get arr i) in
add v k value
done ;
v

(* TOOD: optimize heuristics for resizing *)
let addArray h arr =
let len = Bs.Array.length arr in
for i = 0 to len - 1 do
let k,v = (Bs_Array.unsafe_get arr i) in
add h k v
done
180 changes: 17 additions & 163 deletions jscomp/others/bs_HashMapInt.mli
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,6 @@ type 'b t


val create : int -> 'b t
(** [Hashtbl.create n] creates a new, empty hash table, with
initial size [n]. For best results, [n] should be on the
order of the expected number of elements that will be in
the table. The table grows as needed, so [n] is just an
initial guess.
The optional [random] parameter (a boolean) controls whether
the internal organization of the hash table is randomized at each
execution of [Hashtbl.create] or deterministic over all executions.
A hash table that is created with [~random:false] uses a
fixed hash function ({!Hashtbl.hash}) to distribute keys among
buckets. As a consequence, collisions between keys happen
deterministically. In Web-facing applications or other
security-sensitive applications, the deterministic collision
patterns can be exploited by a malicious user to create a
denial-of-service attack: the attacker sends input crafted to
create many collisions in the table, slowing the application down.
A hash table that is created with [~random:true] uses the seeded
hash function {!Hashtbl.seeded_hash} with a seed that is randomly
chosen at hash table creation time. In effect, the hash function
used is randomly selected among [2^{30}] different hash functions.
All these hash functions have different collision patterns,
rendering ineffective the denial-of-service attack described above.
However, because of randomization, enumerating all elements of the
hash table using {!Hashtbl.fold} or {!Hashtbl.iter} is no longer
deterministic: elements are enumerated in different orders at
different runs of the program.
If no [~random] parameter is given, hash tables are created
in non-random mode by default. This default can be changed
either programmatically by calling {!Hashtbl.randomize} or by
setting the [R] flag in the [OCAMLRUNPARAM] environment variable.
@before 4.00.0 the [random] parameter was not present and all
hash tables were created in non-randomized mode. *)


val clear : 'b t -> unit
(** Empty a hash table. Use [reset] instead of [clear] to shrink the
Expand All @@ -56,146 +18,38 @@ val reset : 'b t -> unit
to its initial size.
@since 4.00.0 *)

val add : 'a t -> key -> 'a -> unit

val findOpt: 'a t -> key -> 'a option


val add : 'b t -> key -> 'b -> unit
(** [Hashtbl.add tbl x y] adds a binding of [x] to [y] in table [tbl].
Previous bindings for [x] are not removed, but simply
hidden. That is, after performing {!Hashtbl.remove}[ tbl x],
the previous binding for [x], if any, is restored.
(Same behavior as with association lists.) *)

val findOpt:
'b t -> key -> 'b option
(** [findOpt tbl x] returns the current binding of [x] in [tbl],
*)

val findAll: 'b t -> key -> 'b list
val findAll: 'a t -> key -> 'a list
(** [Hashtbl.find_all tbl x] returns the list of all data
associated with [x] in [tbl].
The current binding is returned first, then the previous
bindings, in reverse order of introduction in the table. *)

val mem:
'b t -> key -> bool
(** [Hashtbl.mem tbl x] checks if [x] is bound in [tbl]. *)

val remove:
'b t -> key -> unit
(** [Hashtbl.remove tbl x] removes the current binding of [x] in [tbl],
restoring the previous binding if it exists.
It does nothing if [x] is not bound in [tbl]. *)

val removeAll:
'b t -> key -> unit


val replace:
'b t -> key -> 'b -> unit
(** [Hashtbl.replace tbl x y] replaces the current binding of [x]
in [tbl] by a binding of [x] to [y]. If [x] is unbound in [tbl],
a binding of [x] to [y] is added to [tbl].
This is functionally equivalent to {!Hashtbl.remove}[ tbl x]
followed by {!Hashtbl.add}[ tbl x y]. *)


val iter : (key -> 'b -> unit [@bs]) -> 'b t -> unit
(** [Hashtbl.iter f tbl] applies [f] to all bindings in table [tbl].
[f] receives the key as first argument, and the associated value
as second argument. Each binding is presented exactly once to [f].
The order in which the bindings are passed to [f] is unspecified.
However, if the table contains several bindings for the same key,
they are passed to [f] in reverse order of introduction, that is,
the most recent binding is passed first.
If the hash table was created in non-randomized mode, the order
in which the bindings are enumerated is reproducible between
successive runs of the program, and even between minor versions
of OCaml. For randomized hash tables, the order of enumeration
is entirely random. *)


val fold : (key -> 'b -> 'c -> 'c [@bs]) -> 'b t -> 'c -> 'c
(** [Hashtbl.fold f tbl init] computes
[(f kN dN ... (f k1 d1 init)...)],
where [k1 ... kN] are the keys of all bindings in [tbl],
and [d1 ... dN] are the associated values.
Each binding is presented exactly once to [f].
The order in which the bindings are passed to [f] is unspecified.
However, if the table contains several bindings for the same key,
they are passed to [f] in reverse order of introduction, that is,
the most recent binding is passed first.
If the hash table was created in non-randomized mode, the order
in which the bindings are enumerated is reproducible between
successive runs of the program, and even between minor versions
of OCaml. For randomized hash tables, the order of enumeration
is entirely random. *)


val filterMapInplace : (key -> 'b -> 'b option [@bs]) -> 'b t -> unit

val length : 'b t -> int
(** [Hashtbl.length tbl] returns the number of bindings in [tbl].
It takes constant time. Multiple bindings are counted once each, so
[Hashtbl.length] gives the number of times [Hashtbl.iter] calls its
first argument. *)


(*
type statistics = {
num_bindings: int;
(** Number of bindings present in the table.
Same value as returned by {!Hashtbl.length}. *)
num_buckets: int;
(** Number of buckets in the table. *)
max_bucket_length: int;
(** Maximal number of bindings per bucket. *)
bucket_histogram: int array
(** Histogram of bucket sizes. This array [histo] has
length [max_bucket_length + 1]. The value of
[histo.(i)] is the number of buckets whose size is [i]. *)
} *)
val mem: 'b t -> key -> bool

val logStats : _ t -> unit
(** [Hashtbl.stats tbl] returns statistics about the table [tbl]:
number of buckets, size of the biggest bucket, distribution of
buckets by size.
@since 4.00.0 *)
val remove: 'a t -> key -> unit

val removeAll: 'b t -> key -> unit

(** {6 Functorial interface} *)
val replace: 'b t -> key -> 'b -> unit

(** The functorial interface allows the use of specific comparison
and hash functions, either for performance/security concerns,
or because keys are not hashable/comparable with the polymorphic builtins.

For instance, one might want to specialize a table for integer keys:
{[
module IntHash =
struct
type t = int
let equal i j = i=j
let hash i = i land max_int
end
val iter : 'b t -> (key -> 'b -> unit [@bs]) -> unit

module IntHashtbl = Hashtbl.Make(IntHash)
val fold : 'b t -> 'c -> (key -> 'b -> 'c -> 'c [@bs]) -> 'c

let h = IntHashtbl.create 17 in
IntHashtbl.add h 12 "hello";;
]}

This creates a new module [IntHashtbl], with a new type ['a
IntHashtbl.t] of tables from [int] to ['a]. In this example, [h]
contains [string] values so its type is [string IntHashtbl.t].
val filterMapInplace : 'a t -> (key -> 'a -> 'a option [@bs]) -> unit

val length : _ t -> int
val logStats : _ t -> unit

Note that the new type ['a IntHashtbl.t] is not compatible with
the type [('a,'b) Hashtbl.t] of the generic interface. For
example, [Hashtbl.length h] would not type-check, you must use
[IntHashtbl.length].
*)
val toArray : 'a t -> (key * 'a) array
val ofArray : (key * 'a) array -> 'a t
val addArray : 'a t -> (key * 'a) array -> unit



19 changes: 18 additions & 1 deletion jscomp/others/bs_HashMapString.ml
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,21 @@ let iter = N.iter0
let fold = N.fold0
let logStats = N.logStats0
let filterMapInplace = N.filterMapInplace0

let toArray = N.toArray0

let ofArray arr =
let len = Bs.Array.length arr in
let v = create len in
for i = 0 to len - 1 do
let k,value = (Bs.Array.unsafe_get arr i) in
add v k value
done ;
v

(* TOOD: optimize heuristics for resizing *)
let addArray h arr =
let len = Bs.Array.length arr in
for i = 0 to len - 1 do
let k,v = (Bs_Array.unsafe_get arr i) in
add h k v
done
Loading

0 comments on commit 5a36672

Please sign in to comment.