packages feed

pms-infra-agent-server-0.0.1.0: src/PMS/Infra/Agent/Server/CoreModel/TH.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module PMS.Infra.Agent.Server.CoreModel.TH
  ( funcTH_transit
  , instanceTH_IAppState
  ) where

import Control.Monad (when)
import Control.Monad.Except (throwError)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State.Lazy (get)
import Language.Haskell.TH
import qualified Data.Text as T
import qualified GHC.Base
import qualified GHC.Show

import PMS.Infra.Agent.Server.CoreModel.Type
import PMS.Infra.Agent.Server.CoreModel.Utility

instanceTH_IAppState :: Name -> Q [Dec]
instanceTH_IAppState stName = do
  ns <- getGadtsContNames ''Event
  clauseList <- mapM go ns
  return [InstanceD Nothing [] (AppT (ConT ''IAppState) (ConT stName)) [FunD 'actionS clauseList]]
  where
    go n = do
      s <- newName "s"
      r <- newName "r"
      return $
        Clause
          [VarP s, ConP 'EventW [] [AsP r (RecP n [])]]
          (NormalB (AppE (AppE (VarE 'action) (VarE s)) (VarE r)))
          []

    getGadtsContNames :: Name -> Q [Name]
    getGadtsContNames n = reify n >>= \case
      TyConI (DataD _ _ _ _ cs _) -> mapM go' cs
      x -> fail $ "[ERROR] can not get data constructor. " ++ show x

    go' (GadtC [name] _ _) = return name
    go' x = fail $ "[ERROR] can not get gadts data constructor. " ++ show x

funcTH_transit :: Q [Dec]
funcTH_transit = do
  fname <- newName "transit"
  cons <- getContNames ''StateTransition
  clauses <- mapM makeClause cons

  return
    [ SigD fname (AppT (AppT ArrowT (ConT ''StateTransition)) (AppT (ConT ''AppStateContext) (TupleT 0)))
    , FunD fname clauses
    ]
  where
    getContNames :: Name -> Q [Name]
    getContNames n = reify n >>= \case
      TyConI (DataD _ _ _ _ cs _) -> mapM go cs
      x -> fail $ "[ERROR] can not get data constructor. " ++ show x

    go (NormalC x _) = return x
    go x = fail $ "[ERROR] can not get data constructor. " ++ show x

    makeClause :: Name -> Q Clause
    makeClause n = do
      x <- newName "x"
      (curSt, nextSt) <- getStName n
      let liftGet = AppE (VarE 'lift) (VarE 'get)
      return $
        Clause
          [ConP n [] []]
          ( NormalB
              ( InfixE
                  (Just liftGet)
                  (VarE '(GHC.Base.>>=))
                  ( Just
                      ( LamCaseE
                          [ Match
                              (ConP 'AppStateW [] [ConP curSt [] []])
                              ( NormalB
                                  ( InfixE
                                      (Just (UnboundVarE 'changeTo))
                                      (VarE '(GHC.Base.$))
                                      (Just (AppE (ConE 'AppStateW) (ConE nextSt)))
                                  )
                              )
                              []
                          , Match
                              (ConP 'AppStateW [] [VarP x])
                              ( NormalB
                                  ( AppE
                                      (VarE 'throwError)
                                      ( InfixE
                                          (Just (LitE (StringL "invalid state transition. trans:")))
                                          (VarE '(GHC.Base.++))
                                          ( Just
                                              ( InfixE
                                                  (Just (AppE (VarE 'GHC.Show.show) (ConE n)))
                                                  (VarE '(GHC.Base.++))
                                                  ( Just
                                                      ( InfixE
                                                          (Just (LitE (StringL ", curSt:")))
                                                          (VarE '(GHC.Base.++))
                                                          (Just (AppE (VarE 'GHC.Show.show) (VarE x)))
                                                      )
                                                  )
                                              )
                                          )
                                      )
                                  )
                              )
                              []
                          ]
                      )
                  )
              )
          )
          []

    getStName :: Name -> Q (Name, Name)
    getStName n = do
      let modName = "PMS.Infra.Agent.Server.CoreModel.Type."
          stStrs = T.splitOn "To" $ T.replace (T.pack modName) "" $ T.pack $ show n
      when (2 /= length stStrs) $
        fail $ "[ERROR] invalid StateTransition constructor. " ++ show n

      let curSt = mkName $ T.unpack $ T.pack modName `T.append` head stStrs `T.append` "State"
          nextSt = mkName $ T.unpack $ T.pack modName `T.append` last stStrs `T.append` "State"

      return (curSt, nextSt)