needle-0.1.0.0: Control/Arrow/Needle/Internal/UnevenGrid.hs
{-|
Module : Control.Arrow.Needle.Internal.UnevenGrid
Description : Data structure used in parsing needle diagrams
Copyright : (c) Josh Kirklin
License : MIT
Maintainer : jjvk2@cam.ac.uk
Implements a 'Grid' data structure which can be examined and moved around with a monad 'GridExamine', used in needle parsing.
-}
{-# LANGUAGE DeriveFunctor, OverloadedLists, GeneralizedNewtypeDeriving, TupleSections#-}
module Control.Arrow.Needle.Internal.UnevenGrid (
-- * Grids
Grid ()
, grid
, prettyGrid
, height
, GridPosition
, (!!?)
, findPositions
-- * Examining
, GridExamine ()
, gridExamine
, branch
, getGrid
, getPosition
, putPosition
, modifyPosition
, width
-- ** Moving and accessing
, hereGet
, leftGet
, rightGet
, lUpGet
, lDownGet
, rUpGet
, rDownGet
) where
import Prelude as Pre
import Data.Foldable as F
import Data.Vector as V
import Data.Maybe
import Data.List
import Data.Sequence as S
import Control.Applicative
import Control.Monad.State as St
import Control.Monad.Reader
import Control.Arrow
-- | A 'Grid' is a list of rows, the elements of which are not necessarily
-- aligned, with the exception that the left edge of the grid is aligned.
--
-- So for example, the following is a valid shape for a 'Grid':
--
-- > ____________
-- > |___|___|_|__|
-- > |__|_|_|__|_|__
-- > |_|______|_____|
-- >
--
newtype Grid e = Grid { rows :: Vector (GridRow e) }
deriving (Eq, Functor, Show)
newtype GridRow e = GridRow { elements :: Vector (GridElem e) }
deriving (Eq, Functor, Show)
fromLeft :: GridRow e -> Int -> Maybe Int
fromLeft (GridRow row) n = V.findIndex ((> n) . end) $ row
data GridElem e = GridElem {
end :: Int
, unwrap :: e
} deriving (Eq, Functor, Show)
type GridPosition = (Int, Int)
-- | Generate a 'Grid' from a list of rows of elements and widths.
grid :: [[(e, Int)]] -> Grid e
grid rs = Grid . V.fromList . Pre.map (GridRow . V.fromList) $ gridElems
where
widths = Pre.map (Pre.map snd) rs
es = Pre.map (Pre.map fst) rs
f a = Pre.zipWith (+) (0 : f a) a
ends = Pre.map f widths
gridElems = Pre.zipWith (Pre.zipWith GridElem) ends es
-- | Pretty print a grid
prettyGrid :: (e -> Int -> String) -> Grid e -> String
prettyGrid eShow grid = unlines . Pre.map Pre.concat $ lShowns
where
untyped = V.map elements . rows $ grid
widths = V.map ((\a -> V.zipWith (-) a (0 `cons` a)) . V.map end) untyped
unwrappeds = V.map (V.map unwrap) untyped
showns = V.zipWith (V.zipWith eShow) unwrappeds widths
lShowns = Pre.map V.toList . V.toList $ showns
-- | Get the grid height
height :: Grid e -> Int
height = V.length . rows
-- | Get the element at a certain position in the grid. The top left block is
-- in the (0,0) position.
(!!?) :: Grid e -> GridPosition -> Maybe e
(!!?) grid (rowPos, elemPos) = do
row <- rows grid !? rowPos
element <- elements row !? elemPos
return $ unwrap element
-- | Return a list of positions satisfying a predicate in a grid, in reading order.
findPositions :: (e -> Bool) -> Grid e -> [GridPosition]
findPositions pred = V.toList . V.concat . V.toList . V.map (\(i,r) -> V.map (i,) . V.findIndices (pred . unwrap) . elements $ r) . indexed . rows
-- | A monad for examining a grid.
newtype GridExamine e a = GridExamine (StateT GridPosition (Reader (Grid e)) a)
deriving (Functor, Applicative, Monad)
-- | Run a grid examination. A start position must be specified.
gridExamine :: Grid e -> GridPosition -> GridExamine e a -> a
gridExamine grid start (GridExamine s) = runReader (evalStateT s start) grid
-- | Get the entire grid.
getGrid :: GridExamine e (Grid e)
getGrid = GridExamine (lift ask)
-- | Get the current position in the grid.
getPosition :: GridExamine e GridPosition
getPosition = GridExamine get
-- | Set the current position in the grid.
-- This is unsafe -- an in bounds check is NOT performed.
putPosition :: GridPosition -> GridExamine e ()
putPosition = GridExamine . put
-- | Modify the current position in the grid.
-- This is unsafe -- an in bounds check is NOT performed.
modifyPosition :: (GridPosition -> GridPosition) -> GridExamine e ()
modifyPosition f = GridExamine (St.modify f)
here :: GridExamine e (Maybe (GridElem e))
here = do
grid <- getGrid
(rowPos, elemPos) <- getPosition
return $ do
row <- rows grid !? rowPos
element <- elements row !? elemPos
return element
-- | Get the element at the current position.
hereGet :: GridExamine e (Maybe e)
hereGet = do
element <- here
return $ fmap unwrap element
-- | Move to and return the grid element to the left. If there is no element to
-- the left, returns Nothing.
leftGet :: GridExamine e (Maybe e)
leftGet = do
(rowPos, elemPos) <- getPosition
if elemPos > 0
then putPosition (rowPos, elemPos - 1) >> (hereGet)
else return Nothing
-- | Move to and return the grid element to the right. If there is no element to
-- the right, returns Nothing.
rightGet :: GridExamine e (Maybe e)
rightGet = do
grid <- getGrid
(rowPos, elemPos) <- getPosition
fromMaybe (return Nothing) $ do
row <- rows grid !? elemPos
element <- elements row !? (elemPos + 1)
return $ putPosition (rowPos, elemPos + 1) >> return (Just $ unwrap element)
-- | Move to and return the grid element above the column a certain distance
-- from the right edge of the current element. If there is no such element, returns
-- Nothing.
rUpGet :: Int -> GridExamine e (Maybe e)
rUpGet n = do
grid <- getGrid
mCurrentElement <- here
(rowPos, _) <- getPosition
fromMaybe (return Nothing) $ do
currentElement <- mCurrentElement
newRow <- rows grid !? (rowPos - 1)
i <- fromLeft newRow (end currentElement - 1 - n)
return $ putPosition (rowPos - 1, i) >> hereGet
-- | Move to and return the grid element below the column a certain distance
-- from the right edge of the current element. If there is no such element, returns
-- Nothing.
rDownGet :: Int -> GridExamine e (Maybe e)
rDownGet n = do
grid <- getGrid
mCurrentElement <- here
(rowPos, _) <- getPosition
fromMaybe (return Nothing) $ do
currentElement <- mCurrentElement
newRow <- rows grid !? (rowPos + 1)
i <- fromLeft newRow (end currentElement - 1 - n)
return $ putPosition (rowPos + 1, i) >> hereGet
-- | Move to and return the grid element above the column a certain distance
-- from the left edge of the current element. If there is no such element, returns
-- Nothing.
lUpGet :: Int -> GridExamine e (Maybe e)
lUpGet n = do
grid <- getGrid
(rowPos, elemPos) <- getPosition
fromMaybe (return Nothing) $ do
row <- rows grid !? rowPos
let start = fromMaybe 0 $ end <$> (elements row !? (elemPos - 1))
newRow <- rows grid !? (rowPos - 1)
i <- fromLeft newRow (start + n)
return $ putPosition (rowPos - 1, i) >> hereGet
-- | Move to and return the grid element below the column a certain distance
-- from the left edge of the current element. If there is no such element, returns
-- Nothing.
lDownGet :: Int -> GridExamine e (Maybe e)
lDownGet n = do
grid <- getGrid
(rowPos, elemPos) <- getPosition
fromMaybe (return Nothing) $ do
row <- rows grid !? rowPos
let start = fromMaybe 0 $ end <$> (elements row !? (elemPos - 1))
newRow <- rows grid !? (rowPos + 1)
i <- fromLeft newRow (start + n)
return $ putPosition (rowPos + 1, i) >> hereGet
-- | Branch an examination, i.e. perform it and then return to the original position.
branch :: GridExamine e a -> GridExamine e a
branch ge = do
pos <- getPosition
x <- ge
putPosition pos
return x
-- | The width of the current element
width :: GridExamine e (Maybe Int)
width = do
grid <- getGrid
(rowPos, elemPos) <- getPosition
mh <- here
return $ do
h <- mh
row <- rows grid !? rowPos
let start = fromMaybe 0 $ end <$> (elements row !? (elemPos - 1))
return (end h - start)