4 changed files with 107 additions and 1 deletions
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
|
||||
module FSM |
||||
( |
||||
FSMState(..) |
||||
, FSMCallback(..) |
||||
, FSM(..) |
||||
, runFsm |
||||
, makeFsm |
||||
) where |
||||
|
||||
import Control.Concurrent.STM.TVar (TVar, newTVarIO, readTVarIO, |
||||
writeTVar) |
||||
import Control.Monad.Extra (whileM) |
||||
import Control.Monad.IO.Class (MonadIO (..)) |
||||
import Control.Monad.STM (atomically) |
||||
import qualified Data.Map as M |
||||
|
||||
class FSMState a where |
||||
isTerminalState :: a -> Bool |
||||
|
||||
newtype FSMCallback m a = FSMCallback (m (Maybe a)) |
||||
|
||||
data FSM a m = FSM |
||||
{ fsmCurrentState :: TVar a |
||||
, fsmHandlers :: M.Map a (FSMCallback m a) |
||||
} |
||||
|
||||
runFsm :: (MonadIO m, FSMState a, Ord a) => FSM a m -> m () |
||||
runFsm fsm = whileM $ do |
||||
currentState <- liftIO . readTVarIO $ fsmCurrentState fsm |
||||
case M.lookup currentState $ fsmHandlers fsm of |
||||
Just (FSMCallback cb) -> do |
||||
maybeNextState <- cb |
||||
case maybeNextState of |
||||
Just nextState -> do |
||||
liftIO . atomically $ writeTVar (fsmCurrentState fsm) nextState |
||||
pure True |
||||
Nothing -> pure (not . isTerminalState $ currentState) |
||||
Nothing -> pure False |
||||
|
||||
makeFsm :: (MonadIO m1, |
||||
MonadIO m, |
||||
FSMState a, |
||||
Ord a) => a -> [(a, FSMCallback m a)] -> m1 (FSM a m) |
||||
makeFsm initialState handlers = do |
||||
currentState <- liftIO $ newTVarIO initialState |
||||
pure $ FSM currentState (M.fromList handlers) |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
|
||||
module Test.FSM |
||||
( |
||||
unitTests |
||||
) where |
||||
|
||||
import Control.Concurrent.STM.TVar (readTVarIO) |
||||
import FSM |
||||
import System.Timeout (timeout) |
||||
import Test.Tasty |
||||
import Test.Tasty.HUnit (testCase, (@?=)) |
||||
|
||||
unitTests :: TestTree |
||||
unitTests = testGroup "FSM" |
||||
[ testSimpleStateChange |
||||
, testStateWithoutCallback ] |
||||
|
||||
data SimpleState = |
||||
StateInitial |
||||
| StateIntermediate |
||||
| StateFinal |
||||
deriving (Show, Eq, Ord) |
||||
|
||||
instance FSMState SimpleState where |
||||
isTerminalState StateFinal = True |
||||
isTerminalState _ = False |
||||
|
||||
testSimpleStateChange = testCase "Simple state change" $ do |
||||
fsm <- makeFsm StateInitial [(StateInitial, callbackChangeState StateIntermediate), |
||||
(StateIntermediate, callbackChangeState StateFinal), |
||||
(StateFinal, doNothing)] |
||||
timeout 100000 $ runFsm fsm |
||||
endState <- readTVarIO (fsmCurrentState fsm) |
||||
endState @?= StateFinal |
||||
where |
||||
callbackChangeState x = FSMCallback (pure $ Just x) |
||||
doNothing = FSMCallback (pure Nothing) |
||||
|
||||
testStateWithoutCallback = testCase "Test without callback" $ do |
||||
fsm <- makeFsm StateInitial [(StateInitial, callbackChangeState StateIntermediate), |
||||
(StateFinal, doNothing)] |
||||
timeout 100000 $ runFsm fsm |
||||
endState <- readTVarIO (fsmCurrentState fsm) |
||||
endState @?= StateIntermediate |
||||
where |
||||
callbackChangeState x = FSMCallback (pure $ Just x) |
||||
doNothing = FSMCallback (pure Nothing) |
||||
Loading…
Reference in new issue