snack-0.2.0.0: lib/Data/ByteString/Parser.hs
-- |
-- Module : Data.ByteString.Parser
-- License : CC0-1.0
--
-- Maintainer : mordae@anilinux.org
-- Stability : unstable
-- Portability : non-portable (ghc)
--
-- This module provides a parser for 'ByteString'.
--
-- * If you\'d like to parse ASCII text, you might want to take a look at
-- "Data.ByteString.Parser.Char8". It reuses the same 'Parser', but
-- provides functions working with 'Char' instead of 'Word8' as well as
-- more string utilities.
--
-- * If you\'d like to parse Unicode text, look instead at the
-- "Data.Text.Parser". Is is slower, but in a way more correct.
--
module Data.ByteString.Parser
( Parser(..)
, Result(..)
, parseOnly
-- * Bytes
, byte
, notByte
, anyByte
, satisfy
, peekByte
-- * Strings
, string
, Data.ByteString.Parser.take
, scan
, runScanner
, Data.ByteString.Parser.takeWhile
, takeWhile1
, takeTill
, takeTill1
-- * Combinators
, provided
, choice
, Snack.Combinators.count
, optional
, eitherP
, option
, many
, many1
, manyTill
, sepBy
, sepBy1
, wrap
, match
, label
, extent
-- * End Of Input
, takeByteString
, endOfInput
, atEnd
-- * Miscelaneous
-- |
-- These are all generic methods, but since I sometimes forget about them,
-- it is nice to have them listed here for reference what writing parsers.
, Control.Applicative.empty
, pure
, guard
, when
, unless
, void
)
where
import Prelude hiding (null, length, splitAt, take)
import Control.Applicative
import Control.Monad
import Data.Maybe
import Data.Word
import Data.ByteString as BS
import Data.ByteString.Unsafe as BS
import Snack.Combinators
-- |
-- Result represents either success or some kind of failure.
--
-- You can find the problematic offset by subtracting length of the
-- remainder from length of the original input.
--
data Result a
= Success a {-# UNPACK #-} !ByteString
-- ^ Parser successfully match the input.
-- Produces the parsing result and the remainder of the input.
| Failure [String] {-# UNPACK #-} !ByteString
-- ^ Parser failed to match the input.
-- Produces list of expected inputs and the corresponding remainder.
| Error String {-# UNPACK #-} !ByteString {-# UNPACK #-} !Int
-- ^ 'fail' was called somewhere during the parsing.
-- Produces the reason and the remainder at the corresponding point
-- with length of the problematic extent.
deriving (Eq, Show)
instance Functor Result where
{-# INLINE fmap #-}
fmap fn (Success res more) = Success (fn res) more
fmap _ (Failure expected more) = Failure expected more
fmap _ (Error reason more len) = Error reason more len
-- |
-- Parser for 'ByteString' inputs.
--
newtype Parser a =
Parser
{ runParser :: ByteString -> Result a
-- ^ Run the parser on specified input.
}
instance Functor Parser where
{-# INLINE fmap #-}
fmap fn Parser{runParser} = Parser \inp ->
fmap fn (runParser inp)
instance Applicative Parser where
{-# INLINE pure #-}
pure x = Parser \inp -> Success x inp
{-# INLINE (<*>) #-}
(Parser runFn) <*> (Parser runArg) = Parser \inp ->
case runFn inp of
Error reason more len -> Error reason more len
Failure expected more -> Failure expected more
Success fn rest -> fmap fn (runArg rest)
instance Alternative Parser where
{-# INLINE empty #-}
empty = Parser \inp -> Failure [] inp
-- |
-- Tries the right branch only if the left brach produces Failure.
-- Does not mask Error.
--
{-# INLINE (<|>) #-}
(Parser runLeft) <|> (Parser runRight) = Parser \inp ->
case runLeft inp of
Success res more -> Success res more
Error reason more len -> Error reason more len
Failure expected more ->
case runRight inp of
Success res' more' -> Success res' more'
Error reason' more' len' -> Error reason' more' len'
Failure expected' more' ->
-- Longer match (shorter remainder) wins.
case length more `compare` length more' of
LT -> Failure expected more
EQ -> Failure (expected <> expected') more
GT -> Failure expected' more'
instance Monad Parser where
{-# INLINE (>>=) #-}
(Parser runLeft) >>= right = Parser \inp ->
case runLeft inp of
Error reason more len -> Error reason more len
Failure expected more -> Failure expected more
Success res more -> runParser (right res) more
instance MonadPlus Parser
instance MonadFail Parser where
-- |
-- Fail the whole parser with given reason.
--
-- If you want the best error report possible, fail at the end of a
-- relevant 'extent'.
--
-- For example, if you are parsing a mapping that is syntactically valid,
-- but does not contain some mandatory keys, fail after parsing the whole
-- mapping and make sure that the maaping parser and the 'fail' call are
-- enclosed in an 'extent'.
--
-- That way, the error will indicate the extent remainder and length.
--
{-# INLINE CONLIKE fail #-}
fail reason = Parser \inp -> Error reason inp 0
-- |
-- Accepts a single, matching byte.
--
{-# INLINE CONLIKE byte #-}
byte :: Word8 -> Parser Word8
byte c = satisfy (c ==)
-- |
-- Accepts a single, differing byte.
--
{-# INLINE CONLIKE notByte #-}
notByte :: Word8 -> Parser Word8
notByte c = satisfy (c /=)
-- |
-- Discards the remaining input and returns just the parse result.
-- You might want to combine it with 'endOfInput' for the best effect.
--
-- Example:
--
-- @
-- parseOnly (pContacts \<* endOfInput) bstr
-- @
--
{-# INLINE CONLIKE parseOnly #-}
parseOnly :: Parser a -> ByteString -> Maybe a
parseOnly par = \inp ->
case runParser par inp of
Success res _ -> Just res
_otherwise -> Nothing
-- |
-- Accepts a single byte.
--
{-# INLINE anyByte #-}
anyByte :: Parser Word8
anyByte = Parser \inp ->
if null inp
then Failure ["any byte"] inp
else Success (unsafeHead inp) (unsafeTail inp)
-- |
-- Accepts a single byte matching the predicate.
--
{-# INLINE CONLIKE satisfy #-}
satisfy :: (Word8 -> Bool) -> Parser Word8
satisfy isOk = Parser \inp ->
if null inp
then Failure ["more input"] inp
else let c = unsafeHead inp
in if isOk c
then Success c (unsafeTail inp)
else Failure [] inp
-- |
-- Peeks ahead, but does not consume.
--
-- Be careful, peeking behind end of the input fails.
-- You might want to check using 'atEnd' beforehand.
--
{-# INLINE peekByte #-}
peekByte :: Parser Word8
peekByte = Parser \inp ->
if null inp
then Failure ["more input"] inp
else Success (unsafeHead inp) inp
-- |
-- Accepts a matching string.
--
{-# INLINE CONLIKE string #-}
string :: ByteString -> Parser ByteString
string str = Parser \inp ->
let (pfx, sfx) = splitAt (length str) inp
in case pfx == str of
True -> Success pfx sfx
False -> Failure [(show pfx)] inp
-- |
-- Accepts given number of bytes.
-- Fails when not enough bytes are available.
--
{-# INLINE CONLIKE take #-}
take :: Int -> Parser ByteString
take n = Parser \inp ->
if n > length inp
then Failure [show n <> " more bytes"] inp
else Success (unsafeTake n inp) (unsafeDrop n inp)
-- |
-- Scans ahead statefully and then accepts whatever bytes the scanner liked.
-- Scanner returns 'Nothing' to mark end of the acceptable extent.
--
{-# INLINE CONLIKE scan #-}
scan :: s -> (s -> Word8 -> Maybe s) -> Parser ByteString
scan state scanner = fst <$> runScanner state scanner
-- |
-- Like 'scan', but also returns the final scanner state.
--
{-# INLINE CONLIKE runScanner #-}
runScanner :: s -> (s -> Word8 -> Maybe s) -> Parser (ByteString, s)
runScanner state scanner = Parser \inp ->
let (state', n) = scanBytes state scanner 0 (unpack inp)
(res, more) = splitAt n inp
in Success (res, state') more
{-# INLINE scanBytes #-}
scanBytes :: s -> (s -> Word8 -> Maybe s) -> Int -> [Word8] -> (s, Int)
scanBytes !state _scanner !n [] = (state, n)
scanBytes !state scanner !n (x:more) =
case scanner state x of
Just state' -> scanBytes state' scanner (succ n) more
Nothing -> (state, n)
-- |
-- Efficiently consume as long as the input bytes match the predicate.
-- An inverse of 'takeTill'.
--
{-# INLINE CONLIKE takeWhile #-}
takeWhile :: (Word8 -> Bool) -> Parser ByteString
takeWhile test = takeTill (not . test)
-- |
-- Like 'Data.ByteString.Parser.takeWhile', but requires at least a single byte.
--
{-# INLINE CONLIKE takeWhile1 #-}
takeWhile1 :: (Word8 -> Bool) -> Parser ByteString
takeWhile1 test = Data.ByteString.Parser.takeWhile test `provided` (not . null)
-- |
-- Efficiently consume until a byte matching the predicate is found.
-- An inverse of 'Data.ByteString.Parser.takeWhile'.
--
{-# INLINE CONLIKE takeTill #-}
takeTill :: (Word8 -> Bool) -> Parser ByteString
takeTill test = Parser \inp ->
let n = fromMaybe (length inp) $ findIndex test inp
in Success (unsafeTake n inp) (unsafeDrop n inp)
-- |
-- Same as 'takeTill', but requires at least a single byte.
--
{-# INLINE CONLIKE takeTill1 #-}
takeTill1 :: (Word8 -> Bool) -> Parser ByteString
takeTill1 test = Data.ByteString.Parser.takeTill test `provided` (not . null)
-- |
-- Makes the parser not only return the result, but also the original
-- matched extent.
--
{-# INLINE CONLIKE match #-}
match :: Parser a -> Parser (ByteString, a)
match par = Parser \inp ->
case runParser par inp of
Failure expected more -> Failure expected more
Error reason more len -> Error reason more len
Success res more ->
let n = length more
in Success (BS.take n inp, res) more
-- |
-- Names an extent of the parser.
--
-- When the extent returns a Failure, details are discarded and replaced
-- with the extent as a whole.
--
-- When the extent returns an Error, it is adjusted to cover the whole
-- extent, but the reason is left intact.
--
-- You should strive to make labeled extents as small as possible,
-- approximately of a typical token size. For example:
--
-- @
-- pString = label \"string\" $ pStringContents \`wrap\` char \'\"\'
-- @
--
{-# INLINE CONLIKE label #-}
label :: String -> Parser a -> Parser a
label lbl par = Parser \inp ->
case runParser par inp of
Success res more -> Success res more
Failure _expected _more -> Failure [lbl] inp
Error reason more len ->
let len' = len + (length inp - length more)
in Error reason inp len'
-- |
-- Marks an unlabelel extent of the parser.
--
-- When the extent returns an Error, it is adjusted to cover the whole
-- extent, but the reason is left intact.
--
{-# INLINE CONLIKE extent #-}
extent :: Parser a -> Parser a
extent par = Parser \inp ->
case runParser par inp of
Success res more -> Success res more
Failure expected more -> Failure expected more
Error reason more len ->
let len' = len + (length inp - length more)
in Error reason inp len'
-- |
-- Accept whatever input remains.
--
{-# INLINE takeByteString #-}
takeByteString :: Parser ByteString
takeByteString = Parser \inp -> Success inp mempty
-- |
-- Accepts end of input and fails if we are not there yet.
--
{-# INLINE endOfInput #-}
endOfInput :: Parser ()
endOfInput = Parser \case
inp | null inp -> Success () inp
inp -> Failure ["end of input"] inp
-- |
-- Returns whether we are at the end of the input yet.
--
{-# INLINE atEnd #-}
atEnd :: Parser Bool
atEnd = Parser \inp -> Success (null inp) inp
-- vim:set ft=haskell sw=2 ts=2 et: