hpp-0.6.0: src/Hpp/Parser.hs
{-# LANGUAGE DeriveFunctor, LambdaCase, TupleSections #-}
-- | Parsers over streaming input.
module Hpp.Parser (Parser, ParserT, evalParse, await, awaitJust, replace,
droppingWhile, precede, takingWhile, onElements,
onInputSegment, insertInputSegment, onIsomorphism) where
import Control.Arrow ((***))
import Control.Monad.Trans.State.Strict
import Hpp.Types (HasError(..), Error(UserError))
import Control.Monad.Trans.Class (lift)
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe (mapMaybe)
-- * Parsers
-- | A single pre-processor input is either an action or a value
data InputItem m a = Action (m ()) | Value a deriving Functor
-- | Our input is a list of values each of which is either an action
-- or a value.
type Input m a = [InputItem m a]
-- | Functions for working with input sources.
data Source m src i = Source { srcSrc :: src
, _srcAwait :: src -> m (Maybe (i, src))
, _srcPrecede :: [i] -> src -> src }
-- | A 'ParserT' is a bit of state that carries a source of input.
type ParserT m src i = StateT (Source m src i) m
-- | A 'Parser' is a bit of state that carries a source of input
-- consisting of a list of values which are either actions in an
-- underlying monad or sequences of inputs. Thus we have chunks of
-- input values with interspersed effects.
type Parser m i = ParserT m (Input m [i]) i
-- | Pop the head non-effect element from a list.
unconsM :: Applicative m => Input m a -> m (Maybe (a, Input m a))
unconsM [] = pure Nothing
unconsM (Action m : ms) = m *> unconsM ms
unconsM (Value x : ms) = pure (Just (x, ms))
-- | Pop the first non-null, non-effect element from a list.
unconsMNonEmpty :: Monad m => Input m [a] -> m (Maybe (NonEmpty a, Input m [a]))
unconsMNonEmpty r = unconsM r >>= \case
Nothing -> pure Nothing
Just ([], rst) -> unconsMNonEmpty rst
Just (x:xs, rst) -> return (Just (x :| xs, rst))
unconsSource :: Monad m => Input m [i] -> Source m (Input m [i]) i
unconsSource src = Source src aw ropePrecede
where aw r = unconsMNonEmpty r >>= \case
Nothing -> return Nothing
Just (x :| xs, r') -> return (Just (x, Value xs : r'))
flattenSource :: Monad m => Source m (Input m [[i]]) [i] -> Source m (Input m [[i]]) i
flattenSource (Source src0 aw pr) = Source src0 aw' pr'
where aw' src = aw src >>= \case
Nothing -> return Nothing
Just ([], src') -> aw' src'
Just (x:xs, src') -> return (Just (x, pr' xs src'))
pr' xs src = pr [xs] src
await :: Monad m => ParserT m src i (Maybe i)
await = do Source src aw pr <- get
lift (aw src) >>= \case
Nothing -> return Nothing
Just (x, src') -> Just x <$ put (Source src' aw pr)
{-# INLINE await #-}
-- | Push a value back into a parser's source.
replace :: (Monad m) => i -> ParserT m src i ()
replace = precede . pure
ropePrecede :: [i] -> Input m [i] -> Input m [i]
ropePrecede xs [] = [Value xs]
ropePrecede xs ms@(Action _ : _) = Value xs : ms
ropePrecede xs (Value ys : ms) = Value (xs++ys) : ms
-- | Push a stream of values back into a parser's source.
precede :: Monad m => [i] -> ParserT m src i ()
precede xs = do Source src aw pr <- get
put (Source (pr xs src) aw pr)
{-# INLINE precede #-}
-- | Evaluate a 'Parser' with a given input stream.
evalParse :: Monad m => Parser m i o -> [i] -> m o
evalParse m xs = evalStateT m (unconsSource [Value xs])
-- * Operations on Parsers
-- | 'await' that throws an error with the given message if no more
-- input is available. This may be used to locate where in a
-- processing pipeline input was unexpectedly exhausted.
awaitJust :: (Monad m, HasError m) => String -> ParserT m src i i
awaitJust s = await >>= maybe (lift $ throwError err) return
where err = UserError 0 ("awaitJust: " ++ s)
{-# INLINE awaitJust #-}
-- | Discard all values until one fails to satisfy a predicate. At
-- that point, the failing value is 'replace'd, and the
-- 'droppingWhile' stream stops.
droppingWhile :: (Monad m) => (i -> Bool) -> ParserT m src i ()
droppingWhile p = go
where go = await >>= \case
Nothing -> return ()
Just x -> if p x then go else replace x
{-# INLINE droppingWhile #-}
-- | Echo all values until one fails to satisfy a predicate. At that
-- point, the failing value is 'replace'd, and the 'takingWhile'
-- stream stops.
takingWhile :: (Monad m) => (i -> Bool) -> ParserT m src i [i]
takingWhile p = go id
where go acc = await >>= \case
Nothing -> return (acc [])
Just x
| p x -> go (acc . (x:))
| otherwise -> replace x >> return (acc [])
{-# INLINE takingWhile #-}
insertInputSegment :: Monad m => src -> m () -> ParserT m (Input m src) i ()
insertInputSegment xs k =
modify' (\s -> s { srcSrc = [Value xs, Action k] ++ srcSrc s})
onInputSegment :: Monad m => (src -> src) -> ParserT m (Input m src) i ()
onInputSegment f =
do Source src aw pr <- get
case src of
[] -> return ()
(Value xs : ys) -> put (Source (Value (f xs) : ys) aw pr)
(Action m : xs) -> lift m >> put (Source xs aw pr) >> onInputSegment f
{-# INLINABLE onInputSegment #-}
-- * Parser Transformations
-- | A parser on lists of things can embed a parser on things. For
-- example, if we have a parser on lists of words, we can embed a
-- parser on individual words.
onElements :: Monad m => ParserT m (Input m [[i]]) i r -> Parser m [i] r
onElements m = do s@(Source _ aw pr) <- get
(r, Source src' _ _) <- lift (runStateT m (flattenSource s))
r <$ put (Source (onHead (fmap (dropWhile null)) src') aw pr)
where onHead _ [] = []
onHead f (x:xs) = f x : xs
{-# INLINE onElements #-}
-- | Given a function with type @a -> b@, and a partial inverse, @b ->
-- Maybe a@, we can embed a parser on values of type @b@ in a parser
-- on values of type @a@.
onIsomorphism :: Monad m
=> (a -> b)
-> (b -> Maybe a)
-> ParserT m ([b],src) b r
-> ParserT m src a r
onIsomorphism fwd bwd m =
do Source src aw pr <- get
let aw' ([], src') = fmap (fmap (fwd *** ([],))) (aw src')
aw' ((b:bs), src') = return (Just (b, (bs,src')))
pr' xs (bs, src') = (xs++bs, src')
(r, Source (bs, src') _ _) <- lift (runStateT m (Source ([],src) aw' pr'))
r <$ put (Source (pr (mapMaybe bwd bs) src') aw pr)
{-# INLINE onIsomorphism #-}