| -- |
| -- Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) |
| -- |
| -- SPDX-License-Identifier: BSD-2-Clause |
| -- |
| |
| {-# LANGUAGE FlexibleInstances #-} |
| |
| module CapDL.PrintDot where |
| |
| import CapDL.Model |
| import CapDL.PrintUtils |
| import Prelude () |
| import Prelude.Compat |
| import Text.PrettyPrint |
| import Data.List.Compat |
| import qualified Data.Map as Map |
| |
| indent = 2 |
| |
| type NodeMap = Map.Map ObjID String |
| |
| class (Printing a) => DotPrinting a where |
| dotRange :: a -> Int -> Doc |
| |
| instance DotPrinting Word where |
| dotRange n 1 = num n |
| dotRange n len = num n <> text " - " <> num (n + fromIntegral len - 1) |
| |
| instance DotPrinting [Word] where |
| dotRange ns 1 = num ns |
| dotRange [n] len = dotRange n len |
| dotRange ns len = |
| let n = last ns |
| in num (init ns) <> comma <+> dotRange n len |
| |
| angles :: String -> Doc |
| angles st = text ("<" ++ st ++ ">") |
| |
| getName :: ObjID -> NodeMap -> String |
| getName id names = |
| case Map.lookup id names of |
| Just name -> name |
| Nothing -> error $ "Something weird happened when printing: " ++ showID id |
| |
| getObject :: ObjMap a -> ObjID -> KernelObject a |
| getObject ms id = |
| let Just obj = Map.lookup id ms |
| in obj |
| |
| sameNode :: NodeMap -> ObjID -> ObjID -> Bool |
| sameNode names first second = getName first names == getName second names |
| |
| dotObjParams :: KernelObject a -> Doc |
| dotObjParams obj = text " \\n " <> prettyObjParams obj |
| |
| hasCover :: ObjID -> CoverMap -> Bool |
| hasCover ut covers = |
| case Map.lookup ut covers of |
| Nothing -> False |
| Just cover -> not $ null cover |
| |
| dotCovered :: NodeMap -> ObjMap a -> CoverMap -> [ObjID] -> [Doc] |
| dotCovered _ _ _ [] = [] |
| dotCovered names ms covers list@(id:_) |
| | hasCover id covers = |
| dotUntyped names ms covers [] id [Nothing] obj: |
| dotCovered names ms covers (drop (length same) list) |
| | otherwise = |
| (doubleQuotes.text) (getName id names) <> semi: |
| dotCovered names ms covers (drop (length same) list) |
| where |
| same = takeWhile (sameNode names id) list |
| obj = getObject ms id |
| |
| -- Minor problem if a covered object has a cap to the untyped |
| dotCluster :: NodeMap -> ObjMap a -> CoverMap -> ObjID -> [Maybe Word] -> |
| [ObjID] -> Doc |
| dotCluster names ms covers n _ cover = |
| text "subgraph" <+> (doubleQuotes.text) ("cluster_" ++ getName n names) <+> |
| braces (braces (text "rank = source;" <+> (doubleQuotes.text) (getName n names) |
| <+> text "[style = filled];") <> semi <+> |
| hsep (dotCovered names ms covers cover)) <> semi |
| |
| getCover :: ObjID -> CoverMap -> [ObjID] |
| getCover ut covers = |
| case Map.lookup ut covers of |
| Nothing -> [] |
| Just cover -> cover |
| |
| dotUntyped :: NodeMap -> ObjMap a -> CoverMap -> [ObjID] -> ObjID -> |
| [Maybe Word] -> KernelObject a -> Doc |
| dotUntyped names ms covers cov id@(n, _) range obj@(Untyped {}) = |
| if id `notElem` cov |
| then if null cover |
| then (doubleQuotes.text) (getName id names) <+> brackets (text "label =" |
| <+> doubleQuotes (braces (angles "name" <+> text n |
| <> prettyBrackets range <> dotObjParams obj))) <> semi |
| else dotCluster names ms covers id range cover |
| else empty |
| where cover = getCover id covers |
| dotUntyped _ _ _ _ _ _ _ = empty |
| |
| --Do we want the port to be s |
| dotEdge :: DotPrinting a => NodeMap -> a -> Cap -> ObjID -> Doc |
| dotEdge names slot cap n = |
| (doubleQuotes.text) (getName n names) <> text (":\"t" ++ show slot ++ "\"") |
| <> text ":s ->" <+> (doubleQuotes.text) (getName (objID cap) names) |
| <> text ":Object" <> semi |
| |
| sameHeadNode :: NodeMap -> (a, Cap) -> (a, Cap) -> Bool |
| sameHeadNode names (_, first) (_, second) = |
| sameNode names (objID first) (objID second) |
| |
| dotEdges :: DotPrinting a => NodeMap -> [(a, Cap)] -> a -> ObjID -> Doc |
| dotEdges _ [] _ _ = empty |
| dotEdges names list@(first@(_, cap):_) slot n = |
| dotEdge names slot cap n $+$ dotEdges names others slot n |
| where (_,others) = partition (sameHeadNode names first) list |
| |
| dotEdgesList :: DotPrinting a => NodeMap -> [(a, Cap)] -> ObjID -> [Doc] |
| dotEdgesList _ [] _ = [] |
| dotEdgesList names list@((slot, cap):xs) name = |
| if hasObjID cap |
| then dotEdges names sameGroup slot name: |
| dotEdgesList names (drop (length sameGroup) list) name |
| else dotEdgesList names xs name |
| where sameGroup = sameArray list |
| |
| dotTop :: DotPrinting a => a -> Int -> Doc |
| dotTop n len = angles ("a" ++ show n) <+> dotRange n len |
| |
| dotBot :: DotPrinting a => a -> Cap -> [Maybe Word] -> Doc |
| dotBot n cap range = angles ("t" ++ show n) <+> |
| prettyBrackets range <+> maybeCapParams cap |
| |
| dotSlot :: DotPrinting a => (a, Cap) -> [Maybe Word] -> Doc |
| dotSlot (n, cap) range = text "|" <> |
| braces (dotTop n (length range) <+> text "|" <+> dotBot n cap range) |
| |
| dotSlotsRange :: DotPrinting a => [(a, Cap)] -> Doc |
| dotSlotsRange [] = error "dotSlotsRange: empty" |
| dotSlotsRange list@(x:_) = |
| dotSlot x (map (snd.objID.snd) list) |
| |
| dotSlotsList :: DotPrinting a => [(a, Cap)] -> [Doc] |
| dotSlotsList [] = [] |
| dotSlotsList list@(first@(_, cap):xs) = |
| if hasObjID cap |
| then dotSlotsRange sameGroup:dotSlotsList (drop (length sameGroup) list) |
| else (dotSlot first [Nothing]):(dotSlotsList xs) |
| where sameGroup = sameArray list |
| |
| dotNodeHead :: ObjID -> KernelObject a -> [Maybe Word] -> Doc |
| dotNodeHead (name, _) obj range = |
| text "label" <+> equals <+> text "\"" <> |
| braces (angles "Object" <+> text name <> prettyBrackets range |
| <> dotObjParams obj) |
| |
| dotNode :: DotPrinting a => NodeMap -> ObjMap a -> CoverMap -> [ObjID] |
| -> (ObjID, KernelObject a) -> [Maybe Word] -> Doc |
| dotNode names ms covers cov (n, obj) range = |
| let xs = (if hasSlots obj then Map.toList $ slots obj else []) |
| in nest indent ((doubleQuotes.text) (getName n names) <+> |
| brackets (dotNodeHead n obj range <> hcat (dotSlotsList xs) |
| <> text "\"") <> semi $+$ nest indent (vcat (dotEdgesList names xs n) |
| $+$ if hasCover n covers |
| then dotUntyped names ms covers cov n range obj |
| else empty)) |
| |
| dotNodesGroup :: DotPrinting a => NodeMap -> ObjMap a -> CoverMap -> [ObjID] |
| -> [(ObjID, KernelObject a)] -> Doc |
| dotNodesGroup names ms covers cov list = |
| dotNode names ms covers cov (head list) (map (snd.fst) list) |
| |
| dotNodesList :: DotPrinting a => NodeMap -> ObjMap a -> CoverMap -> [ObjID] |
| -> [(ObjID, KernelObject a)] -> [Doc] |
| dotNodesList _ _ _ _ [] = [] |
| dotNodesList names ms covers cov list@(first:_) = |
| dotNodesGroup names ms covers cov sameCaps: |
| dotNodesList names ms covers cov otherCaps |
| where (sameCaps, otherCaps) = |
| partition (\tuple -> sameNode names (fst first) (fst tuple)) list |
| |
| dotNodes :: DotPrinting a => NodeMap -> ObjMap a -> CoverMap -> [ObjID] |
| -> [(ObjID, KernelObject a)] -> Doc |
| dotNodes names ms covers covered list = |
| (vcat (dotNodesList names ms covers covered list)) $+$ text "" |
| |
| getCovered :: ObjMap a -> CoverMap -> [ObjID] -> [ObjID] |
| getCovered _ _ [] = [] |
| getCovered ms covers (x:xs) |
| | hasCover x covers = x : getCovered ms covers xs |
| | otherwise = getCovered ms covers xs |
| |
| getCovUntyped :: ObjMap a -> CoverMap -> [ObjID] |
| getCovUntyped ms covers = |
| concat $ Map.elems $ |
| Map.map (\cover -> getCovered ms covers cover) covers |
| |
| initCovNamesGroup :: NodeMap -> [ObjID] -> NodeMap |
| initCovNamesGroup names list = |
| foldl (\map id -> Map.insert id ("cov_" ++ showID (head list)) map) |
| names list |
| |
| initCovNamesCovered :: NodeMap -> [ObjID] -> NodeMap |
| initCovNamesCovered names cover = |
| foldl initCovNamesGroup names $ groupBy (sameNode names) cover |
| |
| initCovNames :: NodeMap -> CoverMap -> NodeMap |
| initCovNames names covers = |
| foldl initCovNamesCovered names |
| (map snd (Map.toList covers)) |
| |
| initNamesGroup :: NodeMap -> [ObjID] -> NodeMap |
| initNamesGroup names list = |
| foldl (\map id -> Map.insert id (showID (head list)) map) names list |
| |
| initNamesList :: DotPrinting a => NodeMap -> [(ObjID, KernelObject a)] -> NodeMap |
| initNamesList _ [] = Map.empty |
| initNamesList names list@(first:_) = |
| Map.union (initNamesGroup names (map fst sameCaps)) |
| (initNamesList names otherCaps) |
| where (sameCaps, otherCaps) = partition (same first) list |
| |
| initNames :: DotPrinting a => [(ObjID, KernelObject a)] -> NodeMap |
| initNames = initNamesList Map.empty |
| |
| dotLabel :: Arch -> Doc |
| dotLabel arch = text "fontsize = 30; labelloc = top; label = \"arch" |
| <+> prettyArch arch <> text "\";" |
| |
| -- nodesep? ranksep? page? concentrate? minlen? fontsize? |
| dotAttributes :: Doc |
| dotAttributes = text "nodesep = 0.5; ranksep = 3;" |
| $+$ text "node [shape = record]; edge [minlen = 2];" $+$ text "" |
| |
| dotHeader :: String -> Doc |
| dotHeader name = text "digraph" <+> doubleQuotes (text name) |
| <+> lbrace $+$ dotAttributes |
| |
| printDot' :: DotPrinting a => String -> Arch -> ObjMap a -> CoverMap -> Doc |
| printDot' name arch ms covers = |
| let list = (Map.toList ms) |
| names = initNames list |
| names' = initCovNames names covers |
| covered = getCovUntyped ms covers |
| in dotHeader name $+$ |
| dotNodes names' ms covers covered list $+$ |
| dotLabel arch $+$ |
| rbrace |
| |
| printDot :: DotPrinting a => String -> Model a -> Doc |
| printDot name (Model arch ms _ _ untypedCovers) = |
| printDot' name arch ms untypedCovers |