module Oden.Compiler.LiteralEvalSpec where

import           Test.Hspec

import           Oden.Compiler.LiteralEval

import           Oden.Core.Expr
import           Oden.Core.Typed

import           Oden.Identifier
import           Oden.Metadata
import           Oden.Predefined
import           Oden.QualifiedName
import           Oden.SourceInfo
import           Oden.Type.Polymorphic

missing :: Metadata SourceInfo
missing = Metadata Missing

int :: Integer -> TypedExpr
int n = Literal missing (Int n) typeInt

true :: TypedExpr
true = Literal missing (Bool True) typeBool

false :: TypedExpr
false = Literal missing (Bool False) typeBool

binaryOp :: TypedExpr -> TypedExpr -> TypedExpr -> TypedExpr
binaryOp f lhs rhs =
  case typeOf f of
    TFn _ _ (TFn _ rhsType range) ->
      Application missing (Application missing f lhs (TFn missing rhsType range)) rhs range
    _ -> error "Cannot use non-function type as function in binary application"

binaryMethodReference protocol method opType rangeType =
  MethodReference
  missing
  (Unresolved
   (nameInUniverse protocol)
   (Identifier method)
   (ProtocolConstraint missing (nameInUniverse protocol) opType))
  (TFn missing opType (TFn missing opType rangeType))


add = binaryMethodReference "Num" "Add" typeInt typeInt
subtract' = binaryMethodReference "Num" "Subtract" typeInt typeInt
multiply = binaryMethodReference "Num" "Multiply" typeInt typeInt
divide = binaryMethodReference "Num" "Divide" typeInt typeInt
or' = binaryMethodReference "Logical" "Disjunction" typeBool typeBool
and' = binaryMethodReference "Logical" "Conjunction" typeBool typeBool
lessThan = binaryMethodReference "Ordered" "LessThan" typeInt typeBool
greaterThan = binaryMethodReference "Ordered" "GreaterThan" typeInt typeBool
equals = binaryMethodReference "Equality" "EqualTo" typeInt typeInt

spec :: Spec
spec =
  describe "evaluate" $ do

    it "evaluates integer literals" $
      evaluate (int 5) `shouldBe` Just (Int 5)

    it "evaluates addition" $
      evaluate (binaryOp add (int 2) (int 3)) `shouldBe` Just (Int 5)

    it "evaluates nested binary expressions: (3-2)*4 + 10/2" $
      evaluate (binaryOp
                add
                (binaryOp
                 multiply
                 (binaryOp
                  subtract'
                  (int 3)
                  (int 2))
                 (int 4))
                (binaryOp divide (int 10) (int 2)))
      `shouldBe`
      Just (Int 9)

    it "evaluates boolean literals" $
      evaluate true `shouldBe` Just (Bool True)

    it "evaluates boolean expression: (true || false) && true" $
      evaluate (binaryOp
                and'
                (binaryOp
                 or'
                 true
                 false)
                true)
      `shouldBe`
      Just (Bool True)

    it "evaluates single integer comparison: (3 < 5)" $
      evaluate (binaryOp lessThan (int 3) (int 5))
      `shouldBe`
      Just (Bool True)

    it "evaluates integer comparison: (3 < 5) && (10 > 8)" $
      evaluate (binaryOp
                and'
                 (binaryOp lessThan (int 3) (int 5))
                 (binaryOp greaterThan (int 10) (int 8)))
      `shouldBe`
      Just (Bool True)

    it "evaluates if-expressions: if 2 == 3 then 1 else 2" $
      evaluate (If missing (binaryOp equals (int 2) (int 3))
                           (int 1)
                           (int 2)
                   typeInt)
      `shouldBe`
      Just (Int 2)

    it "evaluates expressions with variables to Nothing" $
      evaluate (binaryOp
                add
                (int 5)
                (Symbol missing (Identifier "x") typeInt))
      `shouldBe`
      Nothing

