module Strive.Internal.TH
( options
, makeLenses
) where
import Data.Aeson.TH (Options, defaultOptions, fieldLabelModifier)
import Data.Char (isUpper, toLower, toUpper)
import Data.Maybe (isJust)
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Syntax as TH
options :: Options
options = defaultOptions
{ fieldLabelModifier = underscore . dropPrefix
}
underscore :: String -> String
underscore = concatMap go
where
go c = if isUpper c
then ['_', toLower c]
else [c]
dropPrefix :: String -> String
dropPrefix = drop 1 . dropWhile (/= '_')
makeLenses :: String -> TH.Q [TH.Dec]
makeLenses string = do
maybeName <- TH.lookupTypeName string
case maybeName of
Just name -> do
info <- TH.reify name
case info of
TH.TyConI (TH.DataD _ _ _ _ [TH.RecC _ triples] _) -> do
classes <- makeLensClasses triples
instances <- makeLensInstances name triples
return (classes ++ instances)
_ -> fail "reify failed"
_ -> fail "lookupTypeName failed"
makeLensClasses :: [TH.VarStrictType] -> TH.Q [TH.Dec]
makeLensClasses [] = return []
makeLensClasses (triple : triples) = do
exists <- lensExists triple
if exists
then makeLensClasses triples
else do
klass <- makeLensClass triple
classes <- makeLensClasses triples
return (klass : classes)
makeLensClass :: TH.VarStrictType -> TH.Q TH.Dec
makeLensClass triple = do
exists <- lensExists triple
if exists
then fail "lens already exists"
else do
a <- TH.newName "a"
b <- TH.newName "b"
let klass = TH.ClassD [] name types dependencies declarations
name = TH.mkName (getLensName triple)
types = [TH.PlainTV a, TH.PlainTV b]
dependencies = [TH.FunDep [a] [b]]
declarations = [TH.SigD field typ]
field = TH.mkName (getFieldName triple)
typ = TH.AppT (TH.AppT (TH.ConT (TH.mkName "Lens")) (TH.VarT a)) (TH.VarT b)
return klass
lensExists :: TH.VarStrictType -> TH.Q Bool
lensExists triple = do
let name = getLensName triple
maybeName <- TH.lookupTypeName name
return (isJust maybeName)
getLensName :: TH.VarStrictType -> String
getLensName triple = capitalize (getFieldName triple) ++ "Lens"
capitalize :: String -> String
capitalize "" = ""
capitalize (c : cs) = toUpper c : cs
getFieldName :: TH.VarStrictType -> String
getFieldName (var, _, _) = (lensName . show) var
lensName :: String -> String
lensName x = if y `elem` keywords then y ++ "_" else y
where
y = dropPrefix x
keywords = ["data", "type"]
makeLensInstances :: TH.Name -> [TH.VarStrictType] -> TH.Q [TH.Dec]
makeLensInstances name triples = mapM (makeLensInstance name) triples
makeLensInstance :: TH.Name -> TH.VarStrictType -> TH.Q TH.Dec
makeLensInstance name triple@(var, _, typ) = do
f <- TH.newName "f"
x <- TH.newName "x"
a <- TH.newName "a"
Just fmap' <- TH.lookupValueName "fmap"
let field = TH.mkName (getFieldName triple)
return $ TH.InstanceD
Nothing
[]
(TH.AppT (TH.AppT (TH.ConT (TH.mkName (getLensName triple))) (TH.ConT name)) typ)
[TH.FunD field [TH.Clause [TH.VarP f, TH.VarP x] (TH.NormalB (TH.AppE (TH.AppE (TH.VarE fmap') (TH.LamE [TH.VarP a] (TH.RecUpdE (TH.VarE x) [(var, TH.VarE a)]))) (TH.AppE (TH.VarE f) (TH.AppE (TH.VarE var) (TH.VarE x))))) []]]