open! Base
open Expect_test_helpers_core

let () = Int_conversions.sexp_of_int_style := `Underscores

let%expect_test "find_and_call_1_and_2" =
  let test x =
    let t = Hashtbl.create (module Int) ~size:16 ~growth_allowed:false in
    for i = 0 to x - 1 do
      Hashtbl.add_exn t ~key:i ~data:(i * 7)
    done;
    let if_found a b = assert (a = b) in
    let if_not_found a b =
      assert (a = x);
      assert (b = x * 7)
    in
    require_no_allocation [%here] (fun () ->
      for i = 0 to x do
        Hashtbl.find_and_call1 t i ~a:(i * 7) ~if_found ~if_not_found
      done);
    let if_found ~key ~data:a b =
      assert (a = b);
      assert (key = a / 7)
    in
    let if_not_found a b =
      assert (a = x);
      assert (b = x * 7)
    in
    require_no_allocation [%here] (fun () ->
      for i = 0 to x do
        Hashtbl.findi_and_call1 t i ~a:(i * 7) ~if_found ~if_not_found
      done);
    let if_found a b c =
      assert (a = b);
      assert (b = c / 2)
    in
    let if_not_found a b c =
      assert (a = x);
      assert (b = x * 7);
      assert (c = x * 14)
    in
    require_no_allocation [%here] (fun () ->
      for i = 0 to x do
        Hashtbl.find_and_call2 t i ~a:(i * 7) ~b:(i * 14) ~if_found ~if_not_found
      done);
    let if_found ~key ~data:a b c =
      assert (a = b);
      assert (b = c / 2);
      assert (key = a / 7)
    in
    let if_not_found a b c =
      assert (a = x);
      assert (b = x * 7);
      assert (c = x * 14)
    in
    require_no_allocation [%here] (fun () ->
      for i = 0 to x do
        Hashtbl.findi_and_call2 t i ~a:(i * 7) ~b:(i * 14) ~if_found ~if_not_found
      done);
    print_s (Int.sexp_of_t x)
  in
  (* try various load factors, to exercise all branches of matching on the structure of
     the avl tree *)
  test 1;
  test 3;
  test 10;
  test 17;
  test 25;
  test 29;
  test 33;
  test 3133;
  [%expect {|
    1
    3
    10
    17
    25
    29
    33
    3_133
    |}]
;;

let%expect_test ("find_or_add shouldn't allocate" [@tags "no-js"]) =
  let default = Fn.const () in
  let t = Hashtbl.create (module Int) ~size:16 ~growth_allowed:false in
  Hashtbl.add_exn t ~key:100 ~data:();
  require_no_allocation [%here] (fun () -> Hashtbl.find_or_add t 100 ~default);
  [%expect {| |}]
;;
