packages feed

fsmActions 0.1 → 0.2.0

raw patch · 8 files changed

+370/−187 lines, 8 filesdep +fgldep +graphvizdep −bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: fgl, graphviz

Dependencies removed: bytestring

API changes (from Hackage documentation)

- Data.FsmActions: BadActions :: [(sy, Action)] -> WellFormed sy
- Data.FsmActions: BadLengths :: [(sy, Int)] -> WellFormed sy
- Data.FsmActions: WellFormed :: [sy] -> WellFormed sy
- Data.FsmActions: data WellFormed sy
- Data.FsmActions: instance (Eq sy) => Eq (WellFormed sy)
- Data.FsmActions: instance (Show sy) => Show (WellFormed sy)
- Data.FsmActions: isWellFormed :: (Ord sy) => FSM sy -> WellFormed sy
- Data.FsmActions.ActionMatrix: readAdjMxFromFile :: String -> IO Action
- Data.FsmActions.ActionMatrix: readAdjMxFromString :: ByteString -> ReadMxMonad Action
- Data.FsmActions.ActionMatrix: readFSMFromMxFiles :: (Ord sy) => [(sy, String)] -> IO (FSM sy)
- Data.FsmActions.FsmMatrix: parseFsmFile :: FilePath -> IO (FSM String)
- Data.FsmActions.FsmMatrix: parseFsmString :: String -> ReadMxMonad (FSM String)
- Data.FsmActions.FsmMatrix: printFsmMatrix :: FSM String -> String
+ Data.FsmActions: identity :: Int -> Action
+ Data.FsmActions.ActionMatrix: parseActionMx :: String -> ReadMxMonad Action
+ Data.FsmActions.ActionMatrix: parseActionMxFile :: FilePath -> IO Action
+ Data.FsmActions.ActionMatrix: parseFsmActionMxFiles :: (Ord sy) => [(sy, FilePath)] -> IO (FSM sy)
+ Data.FsmActions.ActionMatrix: printActionMx :: Action -> String
+ Data.FsmActions.FGL: Keep :: SelfLoops
+ Data.FsmActions.FGL: Trim :: SelfLoops
+ Data.FsmActions.FGL: data SelfLoops
+ Data.FsmActions.FGL: fsmToFGL :: FSM sy -> SelfLoops -> Gr () sy
+ Data.FsmActions.FGL: strongCCs :: (Eq sy) => FSM sy -> [[State]]
+ Data.FsmActions.FGL: weakCCs :: (Eq sy) => FSM sy -> [[State]]
+ Data.FsmActions.FsmMatrix: parseFsmMx :: String -> ReadMxMonad (FSM String)
+ Data.FsmActions.FsmMatrix: parseFsmMxFile :: FilePath -> IO (FSM String)
+ Data.FsmActions.FsmMatrix: printFsmMx :: FSM String -> String
+ Data.FsmActions.GraphViz: fsmToDot :: (Ord sy, Show sy) => FSM sy -> DotGraph
+ Data.FsmActions.WellFormed: BadActions :: [(sy, Action)] -> WellFormed sy
+ Data.FsmActions.WellFormed: BadLengths :: [(sy, Int)] -> WellFormed sy
+ Data.FsmActions.WellFormed: Disconnected :: [[State]] -> WellFormed sy
+ Data.FsmActions.WellFormed: WellFormed :: WellFormed sy
+ Data.FsmActions.WellFormed: data WellFormed sy
+ Data.FsmActions.WellFormed: instance (Eq sy) => Eq (WellFormed sy)
+ Data.FsmActions.WellFormed: instance (Show sy) => Show (WellFormed sy)
+ Data.FsmActions.WellFormed: isWellFormed :: (Ord sy) => FSM sy -> WellFormed sy

Files

