open Driver
open Test_common

let output_dir =
  let name = "test_dynamic" in
  if not (Sys.file_exists name) then Sys.mkdir name 0o755;
  name

let backends_to_test =
  [
    Config.Dynamic;
    Config.GradualGuarded;
    Config.GradualMonoClosures;
    Config.GradualMonoValues;
    Config.GradualGuardedDFO;
    Config.GradualMonoDFO;
  ]

let test = test ~output_dir

let int_test = test __POS__ "int" {|
  print 5
  |} "5"

let int_neg_test = test __POS__ "int neg" {|
  print -42
|} "-42"

let true_test = test __POS__ "true" {|
  print true
|} "true"

let false_test = test __POS__ "false" {|
  print false
|} "false"

let arithmetic_test =
  test __POS__ "arithmetic" {|
  let n = 1 + 2 * 5 - 4 in
  print n
|} "7"

let division_test =
  test __POS__ "division" {|
  let n = 10 / 2 in
  print n
|} "5"

let modulo_test =
  test __POS__ "modulo" {|
  let n = 10 mod 3 in
  print n
|} "1"

let int_eq_test = test __POS__ "int_eq" {|
  print (5 = 5)
|} "true"

let int_not_eq = test __POS__ "int_not_eq" {|
  print (5 = 6)
|} "false"

let int_less_than = test __POS__ "int_less_than" {|
  print (5 < 10)
|} "true"

let int_not_less_than =
  test __POS__ "int_not_less_than" {|
  print (10 < 5)
|} "false"

let int_less_equal_test =
  test __POS__ "int_less_equal" {|
  print (5 <= 10)
|} "true"

let int_less_equal2_test =
  test __POS__ "int_less_equal2" {|
  print (5 <= 5)
|} "true"

let int_not_less_equal_test =
  test __POS__ "int_not_less_equal" {|
  print (10 <= 5)
|} "false"

let int_greater_than_test =
  test __POS__ "int_greater_than" {|
  print (10 > 5)
|} "true"

let int_not_greater_than_test =
  test __POS__ "int_not_greater_than" {|
  print (5 > 10)
|} "false"

let int_greater_equal_test =
  test __POS__ "int_greater_equal" {|
  print (10 >= 5)
|} "true"

let int_greater_equal2_test =
  test __POS__ "int_greater_equal2" {|
  print (5 >= 5)
|} "true"

let int_not_greater_equal =
  test __POS__ "int_not_greater_equal" {|
  print (5 >= 10)
|} "false"

let boolean_test =
  test __POS__ "boolean" {|
  print (true && (! false))
|} "true"

let if_trivial_test =
  test __POS__ "if" {|
  let n = if true then 1 else 2 in
  print n
|} "1"

let if_less_test =
  test __POS__ "if" {|
  let n = if 20 < 10 then 14 else 7 in
  print n
|} "7"

let if_test =
  test __POS__ "if"
    {|
  let f = fun n -> if n < 10 then n + 10 else n - 10 in
  print (f 15)
|}
    "5"

let addn_test =
  test __POS__ "addn"
    {|
  let addn = fun n -> fun m -> n + m in
  let add1 = addn 1 in
  print (add1 5)
  |}
    "6"

let fact_test =
  test __POS__ "fact"
    {|
  let rec fact = fun n ->
    if n = 0 then
      1
    else
      n * fact (n - 1)
  in
  print (fact 5)
  |}
    "120"

let tuple_test =
  test __POS__ "tuple" {|
  let n = (1, 2, 3).(0) in
  print n
  |} "1"

let list_test =
  test __POS__ "list"
    {|
  let hd = fun l -> l.(0) in
  let tl = fun l -> l.(1) in
  let cons = fun a b -> (a, b) in
  let l = cons 1 (cons 2 (cons 3 (false))) in
  print (hd (tl (tl l)))
  |}
    "3"

let vector_test =
  test __POS__ "vector"
    {|
  let vect = vector 3 0 in
  let _ = vector_set vect 2 42 in
  let first = vector_get vect 2 in
  print first
  |}
    "42"

let vector_border_test =
  test __POS__ "vector"
    {|
  let v1 : vec[int] = vector 3 0 in
  let tmp1 = vector_set v1 0 1 in
  let tmp2 = vector_set v1 1 2 in
  let tmp3 = vector_set v1 2 3 in
  
  let tmp4 = print (vector_get v1 0) in
  let tmp5 = print (vector_get v1 1) in
  let tmp6 = print (vector_get v1 2) in
  print (vector_length v1)
  |}
    {|1
2
3
3|}

let vector_length_test =
  test __POS__ "vector"
    {|
  let v1 : vec[int] = vector 3 0 in
  let tmp1 = vector_set v1 0 1 in
  let tmp2 = vector_set v1 1 2 in
  let tmp3 = vector_set v1 2 3 in
  
  let tmp4 = print (vector_get v1 0) in
  let tmp5 = print (vector_get v1 1) in
  let tmp6 = print (vector_get v1 2) in
  print (vector_length v1)
  |}
    {|1
2
3
3|}

