packages feed

ribosome-host-0.9.9.9: lib/Ribosome/Host/TH/Api/GenerateEffect.hs

module Ribosome.Host.TH.Api.GenerateEffect where

import qualified Data.Kind as Kind
import Exon (exon)
import Language.Haskell.TH (
  Dec,
  DecQ,
  Name,
  Q,
  Quote (newName),
  Specificity (SpecifiedSpec),
  TyVarBndr (KindedTV),
  Type (AppT, ArrowT, ForallT, StarT, VarT),
  appE,
  clause,
  funD,
  mkName,
  nameBase,
  normalB,
  sigD,
  varE,
  varP,
  varT,
  )
import Prelude hiding (Type)

import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode)
import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (toMsgpack))
import Ribosome.Host.Data.ApiType (ApiType, pattern PolyType)
import qualified Ribosome.Host.Effect.Rpc as Rpc
import Ribosome.Host.Effect.Rpc (Rpc)
import Ribosome.Host.TH.Api.Generate (MethodSpec (MethodSpec), generateFromApi, reifyApiType)
import Ribosome.Host.TH.Api.Param (Param (Param, paramName))

msgpackDecodeConstraint :: ApiType -> Q (Maybe Type)
msgpackDecodeConstraint = \case
  PolyType ->
    Just <$> [t|MsgpackDecode $(varT (mkName "a"))|]
  _ ->
    pure Nothing

msgpackEncodeConstraint :: Param -> Q (Maybe Type)
msgpackEncodeConstraint = \case
  Param _ _ (Just p) ->
    Just <$> [t|MsgpackEncode $(varT p)|]
  Param _ _ Nothing ->
    pure Nothing

effReturnType :: ApiType -> Q (Maybe Name, Type)
effReturnType = \case
  PolyType -> do
    let n = mkName "a"
    pure (Just n, VarT (mkName "a"))
  a -> do
    t <- reifyApiType a
    pure (Nothing, t)

analyzeReturnType :: ApiType -> Q (Maybe Name, Type, Maybe Type)
analyzeReturnType tpe = do
  (n, rt) <- effReturnType tpe
  constraint <- msgpackDecodeConstraint tpe
  pure (n, rt, constraint)

effSig :: Name -> [Param] -> ApiType -> DecQ
effSig name params returnType = do
  stackName <- newName "r"
  stack <- varT stackName
  rpcConstraint <- [t|Member Rpc $(pure stack)|]
  (retTv, retType, decodeConstraint) <- analyzeReturnType returnType
  encodeConstraints <- traverse msgpackEncodeConstraint params
  semT <- [t|Sem|]
  stackKind <- [t|[(Kind.Type -> Kind.Type) -> Kind.Type -> Kind.Type]|]
  let
    paramType = \case
      Param _ _ (Just n) ->
        VarT n
      Param _ t Nothing ->
        t
    paramsType =
      foldr (AppT . AppT ArrowT . paramType) (AppT (AppT semT stack) retType) params
    constraints =
      rpcConstraint : maybeToList decodeConstraint <> catMaybes encodeConstraints
    paramTv = \case
      Param _ _ (Just n) ->
        Just n
      Param _ _ Nothing ->
        Nothing
    paramTvs =
      mapMaybe paramTv params
    tv n =
      KindedTV n SpecifiedSpec StarT
    stackTv =
      KindedTV stackName SpecifiedSpec stackKind
  sigD name (pure (ForallT ((tv <$> paramTvs) <> maybeToList (tv <$> retTv) <> [stackTv]) constraints paramsType))

effBody :: Name -> [Param] -> DecQ
effBody name params =
  funD name [clause (varP <$> names) (normalB effectCons) []]
  where
    effectCons =
      appE [|Rpc.sync|] args
    args =
      foldl appE (varE (mkName [exon|RpcData.#{nameBase name}|])) (paramE <$> params)
    names =
      paramName <$> params
    paramE = \case
      Param n _ p ->
        fromMaybe id (appE [e|toMsgpack|] <$ p) (varE n)

genMethod :: MethodSpec -> Q [Dec]
genMethod (MethodSpec _ name params returnType) = do
  sig <- effSig name params returnType
  body <- effBody name params
  pure [sig, body]

generateEffect :: Q [Dec]
generateEffect =
  generateFromApi genMethod Nothing