{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ParallelListComp #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module RON.Schema.TH.Struct (
mkReplicatedStructLww,
mkReplicatedStructSet,
) where
import RON.Prelude
import Data.Char (toTitle)
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Language.Haskell.TH (bindS, conT, doE, listE, newName, noBindS,
recC, recConE, recP, sigD, varE, varP,
varT)
import qualified Language.Haskell.TH as TH
import Language.Haskell.TH.Syntax (liftData)
import RON.Data (MonadObjectState, ObjectStateT,
Replicated (encoding),
ReplicatedAsObject (Rep, getObject, newObject),
getObjectStateChunk, objectEncoding)
import RON.Data.LWW (LwwRep)
import qualified RON.Data.LWW as LWW
import RON.Data.ORSet (ORSetRep)
import qualified RON.Data.ORSet as ORSet
import RON.Error (MonadE, errorContext)
import RON.Event (ReplicaClock)
import RON.Schema as X
import RON.Schema.TH.Common (fieldExp', fieldPat', liftText,
mkGuideType, mkNameT, newNameT, valDP)
import RON.Types (Object (Object), UUID)
import RON.Util (Instance (Instance))
import qualified RON.UUID as UUID
type instance XField Equipped = XFieldEquipped
data XFieldEquipped = XFieldEquipped
{ haskellName :: Text
, ronName :: UUID
}
equipStruct :: Struct e Resolved -> Struct e Equipped
equipStruct Struct{name, fields, annotations} = Struct
{name, fields = Map.mapWithKey (equipField annotations) fields, annotations}
mkReplicatedStructLww :: StructLww Resolved -> TH.DecsQ
mkReplicatedStructLww structResolved = do
dataType <- mkDataType struct
[instanceReplicated] <- mkInstanceReplicated type'
[instanceReplicatedAO] <- mkInstanceReplicatedAOLww struct
accessors <- fold <$> traverse (mkAccessorsLww name') fields
pure
$ dataType
: instanceReplicated : instanceReplicatedAO
: accessors
where
struct@Struct{name, fields} = equipStruct structResolved
name' = mkNameT name
type' = conT name'
mkReplicatedStructSet :: StructSet Resolved -> TH.DecsQ
mkReplicatedStructSet structResolved = do
dataType <- mkDataType struct
[instanceReplicated] <- mkInstanceReplicated type'
[instanceReplicatedAO] <- mkInstanceReplicatedAOSet struct
accessors <- fold <$> traverse (mkAccessorsSet name') fields
pure
$ dataType
: instanceReplicated : instanceReplicatedAO
: accessors
where
struct@Struct{name, fields} = equipStruct structResolved
name' = mkNameT name
type' = conT name'
equipField :: StructAnnotations -> Text -> Field Resolved -> Field Equipped
equipField structAnnotations schemaName Field{..} =
case UUID.mkName $ Text.encodeUtf8 schemaName of
Just ronName -> Field{ext = XFieldEquipped{haskellName, ronName}, ..}
Nothing -> error $
"Field name is not representable in RON: " ++ show schemaName
where
haskellName = mkHaskellFieldName structAnnotations schemaName
varBangType' :: Text -> TH.TypeQ -> TH.VarBangTypeQ
varBangType' name
= TH.varBangType (mkNameT name)
. TH.bangType (TH.bang TH.noSourceUnpackedness TH.sourceStrict)
mkDataType :: Struct encoding Equipped -> TH.DecQ
mkDataType Struct{name, fields} =
TH.dataD
(TH.cxt [])
name'
[]
Nothing
[recC name'
[ varBangType' haskellName [t| Maybe $(mkGuideType ronType) |]
| Field{ronType, ext = XFieldEquipped{haskellName}} <- toList fields
]]
[]
where
name' = mkNameT name
mkInstanceReplicated :: TH.TypeQ -> TH.DecsQ
mkInstanceReplicated type' = [d|
instance Replicated $type' where
encoding = objectEncoding
|]
mkInstanceReplicatedAOLww :: StructLww Equipped -> TH.DecsQ
mkInstanceReplicatedAOLww Struct{name, fields} = do
ops <- newName "ops"
vars <- traverse (newNameT . haskellName . ext) fields
let packFields = listE
[ [| ($ronName', Instance <$> $(varE var)) |]
| Field{ext = XFieldEquipped{ronName}} <- toList fields
, let ronName' = liftData ronName
| var <- toList vars
]
unpackFields =
[ bindS (varP var) [| LWW.viewField $ronName' $(varE ops) |]
| Field{ext = XFieldEquipped{ronName}} <- toList fields
, let ronName' = liftData ronName
| var <- toList vars
]
let consE = recConE name'
[ fieldExp' haskellName $ varE var
| Field{ext = XFieldEquipped{haskellName}} <- toList fields
| var <- toList vars
]
consP = recP name'
[ fieldPat' haskellName $ varP var
| Field{ext = XFieldEquipped{haskellName}} <- toList fields
| var <- toList vars
]
let getObjectImpl = doE
$ bindS (varP ops) [| getObjectStateChunk |]
: unpackFields
++ [noBindS [| pure $consE |]]
[d| instance ReplicatedAsObject $type' where
type Rep $type' = LwwRep
newObject $consP = Object <$> LWW.newStruct $packFields
getObject = errorContext $(liftText errCtx) $getObjectImpl
|]
where
name' = mkNameT name
type' = conT name'
errCtx = "getObject @" <> name
mkInstanceReplicatedAOSet :: StructSet Equipped -> TH.DecsQ
mkInstanceReplicatedAOSet Struct{name, fields} = do
ops <- newName "ops"
vars <- traverse (newNameT . haskellName . ext) fields
let packFields = listE
[ [| ($ronName', fmap Instance $(varE var)) |]
| Field{ext = XFieldEquipped{ronName}} <- toList fields
, let ronName' = liftData ronName
| var <- toList vars
]
unpackFields =
[ bindS
(varP var)
[| errorContext $(liftText haskellName) $
$(orSetViewField mergeStrategy) $ronName' $(varE ops) |]
| Field{annotations = FieldAnnotations{mergeStrategy}, ext} <-
toList fields
, let
XFieldEquipped{haskellName, ronName} = ext
ronName' = liftData ronName
| var <- toList vars
]
let consE = recConE name'
[ fieldExp' haskellName $ varE var
| Field{ext = XFieldEquipped{haskellName}} <- toList fields
| var <- toList vars
]
consP = recP name'
[ fieldPat' haskellName $ varP var
| Field{ext = XFieldEquipped{haskellName}} <- toList fields
| var <- toList vars
]
let getObjectImpl = doE
$ bindS (varP ops) [| getObjectStateChunk |]
: unpackFields
++ [noBindS [| pure $consE |]]
[d| instance ReplicatedAsObject $type' where
type Rep $type' = ORSetRep
newObject $consP = Object <$> ORSet.newStruct $packFields
getObject = errorContext $(liftText errCtx) $getObjectImpl
|]
where
name' = mkNameT name
type' = conT name'
errCtx = "getObject @" <> name
mkHaskellFieldName :: StructAnnotations -> Text -> Text
mkHaskellFieldName annotations base = prefix <> base' where
StructAnnotations
{ haskellFieldPrefix = prefix
, haskellFieldCaseTransform = caseTransform
}
= annotations
base' = case caseTransform of
Nothing -> base
Just TitleCase -> case Text.uncons base of
Nothing -> base
Just (b, baseTail) -> Text.cons (toTitle b) baseTail
mkAccessorsLww :: TH.Name -> Field Equipped -> TH.DecsQ
mkAccessorsLww name' field = do
a <- varT <$> newName "a"
m <- varT <$> newName "m"
let assignF =
[ sigD assign [t|
(ReplicaClock $m, MonadE $m, MonadObjectState $type' $m)
=> Maybe $guideType -> $m () |]
, valDP assign [| LWW.assignField $ronName' |]
]
readF =
[ sigD read [t|
(MonadE $m, MonadObjectState $type' $m)
=> $m (Maybe $guideType) |]
, valDP read [| LWW.readField $ronName' |]
]
zoomF =
[ sigD zoom [t|
MonadE $m
=> ObjectStateT $guideType $m $a
-> ObjectStateT $type' $m $a |]
, valDP zoom [| LWW.zoomField $ronName' |]
]
sequenceA $ assignF ++ readF ++ zoomF
where
Field{ronType, ext = XFieldEquipped{haskellName, ronName}} = field
ronName' = liftData ronName
type' = conT name'
guideType = mkGuideType ronType
assign = mkNameT $ haskellName <> "_assign"
read = mkNameT $ haskellName <> "_read"
zoom = mkNameT $ haskellName <> "_zoom"
mkAccessorsSet :: TH.Name -> Field Equipped -> TH.DecsQ
mkAccessorsSet name' field = do
a <- varT <$> newName "a"
m <- varT <$> newName "m"
let assignF =
[ sigD assign [t|
(ReplicaClock $m, MonadE $m, MonadObjectState $type' $m)
=> Maybe $guideType -> $m () |]
, valDP assign [| ORSet.assignField $ronName' |]
]
readF =
[ sigD read [t|
(MonadE $m, MonadObjectState $type' $m)
=> $m (Maybe $guideType) |]
, valDP read
[| do
chunk <- getObjectStateChunk
$(orSetViewField mergeStrategy) $ronName' chunk |]
]
zoomF = case ronType of
TObject _ ->
[ sigD zoom [t|
(MonadE $m, ReplicaClock $m)
=> ObjectStateT $(mkGuideType ronType) $m $a
-> ObjectStateT $type' $m $a |]
, valDP zoom [| ORSet.zoomFieldObject $ronName' |]
]
_ -> []
sequenceA $ assignF ++ readF ++ zoomF
where
Field{ronType, annotations = FieldAnnotations{mergeStrategy}, ext} = field
XFieldEquipped{haskellName, ronName} = ext
ronName' = liftData ronName
type' = conT name'
guideType = mkGuideType ronType
assign = mkNameT $ haskellName <> "_assign"
read = mkNameT $ haskellName <> "_read"
zoom = mkNameT $ haskellName <> "_zoom"
orSetViewField :: Maybe MergeStrategy -> TH.ExpQ
orSetViewField = varE . \case
Nothing -> 'ORSet.viewField
Just LWW -> 'ORSet.viewFieldLWW
Just Max -> 'ORSet.viewFieldMax
Just Min -> 'ORSet.viewFieldMin
Just Set -> 'ORSet.viewFieldSet