{- | Serialisation/deserialisation of 'Data.FsmActions.FSM's in edge list format. An 'Data.FsmActions.FSM' may be represented textually as list of {source_state->destination_state,label} strings, each of which represents an edge in its directed graph. (This representation is interesting because it's used by Mathematica for graph I/O.) -} module Data.FsmActions.FsmEdges ( -- * I/O loadFsmEdges, saveFsmEdges, -- * Parsing parseFsmEdges, -- * Pretty-printing printFsmEdges ) where import Control.Exception import qualified Data.Graph.Inductive.Graph as G import Data.Graph.Inductive.Tree (Gr) import Data.List import qualified Text.Parsec as P import qualified Text.Parsec.Language as L import Text.Parsec.String import qualified Text.Parsec.Token as T import Text.PrettyPrint.HughesPJ import Data.FsmActions import Data.FsmActions.Error import Data.FsmActions.Graph -- The intermediate result of parsing will be a list of edges. data Edge = Edge Integer Integer String deriving (Eq, Show) -- | Load an 'Data.FsmActions.FSM' from an FsmEdges file. loadFsmEdges :: FilePath -> IO (FSM String) loadFsmEdges path = do contents <- readFile path let result = parseFsmEdges contents case result of Right fsm -> return fsm Left e -> throw $ FsmError (show e) path -- | Save an 'Data.FsmActions.FSM' to an FsmMatrix file. saveFsmEdges :: FSM String -> FilePath -> IO () saveFsmEdges fsm mxPath = do let mx = printFsmEdges fsm writeFile mxPath mx -- | Parse an FsmEdges-formatted FSM held in a string. Includes -- normalisation and well-formedness checks. parseFsmEdges :: String -> ReadFsmMonad (FSM String) parseFsmEdges fsmString = case P.parse fsmEdgesParser "" fsmString of Right edges -> do (fsm, _) <- (fglToFsm . edgesToFGL) edges return fsm Left err -> throw $ FsmError "FSM edges parse error" (show err) -- FsmEdges-format parser. fsmEdgesParser :: Parser [Edge] fsmEdgesParser = T.braces l $ T.commaSep l fsmEdgeParser where fsmEdgeParser :: Parser Edge fsmEdgeParser = T.braces l $ do source <- T.natural l _ <- T.symbol l "->" destination <- T.natural l _ <- T.comma l label <- T.identifier l return $ Edge source destination label l :: T.TokenParser st l = T.makeTokenParser L.emptyDef -- Turn a list of edges into an FGL graph, inferring the node list -- from those mentioned in the edges. edgesToFGL :: [Edge] -> Gr () String edgesToFGL edges = G.mkGraph gNodes gEdges where gEdges = map fst extracted gNodes = sort $ nub $ concatMap snd extracted extracted = map edgeExtract edges -- Turn an FsmEdges Edge into an FGL (LEdge, [LNodes]) pair. edgeExtract :: Edge -> (G.LEdge String, [G.LNode ()]) edgeExtract (Edge s' d' l) = ((s, d, l), [(s, ()), (d, ())]) where s = fromInteger s' d = fromInteger d' -- | Pretty-print a string FSM in FsmMatrix format. printFsmEdges :: FSM String -> String printFsmEdges = show . ppFsmEdges -- Pretty printer to FsmEdges format (building Doc not String). ppFsmEdges :: FSM String -> Doc ppFsmEdges fsm = ppFsmEdges' $ map tweakEdge $ G.labEdges $ fsmToFGL fsm Trim where -- Convert fgl-format Edge to the ones used by this module. tweakEdge :: G.LEdge String -> Edge tweakEdge (s, d, l) = Edge (toInteger s) (toInteger d) l ppFsmEdges' :: [Edge] -> Doc ppFsmEdges' = braces . vcat . punctuate comma . map ppFsmEdge ppFsmEdge :: Edge -> Doc ppFsmEdge (Edge src dest label) = braces $ integer src <> text "->" <> integer dest <> comma <> text label