let letrec_test =
  test __POS__ "let rec"
    {|
  let rec fibonacci = fun n ->
    if n < 2 then
      n
    else
      (fibonacci (n - 1)) + (fibonacci (n - 2))
    in
    print (fibonacci 10)
|}
    "55"

let letrec_mutual_recursion_test =
  test __POS__ "let rec mutual recursion"
    {|
  let rec is_even = fun n ->
    if n = 0 then
      true
    else
      is_odd (n - 1)
  and is_odd = fun n ->
    if n = 0 then
      false
    else is_even (n - 1)
  in
  print (is_even 10)
|}
    "true"

let for_test =
  test __POS__ "for"
    {|
    for x = 0 to 10 do
      print x
  done
  |}
    {|0
1
2
3
4
5
6
7
8
9|}

let for_negative_test =
  test __POS__ "for negative"
    {|
    for x = -2 to 3 do
      print_int x ;
      print_char '\n' ;
      0
  done
  |}
    {|-2
-1
0
1
2|}

let seq_test =
  test __POS__ "seq"
    {|
  ((print 1 ; print 2) + ((print 3; print 4) + (print 5 ; print 6)))
|}
    {|1
2
3
4
5
6|}

let variant_test =
  test __POS__ "variant"
    {|
type rgb = Red | Green | Blue

let color : rgb = Red in
print_int 10
|}
    "10"

let variant_nested_test =
  test __POS__ "variant nested"
    {|
type ty =
    Int
    | Bool
    | Arrow of ty * ty

let ty : ty = Arrow (Int, Arrow (Int, Int)) in
let is_arrow : ty -> bool = fun ty : ty ->
    match ty with
    | Int -> false
    | Bool -> false
    | Arrow ty1 ty2 -> true
in
print (is_arrow ty) ;
print (is_arrow (Int))
|}
    {|true
false|}

let variant_stream_test =
  test __POS__ "variant stream"
    {|
type stream = SCons of int * (int -> stream)

let stream_first : stream -> int =
    fun st : stream -> match st with SCons hd thunk -> hd
in
let stream_rest : stream -> stream =
    fun st : stream -> match st with SCons hd thunk -> (thunk 0)
in

let make_stream : int -> (int -> stream) -> stream =
    fun (hd : int) (thunk : (int -> stream)) -> SCons (hd, thunk)
in

let rec make_ones : int -> stream =
    fun (_ : int) -> make_stream 1 make_ones
in
let ones : stream = make_ones 0 in
print (stream_first ones) ;
print (stream_first (stream_rest ones)) ;
print (stream_first (stream_rest (stream_rest ones))) ;
0
|}
    {|1
1
1|}

let float_basic_test = test __POS__ "float_basic" {|
  print 3.14
|} "3.14"

let float_negative_test =
  test __POS__ "float_negative" {|
  print (-2.5)
|} "-2.5"

let float_exp_notation_test =
  test __POS__ "float_exp" {|
  print 1.5e3
|} "1500"

let float_addition_test =
  test __POS__ "float_addition" {|
  print (3.14 +. 2.86)
|} "6"

let float_subtraction_test =
  test __POS__ "float_subtraction" {|
  print (5.0 -. 1.5)
|} "3.5"

let float_multiplication_test =
  test __POS__ "float_multiplication" {|
  print (2.0 *. 3.5)
|} "7"

let float_division_test =
  test __POS__ "float_division" {|
  print (10.0 /. 4.0)
|} "2.5"

let float_eq_test = test __POS__ "float_eq" {|
  print (3.14 =. 3.14)
|} "true"

let float_not_eq =
  test __POS__ "float_not_eq" {|
  print (3.14 =. 2.14)
|} "false"

let float_less_than =
  test __POS__ "float_less_than" {|
  print (2.0 <. 3.0)
|} "true"

let float_not_less_than =
  test __POS__ "float_not_less_than" {|
  print (3.0 <. 2.0)
|} "false"

let float_less_equal_test =
  test __POS__ "float_less_equal" {|
  print (2.0 <=. 3.0)
|} "true"

let float_less_equal2_test =
  test __POS__ "float_less_equal2" {|
  print (3.0 <=. 3.0)
|} "true"

let float_not_less_equal_test =
  test __POS__ "float_not_less_equal" {|
  print (3.0 <=. 2.0)
|} "false"

let float_greater_than_test =
  test __POS__ "float_greater_than" {|
  print (3.0 >. 2.0)
|} "true"

let float_not_greater_than_test =
  test __POS__ "float_not_greater_than" {|
  print (2.0 >. 3.0)
|} "false"

