packages feed

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

module Ribosome.Host.TH.Api.Generate where

import Data.Char (toUpper)
import qualified Data.Map.Strict as Map
import Data.MessagePack (Object)
import Exon (exon)
import Language.Haskell.TH (Dec, DecsQ, Name, Q, Type, appT, conT, listT, mkName, newName, runIO, tupleT)
import Prelude hiding (Type)

import qualified Ribosome.Host.Data.ApiInfo as ApiInfo
import Ribosome.Host.Data.ApiInfo (ApiInfo (ApiInfo), ExtType, ExtTypeMeta, RpcDecl (RpcDecl), apiInfo, unExtType)
import Ribosome.Host.Data.ApiType (ApiPrim (..), ApiType (..))
import Ribosome.Host.Data.LuaRef (LuaRef)
import Ribosome.Host.TH.Api.Param (Param (Param))

camelcase :: String -> String
camelcase =
  snd . foldr folder (False, "")
  where
    folder '_' (_, z) = (True, z)
    folder a (True, h : t) = (False, a : toUpper h : t)
    folder a (True, []) = (False, [a])
    folder a (False, z) = (False, a : z)

reifyApiPrim :: ApiPrim -> Q Type
reifyApiPrim = \case
  Boolean -> [t|Bool|]
  Integer -> [t|Int|]
  Float -> [t|Double|]
  String -> [t|Text|]
  Dictionary -> [t|Map Text Object|]
  Object -> [t|Object|]
  Void -> [t|()|]
  LuaRef -> [t|LuaRef|]

reifyApiType :: ApiType -> Q Type
reifyApiType = \case
  Prim t ->
    reifyApiPrim t
  Array t (Just count) ->
    foldl appT (tupleT count) (replicate count (reifyApiType t))
  Array t Nothing ->
    appT listT (reifyApiType t)
  Ext t ->
    conT (mkName t)

polyName :: Int -> ApiType -> Q (Maybe Name)
polyName i = \case
  Prim Object ->
    Just <$> newName [exon|p_#{show i}|]
  _ ->
    pure Nothing

reifyParam :: Int -> (ApiType, String) -> Q Param
reifyParam i (t, n) = do
  name <- newName prefixed
  mono <- reifyApiType t
  paramType <- polyName i t
  pure (Param name mono paramType)
  where
    prefixed =
      [exon|arg#{show i}_#{n}|]

data MethodSpec =
  MethodSpec {
    apiName :: String,
    camelcaseName :: Name,
    params :: [Param],
    returnType :: ApiType
  }
  deriving stock (Eq, Show)

functionData :: RpcDecl -> Q MethodSpec
functionData (RpcDecl name parameters _ _ _ returnType) = do
  params <- zipWithM reifyParam [0..] parameters
  pure (MethodSpec name (mkName (camelcase name)) params returnType)

genExtTypes :: Map ExtType ExtTypeMeta -> (Name -> ExtTypeMeta -> DecsQ) -> Q [[Dec]]
genExtTypes types gen =
  traverse (uncurry gen) (first (mkName . unExtType) <$> Map.toList types)

generateFromApi :: (MethodSpec -> Q [Dec]) -> Maybe (Name -> ExtTypeMeta -> DecsQ) -> Q [Dec]
generateFromApi handleFunction handleExtType = do
  ApiInfo {functions, types} <- either (fail . show) pure =<< runIO apiInfo
  funcs <- traverse functionData functions
  funcDecs <- traverse handleFunction funcs
  tpeDecs <- traverse (genExtTypes types) handleExtType
  pure (join (funcDecs <> fold tpeDecs))