{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings, FlexibleContexts #-} module GraphViz where import Control.Applicative ((<$>), Applicative(..), Alternative(..)) import Control.Monad import Control.Monad.State import Control.Monad.Error import Control.Concurrent (forkIO) import Data.List (intercalate, intersperse, nub) import Data.Char (ord) import qualified Data.Graph.Inductive as Graph -- import Data.HashTable (hashString) import Data.GraphViz (runGraphvizCommand, runGraphvizCanvas', GraphvizCanvas(Xlib), graphElemsToDot, GraphvizOutput(..), toLabel, nonClusteredParams, GraphvizParams(..), GlobalAttributes(GraphAttrs), GraphvizCommand(Dot)) import Data.GraphViz.Attributes.Colors (Color(X11Color), WeightedColor(..)) import Data.GraphViz.Attributes.Colors.X11 (X11Color(..)) import Data.GraphViz.Attributes.HTML import Data.GraphViz.Attributes.Complete (Attribute(Ordering,Label,Shape,Color,Width,Regular), Shape(BoxShape), Label(HtmlLabel), Order(OutEdges)) import qualified Data.Text.Lazy import Language.Prolog htmlStr = Str . Data.Text.Lazy.pack -- Graphical output of derivation tree resolveTree p q = preview =<< execGraphGenT (resolve_ p q) resolveTreeToFile path p q = do graph <- execGraphGenT (resolve_ p q) runGraphvizCommand Dot (toDot [] graph) Png path preview g = ign $ forkIO (ign $ runGraphvizCanvas' (toDot [] g) Xlib) where ign = (>> return ()) toDot attrs g = graphElemsToDot params (labNodes g) (labEdges g) where params = nonClusteredParams { fmtNode = \ (_,l) -> formatNode l , fmtEdge = \ (_, _, l) -> formatEdge l , globalAttributes = [GraphAttrs (Ordering OutEdges : attrs)] -- child nodes are drawn in edge-order , isDirected = True } type Graph = Gr NodeLabel EdgeLabel type NodeLabel = ((ProtoBranch, Branch), [Branch], CutFlag) type EdgeLabel = (ProtoBranch, Branch) type Branch = (Path, [(VariableName, Term)], [Term]) type Path = [Integer] type ProtoBranch = Branch data Gr a b = Gr [(Int,a)] [(Int, Int, b)] deriving Show empty = Gr [] [] insEdge edge (Gr ns es) = Gr ns (es ++ [edge]) insNode node (Gr ns es) = Gr (ns ++ [node]) es gelem n (Gr ns es) = n `elem` map fst ns relabelNode f node (Gr ns es) = Gr (map (\(n,l) -> (n,if n == node then f l else l)) ns) es labNodes (Gr ns _) = ns labEdges (Gr _ es) = es newtype GraphGenT m a = GraphGenT (StateT Graph m a) deriving (Monad, Functor, MonadFix, MonadPlus, Applicative, MonadError e, MonadState Graph, MonadTrans, Alternative) runGraphGenT (GraphGenT st) = runStateT st GraphViz.empty execGraphGenT (GraphGenT st) = execStateT st GraphViz.empty instance Monad m => MonadGraphGen (GraphGenT m) where createConnections currentBranch@(path,_,_) protoBranches branches = do let current = hash path -- Ensure node is present (FIXME Why do we do this?) let protoBranch = error "Unknown protobranch accessed during graph generation" let label = ((protoBranch, currentBranch), branches, WasNotCut) modify $ \graph -> if gelem current graph then relabelNode (const label) current graph else insNode (current, label) graph -- Create nodes and edges to them forM_ (zip protoBranches branches) $ \x@(_,(pathOfTarget,_,_))-> do let new = hash pathOfTarget modify $ insNode (new, (x, [], WasNotCut)) modify $ insEdge (current, new, x) markSolution usf = do return () markCutBranches stackPrefix = do forM_ stackPrefix $ \((path_,u_,gs_),alts_) -> do forM_ alts_ $ \(pathOfChild,_,_) -> do let child = hash pathOfChild modifyLabel child $ \(t,b,_) -> (t,b,WasCut) data CutFlag = WasNotCut | WasCut formatNode :: NodeLabel -> [Data.GraphViz.Attributes.Complete.Attribute] formatNode ((_,(_,u',[])), _, WasNotCut) = -- Success Shape BoxShape : case filterOriginal u' of [] -> [ toLabel ("" :: String) , Data.GraphViz.Attributes.Complete.Width 0.2 , Regular True , Data.GraphViz.Attributes.Complete.Color [(WC (X11Color Green) Nothing)] ] uf -> [ toLabel $ colorize Green $ htmlUnifier uf , Data.GraphViz.Attributes.Complete.Color [(WC (X11Color Green) Nothing)] ] formatNode ((_,(_,_,gs')), [], WasNotCut) = -- Failure [ toLabel $ colorize Red [htmlGoals gs'] , Data.GraphViz.Attributes.Complete.Color [(WC (X11Color Red) Nothing)] ] formatNode ((_,(_,_,gs')), _, WasNotCut) = [ toLabel [htmlGoals gs'] ] formatNode ((_,(_,u',[])), _, WasCut) = -- Cut with Succees Shape BoxShape : case filterOriginal u' of [] -> [ toLabel ("" :: String) , Data.GraphViz.Attributes.Complete.Width 0.2 , Regular True , Data.GraphViz.Attributes.Complete.Color [(WC (X11Color Gray) Nothing)] ] uf -> [ toLabel $ colorize Gray $ htmlUnifier uf , Data.GraphViz.Attributes.Complete.Color [(WC (X11Color Gray) Nothing)] ] formatNode ((_,(_,_,gs')), _, WasCut) = -- Cut [ toLabel $ colorize Gray [htmlGoals gs'] , Data.GraphViz.Attributes.Complete.Color [(WC (X11Color Gray) Nothing)] ] formatEdge :: EdgeLabel -> [Data.GraphViz.Attributes.Complete.Attribute] formatEdge ((_,u ,_),_) = [ toLabel [Font [PointSize 8] $ htmlUnifier $ simplify u] ] simplify = ([] +++) htmlGoals = htmlStr . intercalate "," . map show htmlUnifier [] = [htmlStr " "] htmlUnifier u = intersperse (Newline []) [ htmlStr $ show v ++ " = " ++ show t | (v,t) <- u ] modifyLabel node f = modify $ relabelNode f node colorize color label = [Font [Data.GraphViz.Attributes.HTML.Color (X11Color color)] label] hash :: Path -> Int -- TODO This is a complicated way to hash a list of integers. -- Also, a unique hash value would be nice to avoid collisions. hash = fromEnum . hashString . show filterOriginal = filter $ \(VariableName n _, _) -> n == 0 hashString = fromIntegral . foldr f 0 where f c m = ord c + (m * 128) `rem` 1500007