packages feed

alms-0.6.0: src/Meta/DeriveNotable.hs

{-# LANGUAGE TypeFamilies #-}
module Meta.DeriveNotable (
  deriveNotable
) where

import AST.Notable
import Meta.THHelpers

import Data.Char (toLower)
import Language.Haskell.TH

data DeriveNotableRec
  = DeriveNotableRec {
      dnFrom       :: Maybe Name,
      dnBy         :: Name,
      dnExcept     :: [Name],
      dnContext    :: [(Name, [Int])]
    }

class ExtDN a r where
  extDN :: DeriveNotableRec -> a -> r
instance ExtDN Name (Q [Dec]) where
  extDN = deriveNotableRec
instance ExtDN a r => ExtDN (Maybe Name) (a -> r) where
  extDN dn mn = extDN (dn { dnFrom = mn })
instance ExtDN a r => ExtDN Name (a -> r) where
  extDN dn n = extDN (dn { dnBy = n })
instance ExtDN a r => ExtDN [Name] (a -> r) where
  extDN dn ns = extDN (dn { dnExcept = ns })
instance (ExtDN a r, ix ~ Int) => ExtDN (Name, [ix]) (a -> r) where
  extDN dn context = extDN (dn { dnContext = context : dnContext dn })

deriveNotable :: ExtDN a r => a -> r
deriveNotable = extDN DeriveNotableRec {
  dnBy      = 'newN,
  dnExcept  = [],
  dnFrom    = Nothing,
  dnContext = []
}

deriveNotableRec :: DeriveNotableRec -> Name -> Q [Dec]
deriveNotableRec dnr toName = do
  TyConI tc <- case dnFrom dnr of
    Just n  -> reify n
    Nothing -> do
      TyConI (TySynD _ _ fromType) <- reify toName
      case fromType of
        AppT (AppT _ (AppT _ _)) (AppT (ConT n) _) -> reify n
        AppT (AppT _ (ConT n)) _                   -> reify n
        _ -> fail "deriveNotable: Can't find data type"
  case tc of
    DataD context _ tvs cons _   -> go dnr toName context tvs cons
    NewtypeD context _ tvs con _ -> go dnr toName context tvs [con]
    _ -> fail "deriveNotable supports data and newtype only"

go :: DeriveNotableRec -> Name -> Cxt -> [TyVarBndr] -> [Con] -> Q [Dec]
go dnr toName context tvs cons = do
  context' <- buildContext tvs (dnContext dnr)
  let rtype = foldl appT (conT toName) (map typeOfTyVarBndr tvs)
      quant = forallT tvs (return (context' ++ context))
  declses <- sequence [ deriveOne (dnBy dnr) quant rtype con 
                      | con <- cons,
                        conName con `notElem` dnExcept dnr ]
  return (concat declses)

deriveOne :: Name -> (TypeQ -> TypeQ) -> TypeQ -> Con -> Q [Dec]
deriveOne new quant rtype (NormalC cname params0) = do
  let ptypes  = map (return . snd) params0
      funName = mkName (lowerFirst (nameBase cname))
  params <- mapM (newName . const "x") params0
  prot   <- sigD funName (quant (foldr (\ _tj _tr -> [t| $_tj -> $_tr |])
                                       rtype ptypes))
  decl   <- funD funName
    [
      clause (map varP params)
             (normalB
              (appE (varE new)
                    (foldl appE (conE cname) (map varE params))))
             []
    ]
  return [prot, decl]
deriveOne new tvs rtype (RecC cname params) =
  deriveOne new tvs rtype (NormalC cname [ (s, t) | (_, s, t) <- params ])
deriveOne new tvs rtype (InfixC st1 cname st2) =
  deriveOne new tvs rtype (NormalC cname [st1, st2])
deriveOne new tvs rtype (ForallC _ _ con) = deriveOne new tvs rtype con

lowerFirst :: String -> String
lowerFirst ""     = ""
lowerFirst (c:cs) = toLower c : cs