{-|
Module: CPSBork Assignment 2
Description: Continuation Passing Style Transformations
Copyright: (c) University of Toronto, 2019
               CSC324 Principles of Programming Languages, Fall 2019

* Before starting, please review the exercise guidelines at
http://www.cs.toronto.edu/~lczhang/324/homework.html *

-}
-- This lists what this module exports. Don't change this!

module CPSBork (
    -- Warmup
    cpsFacEnv, fibEnv, cpsFibEnv,
    -- CPS Transform
    cpsDef, cpsExpr
) where

import qualified Data.Map as Map
import Test.QuickCheck (quickCheck)
import Ex10Bork (Env, emptyEnv, Value(..), HaskellProc(..), Expr(..), eval, def)

------------------------------------------------------------------------------
-- Warmup
------------------------------------------------------------------------------

-- | facEnv is an environment containing the function `fac` that computes the 
--   factorial of a number, written in direct style.
facEnv :: Env
facEnv = def [("fac", Lambda ["n"]
                (If (Equal (Var "n") (Literal $ Num 0))
                    (Literal $ Num 1)
                    (Times (Var "n") (App (Var "fac")
                       [(Plus (Var "n") (Literal $ Num (-1)))]))))]

-- | cpsFacEnv is an environment containing the function `cps_fac` that computes the 
--   factorial of a number, written in CPS
cpsFacEnv :: Env
cpsFacEnv = undefined

-- | fibEnv is an environment containing the function `fib` that computes the 
--   n-th fibonacci via recursion, written in direct style.
fibEnv :: Env
fibEnv = undefined

-- | cpsFfibEnv is an environment containing the function `cps_fib` that computes the 
--   n-th fibonacci via recursion, written in CPS
cpsFibEnv :: Env
cpsFibEnv = undefined

-- | An identity function in Bork, used for testing
identityFn :: Expr
identityFn = Lambda ["x"] (Var "x")

-- | Some simple tests. You should write your own.

prop_testFac :: Bool
prop_testFac = eval facEnv (App (Var "fac") [Literal $ Num 3]) == Num 6
prop_testCpsFac :: Bool
prop_testCpsFac = eval cpsFacEnv (App (Var "cps_fac") [Literal $ Num 3, identityFn]) == Num 6

------------------------------------------------------------------------------
-- CPS Transformation
------------------------------------------------------------------------------

-- | Performs CPS Transformations on a list of name -> expression bindings
-- by renaming the names, and CPS transforming the expressions
cpsDef :: [(String, Expr)] -> [(String, Expr)]
cpsDef bindings = map (\(s, e) -> (rename s, cpsExpr e "" id)) bindings 

-- | CPS Transform a single expression
cpsExpr :: Expr -> String -> (Expr -> Expr) -> Expr
-- literals:
cpsExpr (Literal v) s context = undefined
-- variables:
cpsExpr (Var name)  s context = undefined
-- builtins:
cpsExpr (Plus left right)  s context = undefined
cpsExpr (Times left right) s context = undefined
cpsExpr (Equal left right) s context = undefined
-- function definition:
cpsExpr (Lambda params body) s context = undefined
-- function application:
cpsExpr (App fn args) s context = undefined
-- if expressions
cpsExpr (If cond conseq altern) s context = undefined

-- | Helper function that renames a variable by prepending "cps_"
rename :: String -> String
rename s = "cps_" ++ s

-- | Some simple tests. You should also write your own.

prop_testCpsExprLiteral :: Bool
prop_testCpsExprLiteral = result == Num 1
    where bindings = cpsDef [("n", Literal $ Num 1)]
          env = def bindings
          result = eval env $ Var ("cps_n")

prop_testCpsExprVar :: Bool
prop_testCpsExprVar = result == Num 2
    where bindings = cpsDef [("n", Literal $ Num 2),
                             ("m", Var "n")]
          env = def bindings
          result = eval env $ Var ("cps_m")

prop_testCpsExprPlus :: Bool
prop_testCpsExprPlus = result == Num 5
    where bindings = cpsDef [("n", Literal $ Num 2),
                             ("m", (Plus (Var "n") (Literal $ Num 3)))]
          env = def bindings
          result = eval env $ Var "cps_m"

prop_testCpsExprFac :: Bool
prop_testCpsExprFac = result == Num 120
    where bindings = cpsDef [("fac", Lambda ["n"]
                                (If (Equal (Var "n") (Literal $ Num 0))
                                    (Literal $ Num 1)
                                    (Times (Var "n") (App (Var "fac")
                                       [(Plus (Var "n") (Literal $ Num (-1)))]))))]
          env = def bindings
          result = eval env $ (App (Var "cps_fac") [Literal $ Num 5, identityFn])

------------------------------------------------------------------------------
-- Main
------------------------------------------------------------------------------

-- | This main function runs the quickcheck tests.
-- This gets executed when you compile and run this program. We'll talk about
-- "do" notation much later in the course, but for now if you want to add your
-- own tests, just define them above, and add a new `quickcheck` line here.
main :: IO ()
main = do
    quickCheck prop_testFac
    quickCheck prop_testCpsFac
    quickCheck prop_testCpsExprLiteral 
    quickCheck prop_testCpsExprVar 
    quickCheck prop_testCpsExprPlus
    quickCheck prop_testCpsExprFac 
