packages feed

parcom-lib-0.1.0.0: Text/Parcom/Combinators.hs

module Text.Parcom.Combinators
( choice, namedChoice
, before, between
, many, many1, manySepBy
, times
, skip
)
where

import Text.Parcom.Core
import Text.Parcom.Internal

-- | Walk a list of options, return the first one that succeeds.
choice :: (Stream s t) => [Parcom s t a] -> Parcom s t a
choice xs = foldl (<|>) empty xs <?> "I tried to make a choice, but couldn't"

-- | Like @choice@, but each choice tagged with a human-readable name for
-- better error reporting.
namedChoice :: (Stream s t) => [(String, Parcom s t a)] -> Parcom s t a
namedChoice xs = choice (map snd xs) <?> (formatOptionList . map fst) xs

-- | Match two consecutive parser, return the first parser's result iff both
-- succeed.
before :: (Stream s t) => Parcom s t a -> Parcom s t b -> Parcom s t a
before p q = do { v <- p; q; return v }

-- | Match three consecutive parsers, return the middle parser's result iff
-- all three match. Parsers are given in the order inner, left, right.
between :: (Stream s t) => Parcom s t a -> Parcom s t l -> Parcom s t r -> Parcom s t a
between p l r = do { l; v <- p; r; return v }

-- | Match zero or more occurrences of a parser
many :: (Stream s t) => Parcom s t a -> Parcom s t [a]
many p =
    handle p f m
    where
        f e = return []
        m x = do
            xs <- many p
            return (x:xs)

-- | Match one or more occurrences of a parser
many1 :: (Stream s t) => Parcom s t a -> Parcom s t [a]
many1 p = do
    xs <- many p
    if null xs
        then fail "Expected at least one item"
        else return xs

-- | Given an item parser and a separator parser, keep parsing until the
-- separator or the item fails.
manySepBy :: (Stream s t) => Parcom s t a -> Parcom s t b -> Parcom s t [a]
manySepBy p s = go
    where
        go = do
            -- try an item
            handle p f m
            where
                -- item does not parse: return empty list (no matches)
                f e = return []
                -- item does parse: keep the item, try the separator.
                m x = handle s
                    -- separator does not parse: return the one item we have
                    (\e -> return [x])
                    -- separator does parse: recurse and prepend our item
                    (\_ -> go >>= \xs -> return (x:xs))

-- | Run the given parser n times, returning all the results as a list.
times :: (Stream s t) => Int -> Parcom s t a -> Parcom s t [a]
times 0 p = return []
times n p = do
    x <- p
    xs <- times (n - 1) p
    return (x:xs)

-- | Ignore the result of a parser.
skip :: Parcom s t a -> Parcom s t ()
skip p = p >> return ()