polysoup-0.6: src/Text/XML/PolySoup/Parser.hs
{-# LANGUAGE TupleSections #-}
-- | The module defines a generic parser which can be used, in particular,
-- to parse XML forests. The main characteristic of the parser is that it
-- can be used in a sequential (sub-trees are processed in order) and a
-- selective (subtrees are process regardless of their position) way.
module Text.XML.PolySoup.Parser
(
-- * Core
P (..)
, evalP
-- * Parsing
-- ** Selective
-- , find
, first
, every
, every'
-- ** Sequential
, pop
-- ** Peek
, peek
, spy
-- ** Utilities
, many_
) where
import Control.Applicative
import qualified Control.Arrow as Arr
import Data.Maybe (catMaybes)
import Text.XML.PolySoup.Predicate
-- | An XML forest parser.
newtype P a b = P { runP :: [a] -> Maybe (b, [a]) }
instance Functor (P a) where
fmap f (P p) = P $ fmap (fmap $ Arr.first f) p
instance Applicative (P a) where
pure x = P $ Just . (x,)
P p <*> P q = P $ \t0 -> do
(f, t1) <- p t0
(x, t2) <- q t1
return (f x, t2)
instance Alternative (P a) where
empty = P $ \_ -> Nothing
P p <|> P q = P $ \t -> p t <|> q t
instance Monad (P a) where
return = pure
P p >>= f = P $ \t0 -> do
(x, t1) <- p t0
runP (f x) t1
-- | Evaluate parser on the given XML forest.
evalP :: P a b -> [a] -> Maybe b
evalP p = fmap fst . runP p
---------------------------------------------------------------------
-- Selective parsers
---------------------------------------------------------------------
-- -- | A synonym to `first`.
-- find :: Q a b -> P a b
-- find = first
-- | Find the first tree satisfying the given predicate.
first :: Q a b -> P a b
first (Q p) = P $ go [] where
go acc (t:ts) = case p t of
Just v -> Just (v, reverse acc ++ ts)
Nothing -> go (t:acc) ts
go _ [] = Nothing
-- | Select every tree satisfying the given predicate.
every :: Q a b -> P a [b]
every (Q p) =
P $ prep . foldl upd ([], [])
where
prep (x, y) = Just (reverse x, reverse y)
upd (vs, acc) t = case p t of
Just v -> (v:vs, acc)
Nothing -> (vs, t:acc)
-- | A lazy version of `every` which "forgets" non-matching subtrees
-- along the way.
every' :: Q a b -> P a [b]
every' (Q p) =
let prep xs = Just (xs, [])
in P $ prep . catMaybes . map p
---------------------------------------------------------------------
-- Sequential parsers
---------------------------------------------------------------------
-- | Check, if the first tree satisfies the given predicate.
pop :: Q a b -> P a b
pop (Q p) = P $ \tts -> case tts of
(t:ts) -> (,ts) <$> p t
[] -> Nothing
---------------------------------------------------------------------
-- Peek
---------------------------------------------------------------------
-- | Like `pop`, but doesn't consume the tree.
peek :: Q a b -> P a b
peek (Q p) = P $ \tts -> case tts of
(t:_) -> (,tts) <$> p t
[] -> Nothing
-- | Like `first`, but doesn't consume the tree.
spy :: Q a b -> P a b
spy (Q p) = P $ \tts ->
let go (t:ts) = case p t of
Just v -> Just (v, tts)
Nothing -> go ts
go [] = Nothing
in go tts
---------------------------------------------------------------------
-- Utilities
---------------------------------------------------------------------
-- | Many combinator which ignores parsing results.
many_ :: Alternative f => f a -> f ()
many_ v = many_v
where
many_v = some_v <|> pure ()
some_v = v *> many_v