packages feed

purekell-0.1.0.0: test/Purekell/InstanceSpec.hs

module Purekell.InstanceSpec (spec) where

import Test.Hspec
import Test.QuickCheck

import Purekell.AST
import Purekell.Arbitrary (noRecordAccess, noTuple, noTuplePat, noConsExpr, noConsPat)
import Purekell.Instance

spec :: Spec
spec = do
  describe "Instance" $ do
    describe "parseMethodBody" $ do
      it "parses simple equality method" $ do
        let input = "eq x y = x == y"
        let expected = [MethodEquation
              (Name "eq")
              [VarPat (Name "x"), VarPat (Name "y")]
              []
              (InfixApp (Var (Name "x")) (Name "==") (Var (Name "y")))]
        parseMethodBody input `shouldBe` Right expected

      it "parses method with pattern matching on constructors" $ do
        let input = "compare (MkId x) (MkId y) = compare x y"
        let expected = [MethodEquation
              (Name "compare")
              [ConPat (Name "MkId") [VarPat (Name "x")], ConPat (Name "MkId") [VarPat (Name "y")]]
              []
              (App (App (Var (Name "compare")) (Var (Name "x"))) (Var (Name "y")))]
        parseMethodBody input `shouldBe` Right expected

      it "parses method with case expression" $ do
        let input = "show x = case x of { MkId i -> show i }"
        let expected = [MethodEquation
              (Name "show")
              [VarPat (Name "x")]
              []
              (Case (Var (Name "x"))
                [CaseAlt (ConPat (Name "MkId") [VarPat (Name "i")])
                  []
                  (App (Var (Name "show")) (Var (Name "i")))])]
        parseMethodBody input `shouldBe` Right expected

      it "parses multiple equations separated by semicolons" $ do
        let input = "eq (Left x) (Left y) = eq x y; eq (Right x) (Right y) = eq x y; eq _ _ = False"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> length eqs `shouldBe` 3

    describe "printMethodBody" $ do
      it "prints for Haskell" $ do
        let eq = MethodEquation (Name "eq") [VarPat (Name "x"), VarPat (Name "y")] []
                   (InfixApp (Var (Name "x")) (Name "==") (Var (Name "y")))
        printMethodBody Haskell [eq] `shouldBe` "eq x y = x == y"

      it "prints for PureScript" $ do
        let eq = MethodEquation (Name "eq") [VarPat (Name "x"), VarPat (Name "y")] []
                   (InfixApp (Var (Name "x")) (Name "==") (Var (Name "y")))
        printMethodBody PureScript [eq] `shouldBe` "eq x y = x == y"

      it "prints record access differently per target" $ do
        let eq = MethodEquation (Name "eq") [VarPat (Name "x"), VarPat (Name "y")] []
                   (InfixApp
                     (RecordAccess (Var (Name "x")) (Name "uid"))
                     (Name "==")
                     (RecordAccess (Var (Name "y")) (Name "uid")))
        printMethodBody Haskell [eq] `shouldBe` "eq x y = uid x == uid y"
        printMethodBody PureScript [eq] `shouldBe` "eq x y = x.uid == y.uid"

    describe "hsToPs golden tests" $ do
      it "DataID Eq instance body" $ do
        let hsBody = "eq (MkDataID x) (MkDataID y) = x == y"
        case parseMethodBody hsBody of
          Left err -> expectationFailure (show err)
          Right eqs -> do
            printMethodBody Haskell eqs `shouldBe` "eq (MkDataID x) (MkDataID y) = x == y"
            printMethodBody PureScript eqs `shouldBe` "eq (MkDataID x) (MkDataID y) = x == y"

      it "Show instance with string concatenation" $ do
        let hsBody = "show (MkId x) = \"MkId \" <> show x"
        case parseMethodBody hsBody of
          Left err -> expectationFailure (show err)
          Right eqs -> do
            -- Same syntax in both languages with <>
            printMethodBody PureScript eqs `shouldBe` "show (MkId x) = \"MkId \" <> show x"

    describe "Tuple in instance methods" $ do
      it "Haskell prints tuple in method body" $ do
        let eq = MethodEquation (Name "toPair") [ConPat (Name "MkT") [VarPat (Name "x"), VarPat (Name "y")]] []
                   (Tuple [Var (Name "x"), Var (Name "y")])
        printMethodBody Haskell [eq] `shouldBe` "toPair (MkT x y) = (x, y)"

      it "PureScript prints tuple in method body" $ do
        let eq = MethodEquation (Name "toPair") [ConPat (Name "MkT") [VarPat (Name "x"), VarPat (Name "y")]] []
                   (Tuple [Var (Name "x"), Var (Name "y")])
        printMethodBody PureScript [eq] `shouldBe` "toPair (MkT x y) = Tuple x y"

      it "Haskell prints tuple pattern in method args" $ do
        let eq = MethodEquation (Name "fst'") [TuplePat [VarPat (Name "a"), VarPat (Name "b")]] []
                   (Var (Name "a"))
        printMethodBody Haskell [eq] `shouldBe` "fst' (a, b) = a"

      it "PureScript prints tuple pattern in method args" $ do
        let eq = MethodEquation (Name "fst'") [TuplePat [VarPat (Name "a"), VarPat (Name "b")]] []
                   (Var (Name "a"))
        printMethodBody PureScript [eq] `shouldBe` "fst' (Tuple a b) = a"

      it "Haskell tuple in method body roundtrips" $ do
        let input = "toPair (MkT x y) = (x, y)"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

      it "Haskell tuple pattern in method args roundtrips" $ do
        let input = "fst' (a, b) = a"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

    describe "Cons pattern in instance methods" $ do
      it "Haskell prints cons pattern in method args" $ do
        let eq = MethodEquation (Name "head'") [ConsPat (VarPat (Name "x")) WildPat] []
                   (Var (Name "x"))
        printMethodBody Haskell [eq] `shouldBe` "head' (x : _) = x"

      it "PureScript prints cons pattern in method args" $ do
        let eq = MethodEquation (Name "head'") [ConsPat (VarPat (Name "x")) WildPat] []
                   (Var (Name "x"))
        printMethodBody PureScript [eq] `shouldBe` "head' (Cons x _) = x"

    describe "List in instance methods" $ do
      it "list pattern in method args" $ do
        let eq = MethodEquation (Name "single") [ListPat [VarPat (Name "x")]] []
                   (Var (Name "x"))
        printMethodBody Haskell [eq] `shouldBe` "single [x] = x"
        printMethodBody PureScript [eq] `shouldBe` "single [x] = x"

      it "list literal in method body" $ do
        let eq = MethodEquation (Name "wrap") [VarPat (Name "x")] []
                   (ListLit [Var (Name "x")])
        printMethodBody Haskell [eq] `shouldBe` "wrap x = [x]"
        printMethodBody PureScript [eq] `shouldBe` "wrap x = [x]"

    describe "As-pattern in method args" $ do
      it "Haskell prints as-pattern in method args" $ do
        let eq = MethodEquation (Name "head'") [AsPat (Name "xs") (ConsPat (VarPat (Name "x")) WildPat)] []
                   (Var (Name "x"))
        printMethodBody Haskell [eq] `shouldBe` "head' (xs@(x : _)) = x"
        printMethodBody PureScript [eq] `shouldBe` "head' (xs@(Cons x _)) = x"

      it "Haskell as-pattern roundtrips via parseMethodBody" $ do
        let input = "head' (xs@(x : _)) = x"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

    describe "Negated literal in method" $ do
      it "prints and roundtrips negated literal pattern" $ do
        let eq = MethodEquation (Name "isNegOne") [NegLitPat (IntLit 1)] []
                   (Con (Name "True"))
        printMethodBody Haskell [eq] `shouldBe` "isNegOne (-1) = True"
        printMethodBody PureScript [eq] `shouldBe` "isNegOne (-1) = True"

      it "Haskell negated literal roundtrips via parseMethodBody" $ do
        let input = "isNegOne (-1) = True"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

    describe "Where clause in method body" $ do
      it "prints and roundtrips where in method body" $ do
        let input = "f x = y where { y = x + 1 }"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

      it "where with multiple bindings in method body" $ do
        let input = "f x = a + b where { a = x; b = 1 }"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

    describe "Type annotation in method body" $ do
      it "prints and roundtrips type annotation in method body" $ do
        let input = "f x = x :: Int"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

      it "prints type annotation for PureScript" $ do
        let eq = MethodEquation (Name "f") [VarPat (Name "x")] []
                   (Ann (Var (Name "x")) (TyCon (Name "Int")))
        printMethodBody PureScript [eq] `shouldBe` "f x = x :: Int"

    describe "Record update in method body" $ do
      it "prints and roundtrips record update in method body" $ do
        let input = "f x = x { y = 1 }"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input

      it "prints record update for PureScript" $ do
        let eq = MethodEquation (Name "f") [VarPat (Name "x")] []
                   (RecordUpdate (Var (Name "x")) [(Name "y", Literal (IntLit 1))])
        printMethodBody PureScript [eq] `shouldBe` "f x = x { y = 1 }"

    describe "Qualified names in methods" $ do
      it "method with qualified variable" $ do
        let input = "f x = Data.Map.lookup x m"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> do
            printMethodBody Haskell eqs `shouldBe` input
            printMethodBody PureScript eqs `shouldBe` input

      it "method with qualified constructor" $ do
        let input = "f x = Data.Map.Map x"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> do
            printMethodBody Haskell eqs `shouldBe` input
            printMethodBody PureScript eqs `shouldBe` input

    describe "Record pattern in method" $ do
      it "method with record pattern" $ do
        let input = "f (Foo { bar = x }) = x"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> do
            printMethodBody Haskell eqs `shouldBe` input
            printMethodBody PureScript eqs `shouldBe` "f (Foo { bar: x }) = x"

      it "method with record construction" $ do
        let eq = MethodEquation (Name "f") [VarPat (Name "x")] []
                   (RecordUpdate (Con (Name "MkFoo")) [(Name "bar", Var (Name "x"))])
        printMethodBody Haskell [eq] `shouldBe` "f x = MkFoo { bar = x }"
        printMethodBody PureScript [eq] `shouldBe` "f x = MkFoo { bar: x }"

    describe "Roundtrip" $ do
      let noRA (MethodEquation _ _ gs body) =
            noRecordAccess body && all (\(Guard e) -> noRecordAccess e) gs
      let noPsTuple (MethodEquation _ pats gs body) =
            noRA (MethodEquation (Name "") [] gs body)
            && noTuple body && all (\(Guard e) -> noTuple e) gs
            && all noTuplePat pats
            && noConsExpr body && all (\(Guard e) -> noConsExpr e) gs
            && all noConsPat pats
      it "Haskell printMethodBody roundtrips" $ property $
        forAll (arbitrary `suchThat` noRA) $ \eq ->
          parseMethodBody (printMethodBody Haskell [eq]) === Right [eq :: MethodEquation]

      it "PureScript printMethodBody roundtrips" $ property $
        forAll (arbitrary `suchThat` noPsTuple) $ \eq ->
          parseMethodBody (printMethodBody PureScript [eq]) === Right [eq :: MethodEquation]

    describe "Backtick operator in method body" $ do
      it "method body with backtick operator" $ do
        let input = "f x y = x `div` y"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> do
            printMethodBody Haskell eqs `shouldBe` input
            printMethodBody PureScript eqs `shouldBe` input

    describe "Function binding in method where" $ do
      it "method body with where containing function binding" $ do
        let input = "f x = g x where { g y = y + 1 }"
        case parseMethodBody input of
          Left err -> expectationFailure (show err)
          Right eqs -> printMethodBody Haskell eqs `shouldBe` input