babynf-0.1.0.2: lib/Data/BAByNF/Core/Tree.hs
module Data.BAByNF.Core.Tree
( Tree (..)
, Node (..)
, nodes
, empty
, singleton
, asSingleton
, stringify
, stringifyNode
, mergeStrings
, mergeStringsInNode
, dropRefs
, getChildrenWithRef
, getChildWithRef
, tryGetChildWithRef
, getDescendantsWithPath
, getFirstDescendantWithPath
, tryGetFirstPath
, getSubtreeIfRef
, isStringEq
, isRefOf
) where
import Data.ByteString (ByteString)
import Data.ByteString qualified as ByteString
import Data.BAByNF.Core.Ref (Ref)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.BAByNF.Core.Ref as Ref
data Tree a where Tree :: Ref a => [Node a] -> Tree a
deriving instance (Eq a) => Eq (Tree a)
deriving instance (Show a) => Show (Tree a)
data Node a where
StringNode :: ByteString -> Node a
RefNode :: (Ref a) => a -> Tree a -> Node a
deriving instance (Eq a) => Eq (Node a)
deriving instance (Show a) => Show (Node a)
instance Semigroup (Tree a) where
(<>) :: Tree a -> Tree a -> Tree a
(<>) (Tree a) (Tree b) = Tree $ a ++ b
nodes :: (Ref a) => Tree a -> [Node a]
nodes (Tree ns) = ns
empty :: (Ref a) => Tree a
empty = Tree []
singleton :: (Ref a) => Node a -> Tree a
singleton node = Tree [node]
asSingleton :: (Ref a) => Tree a -> Maybe (Node a)
asSingleton (Tree [x]) = Just x
asSingleton _ = Nothing
stringify :: Tree a -> ByteString
stringify (Tree ns) = ByteString.concat . map stringifyNode $ ns
stringifyNode :: Node a -> ByteString
stringifyNode (RefNode _ tree) = stringify tree
stringifyNode (StringNode bs) = bs
mergeStrings :: Tree a -> Tree a
mergeStrings (Tree ns) = Tree $ merge ns
where merge [] = []
merge [x] = [mergeStringsInNode x]
merge (x:xs) = mergeStringsInNode x : merge xs
mergeStringsInNode :: Node a -> Node a
mergeStringsInNode (RefNode ref tree) = RefNode ref (mergeStrings tree)
mergeStringsInNode node = node
dropRefs :: [a] -> Tree a -> Tree a
dropRefs refs (Tree ns) = Tree $ ns >>= applyDrop
where applyDrop node@(StringNode _) = [node]
applyDrop (RefNode ref tree) =
let tree'@(Tree ns') = dropRefs refs tree
in if any (Ref.eq ref) refs
then ns'
else [RefNode ref tree']
getChildrenWithRef :: a -> Tree a -> [Tree a]
getChildrenWithRef ref (Tree ns) = ns >>= filterOnRef
where filterOnRef (RefNode ref' subtree) = [subtree | Ref.eq ref ref']
filterOnRef _ = []
getChildWithRef :: a -> Tree a -> Maybe (Tree a)
getChildWithRef ref tree =
case getChildrenWithRef ref tree of
[] -> Nothing
x : _ -> Just x
tryGetChildWithRef :: (Ref a) => a -> Tree a -> Either String (Tree a)
tryGetChildWithRef ref tree =
case getChildWithRef ref tree of
Nothing -> Left $ "no subtree with ref <" ++ Ref.display ref ++ "> defined"
Just subtree -> Right subtree
getDescendantsWithPath :: (Ref a) => NonEmpty a -> Tree a -> [Tree a]
getDescendantsWithPath (r :| rs) tree =
let matching = getChildrenWithRef r tree
in case rs of
[] -> matching
r' : rs' -> matching >>= getDescendantsWithPath (r' :| rs')
getFirstDescendantWithPath :: (Ref a) => NonEmpty a -> Tree a -> Maybe (Tree a)
getFirstDescendantWithPath refs tree = case getDescendantsWithPath refs tree of
[] -> Nothing
x:_ -> Just x
tryGetFirstPath :: (Ref a) => NonEmpty a -> Tree a -> Either String (Tree a)
tryGetFirstPath (r :| rs) tree =
let e = tryGetChildWithRef r tree
in case rs of
[] -> e
(r':rs') -> e >>= tryGetFirstPath (r' :| rs')
getSubtreeIfRef :: (Ref a) => a -> Node a -> Maybe (Tree a)
getSubtreeIfRef ref (RefNode ref' subtree) = if Ref.eq ref ref' then Just subtree else Nothing
getSubtreeIfRef _ _ = Nothing
isStringEq :: (Ref a) => Node a -> ByteString -> Bool
isStringEq (StringNode bs) bs' = bs == bs'
isStringEq _ _ = False
isRefOf :: (Ref a) => Node a -> a -> Bool
isRefOf (RefNode ref _) ref' = Ref.eq ref ref'
isRefOf _ _ = False