Data/FsmActions.hs view
@@ -16,45 +16,44 @@ -- Copyright (c) 2009 Andy Gimblett - http://www.cs.swan.ac.uk/~csandy/ -- BSD Licence (see http://www.opensource.org/licenses/bsd-license.php) -module Data.FsmActions-    (-- * Data types-     State,-     DestinationSet(..),-     Action(..),-     FSM(..),-     Word(..),-     -- * Simple FSM operations-     states,-     alphabet,-     fsmAction,-     -- * Well-formedness-     WellFormed(..),-     isWellFormed,-     -- * Normalisation-     normalise,-     normaliseAction,-     -- * Operations on actions-     mkAction,-     mkDAction,-     append,-     actionLookup,-     action,-     actionEquiv,-     -- * Destination sets-     destinationSet,-     destinationEquiv,-     -- * Identity-     fsmIdentity,-     -- * Determinism-     isDAction,-     isDFSM+module Data.FsmActions (+    -- * Data types+    State,+    DestinationSet(..),+    Action(..),+    FSM(..),+    Word(..),+    -- * Simple FSM operations+    states,+    alphabet,+    fsmAction,+    -- * Normalisation+    normalise,+    normaliseAction,+    -- * Operations on actions+    mkAction,+    mkDAction,+    append,+    actionLookup,+    action,+    actionEquiv,+    -- * Destination sets+    destinationSet,+    destinationEquiv,+    -- * Identity+    fsmIdentity,+    identity,+    -- * Determinism+    isDAction,+    isDFSM ) where -import Control.Arrow (second) import Control.Monad-import qualified Data.List as L import qualified Data.Map as M+import qualified Data.List as L +--import Data.FsmActions.FGL+ -- | States are integers, counting from zero. type State = Int -- Could be parametric (as in HaLeX), but for now, YAGNI.@@ -96,53 +95,8 @@ fsmAction :: Ord sy => sy -> FSM sy -> Maybe Action fsmAction sy = M.lookup sy . unFSM --- | An 'FSM' is well-formed if all its actions are the same length,--- and none of its actions contain destinations which are out of--- range.-data WellFormed sy-    -- | 'FSM' is well-formed.  (Carries an empty list: this is a slight-    -- wart, as no cargo is necessary; unfortunately, fixing that-    -- would require use of a GADT here, which seems excessive.)-    = WellFormed [sy]-    -- | Lengths of Actions in the 'FSM' don't all match.  Carries a-    -- sorted list of (symbol, 'Action' length) pairs, one for every-    -- symbol in the alphabet of the 'FSM'.-    | BadLengths [(sy, Int)]-    -- | Some 'Action's contain out-of-range (negative or too-high)-    -- destinations.  Carries a sorted list of all such actions and-    -- their corresponding symbols.-    | BadActions [(sy, Action)]-      deriving (Eq, Show) --- | Check if an 'FSM' is well-formed or not.-isWellFormed :: Ord sy => FSM sy -> WellFormed sy-isWellFormed fsm =-    if not $ allSame $ L.map snd actionLengths-    then BadLengths (L.sort actionLengths)-    else if not $ M.null badParts-         then BadActions (L.sort $ M.toList badParts)-         else WellFormed []-    where -- All (symbol, Action length) pairs in FSM.-          actionLengths = L.map (second aLength) (M.toList $ unFSM fsm)-          -- Submap containing only Actions with bad destinations.-          badParts = M.filter isBad $ unFSM fsm-          -- Check if an Action has any bad destinations.-          isBad a = any badDest (flatten a)-              where -- Flatten lists of destination states in an Action.-                    flatten (Action xs) = L.concat $ map destinations xs-          -- Check if a destination is bad (negative or too high).-          badDest x = (x<0) || (x >= (length $ states fsm))-          -- Compute the length of an action-          aLength (Action xs) = length xs --- Check if every element of a list is identical.-allSame :: Eq a => [a] -> Bool-allSame [] = True-allSame [_] = True-allSame (x:y:xs) = (x == y) && allSame (y:xs)--- -- | Build an action given a nested list of destination states. mkAction :: [[State]] -> Action mkAction = Action . map DestinationSet@@ -192,7 +146,7 @@ -- be Nothing. destinationSet :: Ord sy => FSM sy -> State -> Word sy -> Maybe DestinationSet destinationSet fsm src word =-    if (src >= 0) && (src < (length $ states fsm))+    if (src >= 0) && (src < length (states fsm))     then case (action fsm word) of Just (Action ds) -> Just $ ds !! src                                    _ -> Nothing                                    @@ -208,8 +162,11 @@  -- | Compute the identity action for a given FSM. fsmIdentity :: FSM sy -> Action-fsmIdentity = Action . map (\x -> DestinationSet [x]) . states+fsmIdentity = identity . length . states +-- | Compute the identity action for a given number of states+identity :: Int -> Action+identity n = Action $ map (\x -> DestinationSet [x]) [0..n-1]   -- | Test if an 'Action' is deterministic or not.
Data/FsmActions/ActionMatrix.hs view
@@ -1,37 +1,39 @@ {- |  Serialisation/deserialisation of 'Data.FsmActions.FSM's and-'Data.FsmActions.Action's as adjacency matrices.+'Data.FsmActions.Action's as binary adjacency matrices. -An 'Data.FsmActions.Action' may be represented as an adjacency matrix of-0s and 1s.  The rows and columns of the matrix correspond to states of-the 'Data.FA.Core.FSM': a 1 in a cell indicates that the+An 'Data.FsmActions.Action' may be represented as an adjacency matrix+of 0s and 1s.  The rows and columns of the matrix correspond to states+of an 'Data.FsmActions.FSM': a 1 in a cell indicates that the 'Data.FsmActions.Action' causes a transition from the \'row\' state to the \'column\' state.  If any of the rows in the matrix contain more-than one 1, the corresponding 'Data.FsmActions.Action' is a-nondeterministic: an 'Data.FsmActions.NAction'.+than one 1, the corresponding 'Data.FsmActions.Action' and+'Data.FsmActions.FSM' will be nondeterministic.  -} --- TODO: tests, working properly for empty strings, single element--- rows, etc.- module Data.FsmActions.ActionMatrix (-  readFSMFromMxFiles,-  readAdjMxFromFile,-  readAdjMxFromString+    -- * Input+    parseFsmActionMxFiles,+    parseActionMxFile,+    parseActionMx,+    -- * Output+    printActionMx ) where  import Control.Monad.Error-import qualified Data.ByteString.Char8 as B import qualified Data.List as L import qualified Data.Map as M import Data.Maybe (mapMaybe) import System.IO.Error (mkIOError, userErrorType)+import qualified Text.ParserCombinators.Parsec as P+import Text.PrettyPrint.HughesPJ  import Data.FsmActions import Data.FsmActions.Error + -- | This module's internal represenation of adjacency matrices is as -- nested lists of booleans.  These are only ever used as intermediate -- data structures, and should not be generated or manipulated@@ -39,85 +41,99 @@ -- 'Data.FsmActions.Action' type.  If you want serialised matrices for -- storage or transmission, convert them to strings of 0s and 1s using -- the functions in this module.-type AdjacencyMatrix = [AdjacencyMatrixRow]-type AdjacencyMatrixRow = [AdjacencyMatrixCell]-type AdjacencyMatrixCell = Bool+type ActionMatrix = [ActionMatrixRow]+type ActionMatrixRow = [Bool]  -- | Given a list of (symbol, path) pairs, compute an--- 'Data.FsmActions.FSM' whose actions are read from matrices in each of--- the paths using 'readAdjMxFromFile' (and associated with their--- corresponding symbols).+-- 'Data.FsmActions.FSM' whose actions are read from action matrices+-- in the specified paths, associated with their corresponding+-- symbols. -- -- Note that if the same symbol appears multiple times, only one -- instance will appear in the 'Data.FsmActions.FSM'; the choice of which -- appears is not defined.---readFSMFromMxFiles :: Ord sy => [(sy, String)] -> IO (FSM sy)-readFSMFromMxFiles :: Ord sy => [(sy, String)] -> IO (FSM sy)-readFSMFromMxFiles xs =-    liftM (FSM . M.fromList) $ mapM (liftMSnd readAdjMxFromFile) xs+parseFsmActionMxFiles :: Ord sy => [(sy, FilePath)] -> IO (FSM sy)+parseFsmActionMxFiles xs =+    liftM (FSM . M.fromList) $ mapM (liftMSnd parseActionMxFile) xs         where liftMSnd :: Monad m => (a -> m b) -> (c, a) -> m (c, b)               liftMSnd f (x, y) = f y >>= \z -> return (x, z) --- | Read an action matrix from a specified file; uses--- 'readAdjMxFromString' to interpret the file contents.-readAdjMxFromFile :: String -> IO Action-readAdjMxFromFile path =-    do contents <- B.readFile path-       let act = readAdjMxFromString contents-       -- Catch error and act appropriately+-- | Read an action matrix from a specified file, and parse it into an+-- 'Data.FsmActions.Action'.+parseActionMxFile :: FilePath -> IO Action+parseActionMxFile path =+    do contents <- readFile path+       let act = parseActionMx contents        case act of          Right a -> return a          Left e -> throwError (mkIOError userErrorType (show e)                                          Nothing (Just path)) -+-- | Parse an action matrix string, and turn it into an+-- 'Data.FsmActions.Action'.+parseActionMx :: String -> ReadMxMonad Action+parseActionMx actionString =+    case P.parse actionMxParser "" actionString of+      Right mx -> interpretActionMx mx+      Left err -> throwError (MxError "Action matrix parse error" (show err)) --- | Given a bytestring we expect to contain a serialisation of an--- adjacency matrix, compute the corresponding 'Data.FsmActions.Action'.+-- | Parse an action matrix from a string. ----- The serialisation format for an 'Data.FsmActions.Action' on an--- /n/-state 'Data.FsmActions.FSM' is as follows: there are /n/--- (newline-separated) lines, each containing /n/ (comma-separated) 0s--- or 1s.  No other characters are allowed (not even whitespace), and--- it is an error for any of the rows to contain anything other than--- /n/ cells.  (Note that /n/ is not specified, but inferred from the--- number of lines in the string).-readAdjMxFromString :: B.ByteString -> ReadMxMonad Action-readAdjMxFromString s = splitMxString s >>= parseActionMatrix---- | Turn a string into an adjacency matrix.-splitMxString :: B.ByteString -> ReadMxMonad AdjacencyMatrix-splitMxString = mapM readMxRow . B.lines--readMxRow :: B.ByteString -> ReadMxMonad AdjacencyMatrixRow-readMxRow = mapM readMxCell . B.split ','--readMxCell :: B.ByteString -> ReadMxMonad AdjacencyMatrixCell-readMxCell cell =-    if cell == B.singleton '0'-    then return False-    else if cell == B.singleton '1'-         then return True-         else throwError (MxError "Bad cell in matrix string" (show cell))--+-- The string being parsed should contain newline-separated rows,+-- where each row contains comma-separated cells, where each cell is a+-- 0 or a 1.  Trailing newlines are ignored.+actionMxParser :: P.Parser ActionMatrix+actionMxParser = do rows <- parseRow `P.sepEndBy1` P.char '\n'+                    P.skipMany $ P.char '\n' -- Ignore any trailing newlines+                    P.eof+                    return rows+    where parseRow :: P.Parser [Bool]+          parseRow = parseCell `P.sepBy1` P.char ','+          parseCell :: P.Parser Bool+          parseCell = P.choice [ do P.char '0'+                                    return False+                               , do P.char '1'+                                    return True+                               ] --- | Given an 'AdjacencyMatrix', compute the corresponding+-- | Given an 'ActionMatrix', compute the corresponding -- 'Data.FsmActions.Action'.-parseActionMatrix :: AdjacencyMatrix -> ReadMxMonad Action-parseActionMatrix rows =+interpretActionMx :: ActionMatrix -> ReadMxMonad Action+interpretActionMx rows =     if all (== length transitions) rowLengths -- check matrix is square     then return $ normaliseAction $ mkAction transitions     else throwError (MxError "action matrix is not square (see row lengths)"                              (show rowLengths))-    where transitions = L.map parseActionMatrixRow rows+    where transitions = L.map parseActionMxRow rows           rowLengths = L.map length rows --- | Given an 'AdjacencyMatrixRow', compute the list of indices of--- cells in the row which are set (i.e. which represent transitions).-parseActionMatrixRow :: AdjacencyMatrixRow -> [Int]-parseActionMatrixRow xs = mapMaybe isSet (withIdxs xs)+-- | Given an 'ActionMatrixRow', compute the list of indices of cells+-- in the row which are set (i.e. which represent transitions).+parseActionMxRow :: ActionMatrixRow -> [Int]+parseActionMxRow xs = mapMaybe isSet (withIdxs xs)     where -- | Zip the cells of a list together with their indices.           withIdxs ys = zip ys [0..(length ys-1)]           -- | Iff the cell is set, include its index..           isSet (cell, index) = if cell then Just index else Nothing++-- | Pretty-print an action in action matrix format.+printActionMx :: Action -> String+printActionMx = show . ppActionMx++-- Pretty printer to action matrix format.+ppActionMx :: Action -> Doc+ppActionMx (Action dSets) = vcat $ map mkRow dSets+    where -- Space-separated list of cells+          mkRow :: DestinationSet -> Doc+          mkRow (DestinationSet ds) = commas $ map (isCell ds) stateList+          -- List of states to iterate over+          stateList :: [State]+          stateList = [0..length dSets-1]+          -- Check if a certain cell should be set or not+          isCell :: [State] -> State -> Doc+          isCell dests src = if src `elem` dests then char '1' else char '0'+          -- Separate a list of Docs with commas+          commas :: [Doc] -> Doc+          commas [] = empty+          commas (x:[]) = x+          commas (x:xs) = x <> comma <> commas xs
+ Data/FsmActions/FGL.hs view
@@ -0,0 +1,86 @@+{- |++Interface to fgl graph library (<http://hackage.haskell.org/package/fgl>).++-}++-- Copyright (c) 2009 Andy Gimblett - http://www.cs.swan.ac.uk/~csandy/+-- BSD Licence (see http://www.opensource.org/licenses/bsd-license.php)++module Data.FsmActions.FGL (+    SelfLoops(..),+    fsmToFGL,+    strongCCs,+    weakCCs+) where++import qualified Data.Map as M+import Data.Graph.Inductive.Basic (undir)+import Data.Graph.Inductive.Graph (Graph, mkGraph)+import qualified Data.Graph.Inductive.PatriciaTree as P+import qualified Data.Graph.Inductive.Tree as T +import Data.Graph.Inductive.Query.DFS (scc)++import Data.FsmActions++-- | When converting an 'Data.FsmActions.FSM' into a graph, do we keep+-- all self-loops, or only those which are sources of nondeterminism?+data SelfLoops = Keep | Trim++-- | Turn an FSM into an fgl graph with labelled edges.+fsmToFGL :: FSM sy -> SelfLoops -> T.Gr () sy+-- Note use of T.Gr; this instance of Graph allows multiple edges+-- between the same pair of nodes, which is what we _usually_ (but not+-- always) want.+fsmToFGL = fsmToFGL'++-- Generalised FSM to graph conversion; works with any Graph instance.+fsmToFGL' :: (Graph gr) => FSM sy -> SelfLoops -> gr () sy+fsmToFGL' fsm selfs = mkGraph nodes edges+    where nodes = map (\state -> (state, ())) $ states fsm+          edges = fsmEdges selfs fsm++-- Compute an FSM's labelled edges+fsmEdges :: SelfLoops -> FSM sy -> [(State, State, sy)]+fsmEdges selfs = concatMap (symbolEdges selfs) . M.toList . unFSM++-- Given a symbol, action pair, compute the list of edges with that+-- symbol.+symbolEdges :: SelfLoops -> (sy, Action) -> [(State, State, sy)]+symbolEdges selfs (s, a) =+    concatMap (syStateEdges selfs s) $ zipWithIndex $ destinationSets a++-- Given a symbol, a start state, and a destination set, compute the+-- list of edges leading from that state with that symbol, possibly+-- taking account of a desire to trim deterministic self-loops.+syStateEdges :: SelfLoops -> sy -> (State, DestinationSet) ->+                [(State, State, sy)]+syStateEdges Keep s (src, dSet) = syStateEdges' s (src, dSet)+syStateEdges Trim s (src, dSet) =+    if destinations dSet == [src] then [] else syStateEdges' s (src, dSet)++-- Given a symbol, a start state, and a destination set, compute the+-- list of edges leading from that state with that symbol.+syStateEdges' :: sy -> (State, DestinationSet) -> [(State, State, sy)]+syStateEdges' s (src, dSet) = map (\x -> (src, x, s)) $ destinations dSet++-- Create a zip of a list with its index list.+zipWithIndex :: [a] -> [(Int, a)]+zipWithIndex xs = zip [0..(length xs-1)] xs++++-- | Compute an FSM's strongly-connected components.+strongCCs :: Eq sy => FSM sy -> [[State]]+strongCCs = scc . fsmToPatriciaTree Trim++-- | Compute an FSM's weakly-connected components.+weakCCs :: Eq sy => FSM sy -> [[State]]+weakCCs = scc . undir . fsmToPatriciaTree Trim++-- | The PatriciaTree instance of Graph is faster, but not generally+-- useful to us because it doesn't allow multiple edges between the+-- same pair of nodes.  For SCC checks, however, that doesn't matter,+-- so we use it.+fsmToPatriciaTree :: SelfLoops -> FSM sy -> P.Gr () sy+fsmToPatriciaTree = flip fsmToFGL'
Data/FsmActions/FsmMatrix.hs view
@@ -14,12 +14,13 @@  -} -module Data.FsmActions.FsmMatrix-    (parseFsmFile,-     parseFsmString,-     printFsmMatrix,-    )       -where+module Data.FsmActions.FsmMatrix (+    -- * Input+    parseFsmMxFile,+    parseFsmMx,+    -- * Output+    printFsmMx,+) where  import Control.Monad.Error import Data.Char (isSpace)@@ -31,13 +32,14 @@  import Data.FsmActions import Data.FsmActions.Error+import Data.FsmActions.WellFormed  -- | Parse an FsmMatrix-formatted FSM held in a file, by reading the -- file and calling 'parseFsmString'.-parseFsmFile :: FilePath -> IO (FSM String)-parseFsmFile path =+parseFsmMxFile :: FilePath -> IO (FSM String)+parseFsmMxFile path =     do contents <- readFile path-       let result = parseFsmString contents+       let result = parseFsmMx contents        case result of          Right fsm -> return fsm          Left e -> throwError (mkIOError userErrorType (show e)@@ -45,28 +47,35 @@  -- | Parse an FsmMatrix-formatted FSM held in a string.  Includes -- normalisation and well-formedness checks.-parseFsmString :: String -> ReadMxMonad (FSM String)-parseFsmString fsmString =-    case P.parse fsmParser "" fsmString of+parseFsmMx :: String -> ReadMxMonad (FSM String)+parseFsmMx fsmString =+    case P.parse fsmMatrixParser "" fsmString of       Right parts ->-          do fsm <- interpretFsm parts+          do fsm <- interpretFsmMx parts              case (isWellFormed fsm) of-               WellFormed _ -> return fsm-               err -> throwError (MxError "Fsm matrix ill-formed" (show err))+               WellFormed -> return fsm+               Disconnected wccs ->+                   throwError (MxError "FSM disconnected" (show wccs))+               err -> throwError (MxError "FSM matrix ill-formed" (show err))       Left err ->-          throwError (MxError "Fsm matrix parse error" (show err))+          throwError (MxError "FSM matrix parse error" (show err)) +-- TODO: there are well-formedness checks here, but not when reading+-- in from action matrices.  Generalise!  Either remove the checks+-- here, or factor them out into an handy "run this after input"+-- function.+ -- FsmMatrix-format parser.-fsmParser :: P.Parser ([String], [[[Int]]])-fsmParser = do actions <- actionName `P.sepEndBy` nonEOLSpace-               P.char '\n'-               transitionRows <- transitionRow `P.sepEndBy` P.char '\n'-               P.many (P.satisfy isSpace) -- Parse trailing whitespace.-               P.eof-               return (actions, transitionRows)+fsmMatrixParser :: P.Parser ([String], [[[Int]]])+fsmMatrixParser = do actions <- actionName `P.sepEndBy` nonEOLSpace+                     P.char '\n'+                     transitionRows <- transitionRow `P.sepEndBy` P.char '\n'+                     P.many (P.satisfy isSpace) -- Parse trailing whitespace.+                     P.eof+                     return (actions, transitionRows)     where -- An action name is a string of non-whitespace characters.           actionName :: P.Parser String-          actionName = P.many1 (P.satisfy (\c -> not $ isSpace c))+          actionName = P.many1 (P.satisfy (not . isSpace))           -- A row of transitions is a space-separated line of transitions.           transitionRow :: P.Parser [[Int]]           transitionRow = transition `P.sepEndBy1` nonEOLSpace@@ -75,27 +84,27 @@           transition = state `P.sepBy1` P.char ','           -- A state is a natural number.           state :: P.Parser Int-          state = P.many1 P.digit >>= (\c -> return $ read c)+          state = liftM read (P.many1 P.digit)           -- Parse whitespace that isn't an end of line.           nonEOLSpace :: P.Parser String           nonEOLSpace = P.many1 (P.satisfy (\c -> isSpace c && c /= '\n')) --- Turn some  FsmMatrix-formatted data into an (normalised) FSM.-interpretFsm :: ([String], [[[Int]]]) -> ReadMxMonad (FSM String)-interpretFsm (actionNames, stateLines) = -    case (all (== (length actionNames)) lineLengths) of-      True -> return $ normalise $ FSM $ M.fromList $ zip actionNames actions-      False -> throwError (MxError "FSM matrix ill-formed" (show lineLengths))+-- Turn some FsmMatrix-formatted data into an (normalised) FSM.+interpretFsmMx :: ([String], [[[Int]]]) -> ReadMxMonad (FSM String)+interpretFsmMx (actionNames, stateLines) = +    if all (== (length actionNames)) lineLengths+      then return $ normalise $ FSM $ M.fromList $ zip actionNames actions+      else throwError (MxError "FSM matrix ill-formed" (show lineLengths))     where actions = map mkAction $ L.transpose stateLines           lineLengths = L.map length stateLines  -- | Pretty-print a string FSM in FsmMatrix format.-printFsmMatrix :: FSM String -> String-printFsmMatrix = show . ppFsmMatrix+printFsmMx :: FSM String -> String+printFsmMx = show . ppFsmMx  -- Pretty printer to FsmMatrix format (building Doc not String).-ppFsmMatrix :: FSM String -> Doc-ppFsmMatrix fsm = actionRow $$ transitionRows+ppFsmMx :: FSM String -> Doc+ppFsmMx fsm = actionRow $$ transitionRows     where -- Space-separated list of action names.           actionRow :: Doc           actionRow = hsep $ map (text . fst) asList
+ Data/FsmActions/GraphViz.hs view
@@ -0,0 +1,29 @@+{- |++GraphViz (dot) rendering using the graphviz library.++-}++-- Copyright (c) 2009 Andy Gimblett - http://www.cs.swan.ac.uk/~csandy/+-- BSD Licence (see http://www.opensource.org/licenses/bsd-license.php)++module Data.FsmActions.GraphViz (+    fsmToDot+) where++import Data.GraphViz+import Data.Graph.Inductive.Graph (Graph)++import Data.FsmActions+import Data.FsmActions.FGL++-- | Turn an FSM into a 'Data.GraphViz.DotGraph', trimming any+-- self-loops which aren't sources of nondeterminism.+fsmToDot :: (Ord sy, Show sy) => FSM sy -> DotGraph+fsmToDot = fglDot . flip fsmToFGL Trim++-- Turn an FGL into a DotGraph with labelled edges.+fglDot :: (Ord b, Show b, Graph gr) => gr a b -> DotGraph+fglDot g = graphToDot g [] nodeFn edgeFn+    where nodeFn _ = []+          edgeFn (_, _, label) = [Label $ Left $ show label]
+ Data/FsmActions/WellFormed.hs view
@@ -0,0 +1,75 @@+{- |++Well-formedness checks for finite state machines.++-}++-- Copyright (c) 2009 Andy Gimblett - http://www.cs.swan.ac.uk/~csandy/+-- BSD Licence (see http://www.opensource.org/licenses/bsd-license.php)++module Data.FsmActions.WellFormed (+    WellFormed(..),+    isWellFormed,+) where++import Control.Arrow (second)+{-+-- We use a PatriciaTree because we care about speed, and it doesn't+-- matter if duplicate edges are lost when checked for SCCs.+import Data.Graph.Inductive.PatriciaTree (Gr)+-}+import qualified Data.Map as M+import qualified Data.List as L++import Data.FsmActions+import Data.FsmActions.FGL++-- | An 'FSM' is well-formed if all its actions are the same length,+-- none of its actions contain destinations which are out of range,+-- and it is not disjoint.+data WellFormed sy+    -- | Lengths of Actions in the 'FSM' don't all match.  Carries a+    -- sorted list of (symbol, 'Action' length) pairs, one for every+    -- symbol in the alphabet of the 'FSM'.+    = BadLengths [(sy, Int)]+    -- | Some 'Action's contain out-of-range (negative or too-high)+    -- destinations.  Carries a sorted list of all such actions and+    -- their corresponding symbols.+    | BadActions [(sy, Action)]+    -- | The FSM is disconnected, i.e. not even weakly-connected.+    -- Carries a list of its weakly-connected components (each is a+    -- list of 'State's).+    | Disconnected [[State]]+    -- | Well-formed.+    | WellFormed+      deriving (Eq, Show)++-- | Check if an 'FSM' is well-formed or not.+isWellFormed :: Ord sy => FSM sy -> WellFormed sy+isWellFormed fsm+    | not $ allSame $ L.map snd actionLengths =+        BadLengths (L.sort actionLengths)+    | not $ M.null badParts = BadActions (L.sort $ M.toList badParts)+    | length wccs /= 1 = Disconnected wccs+    | otherwise = WellFormed+    where -- All (symbol, Action length) pairs in FSM.+          actionLengths = L.map (second aLength) (M.toList $ unFSM fsm)+          -- Submap containing only Actions with bad destinations.+          badParts = M.filter isBad $ unFSM fsm+          -- Check if an Action has any bad destinations.+          isBad a = any badDest (flatten a)+              where -- Flatten lists of destination states in an Action.+                    flatten (Action xs) = L.concat $ map destinations xs+          -- Check if a destination is bad (negative or too high).+          badDest x = (x<0) || (x >= length  (states fsm))+          -- Compute the length of an action+          aLength (Action xs) = length xs+          -- Compute the FSM's undirected strongly-connected+          -- components.+          wccs = weakCCs fsm++-- Check if every element of a list is identical.+allSame :: Eq a => [a] -> Bool+allSame [] = True+allSame [_] = True+allSame (x:y:xs) = (x == y) && allSame (y:xs)
doc/fsmActions.pdf view

binary file changed (120770 → 139729 bytes)

fsmActions.cabal view
@@ -1,5 +1,6 @@ Name:                   fsmActions-Version:                0.1+Version:                0.2.0+Stability:              Alpha Synopsis:               Finite state machines and FSM actions Description:     This is a library for representing and manipulating finite state@@ -10,17 +11,27 @@ Category:               Data License:                BSD3 License-file:           LICENSE+Homepage:               http://projects.haskell.org/fsmActions/+Copyright:              Andy Gimblett <haskell@gimbo.org.uk> Author:                 Andy Gimblett <haskell@gimbo.org.uk> Maintainer:             Andy Gimblett <haskell@gimbo.org.uk> Build-Type:             Simple-Cabal-Version:          >=1.2+Cabal-Version:          >=1.6 Extra-source-files:     README                         doc/fsmActions.pdf +Source-Repository head+    Type:         darcs+    Location:     http://code.haskell.org/fsmActions+ Library-  Build-Depends:        base >= 3 && < 5, bytestring, containers, mtl, parsec, pretty+  Build-Depends: base >= 3 && < 5, containers, fgl, graphviz+                        >=2999.0.0.0, mtl, parsec, pretty   Exposed-modules:      Data.FsmActions,                         Data.FsmActions.ActionMatrix,                         Data.FsmActions.Error,-                        Data.FsmActions.FsmMatrix+                        Data.FsmActions.FGL,+                        Data.FsmActions.FsmMatrix,+                        Data.FsmActions.GraphViz,+                        Data.FsmActions.WellFormed   ghc-options:          -fwarn-tabs -Wall