{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
module Database.Bolt.Extras.Template.Internal.Converters
(
makeNodeLike
, makeNodeLikeWith
, makeURelationLike
, makeURelationLikeWith
) where
import Data.Map.Strict (fromList, member, notMember, (!))
import Data.Text (Text, pack, unpack)
import Database.Bolt (Node (..), URelationship (..), Value (..))
import Database.Bolt.Extras (FromValue (..), Labels (..),
NodeLike (..),
Properties (..), ToValue (..),
URelationLike (..))
import Database.Bolt.Extras.Utils (currentLoc, dummyId)
import Instances.TH.Lift ()
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
tupE' :: [Exp] -> Exp
#if MIN_VERSION_template_haskell(2, 16, 0)
tupE' :: [Exp] -> Exp
tupE' = [Maybe Exp] -> Exp
TupE ([Maybe Exp] -> Exp) -> ([Exp] -> [Maybe Exp]) -> [Exp] -> Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Exp -> Maybe Exp) -> [Exp] -> [Maybe Exp]
forall a b. (a -> b) -> [a] -> [b]
map Exp -> Maybe Exp
forall a. a -> Maybe a
Just
#else
tupE' = TupE
#endif
data BiClassInfo = BiClassInfo { BiClassInfo -> Name
className :: Name
, BiClassInfo -> Name
dataName :: Name
, BiClassInfo -> Name
classToFun :: Name
, BiClassInfo -> Name
classFromFun :: Name
}
nodeLikeClass :: BiClassInfo
nodeLikeClass :: BiClassInfo
nodeLikeClass = BiClassInfo :: Name -> Name -> Name -> Name -> BiClassInfo
BiClassInfo { className :: Name
className = ''NodeLike
, dataName :: Name
dataName = 'Node
, classToFun :: Name
classToFun = 'toNode
, classFromFun :: Name
classFromFun = 'fromNode
}
uRelationLikeClass :: BiClassInfo
uRelationLikeClass :: BiClassInfo
uRelationLikeClass = BiClassInfo :: Name -> Name -> Name -> Name -> BiClassInfo
BiClassInfo { className :: Name
className = ''URelationLike
, dataName :: Name
dataName = 'URelationship
, classToFun :: Name
classToFun = 'toURelation
, classFromFun :: Name
classFromFun = 'fromURelation
}
makeNodeLike :: Name -> Q [Dec]
makeNodeLike :: Name -> Q [Dec]
makeNodeLike Name
name = BiClassInfo -> Name -> (String -> String) -> Q [Dec]
makeBiClassInstance BiClassInfo
nodeLikeClass Name
name String -> String
forall a. a -> a
id
makeNodeLikeWith :: Name -> (String -> String) -> Q [Dec]
makeNodeLikeWith :: Name -> (String -> String) -> Q [Dec]
makeNodeLikeWith = BiClassInfo -> Name -> (String -> String) -> Q [Dec]
makeBiClassInstance BiClassInfo
nodeLikeClass
makeURelationLike :: Name -> Q [Dec]
makeURelationLike :: Name -> Q [Dec]
makeURelationLike Name
name = BiClassInfo -> Name -> (String -> String) -> Q [Dec]
makeBiClassInstance BiClassInfo
uRelationLikeClass Name
name String -> String
forall a. a -> a
id
makeURelationLikeWith :: Name -> (String -> String) -> Q [Dec]
makeURelationLikeWith :: Name -> (String -> String) -> Q [Dec]
makeURelationLikeWith = BiClassInfo -> Name -> (String -> String) -> Q [Dec]
makeBiClassInstance BiClassInfo
uRelationLikeClass
makeBiClassInstance :: BiClassInfo -> Name -> (String -> String) -> Q [Dec]
makeBiClassInstance :: BiClassInfo -> Name -> (String -> String) -> Q [Dec]
makeBiClassInstance BiClassInfo {Name
classFromFun :: Name
classToFun :: Name
dataName :: Name
className :: Name
classFromFun :: BiClassInfo -> Name
classToFun :: BiClassInfo -> Name
dataName :: BiClassInfo -> Name
className :: BiClassInfo -> Name
..} Name
typeCon String -> String
fieldLabelModifier = do
TyConI Dec
declaration <- Name -> Q Info
reify Name
typeCon
let (Name
tyName, [Con]
constr) = Dec -> (Name, [Con])
getTypeCons Dec
declaration
let label :: String
label = Name -> String
nameBase Name
tyName
let ([Name]
dataFields, [Type]
fieldTypes) = [(Name, Type)] -> ([Name], [Type])
forall a b. [(a, b)] -> ([a], [b])
unzip ([(Name, Type)] -> ([Name], [Type]))
-> [(Name, Type)] -> ([Name], [Type])
forall a b. (a -> b) -> a -> b
$ (Con -> [(Name, Type)]) -> [Con] -> [(Name, Type)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ((Name, [(Name, Type)]) -> [(Name, Type)]
forall a b. (a, b) -> b
snd ((Name, [(Name, Type)]) -> [(Name, Type)])
-> (Con -> (Name, [(Name, Type)])) -> Con -> [(Name, Type)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Con -> (Name, [(Name, Type)])
getConsFields) [Con]
constr
let (Name
consName, [(Name, Type)]
_) = [(Name, [(Name, Type)])] -> (Name, [(Name, Type)])
forall a. [a] -> a
head ([(Name, [(Name, Type)])] -> (Name, [(Name, Type)]))
-> [(Name, [(Name, Type)])] -> (Name, [(Name, Type)])
forall a b. (a -> b) -> a -> b
$ (Con -> (Name, [(Name, Type)]))
-> [Con] -> [(Name, [(Name, Type)])]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Con -> (Name, [(Name, Type)])
getConsFields [Con]
constr
Name
fresh <- String -> Q Name
newName String
"x"
Clause
toClause <- String -> Name -> Name -> [Name] -> (String -> String) -> Q Clause
makeToClause String
label Name
dataName Name
consName [Name]
dataFields String -> String
fieldLabelModifier
Clause
fromClause <- String
-> Name
-> Name
-> [Name]
-> [Type]
-> (String -> String)
-> Q Clause
makeFromClause String
label Name
consName Name
fresh [Name]
dataFields [Type]
fieldTypes String -> String
fieldLabelModifier
let bodyDecl :: [Dec]
bodyDecl = [Name -> [Clause] -> Dec
FunD Name
classToFun [Clause
toClause], Name -> [Clause] -> Dec
FunD Name
classFromFun [Clause
fromClause]]
[Dec] -> Q [Dec]
forall (f :: * -> *) a. Applicative f => a -> f a
pure [Maybe Overlap -> [Type] -> Type -> [Dec] -> Dec
InstanceD Maybe Overlap
forall a. Maybe a
Nothing [] (Type -> Type -> Type
AppT (Name -> Type
ConT Name
className) (Name -> Type
ConT Name
typeCon)) [Dec]
bodyDecl]
getConsFields :: Con -> (Name, [(Name, Type)])
getConsFields :: Con -> (Name, [(Name, Type)])
getConsFields (RecC Name
cName [VarBangType]
decs) = (Name
cName, (VarBangType -> (Name, Type)) -> [VarBangType] -> [(Name, Type)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(Name
fname, Bang
_, Type
ftype) -> (Name
fname, Type
ftype)) [VarBangType]
decs)
getConsFields (ForallC [TyVarBndr]
_ [Type]
_ Con
cons) = Con -> (Name, [(Name, Type)])
getConsFields Con
cons
getConsFields (RecGadtC (Name
cName:[Name]
_) [VarBangType]
decs Type
_) = (Name
cName, (VarBangType -> (Name, Type)) -> [VarBangType] -> [(Name, Type)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(Name
fname, Bang
_, Type
ftype) -> (Name
fname, Type
ftype)) [VarBangType]
decs)
getConsFields (NormalC Name
cName [BangType]
_) = (Name
cName, [])
getConsFields Con
_ = String -> (Name, [(Name, Type)])
forall a. HasCallStack => String -> a
error (String -> (Name, [(Name, Type)]))
-> String -> (Name, [(Name, Type)])
forall a b. (a -> b) -> a -> b
$ String
$currentLoc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"unsupported data declaration."
getTypeCons :: Dec -> (Name, [Con])
getTypeCons :: Dec -> (Name, [Con])
getTypeCons (DataD [Type]
_ Name
typeName [TyVarBndr]
_ Maybe Type
_ [Con]
constructors [DerivClause]
_) = (Name
typeName, [Con]
constructors)
getTypeCons (NewtypeD [Type]
_ Name
typeName [TyVarBndr]
_ Maybe Type
_ Con
constructor [DerivClause]
_) = (Name
typeName, [Con
constructor])
getTypeCons Dec
otherDecl = String -> (Name, [Con])
forall a. HasCallStack => String -> a
error (String -> (Name, [Con])) -> String -> (Name, [Con])
forall a b. (a -> b) -> a -> b
$ String
$currentLoc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"unsupported declaration: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Dec -> String
forall a. Show a => a -> String
show Dec
otherDecl String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\nShould be either 'data' or 'newtype'."
makeToClause :: String -> Name -> Name -> [Name] -> (String -> String) -> Q Clause
makeToClause :: String -> Name -> Name -> [Name] -> (String -> String) -> Q Clause
makeToClause String
label Name
dataCons Name
consName [Name]
dataFields String -> String
fieldLabelModifier
| [Name] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Name]
dataFields = Clause -> Q Clause
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Clause -> Q Clause) -> Clause -> Q Clause
forall a b. (a -> b) -> a -> b
$ [Pat] -> Body -> [Dec] -> Clause
Clause [Pat
WildP] (Exp -> Body
NormalB (Exp -> Body) -> Exp -> Body
forall a b. (a -> b) -> a -> b
$ [Name] -> Exp
result []) []
| Bool
otherwise = do
[Name]
fieldVars <- [Q Name] -> Q [Name]
forall a. [Q a] -> Q [a]
sequenceQ ([Q Name] -> Q [Name]) -> [Q Name] -> Q [Name]
forall a b. (a -> b) -> a -> b
$ String -> Q Name
newName String
"_field" Q Name -> [Name] -> [Q Name]
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ [Name]
dataFields
Clause -> Q Clause
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Clause -> Q Clause) -> Clause -> Q Clause
forall a b. (a -> b) -> a -> b
$ [Pat] -> Body -> [Dec] -> Clause
Clause [[Name] -> Pat
recPat [Name]
fieldVars] (Exp -> Body
NormalB (Exp -> Body) -> Exp -> Body
forall a b. (a -> b) -> a -> b
$ [Name] -> Exp
result [Name]
fieldVars) []
where
recPat :: [Name] -> Pat
recPat :: [Name] -> Pat
recPat [Name]
fieldVars = Pat -> Pat
ParensP (Pat -> Pat) -> Pat -> Pat
forall a b. (a -> b) -> a -> b
$ Name -> [FieldPat] -> Pat
RecP Name
consName ([FieldPat] -> Pat) -> [FieldPat] -> Pat
forall a b. (a -> b) -> a -> b
$ [Name] -> [Pat] -> [FieldPat]
forall a b. [a] -> [b] -> [(a, b)]
zip [Name]
dataFields ([Pat] -> [FieldPat]) -> [Pat] -> [FieldPat]
forall a b. (a -> b) -> a -> b
$ Name -> Pat
VarP (Name -> Pat) -> [Name] -> [Pat]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Name]
fieldVars
valuesExp :: [Name] -> [Exp]
valuesExp :: [Name] -> [Exp]
valuesExp = (Name -> Exp) -> [Name] -> [Exp]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Exp -> Exp -> Exp
AppE (Name -> Exp
VarE 'toValue) (Exp -> Exp) -> (Name -> Exp) -> Name -> Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Name -> Exp
VarE)
fieldNames :: [String]
fieldNames :: [String]
fieldNames = (Name -> String) -> [Name] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Name -> String
nameBase [Name]
dataFields
pairs :: [Name] -> [Exp]
pairs :: [Name] -> [Exp]
pairs = (String -> Exp -> Exp) -> [String] -> [Exp] -> [Exp]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (\String
fld Exp
val -> [Exp] -> Exp
tupE' [String -> Exp
strToTextE (String -> Exp) -> String -> Exp
forall a b. (a -> b) -> a -> b
$ String -> String
fieldLabelModifier String
fld, Exp
val]) [String]
fieldNames ([Exp] -> [Exp]) -> ([Name] -> [Exp]) -> [Name] -> [Exp]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Name] -> [Exp]
valuesExp
mapE :: [Name] -> Exp
mapE :: [Name] -> Exp
mapE [Name]
vars = Exp -> Exp -> Exp
AppE (Name -> Exp
VarE 'fromList) ([Exp] -> Exp
ListE ([Exp] -> Exp) -> [Exp] -> Exp
forall a b. (a -> b) -> a -> b
$ [Name] -> [Exp]
pairs [Name]
vars)
fieldFun :: Exp -> Exp
fieldFun :: Exp -> Exp
fieldFun | Name -> String
nameBase Name
dataCons String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"URelationship" = Exp -> Exp
forall a. a -> a
id
| Name -> String
nameBase Name
dataCons String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"Node" = [Exp] -> Exp
ListE ([Exp] -> Exp) -> (Exp -> [Exp]) -> Exp -> Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Exp -> [Exp] -> [Exp]
forall a. a -> [a] -> [a]
:[])
| Bool
otherwise = String -> Exp -> Exp
forall a. HasCallStack => String -> a
error (String -> Exp -> Exp) -> String -> Exp -> Exp
forall a b. (a -> b) -> a -> b
$ String
$currentLoc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"unsupported data type."
result :: [Name] -> Exp
result :: [Name] -> Exp
result = Exp -> Exp -> Exp
AppE (Exp -> Exp -> Exp
AppE (Exp -> Exp -> Exp
AppE (Name -> Exp
ConE Name
dataCons) (Lit -> Exp
LitE (Lit -> Exp) -> (Int -> Lit) -> Int -> Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Lit
IntegerL (Integer -> Lit) -> (Int -> Integer) -> Int -> Lit
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Exp) -> Int -> Exp
forall a b. (a -> b) -> a -> b
$ Int
dummyId)) (Exp -> Exp
fieldFun (Exp -> Exp) -> Exp -> Exp
forall a b. (a -> b) -> a -> b
$ String -> Exp
strToTextE String
label)) (Exp -> Exp) -> ([Name] -> Exp) -> [Name] -> Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Name] -> Exp
mapE
makeFromClause :: String -> Name -> Name -> [Name] -> [Type] -> (String -> String) -> Q Clause
makeFromClause :: String
-> Name
-> Name
-> [Name]
-> [Type]
-> (String -> String)
-> Q Clause
makeFromClause String
label Name
conName Name
varName [Name]
dataFields [Type]
fieldTypes String -> String
fieldLabelModifier = do
let maybeFields :: [Bool]
maybeFields = (Type -> Bool) -> [Type] -> [Bool]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Type -> Bool
isMaybe [Type]
fieldTypes
let fieldNames :: [Text]
fieldNames = (Name -> Text) -> [Name] -> [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (String -> Text
pack (String -> Text) -> (Name -> String) -> Name -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
fieldLabelModifier (String -> String) -> (Name -> String) -> Name -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Name -> String
nameBase) [Name]
dataFields
let maybeNames :: [(Text, Bool)]
maybeNames = [Text] -> [Bool] -> [(Text, Bool)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Text]
fieldNames [Bool]
maybeFields
let dataLabel :: Text
dataLabel = String -> Text
pack String
label
[Exp]
fieldNamesE <- (Text -> Q Exp) -> [Text] -> Q [Exp]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (\Text
x -> [|x|]) [Text]
fieldNames
let maybeNamesE :: [Exp]
maybeNamesE = (Exp -> Bool -> Exp) -> [Exp] -> [Bool] -> [Exp]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (\Exp
n Bool
m -> [Exp] -> Exp
tupE' [Exp
n, Name -> Exp
ConE (Name -> Exp) -> Name -> Exp
forall a b. (a -> b) -> a -> b
$ if Bool
m then Name
trueName else Name
falseName]) [Exp]
fieldNamesE [Bool]
maybeFields
let varExp :: Q Exp
varExp = Exp -> Q Exp
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Name -> Exp
VarE Name
varName)
Guard
guardSuccess <- Exp -> Guard
NormalG (Exp -> Guard) -> Q Exp -> Q Guard
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [|checkLabels $(varExp) [dataLabel] && checkProps $(varExp) maybeNames|]
Guard
guardFail <- Exp -> Guard
NormalG (Exp -> Guard) -> Q Exp -> Q Guard
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [|otherwise|]
Exp
failExp <- [|unpackError $(varExp) (unpack dataLabel)|]
let successExp :: Exp
successExp = (Exp -> Exp -> Exp) -> Exp -> [Exp] -> Exp
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Exp
a Exp
f -> Exp -> Exp -> Exp
AppE Exp
a (Exp -> Exp) -> Exp -> Exp
forall a b. (a -> b) -> a -> b
$ Exp -> Exp -> Exp
AppE (Exp -> Exp -> Exp
AppE (Name -> Exp
VarE 'getProp) (Name -> Exp
VarE Name
varName)) Exp
f) (Name -> Exp
ConE Name
conName) [Exp]
maybeNamesE
let successCase :: (Guard, Exp)
successCase = (Guard
guardSuccess, Exp
successExp)
let failCase :: (Guard, Exp)
failCase = (Guard
guardFail, Exp
failExp)
Clause -> Q Clause
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Clause -> Q Clause) -> Clause -> Q Clause
forall a b. (a -> b) -> a -> b
$ [Pat] -> Body -> [Dec] -> Clause
Clause [Name -> Pat
VarP Name
varName] ([(Guard, Exp)] -> Body
GuardedB [(Guard, Exp)
successCase, (Guard, Exp)
failCase]) []
isMaybe :: Type -> Bool
isMaybe :: Type -> Bool
isMaybe (AppT (ConT Name
t) Type
_) = Name
t Name -> Name -> Bool
forall a. Eq a => a -> a -> Bool
== ''Maybe
isMaybe Type
_ = Bool
False
strToTextE :: String -> Exp
strToTextE :: String -> Exp
strToTextE String
str = Exp -> Exp -> Exp
AppE (Name -> Exp
VarE 'pack) (Lit -> Exp
LitE (Lit -> Exp) -> (String -> Lit) -> String -> Exp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Lit
StringL (String -> Exp) -> String -> Exp
forall a b. (a -> b) -> a -> b
$ String
str)
checkProps :: Properties t => t -> [(Text, Bool)] -> Bool
checkProps :: t -> [(Text, Bool)] -> Bool
checkProps t
container = ((Text, Bool) -> Bool) -> [(Text, Bool)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\(Text
fieldName, Bool
fieldMaybe) -> Bool
fieldMaybe Bool -> Bool -> Bool
|| Text
fieldName Text -> Map Text Value -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`member` t -> Map Text Value
forall a. Properties a => a -> Map Text Value
getProps t
container)
checkLabels :: Labels t => t -> [Text] -> Bool
checkLabels :: t -> [Text] -> Bool
checkLabels t
container = (Text -> Bool) -> [Text] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t -> [Text]
forall a. Labels a => a -> [Text]
getLabels t
container)
getProp :: (Properties t, FromValue a) => t -> (Text, Bool) -> a
getProp :: t -> (Text, Bool) -> a
getProp t
container (Text
fieldName, Bool
fieldMaybe) | Bool
fieldMaybe Bool -> Bool -> Bool
&& Text
fieldName Text -> Map Text Value -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`notMember` t -> Map Text Value
forall a. Properties a => a -> Map Text Value
getProps t
container = Value -> a
forall a. FromValue a => Value -> a
fromValue (Value -> a) -> Value -> a
forall a b. (a -> b) -> a -> b
$ () -> Value
N ()
| Bool
otherwise = Value -> a
forall a. FromValue a => Value -> a
fromValue (t -> Map Text Value
forall a. Properties a => a -> Map Text Value
getProps t
container Map Text Value -> Text -> Value
forall k a. Ord k => Map k a -> k -> a
! Text
fieldName)
unpackError :: Show c => c -> String -> a
unpackError :: c -> String -> a
unpackError c
container String
label = String -> a
forall a. HasCallStack => String -> a
error (String -> a) -> String -> a
forall a b. (a -> b) -> a -> b
$ String
$currentLoc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" could not unpack " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
label String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" from " String -> String -> String
forall a. [a] -> [a] -> [a]
++ c -> String
forall a. Show a => a -> String
show c
container