-- | A class for tree types and representations of selections on tree types, as well as functions for converting between text and tree selections. module Language.GroteTrap.Trees ( -- * Paths and navigation Path, root, Nav, up, into, down, left, right, sibling, -- * Tree types Tree(..), followM, follow, depth, selectDepth, flatten, -- * Tree selections Selectable(..), TreeSelection, select, allSelections, selectionToRange, rangeToSelection, posToPath, isValidRange, -- * Suggesting and fixing suggest, repair ) where import Language.GroteTrap.Range import Data.List (sortBy, findIndex) import Data.Maybe (isJust) import Control.Monad.Error () ------------------------------------ -- Paths and navigation ------------------------------------ -- | A path in a tree. Each integer denotes the selection of a child; these indices are 0-relative. type Path = [Int] -- | @root@ is the empty path. root :: Path root = [] -- | Navigation transforms one path to another. type Nav = Path -> Path -- | Move up to parent node. Moving up from root has no effect. up :: Nav up [] = [] up path = init path -- | Move down into the nth child node. into :: Int -> Nav into i = (++[i]) -- | Move down into first child node. down :: Nav down = into 0 -- | Move left one sibling. left :: Nav left = sibling (-1) -- | Move right one sibling. right :: Nav right = sibling 1 -- | Move @n@ siblings (@n@ can be negative). sibling :: Int -> Nav sibling 0 p = p -- because sibling 0 [] == [] sibling d p = if newindex < 0 then p else into newindex parent where index = last p newindex = index + d parent = up p ------------------------------------ -- Parents and children ------------------------------------ -- | Tree types. class Tree p where -- | Yields this tree's subtrees. children :: p -> [p] -- | Breadth-first, pre-order traversal. flatten :: Tree t => t -> [t] flatten t = t : concatMap flatten (children t) -- | Follows a path in a tree, returning the result in a monad. followM :: (Monad m, Tree t) => t -> Path -> m t followM parent [] = return parent followM parent (t:ts) = do c <- childM parent t followM c ts -- | Moves down into a child. childM :: (Monad m, Tree p) => p -> Int -> m p childM t i = if i >= 0 && i < length cs then return (cs !! i) else fail ("child " ++ show i ++ " does not exist") where cs = children t -- | Follows a path in a tree. follow :: Tree t => t -> Path -> t follow t = fromError . followM t fromError :: Either String a -> a fromError = either error id {- indexIn :: (Eq p, Parent p) => p -> p -> Maybe Int indexIn child = elemIndex child . children -} -- | Yields the depth of the tree. depth :: Tree t => t -> Int depth t | null depths = 1 | otherwise = 1 + maximum (map depth $ children t) where depths = map depth $ children t -- | Yields all ancestors at the specified depth. selectDepth :: Tree t => Int -> t -> [t] selectDepth 0 t = [t] selectDepth d t = concatMap (selectDepth (d - 1)) (children t) ------------------------------------ -- Tree selections ------------------------------------ -- | Selection in a tree. The path indicates the left side of the selection; the int tells how many siblings to the right are included in the selection. type TreeSelection = (Path, Int) -- | Selectable trees. class Tree t => Selectable t where -- | Tells whether complete subranges of children may be selected in this tree. If not, valid TreeSelections in this tree always have a second element @0@. allowSubranges :: t -> Bool -- | Enumerates all possible selections of a tree. allSelections :: Selectable a => a -> [TreeSelection] allSelections p = ([], 0) : subranges ++ recurse where subranges | allowSubranges p = [ ([from], to - from) | from <- [0 .. length cs - 2] , to <- [from + 1 .. length cs - 1] , from > 0 || to < length cs - 1 ] | otherwise = [] cs = children p recurse = concat $ zipWith label cs [0 ..] label c i = map (rt i) (allSelections c) rt i (path, offset) = (i : path, offset) -- | Selects part of a tree. select :: (Monad m, Tree t) => t -> TreeSelection -> m [t] select t (path, offset) = (sequence . map (followM t) . take offset . iterate right) path -- | Computes the range of a valid selection. selectionToRange :: (Tree a, KnowsPosition a) => a -> TreeSelection -> Range selectionToRange parent (path, offset) = (from, to) where from = begin $ follow parent path to = end $ follow parent (sibling offset path) -- | Converts a specified range to a corresponding selection and returns it in a monad. rangeToSelection :: (Tree a, KnowsPosition a, Monad m) => a -> Range -> m TreeSelection rangeToSelection p ran@(b, e) -- If the range matches that of the root, we're done. | range p == ran = return ([], 0) | otherwise = -- Find the children whose ranges contain b and e. case ( findIndex (\c -> b `inRange` range c) cs , findIndex (\c -> e `inRange` range c) cs) of (Just l, Just r) -> if l == r -- b and e are contained by the same child! -- Recurse into child. then rangeToSelection (cs !! l) ran -- ... and prepend child index, of course. >>= (\(path, offset) -> return (l : path, offset)) else if begin (cs !! l) == b && end (cs !! r) == e -- b is the beginning of l, and e is the end -- of r: a selection of a range of children. -- Note that r - l > 0; else it would've been -- caught by the previous test. -- This also means that there are many ways -- to select a single node: either select it -- directly, or select all its children. then return ([l], r - l) -- All other cases are bad. else fail "text selection does not have corresponding tree selection" -- Either position is not contained -- within any child. Can't be valid. _ -> fail "text selection does not have corresponding tree selection" where cs = children p -- | Returns the path to the deepest descendant whose range contains the specified position. posToPath :: (Tree a, KnowsPosition a) => a -> Pos -> Path posToPath p pos = case break (\c -> pos `inRange` range c) (children p) of (_, []) -> [] (no, c:_) -> length no : posToPath c pos -- | Tells whether the text selection corresponds to a tree selection. isValidRange :: (KnowsPosition a, Selectable a) => a -> Range -> Bool isValidRange p = isJust . rangeToSelection p ------------------------------------ -- Suggesting and fixing ------------------------------------ -- | Yields all possible selections, ordered by distance to the specified range, closest first. suggest :: (Selectable a, KnowsPosition a) => a -> Range -> [TreeSelection] suggest p r = sortBy distance $ allSelections p where distance s1 s2 = (selectionToRange p s1 `distRange` r) `compare` (selectionToRange p s2 `distRange` r) -- | Takes @suggest@'s first suggestion and yields its range. repair :: (KnowsPosition a, Selectable a) => a -> Range -> Range repair p = selectionToRange p . head . suggest p