module Oden.Compiler.LiteralEvalSpec where

import           Test.Hspec

import           Oden.Compiler.LiteralEval

import           Oden.Core.Expr
import           Oden.Core.Typed

import           Oden.Identifier
import           Oden.Metadata
import           Oden.Predefined
import           Oden.QualifiedName
import           Oden.SourceInfo
import           Oden.Type.Polymorphic

missing :: Metadata SourceInfo
missing = Metadata Missing

int :: Integer -> TypedExpr
int n = Literal missing (Int n) typeInt

true :: TypedExpr
true = Literal missing (Bool True) typeBool

false :: TypedExpr
false = Literal missing (Bool False) typeBool

binaryOp :: TypedExpr -> TypedExpr -> TypedExpr -> TypedExpr
binaryOp f lhs rhs =
  case typeOf f of
    TFn _ _ (TFn _ rhsType range) ->
      Application missing (Application missing f lhs (TFn missing rhsType range)) rhs range
    _ -> error "Cannot use non-function type as function in binary application"

binaryMethodReference protocol method opType rangeType =
  MethodReference
  missing
  (Unresolved
   (nameInUniverse protocol)
   (Identifier method)
   (ProtocolConstraint missing (nameInUniverse protocol) opType))
  (TFn missing opType (TFn missing opType rangeType))


add = binaryMethodReference "Num" "Add" typeInt typeInt
subtract' = binaryMethodReference "Num" "Subtract" typeInt typeInt
multiply = binaryMethodReference "Num" "Multiply" typeInt typeInt
divide = binaryMethodReference "Num" "Divide" typeInt typeInt
or' = binaryMethodReference "Logical" "Disjunction" typeBool typeBool
and' = binaryMethodReference "Logical" "Conjunction" typeBool typeBool
lessThan = binaryMethodReference "Ordered" "LessThan" typeInt typeBool
greaterThan = binaryMethodReference "Ordered" "GreaterThan" typeInt typeBool
equals = binaryMethodReference "Equality" "EqualTo" typeInt typeInt

spec :: Spec
spec =
  describe "evaluate" $ do

    it "evaluates integer literals" $
      evaluate (int 5) `shouldBe` Just (Int 5)

    it "evaluates addition" $
      evaluate (binaryOp add (int 2) (int 3)) `shouldBe` Just (Int 5)

    it "evaluates nested binary expressions: (3-2)*4 + 10/2" $
      evaluate (binaryOp
                add
                (binaryOp
                 multiply
                 (binaryOp
                  subtract'
                  (int 3)
                  (int 2))
                 (int 4))
                (binaryOp divide (int 10) (int 2)))
      `shouldBe`
      Just (Int 9)

    it "evaluates boolean literals" $
      evaluate true `shouldBe` Just (Bool True)

    it "evaluates boolean expression: (true || false) && true" $
      evaluate (binaryOp
                and'
                (binaryOp
                 or'
                 true
                 false)
                true)
      `shouldBe`
      Just (Bool True)

    it "evaluates single integer comparison: (3 < 5)" $
      evaluate (binaryOp lessThan (int 3) (int 5))
      `shouldBe`
      Just (Bool True)

    it "evaluates integer comparison: (3 < 5) && (10 > 8)" $
      evaluate (binaryOp
                and'
                 (binaryOp lessThan (int 3) (int 5))
                 (binaryOp greaterThan (int 10) (int 8)))
      `shouldBe`
      Just (Bool True)

    it "evaluates if-expressions: if 2 == 3 then 1 else 2" $
      evaluate (If missing (binaryOp equals (int 2) (int 3))
                           (int 1)
                           (int 2)
                   typeInt)
      `shouldBe`
      Just (Int 2)

    it "evaluates expressions with variables to Nothing" $
      evaluate (binaryOp
                add
                (int 5)
                (Symbol missing (Identifier "x") typeInt))
      `shouldBe`
      Nothing
