packages feed

purekell-0.1.0.0: test/Purekell/DivergentSpec.hs

module Purekell.DivergentSpec (spec) where

import Test.Hspec

import Purekell.AST
import Purekell.Codec (runParse, runPrint)
import Purekell.Haskell (haskellExpr, haskellPat)
import Purekell.PureScript (purescriptExpr, purescriptPat)

spec :: Spec
spec = do
  describe "Divergent constructs" $ do
    describe "Record access" $ do
      let ast = RecordAccess (Var (Name "rec")) (Name "field")

      it "Haskell prints as function application: field rec" $ do
        runPrint haskellExpr ast `shouldBe` "field rec"

      it "PureScript prints as dot access: rec.field" $ do
        runPrint purescriptExpr ast `shouldBe` "rec.field"

      it "PureScript parses dot access" $ do
        runParse purescriptExpr "rec.field" `shouldBe` Right ast

      it "Haskell -> PureScript translation works for RecordAccess" $ do
        -- The Haskell output "field rec" is App (Var field) (Var rec)
        -- when parsed by Haskell; but the AST RecordAccess translates
        -- to different syntax in each language
        let hsText = runPrint haskellExpr ast
        hsText `shouldBe` "field rec"
        let psText = runPrint purescriptExpr ast
        psText `shouldBe` "rec.field"

    describe "Chained record access" $ do
      let ast = RecordAccess (RecordAccess (Var (Name "rec")) (Name "inner")) (Name "field")

      it "PureScript prints chained dots" $ do
        runPrint purescriptExpr ast `shouldBe` "rec.inner.field"

      it "PureScript parses chained dots" $ do
        runParse purescriptExpr "rec.inner.field" `shouldBe` Right ast

      it "Haskell prints as nested application" $ do
        runPrint haskellExpr ast `shouldBe` "field (inner rec)"

    describe "Tuple expressions" $ do
      describe "Pair" $ do
        let ast = Tuple [Var (Name "a"), Var (Name "b")]

        it "Haskell prints as (a, b)" $ do
          runPrint haskellExpr ast `shouldBe` "(a, b)"

        it "PureScript prints as Tuple a b" $ do
          runPrint purescriptExpr ast `shouldBe` "Tuple a b"

        it "Haskell parses (a, b) as Tuple" $ do
          runParse haskellExpr "(a, b)" `shouldBe` Right ast

      describe "Triple" $ do
        let ast = Tuple [Var (Name "a"), Var (Name "b"), Var (Name "c")]

        it "Haskell prints as (a, b, c)" $ do
          runPrint haskellExpr ast `shouldBe` "(a, b, c)"

        it "PureScript prints as nested Tuple" $ do
          runPrint purescriptExpr ast `shouldBe` "Tuple a (Tuple b c)"

        it "Haskell parses (a, b, c) as Tuple" $ do
          runParse haskellExpr "(a, b, c)" `shouldBe` Right ast

      describe "4-tuple" $ do
        let ast = Tuple [Var (Name "a"), Var (Name "b"), Var (Name "c"), Var (Name "d")]

        it "Haskell prints as (a, b, c, d)" $ do
          runPrint haskellExpr ast `shouldBe` "(a, b, c, d)"

        it "PureScript prints as nested Tuple" $ do
          runPrint purescriptExpr ast `shouldBe` "Tuple a (Tuple b (Tuple c d))"

        it "Haskell roundtrips" $ do
          runParse haskellExpr "(a, b, c, d)" `shouldBe` Right ast

      describe "5-tuple" $ do
        let ast = Tuple [Var (Name "a"), Var (Name "b"), Var (Name "c"), Var (Name "d"), Var (Name "e")]

        it "Haskell prints as (a, b, c, d, e)" $ do
          runPrint haskellExpr ast `shouldBe` "(a, b, c, d, e)"

        it "PureScript prints as nested Tuple" $ do
          runPrint purescriptExpr ast `shouldBe` "Tuple a (Tuple b (Tuple c (Tuple d e)))"

        it "Haskell roundtrips" $ do
          runParse haskellExpr "(a, b, c, d, e)" `shouldBe` Right ast

    describe "Tuple patterns" $ do
      describe "Pair pattern" $ do
        let ast = TuplePat [VarPat (Name "x"), VarPat (Name "y")]

        it "Haskell prints as (x, y)" $ do
          runPrint haskellPat ast `shouldBe` "(x, y)"

        it "PureScript prints as Tuple x y" $ do
          runPrint purescriptPat ast `shouldBe` "Tuple x y"

        it "Haskell parses (x, y) as TuplePat" $ do
          runParse haskellPat "(x, y)" `shouldBe` Right ast

      describe "Triple pattern" $ do
        let ast = TuplePat [VarPat (Name "x"), VarPat (Name "y"), VarPat (Name "z")]

        it "Haskell prints as (x, y, z)" $ do
          runPrint haskellPat ast `shouldBe` "(x, y, z)"

        it "PureScript prints as nested Tuple" $ do
          runPrint purescriptPat ast `shouldBe` "Tuple x (Tuple y z)"

        it "Haskell parses (x, y, z) as TuplePat" $ do
          runParse haskellPat "(x, y, z)" `shouldBe` Right ast

      describe "4-tuple pattern" $ do
        let ast = TuplePat [VarPat (Name "a"), VarPat (Name "b"), VarPat (Name "c"), VarPat (Name "d")]

        it "Haskell prints as (a, b, c, d)" $ do
          runPrint haskellPat ast `shouldBe` "(a, b, c, d)"

        it "PureScript prints as nested Tuple" $ do
          runPrint purescriptPat ast `shouldBe` "Tuple a (Tuple b (Tuple c d))"

        it "Haskell roundtrips" $ do
          runParse haskellPat "(a, b, c, d)" `shouldBe` Right ast

    describe "Nested tuple expressions" $ do
      describe "Tuple as first element" $ do
        let ast = Tuple [Tuple [Var (Name "a"), Var (Name "b")], Var (Name "c")]

        it "Haskell prints as ((a, b), c)" $ do
          runPrint haskellExpr ast `shouldBe` "((a, b), c)"

        it "Haskell parses ((a, b), c)" $ do
          runParse haskellExpr "((a, b), c)" `shouldBe` Right ast

        it "PureScript prints as Tuple (Tuple a b) c" $ do
          runPrint purescriptExpr ast `shouldBe` "Tuple (Tuple a b) c"

      describe "Tuple as second element" $ do
        let ast = Tuple [Var (Name "a"), Tuple [Var (Name "b"), Var (Name "c")]]

        it "Haskell prints as (a, (b, c))" $ do
          runPrint haskellExpr ast `shouldBe` "(a, (b, c))"

        it "Haskell parses (a, (b, c))" $ do
          runParse haskellExpr "(a, (b, c))" `shouldBe` Right ast

        it "PureScript prints as Tuple a (Tuple b c)" $ do
          runPrint purescriptExpr ast `shouldBe` "Tuple a (Tuple b c)"

      describe "Triple vs nested pair" $ do
        -- (a, b, c) and (a, (b, c)) are different ASTs but print the same in PS
        let triple = Tuple [Var (Name "a"), Var (Name "b"), Var (Name "c")]
            nested = Tuple [Var (Name "a"), Tuple [Var (Name "b"), Var (Name "c")]]

        it "PureScript prints both as Tuple a (Tuple b c)" $ do
          runPrint purescriptExpr triple `shouldBe` "Tuple a (Tuple b c)"
          runPrint purescriptExpr nested `shouldBe` "Tuple a (Tuple b c)"

        it "Haskell distinguishes them" $ do
          runPrint haskellExpr triple `shouldBe` "(a, b, c)"
          runPrint haskellExpr nested `shouldBe` "(a, (b, c))"

        it "Haskell roundtrips preserve the distinction" $ do
          runParse haskellExpr "(a, b, c)" `shouldBe` Right triple
          runParse haskellExpr "(a, (b, c))" `shouldBe` Right nested

    describe "Nested tuple patterns" $ do
      describe "Tuple in first position" $ do
        let ast = TuplePat [TuplePat [VarPat (Name "a"), VarPat (Name "b")], VarPat (Name "c")]

        it "Haskell prints as ((a, b), c)" $ do
          runPrint haskellPat ast `shouldBe` "((a, b), c)"

        it "Haskell parses ((a, b), c) pattern" $ do
          runParse haskellPat "((a, b), c)" `shouldBe` Right ast

        it "PureScript prints as Tuple (Tuple a b) c" $ do
          runPrint purescriptPat ast `shouldBe` "Tuple (Tuple a b) c"

      describe "Tuple in second position" $ do
        let ast = TuplePat [VarPat (Name "a"), TuplePat [VarPat (Name "b"), VarPat (Name "c")]]

        it "Haskell prints as (a, (b, c))" $ do
          runPrint haskellPat ast `shouldBe` "(a, (b, c))"

        it "Haskell parses (a, (b, c)) pattern" $ do
          runParse haskellPat "(a, (b, c))" `shouldBe` Right ast

        it "PureScript prints as Tuple a (Tuple b c)" $ do
          runPrint purescriptPat ast `shouldBe` "Tuple a (Tuple b c)"

      describe "Triple vs nested pair patterns" $ do
        let triple = TuplePat [VarPat (Name "a"), VarPat (Name "b"), VarPat (Name "c")]
            nested = TuplePat [VarPat (Name "a"), TuplePat [VarPat (Name "b"), VarPat (Name "c")]]

        it "PureScript prints both as Tuple a (Tuple b c)" $ do
          runPrint purescriptPat triple `shouldBe` "Tuple a (Tuple b c)"
          runPrint purescriptPat nested `shouldBe` "Tuple a (Tuple b c)"

        it "Haskell distinguishes them" $ do
          runPrint haskellPat triple `shouldBe` "(a, b, c)"
          runPrint haskellPat nested `shouldBe` "(a, (b, c))"

        it "Haskell roundtrips preserve the distinction" $ do
          runParse haskellPat "(a, b, c)" `shouldBe` Right triple
          runParse haskellPat "(a, (b, c))" `shouldBe` Right nested

    describe "Cons patterns" $ do
      describe "Simple cons" $ do
        let ast = ConsPat (VarPat (Name "x")) (VarPat (Name "xs"))

        it "Haskell prints as x : xs" $ do
          runPrint haskellPat ast `shouldBe` "x : xs"

        it "PureScript prints as Cons x xs" $ do
          runPrint purescriptPat ast `shouldBe` "Cons x xs"

        it "Haskell parses x : xs" $ do
          runParse haskellPat "x : xs" `shouldBe` Right ast

      describe "Nested cons" $ do
        let ast = ConsPat (VarPat (Name "x")) (ConsPat (VarPat (Name "y")) (VarPat (Name "zs")))

        it "Haskell prints as x : y : zs" $ do
          runPrint haskellPat ast `shouldBe` "x : y : zs"

        it "PureScript prints as Cons x (Cons y zs)" $ do
          runPrint purescriptPat ast `shouldBe` "Cons x (Cons y zs)"

      describe "Cons with constructor" $ do
        let ast = ConsPat (ConPat (Name "Just") [VarPat (Name "x")]) (VarPat (Name "xs"))

        it "Haskell prints as (Just x) : xs" $ do
          runPrint haskellPat ast `shouldBe` "(Just x) : xs"

        it "PureScript prints as Cons (Just x) xs" $ do
          runPrint purescriptPat ast `shouldBe` "Cons (Just x) xs"

      describe "Cross-language one-way" $ do
        it "PS Cons x xs parses as ConPat, not ConsPat" $ do
          let ast = ConsPat (VarPat (Name "x")) (VarPat (Name "xs"))
              psText = runPrint purescriptPat ast
          psText `shouldBe` "Cons x xs"
          runParse purescriptPat psText `shouldBe`
            Right (ConPat (Name "Cons") [VarPat (Name "x"), VarPat (Name "xs")])

    describe "Record construction" $ do
      let ast = RecordUpdate (Con (Name "MkFoo")) [(Name "bar", Literal (IntLit 1))]

      it "Haskell prints with = separator" $ do
        runPrint haskellExpr ast `shouldBe` "MkFoo { bar = 1 }"

      it "PureScript prints with : separator" $ do
        runPrint purescriptExpr ast `shouldBe` "MkFoo { bar: 1 }"

      it "Haskell roundtrips" $ do
        runParse haskellExpr "MkFoo { bar = 1 }" `shouldBe` Right ast

      it "PureScript roundtrips" $ do
        runParse purescriptExpr "MkFoo { bar: 1 }" `shouldBe` Right ast

      it "cross-language roundtrip" $ do
        let hsText = runPrint haskellExpr ast
        let psText = runPrint purescriptExpr ast
        runParse purescriptExpr hsText `shouldBe` Right ast
        runParse haskellExpr psText `shouldBe` Right ast

    describe "Record construction with QCon" $ do
      let ast = RecordUpdate (QCon [Name "Data", Name "Foo"] (Name "MkBar")) [(Name "x", Literal (IntLit 1))]

      it "Haskell prints with = separator" $ do
        runPrint haskellExpr ast `shouldBe` "Data.Foo.MkBar { x = 1 }"

      it "PureScript prints with : separator" $ do
        runPrint purescriptExpr ast `shouldBe` "Data.Foo.MkBar { x: 1 }"

    describe "Record update (non-constructor base)" $ do
      let ast = RecordUpdate (Var (Name "rec")) [(Name "bar", Literal (IntLit 1))]

      it "Haskell prints with = separator" $ do
        runPrint haskellExpr ast `shouldBe` "rec { bar = 1 }"

      it "PureScript also prints with = separator" $ do
        runPrint purescriptExpr ast `shouldBe` "rec { bar = 1 }"

    describe "Record patterns" $ do
      let ast = RecordPat (Name "Foo") [(Name "bar", VarPat (Name "x"))]

      it "Haskell prints with = separator" $ do
        runPrint haskellPat ast `shouldBe` "Foo { bar = x }"

      it "PureScript prints with : separator" $ do
        runPrint purescriptPat ast `shouldBe` "Foo { bar: x }"

      it "Haskell roundtrips" $ do
        runParse haskellPat "Foo { bar = x }" `shouldBe` Right ast

      it "PureScript roundtrips" $ do
        runParse purescriptPat "Foo { bar: x }" `shouldBe` Right ast

      it "cross-language roundtrip" $ do
        let hsText = runPrint haskellPat ast
        let psText = runPrint purescriptPat ast
        runParse purescriptPat hsText `shouldBe` Right ast
        runParse haskellPat psText `shouldBe` Right ast

    describe "Multi-field record pattern" $ do
      let ast = RecordPat (Name "Foo") [(Name "bar", VarPat (Name "x")), (Name "baz", VarPat (Name "y"))]

      it "Haskell prints with = separator" $ do
        runPrint haskellPat ast `shouldBe` "Foo { bar = x, baz = y }"

      it "PureScript prints with : separator" $ do
        runPrint purescriptPat ast `shouldBe` "Foo { bar: x, baz: y }"

      it "both roundtrip" $ do
        runParse haskellPat "Foo { bar = x, baz = y }" `shouldBe` Right ast
        runParse purescriptPat "Foo { bar: x, baz: y }" `shouldBe` Right ast

    describe "Tuple cross-language one-way translation" $ do
      it "HS Tuple -> PS text -> PS parse gives App/Con, not Tuple" $ do
        let ast = Tuple [Var (Name "a"), Var (Name "b")]
            psText = runPrint purescriptExpr ast
        psText `shouldBe` "Tuple a b"
        -- Parsing this back gives constructor application, not Tuple node
        runParse purescriptExpr psText `shouldBe`
          Right (App (App (Con (Name "Tuple")) (Var (Name "a"))) (Var (Name "b")))

      it "HS TuplePat -> PS text -> PS parse gives ConPat, not TuplePat" $ do
        let ast = TuplePat [VarPat (Name "x"), VarPat (Name "y")]
            psText = runPrint purescriptPat ast
        psText `shouldBe` "Tuple x y"
        -- Parsing this back gives constructor pattern, not TuplePat node
        runParse purescriptPat psText `shouldBe`
          Right (ConPat (Name "Tuple") [VarPat (Name "x"), VarPat (Name "y")])