Skip to content

Commit

Permalink
Implement Js.Dict
Browse files Browse the repository at this point in the history
  • Loading branch information
davesnx committed Aug 8, 2023
1 parent 510efe8 commit 9acab00
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 19 deletions.
75 changes: 62 additions & 13 deletions packages/js/js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2523,23 +2523,72 @@ module Date = struct
(** Provide bindings for JS Date *)
end

module Dict = struct
module type Dictionary = sig
(* Implemented as an assosiative list *)
type 'a t
type key = string

val empty : unit -> 'a t
val entries : 'a t -> (key * 'a) array
val fromArray : (key * 'a) array -> 'a t
val fromList : (key * 'a) list -> 'a t
val keys : 'a t -> key array
val values : 'a t -> 'a array
val set : 'a t -> key -> 'a -> 'a t
val get : 'a t -> key -> 'a option
val unsafeGet : 'a t -> key -> 'a
val map : ('a -> 'b) -> 'a t -> 'b t
val unsafeDeleteKey : 'a t -> key -> 'a t
end

module Dict : Dictionary = struct
(** Provide utilities for JS dictionary object *)

type 'a t
type key = string
type 'a t = (key * 'a) list

exception NotFound

let empty () : 'a t = []
let entries (dict : 'a t) : (string * 'a) array = dict |> Stdlib.Array.of_list

let get (dict : 'a t) (k : key) : 'a option =
let rec get' dict k =
match dict with
| [] -> None
| (k', x) :: rest -> if k = k' then Some x else get' rest k
in
get' dict k

let map (f : 'a -> 'b) (dict : 'a t) =
Stdlib.List.map (fun (k, a) -> (k, f a)) dict

let set (dict : 'a t) (k : key) (x : 'a) : 'a t =
let update (dict : 'a t) (key : key) (value : 'a) =
Stdlib.List.map
(fun (k, v) -> if Stdlib.String.equal k key then (k, value) else (k, v))
dict
in
match get dict k with None -> (k, x) :: dict | Some v -> update dict k v

let fromList (lst : (key * 'a) list) : 'a t =
Stdlib.List.fold_left (fun acc (k, v) -> set acc k v) [] lst
|> Stdlib.List.rev

let fromArray (arr : (key * 'a) array) : 'a t =
Stdlib.Array.to_list arr |> fromList

let keys (dict : 'a t) =
Stdlib.List.map (fun (k, _) -> k) dict |> Stdlib.Array.of_list

let values (dict : 'a t) =
Stdlib.List.map (fun (_, value) -> value) dict |> Stdlib.Array.of_list

let unsafeGet (dict : 'a t) (k : key) : 'a =
match get dict k with None -> raise NotFound | Some x -> x

let get _ _ = notImplemented "Js.Dict" "get"
let unsafeGet _ _ = notImplemented "Js.Dict" "unsafeGet"
let set _ _ = notImplemented "Js.Dict" "set"
let keys _ = notImplemented "Js.Dict" "keys"
let empty _ = notImplemented "Js.Dict" "empty"
let unsafeDeleteKey _ _ = notImplemented "Js.Dict" "unsafeDeleteKey"
let entries _ = notImplemented "Js.Dict" "entries"
let values _ = notImplemented "Js.Dict" "values"
let fromList _ = notImplemented "Js.Dict" "fromList"
let fromArray _ = notImplemented "Js.Dict" "fromArray"
let map _ _ = notImplemented "Js.Dict" "map"
let unsafeDeleteKey (dict : 'a t) (key : key) =
List.filter (fun (k, _) -> k <> key) dict
end

module Global = struct
Expand Down
83 changes: 77 additions & 6 deletions packages/js/test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ let assert_option x left right =
let assert_string_array left right =
Alcotest.check (Alcotest.array Alcotest.string) "should be equal" right left

let assert_int_array left right =
Alcotest.check (Alcotest.array Alcotest.int) "should be equal" right left

let assert_dict_entries type_ left right =
Alcotest.check
(Alcotest.array (Alcotest.pair Alcotest.string type_))
"should be equal" right left

let assert_int_dict_entries = assert_dict_entries Alcotest.int
let assert_string_dict_entries = assert_dict_entries Alcotest.string
let assert_option_int = assert_option Alcotest.int

let assert_int left right =
Alcotest.check Alcotest.int "should be equal" right left

Expand Down Expand Up @@ -143,11 +155,9 @@ let string2_tests =
assert_float (Js.String2.charCodeAt "lola" 1) 111.;
assert_float (Js.String2.charCodeAt "lola" 0) 108.);
case "codePointAt" (fun () ->
assert_option Alcotest.int
(Js.String2.codePointAt "lola" 1)
(Some 111);
(* assert_option Alcotest.int (Js.String2.codePointAt {js|¿😺?|js} 1) (Some 0x1f63a); *)
assert_option Alcotest.int (Js.String2.codePointAt "abc" 5) None);
assert_option_int (Js.String2.codePointAt "lola" 1) (Some 111);
(* assert_option_int (Js.String2.codePointAt {js|¿😺?|js} 1) (Some 0x1f63a); *)
assert_option_int (Js.String2.codePointAt "abc" 5) None);
case "concat" (fun () ->
assert_string (Js.String2.concat "cow" "bell") "cowbell");
case "concatMany" (fun () ->
Expand Down Expand Up @@ -378,4 +388,65 @@ let array_tests =
(fun () -> Js.Array.from 3));
] )

let () = Alcotest.run "Js_tests" [ string2_tests; re_tests; array_tests ]
let obj () = Js.Dict.fromList [ ("foo", 43); ("bar", 86) ]
let long_obj () = Js.Dict.fromList [ ("david", 99); ("foo", 43); ("bar", 86) ]

let obj_duplicated () =
Js.Dict.fromList [ ("foo", 43); ("bar", 86); ("bar", 1) ]

let dict_tests =
( "Js.Dict",
[
case "empty" (fun _ ->
assert_string_dict_entries (Js.Dict.entries (Js.Dict.empty ())) [||]);
case "get" (fun _ ->
assert_option_int (Js.Dict.get (obj ()) "foo") (Some 43));
case "get from missing property" (fun _ ->
assert_option_int (Js.Dict.get (obj ()) "baz") None);
case "unsafe_get" (fun _ ->
assert_int (Js.Dict.unsafeGet (obj ()) "foo") 43);
case "set" (fun _ ->
let o = Js.Dict.empty () in
let new_ = Js.Dict.set o "foo" 36 in
assert_option_int (Js.Dict.get new_ "foo") (Some 36));
case "keys" (fun _ ->
assert_string_array
(Js.Dict.keys (long_obj ()))
[| "david"; "foo"; "bar" |]);
case "keys duplicated" (fun _ ->
assert_string_array
(Js.Dict.keys (obj_duplicated ()))
[| "foo"; "bar" |]);
case "entries" (fun _ ->
assert_int_dict_entries
(Js.Dict.entries (obj ()))
[| ("foo", 43); ("bar", 86) |]);
case "values" (fun _ ->
assert_int_array (Js.Dict.values (obj ())) [| 43; 86 |]);
case "values duplicated" (fun _ ->
assert_int_array (Js.Dict.values (obj_duplicated ())) [| 43; 86 |]);
case "fromList - []" (fun _ ->
assert_int_dict_entries (Js.Dict.entries (Js.Dict.fromList [])) [||]);
case "fromList" (fun _ ->
assert_int_dict_entries
(Js.Dict.entries (Js.Dict.fromList [ ("x", 23); ("y", 46) ]))
[| ("x", 23); ("y", 46) |]);
case "fromArray - []" (fun _ ->
assert_int_dict_entries (Js.Dict.entries (Js.Dict.fromArray [||])) [||]);
case "fromArray" (fun _ ->
assert_int_dict_entries
(Js.Dict.entries (Js.Dict.fromArray [| ("x", 23); ("y", 46) |]))
[| ("x", 23); ("y", 46) |]);
case "map" (fun _ ->
let prices =
Js.Dict.fromList [ ("pen", 1); ("book", 5); ("stapler", 7) ]
in
let discount price = price * 10 in
let salePrices = Js.Dict.map discount prices in
assert_int_dict_entries
(Js.Dict.entries salePrices)
[| ("pen", 10); ("book", 50); ("stapler", 70) |]);
] )

let () =
Alcotest.run "Js_tests" [ string2_tests; re_tests; array_tests; dict_tests ]

0 comments on commit 9acab00

Please sign in to comment.