{- |
Serialisation/deserialisation of 'Data.FsmActions.FSM's as FSM transition
matrices.
A 'Data.FsmActions.FSM' may be represented as an transition matrix whose
rows correspond to states of the FSM, and whose columns correspond to
its possible actions (labels on its transitions). A given cell then
represents the transition(s) from some (row) state under some (column)
action, and contains a comma-separated list of integers: the row
numbers of the destination states. (Of course, for a deterministic
action, there's just one, and no comma.) Rows are numbered from 0 and
increment strictly.
-}moduleData.FsmActions.FsmMatrix(-- * I/OloadFsmMx,saveFsmMx,-- * ParsingparseFsmMx,-- * Pretty-printingprintFsmMx)whereimportControl.ExceptionimportControl.Monad.ErrorimportData.Char(isSpace)importqualifiedData.ListasLimportqualifiedText.ParsecasPimportText.Parsec.StringimportText.PrettyPrint.HughesPJimportData.FsmActionsimportData.FsmActions.ErrorimportData.FsmActions.WellFormed-- | Load an 'Data.FsmActions.FSM' from an FsmMatrix file.loadFsmMx::FilePath->IO(FSMString)loadFsmMxpath=docontents<-readFilepathletresult=parseFsmMxcontentscaseresultofRightfsm->returnfsmLefte->throw$FsmError(showe)path-- | Save an 'Data.FsmActions.FSM' to an FsmMatrix file.saveFsmMx::FSMString->FilePath->IO()saveFsmMxfsmmxPath=doletmx=printFsmMxfsmwriteFilemxPathmx-- | Parse an FsmMatrix-formatted FSM held in a string. Includes-- normalisation and well-formedness checks.parseFsmMx::String->ReadFsmMonad(FSMString)parseFsmMxfsmString=caseP.parsefsmMatrixParser""fsmStringofRightparts->interpretFsmMxparts>>=polishFSMLefterr->throw$FsmError"FSM matrix parse error"(showerr)-- FsmMatrix-format parser.fsmMatrixParser::Parser([String],[[[Int]]])fsmMatrixParser=doactions<-actionName`P.sepEndBy`nonEOLSpace_<-P.char'\n'transitionRows<-transitionRow`P.sepEndBy`P.char'\n'_<-P.many(P.satisfyisSpace)-- Parse trailing whitespace.P.eofreturn(actions,transitionRows)where-- An action name is a string of non-whitespace characters.actionName::ParserStringactionName=P.many1(P.satisfy(not.isSpace))-- A row of transitions is a space-separated line of transitions.transitionRow::Parser[[Int]]transitionRow=transition`P.sepEndBy1`nonEOLSpace-- A transition is a comma-separated list of states (no spaces).transition::Parser[Int]transition=state`P.sepBy1`P.char','-- A state is a natural number.state::ParserIntstate=liftMread(P.many1P.digit)-- Parse whitespace that isn't an end of line.nonEOLSpace::ParserStringnonEOLSpace=P.many1(P.satisfy(\c->isSpacec&&c/='\n'))-- Turn some FsmMatrix-formatted data into an (normalised) FSM.interpretFsmMx::([String],[[[Int]]])->ReadFsmMonad(FSMString)interpretFsmMx(actionNames,stateLines)=ifall(==lengthactionNames)lineLengthsthenreturn$normalise$fromList$zipactionNamesactionselsethrowError(FsmError"FSM matrix ill-formed"(showlineLengths))whereactions=mapmkAction$L.transposestateLineslineLengths=L.maplengthstateLines-- | Pretty-print a string FSM in FsmMatrix format.printFsmMx::FSMString->StringprintFsmMx=show.ppFsmMx-- Pretty printer to FsmMatrix format (building Doc not String).ppFsmMx::FSMString->DocppFsmMxfsm=actionRow$$transitionRowswhere-- Space-separated list of action names.actionRow::Doc--actionRow = hsep $ map (text . fst) asListactionRow=hsep$map(text.fst)$toListfsm-- Newline-separated list of transition rows.transitionRows::DoctransitionRows=vcat$maptransitionRowtransitions-- Space-separated list of transitions.transitionRow::[DestinationSet]->DoctransitionRow=hsep.maptransition-- Comma-separated list of state numbers.transition::DestinationSet->Doctransition=commas.mapint.destinations-- Extract transitions from FSM.transitions::[[DestinationSet]]transitions=L.transpose$map(destinationSets.snd)$toListfsm-- Separate a list of Docs with commascommas::[Doc]->Doccommas[]=emptycommas(x:[])=xcommas(x:xs)=x<>comma<>commasxs