packages feed

fay-0.23.1.13: src/tests/Test/Desugar.hs

{-# LANGUAGE ViewPatterns #-}
module Test.Desugar
  ( tests
  , devTest
  , parseE
  , parseM
  , des
  ) where

import           Fay.Compiler.Prelude

import           Fay.Compiler.Desugar
import           Fay.Compiler.Parse     (parseFay)
import           Fay.Types.CompileError (CompileError (..))

import           Language.Haskell.Exts  hiding (alt, binds, loc, name)
import           Test.Tasty
import           Test.Tasty.HUnit
-- import           Text.Groom

tests :: TestTree
tests = testGroup "desugar" $ map (\(T k a b) -> testCase k $ doDesugar k a b) testDeclarations

data DesugarTest = T String String String

testDeclarations :: [DesugarTest]
testDeclarations =
  [T "LambdaCase"
     "import Prelude; f = \\gen0 -> case gen0 of { _ -> 2 }"
     "import Prelude; f = \\case { _ -> 2 }"
  ,T "MultiWayIf"
     "import Prelude; f = case () of { _ | True -> 1 | False -> 2 }"
     "import Prelude; f = if | True -> 1 | False -> 2"
  ,T "TupleCon"
     "import Prelude; f = \\gen0 gen1 gen2 -> (gen0, gen1, gen2)"
     "import Prelude; f = (,,)"
  ,T "Do"
     "import Prelude; f = (>>=) x (\\gen0 -> (>>) y z)"
     "import Prelude; f = do { gen0 <- x; y; z }"
  ,T "TupleSection"
     "import Prelude; f = \\gen0 gen1 -> (gen0,2,gen1)"
     "import Prelude; f = (,2,)"
  ,T "ImplicitPrelude1" -- Add missing Prelude import
     "import Prelude"
     ""
  ,T "ImplicitPrelude2" -- Keep existing Prelude import
     "import Prelude ()"
     "import Prelude ()"
  ,T "ImplicitPrelude3" -- Keep existing qualified import
     "import qualified Prelude"
     "import qualified Prelude"
  ,T "NoImplicitPrelude"
     "{-# LANGUAGE NoImplicitPrelude #-}"
     "{-# LANGUAGE NoImplicitPrelude #-}"
  ,T "OperatorSectionRight"
     "import Prelude; f = \\gen0 -> g gen0 x"
     "import Prelude; f = (`g` x)"
  ,T "OperatorSectionLeft"
     "import Prelude; f = \\gen0 -> g x gen0"
     "import Prelude; f = (x `g`)"
  ,T "InfixOpOp"
     "import Prelude; f = (+) x y"
     "import Prelude; f = x + y"
  ,T "InfixOpFun"
     "import Prelude; f = g x y"
     "import Prelude; f = x `g` y"
  ,T "InfixOpCons"
     "import Prelude; f = (:) x y"
     "import Prelude; f = x : y"
  ,T "ExpParen"
     "import Prelude; f = x y"
     "import Prelude; f = (x (y))"
  ,T "PatParen"
     "import Prelude; f x = y"
     "import Prelude; f (x) = y"
  ,T "PatInfixOp"
     "import Prelude; f ((:) x y) = z"
     "import Prelude; f (x : y) = z"
  ,T "PatFieldPun"
     "import Prelude; f R { x = x } = y"
     "import Prelude; f R { x } = y"
  ,T "PatField"
     "import Prelude; f = R { x = x }"
     "import Prelude; f = R { x }"
  ,T "FFITopLevel"
     "import Prelude; f :: Int; f = ffi \"1\" :: Int"
     "import Prelude; f :: Int; f = ffi \"1\""
  ,T "FFIWhere"
     "import Prelude; f = () where x :: Int; x = ffi \"1\" :: Int"
     "import Prelude; f = () where x :: Int; x = ffi \"1\""
  ]

parseAndDesugar :: String -> String -> IO (Module SrcLoc, Either CompileError (Module SrcLoc))
parseAndDesugar name s =
  case parseFay "test" s :: ParseResult (Module SrcSpanInfo) of
    ParseFailed a b -> error $ show (name, a, b)
    ParseOk (fmap srcSpanInfoToSrcLoc -> m) -> do
      d <- desugar' "gen" noLoc $ m
      return (m, d)
  where
    srcSpanInfoToSrcLoc :: SrcSpanInfo -> SrcLoc
    srcSpanInfoToSrcLoc = (\ss -> SrcLoc { srcFilename = srcSpanFilename ss
                                         , srcLine     = srcSpanStartLine ss
                                         , srcColumn   = srcSpanStartColumn ss
                                         }) . srcInfoSpan
doDesugar :: String -> String -> String -> Assertion
doDesugar testName a b = do
  (originalExpected, desugaredExpected, desugared) <- parseAndDesugarAll testName a b
  assertEqual "identity"  (unAnn originalExpected) (unAnn desugaredExpected)
  assertEqual "desugared" (unAnn originalExpected) (unAnn desugared        )
  assertEqual "both"      (unAnn desugared       ) (unAnn desugaredExpected)

parseAndDesugarAll :: String -> String -> String -> IO (Module SrcLoc, Module SrcLoc, Module SrcLoc)
parseAndDesugarAll testName a b = do
  (originalExpected',Right desugaredExpected) <- parseAndDesugar (testName ++ " expected") a
  (_                ,Right desugared        ) <- parseAndDesugar (testName ++ " input")    b
  -- We need to desugar parens in the original module since we
  -- strip it away in desugaring but there isn't alawys a way to construct
  -- this paren directly from a source string
  let originalExpected = desugarPatParen . desugarExpParen $ originalExpected'
  return (originalExpected, desugaredExpected, desugared)

-- When developing:

devTest :: String -> IO ()
devTest nam = do
  let (T _ a b) = fromJust (find (\(T n _ _) -> n == nam) testDeclarations)
  (originalExpected, desugaredExpected, desugared) <- parseAndDesugarAll "" a b
  if unAnn desugared == unAnn desugaredExpected && unAnn desugared == unAnn originalExpected
    then putStrLn "OK"
    else do
      putStrLn "--- originalExpected"
      g $ unAnn originalExpected
      putStrLn "--- desugaredExpected"
      g $ unAnn desugaredExpected
      putStrLn "--- desugared"
      g $ unAnn desugared
      putStrLn "--- originalExpected"
      pretty originalExpected
      putStrLn "--- desugaredExpected"
      pretty desugaredExpected
      putStrLn "--- desugared"
      pretty desugared
  when (unAnn originalExpected  /= unAnn desugaredExpected) $ putStrLn "originalExpected /= desugaredExpected"
  when (unAnn desugared         /= unAnn desugaredExpected) $ putStrLn "desugared /= desugaredExpected"
  when (unAnn desugared         /= unAnn originalExpected ) $ putStrLn "desugared /= originalExpected"
  when (unAnn desugaredExpected /= unAnn originalExpected ) $ putStrLn "desugaredExpected /= undesugared"

g :: Show a => a -> IO ()
g = putStrLn . show -- groom

unAnn :: Functor f => f a -> f ()
unAnn = void

pretty :: Module SrcLoc -> IO ()
pretty = putStrLn . prettyPrint

parseM :: String -> Module ()
parseM s = let ParseOk m = parseFay "module" s :: ParseResult (Module SrcSpanInfo) in unAnn m

parseE :: String -> Exp ()
parseE s = let ParseOk m = parseFay "exp" s :: ParseResult (Exp SrcSpanInfo) in unAnn m

des :: String -> IO ()
des s = do
  Right r <- desugar' "gen" () $ parseM s
  g r