module EvalMaybeExpr (evalMaybeExpr, testEvalMaybeExpr) where

import Test.QuickCheck
import Data.List (lookup)

data MaybeExpr =
Id String |
Leaf Int |
Add MaybeExpr MaybeExpr |
Sub MaybeExpr MaybeExpr |
Mul MaybeExpr MaybeExpr |
Uminus MaybeExpr
deriving (Eq, Show)

type Assoc a = [ (String, a) ]

-- Return a Maybe wrapping the results of evaluating expr, looking up
-- id's in assoc. If an id is not found in assoc, then the function
-- should return Nothing.
evalMaybeExpr :: MaybeExpr -> Assoc Int -> Maybe Int
evalMaybeExpr (Id x) env = lookup x env
evalMaybeExpr (Leaf x) _ = Just x
evalMaybeExpr (Add e1 e2) env = liftA2 (+) (evalMaybeExpr e1 env) (evalMaybeExpr e2 env)
evalMaybeExpr (Sub e1 e2) env = liftA2 (-) (evalMaybeExpr e1 env) (evalMaybeExpr e2 env)
evalMaybeExpr (Mul e1 e2) env = liftA2 (*) (evalMaybeExpr e1 env) (evalMaybeExpr e2 env)
evalMaybeExpr (Uminus e) env = fmap negate (evalMaybeExpr e env)

testEvalMaybeExpr = do
print "**** test evalMaybeExpr"
-- Unit Tests
quickCheck $ counterexample "Leaf" $ evalMaybeExpr (Leaf 42) [] == Just 42
quickCheck $ counterexample "Add" $
evalMaybeExpr (Add (Leaf 33) (Leaf 22)) [] == Just 55
quickCheck $ counterexample "Sub" $
evalMaybeExpr (Sub (Leaf 33) (Leaf 22)) [] == Just 11
quickCheck $ counterexample "Mul" $
evalMaybeExpr (Mul (Leaf 31) (Leaf 4)) [] == Just 124
quickCheck $ counterexample "Uminus" $
evalMaybeExpr (Uminus (Leaf 33)) [] == Just (-33)
quickCheck $ counterexample "Complex" $
evalMaybeExpr (Mul (Leaf 4)
(Sub (Add (Leaf 3) (Uminus (Leaf 33)))))
[] == Just (-200)

-- ID unit tests
quickCheck $ counterexample "OK id lookup" $
evalMaybeExpr (Id "a") [("a", 42)] == Just 42
quickCheck $ counterexample "Fail id lookup" $
evalMaybeExpr (Id "a") [("b", 42)] == Nothing
quickCheck $ counterexample "ID lookup: a: OK, b: Fail" $
evalMaybeExpr (Add (Id "a") (Id "b")) [("a", 42)] == Nothing
quickCheck $ counterexample "ID lookup: a: OK, b: OK" $
evalMaybeExpr (Add (Id "a") (Id "b")) [("a", 42), ("b", 22)] == Just 64
quickCheck $ counterexample "Complex ID lookup" $
evalMaybeExpr (Mul (Id "a")
(Sub (Add (Id "b") (Uminus (Id "c"))))
[("a", 4), ("b", 3), ("c", 33), ("d", 20))] == Just (-200)

-- Property-Based Tests
-- ID Lookup
quickCheck $ counterexample "Random ID lookup OK" $
(\id1 val1 -> evalMaybeExpr (Id id1) [(id1, val1)] == Just val1)
quickCheck $ counterexample "Random ID lookup Fail" $
(\id1 val1 -> evalMaybeExpr (Id id1) [(id1 ++ "x", val1)] == Nothing)

-- Commmutativity
quickCheck $ counterexample "e1 + e2 == e2 + e1" $
(\e1 e2 ->
evalMaybeExpr (Add (Leaf e1) (Leaf e2)) [] == evalMaybeExpr (Add (Leaf e2) (Leaf e1)) [])
quickCheck $ counterexample "e1 * e2 == e2 * e1" $
(\e1 e2 ->
evalMaybeExpr (Mul (Leaf e1) (Leaf e2)) [] == evalMaybeExpr (Mul (Leaf e2) (Leaf e1)) [])

-- Associativity
quickCheck $ counterexample "(e1 + e2) + e3 == e1 + (e2 + e3)" $
(\e1 e2 e3 ->
evalMaybeExpr (Add (Add (Leaf e1) (Leaf e2)) (Leaf e3)) [] == evalMaybeExpr (Add (Leaf e1) (Add (Leaf e2) (Leaf e3))) [])
quickCheck $ counterexample "(e1 * e2) * e3 == e1 * (e2 * e3)" $
(\e1 e2 e3 ->
evalMaybeExpr (Mul (Mul (Leaf e1) (Leaf e2)) (Leaf e3)) [] == evalMaybeExpr (Mul (Leaf e1) (Mul (Leaf e2) (Leaf e3))) [])

-- Subtraction
quickCheck $ counterexample "e1 - e2 = -1*(e2 - e1)" $
(\e1 e2 ->
evalMaybeExpr (Sub (Leaf e1) (Leaf e2)) [] == evalMaybeExpr (Mul (Leaf (-1)) (Sub (Leaf e2) (Leaf e1))) [])

-- Distributivity
quickCheck $ counterexample "e1 * (e2 + e3) == e1e2 + e1e3" $
(\e1 e2 e3 ->
evalMaybeExpr (Mul (Leaf e1) (Add (Leaf e2) (Leaf e3))) [] == evalMaybeExpr (Add (Mul (Leaf e1) (Leaf e2))
(Mul (Leaf e1) (Leaf e3))) [])
quickCheck $ counterexample "e1 * (e2 - e3) == e1e2 - e1e3" $
(\e1 e2 e3 ->
evalMaybeExpr (Mul (Leaf e1) (Sub (Leaf e2) (Leaf e3))) [] == evalMaybeExpr (Sub (Mul (Leaf e1) (Leaf e2))
(Mul (Leaf e1) (Leaf e3))) [])

When I'm running the above program I'm getting the error as

ghci> :l "Main.hs"
[1 of 6] Compiling EvalIdExpr ( EvalIdExpr.hs, interpreted )
[2 of 6] Compiling EvalIntExpr ( EvalIntExpr.hs, interpreted )
[3 of 6] Compiling EvalMaybeExpr ( EvalMaybeExpr.hs, interpreted )

EvalMaybeExpr.hs:58:57: error: parse error on input ‘)’
|
58 | [("a", 4), ("b", 3), ("c", 33), ("d", 20))] == Just (-200)
| ^
Failed, two modules loaded.

Fix the error and write the correct code