packages feed

snack-0.1.0.0: lib/Snack/Combinators.hs

-- |
-- Module      :  Snack.Combinators
-- License     :  CC0-1.0
--
-- Maintainer  :  mordae@anilinux.org
-- Stability   :  unstable
-- Portability :  non-portable (ghc)
--

module Snack.Combinators
  ( provided
  , choice
  , count
  , eitherP
  , option
  , many1
  , manyTill
  , sepBy
  , sepBy1
  , wrap
  , inRange
  , notInRange
  )
where
  import Control.Applicative
  import Data.Maybe

  import {-# SOURCE #-} qualified Data.ByteString.Parser as BSP
  import {-# SOURCE #-} qualified Data.Text.Parser as TP


  -- |
  -- Fails if the value returned by the parser does not conform to the
  -- predicate. Generalized form of 'Data.ByteString.Parser.Char8.string'.
  --
  -- Example:
  --
  -- @
  -- pInput = takeWhile isLetter `provided` (odd . length)
  -- @
  --
  {-# INLINE CONLIKE provided #-}
  {-# SPECIALIZE provided :: (a -> Bool) -> BSP.Parser a -> BSP.Parser a #-}
  {-# SPECIALIZE provided :: (a -> Bool) -> TP.Parser a -> TP.Parser a #-}
  provided :: (Alternative m, Monad m) => (a -> Bool) -> m a -> m a
  provided test par = do
    x <- par
    if test x
       then pure x
       else Control.Applicative.empty


  -- |
  -- Tries various parsers, one by one. Alias for 'asum'.
  --
  -- Example:
  --
  -- @
  -- pExpression = choice [ pConstant
  --                      , pVariable
  --                      , pBinaryOperation
  --                      , pFunctionApplication
  --                      ]
  -- @
  --
  {-# INLINE CONLIKE choice #-}
  {-# SPECIALIZE choice :: [BSP.Parser a] -> BSP.Parser a #-}
  {-# SPECIALIZE choice :: [TP.Parser a] -> TP.Parser a #-}
  choice :: (Alternative f) => [f a] -> f a
  choice = asum


  -- |
  -- Replicates the parser given number of times, collecting the results
  -- in a list. Fails if any instance of the parser fails.
  --
  -- Example:
  --
  -- @
  -- pFourWords = (:) \<$\> word \<*\> count 3 (blank *> word)
  --   where word  = takeWhile1 isLetter
  --         blank = takeWhile1 isSpace
  -- @
  --
  {-# INLINE CONLIKE count #-}
  {-# SPECIALIZE count :: Int -> BSP.Parser a -> BSP.Parser [a] #-}
  {-# SPECIALIZE count :: Int -> TP.Parser a -> TP.Parser [a] #-}
  count :: (Monad m) => Int -> m a -> m [a]
  count n p = Prelude.sequence (Prelude.replicate n p)


  -- |
  -- Captures first parser as @Left@ or the second as @Right@.
  --
  {-# INLINE CONLIKE eitherP #-}
  {-# SPECIALIZE eitherP :: BSP.Parser a -> BSP.Parser b -> BSP.Parser (Either a b) #-}
  {-# SPECIALIZE eitherP :: TP.Parser a -> TP.Parser b -> TP.Parser (Either a b) #-}
  eitherP :: (Alternative f) => f a -> f b -> f (Either a b)
  eitherP left right = (Left <$> left) <|> (Right <$> right)


  -- |
  -- Shortcut for 'optional' with a default value.
  --
  -- Example:
  --
  -- @
  -- data Contact =
  --  Contact
  --    { contactName  :: Text
  --    , contactEmail :: Maybe Text
  --    }
  --
  -- pContact = Contact \<$\> pFullName \<*\> option pEmail
  -- @
  --
  {-# INLINE CONLIKE option #-}
  {-# SPECIALIZE option :: a -> BSP.Parser a -> BSP.Parser a #-}
  {-# SPECIALIZE option :: a -> TP.Parser a -> TP.Parser a #-}
  option :: (Alternative f) => a -> f a -> f a
  option dfl par = fromMaybe dfl <$> optional par


  -- |
  -- Like 'many1', but requires at least one match.
  --
  {-# INLINE many1 #-}
  {-# SPECIALIZE many1 :: BSP.Parser a -> BSP.Parser [a] #-}
  {-# SPECIALIZE many1 :: TP.Parser a -> TP.Parser [a] #-}
  many1 :: (Alternative f) => f a -> f [a]
  many1 = some


  -- |
  -- Like 'many', but stops once the second parser matches the input ahead.
  --
  -- Example:
  --
  -- @
  -- pBodyLines = pLine `manyTill` pEnd
  --   where pLine = takeTill (== '\n')
  --         pEnd  = string "\n.\n"
  -- @
  --
  {-# INLINE CONLIKE manyTill #-}
  {-# SPECIALIZE manyTill :: BSP.Parser a -> BSP.Parser a -> BSP.Parser [a] #-}
  {-# SPECIALIZE manyTill :: TP.Parser a -> TP.Parser a -> TP.Parser [a] #-}
  manyTill :: (Alternative f) => f a -> f a -> f [a]
  manyTill par stop = loop
    where loop = (stop *> pure []) <|> ((:) <$> par <*> loop)


  -- |
  -- Similar to 'many', but interleaves the first parser with the second.
  --
  -- Example:
  --
  -- @
  -- pLines = pLine `sepBy` char '\n'
  -- @
  --
  {-# INLINE CONLIKE sepBy #-}
  {-# SPECIALIZE sepBy :: BSP.Parser a -> BSP.Parser b -> BSP.Parser [a] #-}
  {-# SPECIALIZE sepBy :: TP.Parser a -> TP.Parser b -> TP.Parser [a] #-}
  sepBy :: (Alternative f) => f a -> f b -> f [a]
  sepBy par sep = sepBy1 par sep <|> pure []


  -- |
  -- Like 'sepBy', but requires at least one match.
  --
  {-# INLINE CONLIKE sepBy1 #-}
  {-# SPECIALIZE sepBy1 :: BSP.Parser a -> BSP.Parser b -> BSP.Parser [a] #-}
  {-# SPECIALIZE sepBy1 :: TP.Parser a -> TP.Parser b -> TP.Parser [a] #-}
  sepBy1 :: (Alternative f) => f a -> f b -> f [a]
  sepBy1 par sep = loop
    where loop = (:) <$> par <*> ((sep *> loop) <|> pure [])


  -- |
  -- Wraps the parser from both sides.
  --
  -- Example:
  --
  -- @
  -- pToken = takeWhile1 (inClass "A-Za-z0-9_") `wrap` takeWhile isSpace
  -- @
  --
  {-# INLINE CONLIKE wrap #-}
  {-# SPECIALIZE wrap :: BSP.Parser a -> BSP.Parser b -> BSP.Parser a #-}
  {-# SPECIALIZE wrap :: TP.Parser a -> TP.Parser b -> TP.Parser a #-}
  wrap :: (Applicative f) => f a -> f b -> f a
  wrap par wrapper = wrapper *> par <* wrapper


  -- |
  -- Tests whether the character lies within given range.
  --
  -- Definition:
  --
  -- @
  -- inRange lo hi = \c -> (lo <= c && c <= hi)
  -- @
  --
  {-# INLINE CONLIKE inRange #-}
  inRange :: Char -> Char -> Char -> Bool
  inRange lo hi = \c -> (lo <= c && c <= hi)


  -- |
  -- Negation of 'inRange'.
  --
  -- Definition:
  --
  -- @
  -- notInRange lo hi = \c -> (c <= lo || hi <= c)
  -- @
  --
  {-# INLINE CONLIKE notInRange #-}
  notInRange :: Char -> Char -> Char -> Bool
  notInRange lo hi = \c -> (lo >= c || c >= hi)


-- vim:set ft=haskell sw=2 ts=2 et: