{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.ProtoLens.Encoding.Parser
( Parser
, runParser
, atEnd
, isolate
, getWord8
, getWord32le
, getBytes
, (<?>)
) where
import Data.Bits (shiftL, (.|.))
import Data.Word (Word8, Word32)
import Data.ByteString (ByteString, packCStringLen)
import qualified Data.ByteString.Unsafe as B
import Foreign.Ptr
import Foreign.Storable
import System.IO.Unsafe (unsafePerformIO)
import Data.ProtoLens.Encoding.Parser.Internal
runParser :: Parser a -> ByteString -> Either String a
runParser (Parser m) b =
case unsafePerformIO $ B.unsafeUseAsCStringLen b
$ \(p, len) -> m (p `plusPtr` len) (castPtr p) of
ParseSuccess _ x -> Right x
ParseFailure s -> Left s
atEnd :: Parser Bool
atEnd = Parser $ \end pos -> return $ ParseSuccess pos (pos == end)
getWord8 :: Parser Word8
getWord8 = withSized 1 "getWord8: Unexpected end of input" peek
getWord32le :: Parser Word32
getWord32le = withSized 4 "getWord32le: Unexpected end of input" $ \pos -> do
b1 <- fromIntegral <$> peek pos
b2 <- fromIntegral <$> peek (pos `plusPtr'` 1)
b3 <- fromIntegral <$> peek (pos `plusPtr'` 2)
b4 <- fromIntegral <$> peek (pos `plusPtr'` 3)
let f b b' = b `shiftL` 8 .|. b'
return $! f (f (f b4 b3) b2) b1
getBytes :: Int -> Parser ByteString
getBytes n = withSized n "getBytes: Unexpected end of input"
$ \pos -> packCStringLen (castPtr pos, n)
withSized :: Int -> String -> (Ptr Word8 -> IO a) -> Parser a
withSized len message f
| len >= 0 = Parser $ \end pos ->
let pos' = pos `plusPtr'` len
in if pos' > end
then return $ ParseFailure message
else ParseSuccess pos' <$> f pos
| otherwise = fail "withSized: negative length"
{-# INLINE withSized #-}
isolate :: Int -> Parser a -> Parser a
isolate len (Parser m)
| len >= 0 = Parser $ \end pos ->
let end' = pos `plusPtr` len
in if end' > end
then return $ ParseFailure "isolate: unexpected end of input"
else m end' pos
| otherwise = fail "isolate: negative length"
(<?>) :: Parser a -> String -> Parser a
Parser m <?> msg = Parser $ \end p -> wrap <$> m end p
where
wrap (ParseFailure s) = ParseFailure (msg ++ ": " ++ s)
wrap r = r
plusPtr' :: Ptr a -> Int -> Ptr a
plusPtr' = plusPtr