tracetree-0.1.0.1: src/Debug/Trace/Tree/Rose.hs
-- | Additional operations on standard rose trees
module Debug.Trace.Tree.Rose (
-- * Annotating nodes
Depth
, Offset
, Coords(..)
, Metadata(..)
, isFirstChild
, annotate
) where
import Control.Monad.State
import Data.Tree
{-------------------------------------------------------------------------------
Annotating the tree
-------------------------------------------------------------------------------}
-- | Depth of a node in the tree
type Depth = Int
-- | Offset of a node in the tree
--
-- This is the horizontal offset of a node across all nodes at that depth.
--
-- For example, the offsets of
--
-- > A
-- > / \
-- > B C
-- > / \ \
-- > D E F
--
-- are given by
--
-- > (A,0)
-- > / \
-- > (B,0) (C,1)
-- > / \ \
-- > (D,0) (E,1) (F,2)
--
-- Similarly, the offsets of
--
-- > A
-- > / \
-- > B C
-- > / \
-- > D E
--
-- are given by
--
-- > (A,0)
-- > / \
-- > (B,0) (C,1)
-- > / \
-- > (D,0) (E,1)
--
-- Note that in this second example, D gets number 0 because it's the first
-- node at this level; it's therefore not the case that the nodes with number 0
-- necessarily make up the _spine_ of the tree.
type Offset = Int
-- | Coordinates of a node in the tree
data Coords = Coords {
depth :: Depth -- ^ The "y coordinate" (depth in the tree)
, offset :: Offset -- ^ The "x coordinate" (across all nodes at this depth)
}
deriving (Show, Eq, Ord)
-- | Metadata of a node in the tree
data Metadata = Metadata {
isSpine :: Bool
, nthChild :: Int
, coords :: Coords
}
deriving (Show, Eq, Ord)
isFirstChild :: Metadata -> Bool
isFirstChild Metadata{..} = nthChild == 0
{-------------------------------------------------------------------------------
Auxiliary: operations on trees
-------------------------------------------------------------------------------}
-- | Mark the spine of a tree
markSpine :: Tree a -> Tree (a, Bool)
markSpine = go True
where
go :: Bool -> Tree a -> Tree (a, Bool)
go isSpine (Node a ts) = Node (a, isSpine)
$ map (uncurry go) $ zip (isSpine : repeat False) ts
-- | Mark the first child of each node
markNthChild :: Tree a -> Tree (a, Int)
markNthChild = go 0
where
go :: Int -> Tree a -> Tree (a, Int)
go nth (Node a ts) = Node (a, nth) $ zipWith go [0..] ts
-- | Mark each node with its depth in the tree
markDepth :: Tree a -> Tree (a, Depth)
markDepth = go 0
where
go :: Depth -> Tree a -> Tree (a, Depth)
go d (Node a ts) = Node (a, d) $ map (go (d + 1)) ts
-- | Mark each node with its coordinates
markCoords :: forall a. Tree a -> Tree (a, Coords)
markCoords t = evalState (unfoldTreeM_BF go (markDepth t)) (Coords 0 0)
where
go :: MonadState Coords m
=> Tree (a, Depth) -> m ((a, Coords), [Tree (a, Depth)])
go (Node (a, depth) ts) = do
cs <- state $ \(Coords curDepth curOffset) ->
if depth > curDepth -- first node at the next depth level?
then (Coords depth 0 , Coords depth 1)
else (Coords depth curOffset , Coords depth (curOffset + 1))
return ((a, cs), ts)
annotate :: Tree a -> Tree (a, Metadata)
annotate = fmap aux
. markCoords
. markNthChild
. markSpine
where
aux :: (((a, Bool), Int), Coords) -> (a, Metadata)
aux (((a, isSpine), nthChild), coords) = (a, Metadata{..})
{-------------------------------------------------------------------------------
Debugging
-------------------------------------------------------------------------------}
_testTree :: Tree String
_testTree =
Node "a" [ Node "b" [ Node "d" []
, Node "e" []
]
, Node "c" [ Node "f" []
]
]
-- Tree with deeper nodes on the right than on the left
_testTree2 :: Tree String
_testTree2 =
Node "a" [ Node "b" []
, Node "c" [ Node "d" []
, Node "e" []
]
]