packages feed

typed-fsm-0.2.0.1: src/TypedFsm/Driver/General.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}

-- | Running FSM
module TypedFsm.Driver.General where

import Data.Bool.Singletons (SBool (..))
import Data.Eq.Singletons (SEq (..))
import Data.IFunctor (At (..))
import Data.Singletons (SingI (..))
import TypedFsm.Core (Operate (..), StateTransMsg (Msg))
import TypedFsm.Driver.Common
import Unsafe.Coerce (unsafeCoerce)

anyToSomeMsg
  :: forall ps input
   . (SingI input, SEq ps)
  => AnyMsg ps -> Maybe (SomeMsg ps input)
anyToSomeMsg (AnyMsg (msg :: Msg ps from to)) =
  case sing @from %== sing @input of
    -- (from == input) ~ True
    -- ==> from ~ input
    STrue -> unsafeCoerce (Just (SomeMsg msg))
    SFalse -> Nothing

newtype UnexpectMsg ps = UnexpectMsg (AnyMsg ps)

data UnexpectMsgHandler ps m
  = Ignore
  | IgnoreAndTrace (AnyMsg ps -> m ())
  | Terminal

runOperate
  :: forall ps m a (input :: ps) (output :: ps)
   . ( Monad m
     , SingI input
     , SEq ps
     )
  => UnexpectMsgHandler ps m
  -> [AnyMsg ps]
  -> Operate m (At a output) input
  -> m (Result ps (UnexpectMsg ps) m a)
runOperate unHandler anyMsgs = \case
  IReturn (At a) -> pure (Finish a)
  LiftM m -> m >>= (runOperate unHandler anyMsgs)
  In f -> loop anyMsgs
   where
    loop [] = pure $ Cont $ SomeOperate (In f)
    loop (anyMsg : evns') = do
      case anyToSomeMsg @_ @input anyMsg of
        Nothing -> case unHandler of
          Ignore -> loop evns'
          IgnoreAndTrace trace -> trace anyMsg >> loop evns'
          Terminal -> pure (ErrorInfo $ UnexpectMsg anyMsg)
        Just (SomeMsg msg) -> runOperate unHandler evns' (f msg)