packages feed

mockcat-1.0.0.0: src/Test/MockCat/TH/ClassAnalysis.hs

{-# LANGUAGE TemplateHaskellQuotes #-}
module Test.MockCat.TH.ClassAnalysis
  ( ClassName2VarNames (..),
    VarName2ClassNames (..),
    toClassInfos,
    toClassInfo,
    getTypeNames,
    filterClassInfo,
    filterMonadicVarInfos,
    hasMonadInVarInfo,
    getClassName,
    getClassNames,
    VarAppliedType (..),
    applyVarAppliedTypes,
    updateType,
    findClass,
    hasClass
  )
where

import Control.Monad (guard)
import Data.List (find)
import qualified Data.Map.Strict as Map
import Data.Maybe (isJust)
import Data.Text (pack, splitOn, unpack)
import Language.Haskell.TH
  ( Name,
    Pred,
    Type (..),
  )

data ClassName2VarNames = ClassName2VarNames Name [Name]

instance Show ClassName2VarNames where
  show (ClassName2VarNames cName varNames) = showClassDef cName varNames

data VarName2ClassNames = VarName2ClassNames Name [Name]

instance Show VarName2ClassNames where
  show (VarName2ClassNames varName classNames) = show varName <> " class is " <> unwords (showClassName <$> classNames)

toClassInfos :: [Pred] -> [ClassName2VarNames]
toClassInfos = map toClassInfo

toClassInfo :: Pred -> ClassName2VarNames
toClassInfo (AppT t1 t2) =
  let (ClassName2VarNames name vars) = toClassInfo t1
   in ClassName2VarNames name (vars ++ getTypeNames t2)
toClassInfo (ConT name) = ClassName2VarNames name []
toClassInfo _ = error "Unsupported Type structure"

getTypeNames :: Pred -> [Name]
getTypeNames (VarT name) = [name]
getTypeNames (ConT name) = [name]
getTypeNames _ = []

filterClassInfo :: Name -> [ClassName2VarNames] -> [ClassName2VarNames]
filterClassInfo name = filter (hasVarName name)
  where
    hasVarName :: Name -> ClassName2VarNames -> Bool
    hasVarName target (ClassName2VarNames _ varNames) = target `elem` varNames

filterMonadicVarInfos :: [VarName2ClassNames] -> [VarName2ClassNames]
filterMonadicVarInfos = filter hasMonadInVarInfo

hasMonadInVarInfo :: VarName2ClassNames -> Bool
hasMonadInVarInfo (VarName2ClassNames _ classNames) = ''Monad `elem` classNames

getClassName :: Type -> Name
getClassName (ConT name) = name
getClassName (AppT ty _) = getClassName ty
getClassName d = error $ "unsupported class definition: " <> show d

getClassNames :: Type -> [Name]
getClassNames (AppT (ConT name1) (ConT name2)) = [name1, name2]
getClassNames (AppT ty (ConT name)) = getClassNames ty ++ [name]
getClassNames (AppT ty1 ty2) = getClassNames ty1 ++ getClassNames ty2
getClassNames _ = []

showClassName :: Name -> String
showClassName n = splitLast "." $ show n

showClassDef :: Name -> [Name] -> String
showClassDef className varNames = showClassName className <> " " <> unwords (show <$> varNames)

splitLast :: String -> String -> String
splitLast delimiter = last . split delimiter

split :: String -> String -> [String]
split delimiter str = unpack <$> splitOn (pack delimiter) (pack str)

data VarAppliedType = VarAppliedType Name (Maybe Name)
  deriving (Show)

applyVarAppliedTypes :: [VarAppliedType] -> Type -> Type
applyVarAppliedTypes varAppliedTypes = transform
  where
    mapping =
      Map.fromList
        [ (varName, className)
        | VarAppliedType varName (Just className) <- varAppliedTypes
        ]
    transform (VarT n) =
      case Map.lookup n mapping of
        Just className -> ConT className
        Nothing -> VarT n
    transform (AppT t1 t2) = AppT (transform t1) (transform t2)
    transform (SigT t k) = SigT (transform t) k
    transform (ParensT t) = ParensT (transform t)
    transform (InfixT t1 n t2) = InfixT (transform t1) n (transform t2)
    transform (UInfixT t1 n t2) = UInfixT (transform t1) n (transform t2)
    transform (ForallT tvs ctx t) = ForallT tvs (map transform ctx) (transform t)
    transform t = t

updateType :: Type -> [VarAppliedType] -> Type
updateType (AppT (VarT v1) (VarT v2)) varAppliedTypes =
  let x = maybe (VarT v1) ConT (findClass v1 varAppliedTypes)
      y = maybe (VarT v2) ConT (findClass v2 varAppliedTypes)
   in AppT x y
updateType ty _ = ty

hasClass :: Name -> [VarAppliedType] -> Bool
hasClass varName = any (\(VarAppliedType v c) -> v == varName && isJust c)

findClass :: Name -> [VarAppliedType] -> Maybe Name
findClass varName types = do
  guard $ hasClass varName types
  (VarAppliedType _ c) <- find (\(VarAppliedType v _) -> v == varName) types
  c