{- |
Generating and drawing graphs of FSMs.
Includes:
- Interface to fgl graph library
(<http://hackage.haskell.org/package/fgl>).
- Interface to graphviz library for dot output
(<http://hackage.haskell.org/package/graphviz>).
- Home-grown GML (Graph Modelling Language) output.
-}-- Copyright (c) 2009 Andy Gimblett - http://www.cs.swan.ac.uk/~csandy/-- BSD Licence (see http://www.opensource.org/licenses/bsd-license.php)-- We need these declarations for the CleanShow typeclass.{-# LANGUAGE TypeSynonymInstances #-}{-# LANGUAGE UndecidableInstances #-}{-# LANGUAGE FlexibleInstances #-}{-# LANGUAGE OverlappingInstances #-}moduleData.FsmActions.Graph(-- * FGL graph operations.SelfLoops(..),fsmToFGL,strongCCs,weakCCs,-- * Dot and GML format output.CleanShow,fsmToDot,fsmToGML)whereimportData.Graph.Inductive.Basic(undir)importData.Graph.Inductive.Graph(Graph,labEdges,mkGraph)importqualifiedData.Graph.Inductive.PatriciaTreeasPimportqualifiedData.Graph.Inductive.TreeasTimportData.Graph.Inductive.Query.DFS(scc)importData.GraphVizimportText.PrettyPrint.HughesPJimportData.FsmActions-- | When converting an 'Data.FsmActions.FSM' into a graph, do we keep-- all self-loops, or only those which are sources of nondeterminism?dataSelfLoops=Keep|Trim-- | Turn an FSM into an fgl graph with labelled edges.fsmToFGL::FSMsy->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'::(Graphgr)=>FSMsy->SelfLoops->gr()syfsmToFGL'fsmselfs=mkGraphnodesedgeswherenodes=map(\state->(state,()))$statesfsmedges=fsmEdgesselfsfsm-- Compute an FSM's labelled edgesfsmEdges::SelfLoops->FSMsy->[(State,State,sy)]fsmEdgesselfs=concat.fsmMap(symbolEdgesselfs)-- Given a symbol, action pair, compute the list of edges with that-- symbol.symbolEdges::SelfLoops->sy->Action->[(State,State,sy)]symbolEdgesselfss=concatMap(syStateEdgesselfss).zipWithIndex.destinationSets-- 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)]syStateEdgesKeeps(src,dSet)=syStateEdges's(src,dSet)syStateEdgesTrims(src,dSet)=ifdestinationsdSet==[src]then[]elsesyStateEdges'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))$destinationsdSet-- Create a zip of a list with its index list.zipWithIndex::[a]->[(Int,a)]zipWithIndexxs=zip[0..(lengthxs-1)]xs-- | Compute an FSM's strongly-connected components.strongCCs::Eqsy=>FSMsy->[[State]]strongCCs=scc.fsmToPatriciaTreeTrim-- | Compute an FSM's weakly-connected components.weakCCs::Eqsy=>FSMsy->[[State]]weakCCs=scc.undir.fsmToPatriciaTreeTrim-- | 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->FSMsy->P.Gr()syfsmToPatriciaTree=flipfsmToFGL'-- | Subclass 'Show' so that 'show' calls on 'String's and 'Char's-- don't get quotes inserted.class(Showa)=>CleanShowawherecleanShow::a->StringcleanShow=show-- by default, turn it to a Stringinstance(Showa)=>CleanShowainstanceCleanShowStringwherecleanShow=id-- don't need to do anything for a StringinstanceCleanShowCharwherecleanShowc=cleanShow[c]-- just lift it to String-- | Turn an FSM into a 'Data.GraphViz.DotGraph', trimming any-- self-loops which aren't sources of nondeterminism.fsmToDot::(Ordsy,CleanShowsy)=>FSMsy->DotGraphfsmToDot=fglDot.flipfsmToFGLTrim-- Turn an FGL into a DotGraph with labelled edges.fglDot::(Ordb,CleanShowb,Graphgr)=>grab->DotGraphfglDotg=graphToDotg[]nodeFnedgeFnwherenodeFn_=[]edgeFn(_,_,label)=[Label$StrLabel$cleanShowlabel]-- | Turn an FSM into a GML-formatted graph', trimming any self-loops-- which aren't sources of nondeterminism.fsmToGML::CleanShowsy=>FSMsy->DocfsmToGMLf=text"graph"<+>bracketsbodywherebody=vcat[directed,planar,fNodes,fEdges]directed=text"directed 1"planar=text"IsPlanar 1"fNodes=vcat$mapgmlNode$statesffEdges=vcat$mapgmlEdge$labEdges$fsmToFGLfTrimgmlNode::State->DocgmlNodei=text"node"<+>brackets(vcat[text"id"<+>text(showi),text"label"<+>doubleQuotes(text$showi)])gmlEdge::CleanShowsy=>(State,State,sy)->DocgmlEdge(src,dest,label)=text"edge"<+>brackets(vcat[text"source"<+>text(showsrc),text"target"<+>text(showdest),text"label"<+>doubleQuotes(text$cleanShowlabel)])