let float_greater_equal_test =
  test __POS__ "float_greater_equal" {|
  print (3.0 >=. 2.0)
|} "true"

let float_greater_equal2_test =
  test __POS__ "float_greater_equal2" {|
  print (3.0 >=. 3.0)
|} "true"

let float_not_greater_equal =
  test __POS__ "float_not_greater_equal" {|
  print (2.0 >=. 3.0)
|} "false"

let float_zero_test = test __POS__ "float_zero" {|
  print 0.0
|} "0"

let float_large_test =
  test __POS__ "float_large" {|
  print 1234567.89
|} "1.23457e+06"

let float_small_test = test __POS__ "float_small" {|
  print 0.000001
|} "1e-06"

let float_complex_expr_test =
  test __POS__ "float_complex_expr"
    {|
  print ((10.0 /. 2.0) *. (3.0 +. 1.0) -. 5.0)
|} "15"

let float_max_test =
  test __POS__ "float_max" {|
  print (float_max 1.0 2.0)
|} "2"

let float_min_test =
  test __POS__ "float_min" {|
  print (float_min 1.0 2.0)
|} "1"

let float_exp_test = test __POS__ "float_exp" {|
  print (exp 1.0)
|} "2.71828"

let float_log_test =
  test __POS__ "float_log" {|
  print (log 2.71828182845905)
|} "1"

let float_round_test = test __POS__ "float_round" {|
  print (round 3.6)
|} "4"

let float_negate_test =
  test __POS__ "float_negate" {|
  print (float_negate 3.5)
|} "-3.5"

let float_sqrt_test = test __POS__ "float_sqrt" {|
  print (sqrt 16.0)
|} "4"

let float_sin_test = test __POS__ "float_sin" {|
  print (sin 0.0)
|} "0"

let float_cos_test = test __POS__ "float_cos" {|
  print (cos 0.0)
|} "1"

let int_of_float_test =
  test __POS__ "int_of_float" {|
  print (int_of_float 3.14)
|} "3"

let float_of_int_test =
  test __POS__ "float_of_int" {|
  print (float_of_int 5)
|} "5"

let comment_test =
  test __POS__ "comment"
    {|
  (* This is a comment *)
  print 42 (** This is also a comment *)
|}
    "42"

let comment_nested_test =
  test __POS__ "comment nested"
    {|
  (* This is a comment (* with a nested comment *) still in the comment *)
  print 42
|}
    "42"

let char_test = test __POS__ "char" {|
  print_char 'a'
|} "a"

let char_escape_test =
  test __POS__ "char escape" {|
  print 10 ;
  print_char '\n' ;
  print 20
|}
    "10\n\n20"

let suite =
  [
    ("int", [ int_test; int_neg_test ]);
    ("constants", [ true_test; false_test; boolean_test ]);
    ("arithmetic", [ arithmetic_test; division_test; modulo_test ]);
    ( "comparison",
      [
        int_eq_test;
        int_not_eq;
        int_less_than;
        int_not_less_than;
        int_less_equal_test;
        int_less_equal2_test;
        int_not_less_equal_test;
        int_greater_than_test;
        int_not_greater_than_test;
        int_greater_equal_test;
        int_greater_equal2_test;
        int_not_greater_equal;
      ] );
    ( "primitives",
      [
        tuple_test;
        list_test;
        vector_test;
        vector_border_test;
        vector_length_test;
      ] );
    ("if", [ if_trivial_test; if_less_test; if_test ]);
    ("functions", [ addn_test ]);
    ("recursion", [ fact_test; letrec_test; letrec_mutual_recursion_test ]);
    ("for", [ for_test; for_negative_test ]);
    ("seq", [ seq_test ]);
    ("variant", [ variant_test; variant_nested_test; variant_stream_test ]);
    ( "float",
      [
        float_basic_test;
        float_negative_test;
        float_exp_notation_test;
        float_addition_test;
        float_subtraction_test;
        float_multiplication_test;
        float_division_test;
        float_eq_test;
        float_not_eq;
        float_less_than;
        float_not_less_than;
        float_less_equal_test;
        float_less_equal2_test;
        float_not_less_equal_test;
        float_greater_than_test;
        float_not_greater_than_test;
        float_greater_equal_test;
        float_greater_equal2_test;
        float_not_greater_equal;
        float_zero_test;
        float_large_test;
        float_small_test;
        float_complex_expr_test;
        float_max_test;
        float_min_test;
        float_exp_test;
        float_log_test;
        float_round_test;
        float_negate_test;
        float_sqrt_test;
        float_sin_test;
        float_cos_test;
        int_of_float_test;
        float_of_int_test;
      ] );
    ("comments", [ comment_test; comment_nested_test ]);
    ("char", [ char_test; char_escape_test ]);
  ]

let () =
  Alcotest.run "test_dynamic" @@ to_alcotest_test_suite backends_to_test suite
