#use "ass13.ml"

let test tested_method e expect yield =
    if yield = expect then
        print_endline "SUCCESS"
    else
        Printf.printf "FAILURE: Method:   %s
         Input:    %s
         Expected: %s
         Got:      %s\n"
         tested_method (expr2string e) expect yield

let test_expr2string expect e = test "expr2string" e expect (expr2string e)
let test_freevars expect e =
    let yield = match freevars e with
          [] -> ""
        | [x] -> x
        | h::t -> List.fold_left (fun a b -> a ^ ", " ^ b) h t
    in
    test "freevars" e expect yield
let test_eval expect e = test "eval" e expect (expr2string (eval e));;

(* Test the 'expr2string' function *)
test_expr2string "let a = 1 in b" (Let ("a", (Num 1), (Var "b")));;
test_expr2string "a + 3" (BinopAp (Add, Var "a", Num 3));;
test_expr2string "-a" (MonopAp (Neg, Var "a"));;
test_expr2string "a * 3" (BinopAp (Mul, Var "a", Num 3));;

let l arg body = Fun (arg, body);;
let app func arg = FunAp (func, arg);;

(* Define resursive factorial function for a more extensive test *)
let fac =
    let eq = BinopAp (Eq, Var "n", Num 0) in
    let sub = BinopAp (Sub, Var "n", Num 1) in
    let mul = BinopAp (Mul, Var "n", app (Var "fac") sub) in
    let body = Cond (eq, Num 1, mul) in
    LetRec ("fac", Fun ("n", body), Var "fac");;

test_expr2string ("let rec fac = fun n -> if n = 0 then 1 else "
                  ^ "n * (fac (n - 1)) in fac") fac;;

(* Write the expressions of assignment series 1 in our data types *)
let ass1a =
    let c = Var "c" in
    let bcc = l "b" (l "c" c) in
    let a = Var "a" in
    let aaa = l "a" (app a a) in
    let aaa_aaa = app aaa aaa in
    let v = Var "v" in
    let uvw = l "u" (app (l "v" (l "w" v)) (l "a" a)) in
    (app (app bcc aaa_aaa) uvw);;

let ass1b =
    let u = Var "u" in
    let v = Var "v" in
    let w = Var "w" in
    let uvw = l "u" (l "v" (l "w" (app (app u v) w))) in
    let x = Var "x" in
    let y = Var "y" in
    let z = Var "z" in
    let xyz = l "x" (l "y" (l "z" (app x (app y z)))) in
    let a = Var "a" in
    let b = Var "b" in
    let c = Var "c" in
    (app (app (app (app uvw xyz) a) b) c);;

(* Test the 'freevars' function *)
test_freevars "a" (Fun ("b", (BinopAp (Add, Var "a", Num 3))));;
test_freevars "" (Fun ("a", (BinopAp (Add, Var "a", Num 3))));;
test_freevars "" ass1a;;
test_freevars "a, b, c" ass1b;;

(* Test the 'eval' function *)
test_eval "-5" (BinopAp (Sub, Num 4, Num 9));;
test_eval "false" (BinopAp (Lt, Num 6, Num 5));;
test_eval "true" (BinopAp (Or, Bool true, Bool false));;
test_eval "c + b" (Let ("a", Var "c", (BinopAp (Add, Var "a", Var "b"))));;
test_eval "a" (Cond (Bool true, Var "a", Var "b"));;
test_eval "b" (Cond (Bool false, Var "a", Var "b"));;
test_eval "fun u -> fun w -> fun a -> a" ass1a;;
test_eval "(a (b c))" ass1b;;

(* Test resolving of name clash *)
let x = Var "x" in
let y = Var "y" in
let z = Var "z" in
test_eval "fun y' -> fun z -> ((y y') z)"
    (app (l "x" (l "y" (l "z" (app (app x y) z)))) y);;
