packages feed

cparsing-0.1.0.0: Data/SmartTrav/TH.hs

{-# LANGUAGE LambdaCase #-}

module Data.SmartTrav.TH where
  
import Language.Haskell.TH
import Data.Maybe
import Control.Monad
import Control.Applicative
  
-- | Derive SmartTrav
deriveSmartTrav :: Name -> Q [Dec]
deriveSmartTrav nm = reify nm >>= (\case
  TyConI dt -> case dt of
    DataD _ tyConName typArgs _ dataCons _ -> 
      createInstance tyConName typArgs dataCons  
    NewtypeD _ tyConName typArgs _ dataCon _ -> 
      createInstance tyConName typArgs [dataCon]  
    _ -> fail "Unsupported data type"
  _ -> fail "Expected the name of a data type or newtype"
  )
  
createInstance :: Name -> [TyVarBndr] -> [Con] -> Q [Dec]
createInstance tyConName typArgs dataCons   
          = do (clauses, preds) <- unzip <$> mapM createClause dataCons
               return [InstanceD Nothing (concat preds) 
                                 (AppT (ConT className) 
                                   (foldl AppT (ConT tyConName) 
                                          (map getTypVarTyp (init typArgs))))
                                 [FunD funName clauses]
                      ]
  where  -- | Gets the variable that is traversed on
        varToTraverseOn :: Q Name
        varToTraverseOn = case reverse typArgs of 
          (PlainTV last : _)        -> return last
          (KindedTV last StarT : _) -> return last
          (KindedTV last _ : _)     -> fail $ "The kind of the last type parameter is not *"
          []                        -> fail $ "The kind of type " ++ show tyConName ++ " is *"
  
        -- | Creates a clause for a constructor, the needed context is also generated
        createClause :: Con -> Q (Clause,[Pred])
        createClause (RecC conName conArgs) 
          = createClause' conName (map (\(_,_,r) -> r) conArgs)
        createClause (NormalC conName conArgs) 
          = createClause' conName (map snd conArgs)
        
        createClause' conName argTypes
          = do bindedNames <- replicateM (length argTypes) (newName "p")
               (handleParams,ctx) <- unzip <$> zipWithM processParam
                                                        bindedNames argTypes
               return $ (Clause [ VarP desc, VarP asc, VarP f
                               , ConP conName (map VarP bindedNames) ] 
                               (NormalB (createExpr conName handleParams)) []
                        , concat ctx)


        -- | Creates an expression for the body of a smartTrav clause
        -- using the matches created for parameters
        createExpr :: Name -> [Exp] -> Exp
        createExpr ctrName []
          = AppE applPure $ ConE ctrName
        createExpr ctrName (param1:params)
          = foldl (\coll new -> InfixE (Just coll) applStar (Just new)) 
                  (InfixE (Just $ ConE ctrName) applDollar (Just param1))
                  params
        
        applStar = VarE (mkName "Control.Applicative.<*>")
        applStarR = VarE (mkName "Control.Applicative.*>")
        applStarL = VarE (mkName "Control.Applicative.<*")
        applDollar = VarE (mkName "Control.Applicative.<$>")
        applPure = VarE (mkName "Control.Applicative.pure")
        
        className = mkName "SmartTrav"
        funName = mkName "smartTrav"
        desc = mkName "desc"
        asc = mkName "asc"
        f = mkName "f"
       
        -- | Creates the expression and the predicate for a parameter
        processParam :: Name -> Type -> Q (Exp, [Pred])
        processParam name (VarT v)  -- found the type variable to traverse on
          = do travV <- varToTraverseOn 
               if v == travV then return (AppE (VarE f) (VarE name), [])
                             else return (AppE applPure (VarE name), [])
        processParam name (AppT tf ta) = do
          expr <- createExprForHighKind' name (VarE f) ta
          case expr of Just (e,ctx) -> return (e, if isTypVar tf then AppT (ConT className) tf : ctx
                                                                 else ctx)
                       Nothing -> return (AppE applPure (VarE name), [])
        processParam name _
          = return (AppE applPure (VarE name), [])
  
        -- | Create an expression and a context for a higher kinded parameter
        createExprForHighKind' :: Name -> Exp -> Type -> Q (Maybe (Exp, [Pred]))
        createExprForHighKind' name f (AppT tf ta)
          = do res <- createExprForHighKind' name (applExpr f) ta
               case res of Just (e,ctx) -> return $ Just (e, if isTypVar tf then AppT (ConT className) tf : ctx
                                                                            else ctx)
                           Nothing -> return Nothing
        createExprForHighKind' name f (VarT v)
          = do travV <- varToTraverseOn 
               if v == travV then
                 return $ Just (InfixE (Just $ VarE desc) applStarR 
                                       (Just $ InfixE (Just (applExpr f `AppE` (VarE name))) 
                                                       applStarL 
                                                       (Just $ VarE asc)), [])
                else return Nothing
        createExprForHighKind' _ name _
          = return Nothing
          
        applExpr f = (((VarE funName) `AppE` (VarE desc)) `AppE` (VarE asc)) 
                          `AppE` f
                          
               
isTypVar :: Type -> Bool
isTypVar (VarT _) = True
isTypVar _ = False               
  
getTypVarTyp :: TyVarBndr -> Type
getTypVarTyp (PlainTV n) = VarT n
getTypVarTyp (KindedTV n _) = VarT n
  
  
thExamine :: Q [Dec] -> Q [Dec]
thExamine decl = do d <- decl
                    runIO (print d)
                    return d