packages feed

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)