{-# LANGUAGE LambdaCase, RecordWildCards, ViewPatterns #-}
module Data.MakeEnum(
makeEnum,
makeEnumWith,
) where
import Control.Monad
import Data.Functor.Identity
import Data.Maybe
import Data.Monoid
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Lens.Micro hiding(filtered)
import Data.MakeEnum.Options
makeEnum :: Name -> [Name] -> Q [Dec]
makeEnum tyName omit = makeEnumWith tyName omit defaultOptions
makeEnumWith :: Name -> [Name] -> Options -> Q [Dec]
makeEnumWith tyName omit options = reify tyName >>= \case
TyConI (unwrapDec -> Just dec) -> do
let deducedOpts = deduceOptions dec options
let (dec', origCons, name) = buildReducedEnum deducedOpts omit' dec
(fromSig, fromFun) <- buildFromFun deducedOpts name origCons
(toSig, toFun) <- buildToFun deducedOpts name origCons
pure [dec', fromSig, fromFun, toSig, toFun]
_ -> fail "unsupported type"
where omit' = Just <$> omit
data DataDef = DataDef Cxt Name [TyVarBndr] (Maybe Kind) [Con] [DerivClause]
deriving (Eq, Ord, Show)
unwrapDec :: Dec -> Maybe DataDef
unwrapDec (DataD cx name bndrs kind cons derivs) = Just $ DataDef cx name bndrs kind cons derivs
unwrapDec _ = Nothing
type DeducedOptions = OptionsT Identity
deduceOptions :: DataDef -> Options -> DeducedOptions
deduceOptions (DataDef _ name _ _ _ _) Options { .. } =
Options
{ newEnumName = Identity $ fromMaybe (nameBase name) newEnumName
, fromFunctionName = Identity $ fromMaybe ("from" <> nameBase name) fromFunctionName
, toFunctionName = Identity $ fromMaybe ("to" <> nameBase name) toFunctionName
, ..
}
buildReducedEnum :: DeducedOptions -> [Maybe Name] -> DataDef -> (Dec, [Con], Name)
buildReducedEnum Options { .. } omit (DataDef cx name bndrs kind cons _) = (DataD cx name' bndrs kind cons' derivs, filtered, name)
where filtered = filter ((`notElem` omit) . (^? nameT)) cons
cons' = nameT `over` (mkName . ctorNameModifier . nameBase) <$> filtered
name' = mkName $ runIdentity newEnumName
derivs = [DerivClause Nothing $ ConT <$> deriveClasses]
buildFromFun :: DeducedOptions -> Name -> [Con] -> Q (Dec, Dec)
buildFromFun Options { .. } name cons = do
Module _ (ModName thisModName) <- thisModule
let funName = mkName $ runIdentity fromFunctionName
let funSig = SigD funName $ ArrowT `AppT` ConT name `AppT` (ConT (mkName "Maybe") `AppT` ConT (mkName $ runIdentity newEnumName))
clauses <- mapM (mkClause thisModName) cons
let fallback = Clause [WildP] (NormalB $ ConE $ mkName "Nothing") []
let funDef = FunD funName $ clauses ++ [fallback]
pure (funSig, funDef)
where
mkClause thisModName (NormalC n ts) = do
let thisName = mkName $ thisModName <> "." <> ctorNameModifier (nameBase n)
binders <- replicateM (length ts) $ newName "p"
let body = NormalB $ ConE (mkName "Just") `AppE` (ConE thisName `foldBinders` binders)
pure $ Clause [ConP n $ VarP <$> binders] body []
mkClause _ p = fail $ "this type of constructor is not supported yet:\n" <> pprint p
buildToFun :: DeducedOptions -> Name -> [Con] -> Q (Dec, Dec)
buildToFun Options { .. } name cons = do
Module _ (ModName thisModName) <- thisModule
let funName = mkName $ runIdentity toFunctionName
let funSig = SigD funName $ ArrowT `AppT` ConT (mkName $ runIdentity newEnumName) `AppT` ConT name
clauses <- mapM (mkClause thisModName) cons
let funDef = FunD funName clauses
pure (funSig, funDef)
where
mkClause thisModName (NormalC n ts) = do
let thisName = mkName $ thisModName <> "." <> ctorNameModifier (nameBase n)
binders <- replicateM (length ts) $ newName "p"
let body = NormalB $ ConE n `foldBinders` binders
pure $ Clause [ConP thisName $ VarP <$> binders] body []
mkClause _ p = fail $ "this type of constructor is not supported yet:\n" <> pprint p
foldBinders :: Exp -> [Name] -> Exp
foldBinders name = foldl AppE name . map VarE
nameT :: Traversal' Con Name
nameT f (NormalC n bts) = (`NormalC` bts) <$> f n
nameT f (RecC n vbts) = (`RecC` vbts) <$> f n
nameT f (InfixC bt1 n bt2) = (\n' -> InfixC bt1 n' bt2) <$> f n
nameT f (ForallC tvbs cx n) = ForallC tvbs cx <$> nameT f n
nameT _ c@GadtC {} = pure c
nameT _ c@RecGadtC {} = pure c