-- | Framework code for the parser -- -- Rewritten to use Data.Binary. -- See revision 9 for the UULIB version. module ParseUtil ( module Control.Monad , module Data.Bits , module Data.Word , module Data.Binary.Get , module Control.Applicative , pNullString, pString , pCount1U30, pCountU30, pCount , runParser , pDecompress , pPos, pEnd , pD64, pW32, pS32, pU32, pU30, pS24, pU16, pU8, pByte , pTable , pInfo, (), pInfo' , Parser, parseIt, parseAny , Bit, takeBits, takeBit, padded ) where import Control.Applicative import Control.Monad import Control.Monad.State.Strict import Data.Array.IArray import Data.Bits import Data.Binary.Get import Data.Binary.IEEE754 import Data.ByteString.Lazy(ByteString) import qualified Data.ByteString.Lazy as L import qualified Data.ByteString as B import qualified Codec.Compression.Zlib as Z import Data.Word import System.IO import Debug.Trace -- | Parser type. type Parser a = Get a parseIt :: Parser a -> FilePath -> IO a parseIt p = parseBase $ (\str -> (str, p)) parseBase :: (L.ByteString -> (L.ByteString, Parser a)) -> FilePath -> IO a parseBase f = fmap (uncurry (flip runParser) . f . L.fromChunks . return) . B.readFile parseAny :: [L.ByteString -> Maybe (L.ByteString, Parser a)] -> FilePath -> IO a parseAny alts = parseBase findAlts where findAlts path = foldr (\f r -> maybe r id $ f path) (error ("no parser applicable to input")) alts {-# INLINE parseIt #-} {-# INLINE parseBase #-} {-# INLINE parseAny #-} -- | Expects a given byte to be present in the input. pByte :: Word8 -> Parser Word8 pByte b = do b' <- getWord8 if b == b' then return b' else do pos <- bytesRead fail ("expecting byte " ++ show b ++ " at " ++ show pos ++ ", but found: " ++ show b') -- | Parses an 8-bits value pU8 :: Parser Word8 pU8 = getWord8 -- | Parses a 16-bits value in little endian pU16 :: Parser Word16 pU16 = getWord16le -- | Parses a 24-bits value in little endian pS24 :: Parser Word32 pS24 = do w1 <- fmap fromIntegral pU8 w2 <- fmap fromIntegral pU8 w3 <- fmap fromIntegral pU8 let w4 = if w3 `testBit` 7 -- most significant bit (sign bit) set then 0xFF000000 -- sign extend ones else 0x00000000 -- sign extend zeros return (w4 .|. shiftL w3 16 .|. shiftL w2 8 .|. w1) pU30 :: Parser Word32 pU30 = pVarLength False pU32 :: Parser Word32 pU32 = pVarLength False pS32 :: Parser Word32 pS32 = pVarLength True {-# INLINE pU8 #-} {-# INLINE pU30 #-} -- | Parses a variable length 32 bits value. -- The MSB of each byte indicates if the next byte is also part of the value (max 5). -- The representation is in little endian. pVarLength :: Bool -> Parser Word32 pVarLength isSigned = accept' 1 where accept' n = do b1 <- pU8 if n == 5 || not (b1 `testBit` 7) then -- this is the last byte read. Here we need to consider sign extension. let b | isSigned && n < 5 && b1 `testBit` 6 = 0xFFFFFF80 -- last bit is stored sign extended | otherwise = 0x00000000 in return (b .|. fromIntegral b1) else do v2 <- accept' (n+1) let v1 = clearBit (fromIntegral b1) 7 return (shiftL v2 7 .|. v1) {-# INLINE pVarLength #-} -- | 32 bits fixed size value (little endian). pW32 :: Parser Word32 pW32 = getWord32le -- | IEEE754 floating point value. pD64 :: Parser Double pD64 = getFloat64le -- | Returns the current position in the input. pPos :: Parser Int pPos = fmap fromIntegral $ bytesRead -- | Decompress a gzipped stream. pDecompress :: Word32 -> Parser a -> Parser a pDecompress length p = do s <- getRemainingLazyByteString let s' = Z.decompressWith Z.defaultDecompressParams { Z.decompressBufferSize = fromIntegral length } s x = runParser p s' return x {-# INLINE pDecompress #-} pEnd :: Parser () pEnd = do rem <- remaining if rem > 0 then fail (show rem ++ " bytes remain unparsed") else return () -- | Runs the parser. runParser :: Parser a -> ByteString -> a runParser = runGet -- Useful derived combinators -- | Parses a given number of items. pCount :: Parser a -> Int -> Parser [a] pCount p n | n < 0 = fail ("pCount: " ++ show n ++ " < 0.") | otherwise = pCount' p n pCount' :: Parser a -> Int -> Parser [a] pCount' p n = replicateM n p -- | First parses a counter, then an equal number of items. pCountU30 :: Parser a -> Parser [a] pCountU30 p = fmap fromIntegral pU30 >>= pCount p -- | First parses a counter, then parses one item less. pCount1U30 :: Parser a -> Parser [a] pCount1U30 p = fmap fromIntegral pU30 >>= (\n -> pCount p (0 `max` (n - 1))) {-# INLINE pCount #-} {-# INLINE pCount' #-} {-# INLINE pCountU30 #-} {-# INLINE pCount1U30 #-} -- | Parses a string (fixed-length) pString :: Parser ByteString pString = fmap L.pack $ pCountU30 pU8 "string" -- | Parses a string (null-terminated) pNullString :: Parser ByteString pNullString = fmap L.pack run where run = pU8 >>= \c -> if c == 0 then return [] else fmap (c:) run -- Dispatch based on the next symbol. -- Note: relies on let/lambda-floating for efficiency. pTable :: [(Word8, Parser a)] -> Parser a pTable entries = let keys = map fst entries failures = [ (x, fail ("No alternative for byte: " ++ show x)) | x <- [0 .. 255], not (x `elem` keys) ] a = mkByteArray (failures ++ entries) in do x <- pU8 a ! x {-# INLINE pTable #-} mkByteArray :: [(Word8,a)] -> Array Word8 a mkByteArray = array (0,0xFF) pInfo :: String -> Parser () pInfo = const (return ()) -- pInfo = pInfo' pInfo' :: String -> Parser () pInfo' s = do pos <- pPos trace ("@" ++ show pos ++ ": " ++ s) (return ()) infix 2 () :: Parser a -> String -> Parser a p s = do pInfo ("started: " ++ s) x <- p pInfo ("finished: " ++ s) return x {-# INLINE () #-} {-# INLINE pInfo #-} -- | Monad for parsing bitwise datastructures -- on top of the binary Get interface. -- Reads in a stream of bits, by taking bits -- from individual bytes. The bits per byte -- are provided in big-endian. -- When you request a variable-sized word, -- bits are taken from the stream. The last -- bit read will be the least significant -- bit in the delivered word. type Bit a = StateT BitState Get a data BitState = BitState { lastByte :: !Word8, lastIndex :: !Int } padded :: Bit a -> Get a padded m = evalStateT m (BitState 0 8) takeBits :: Int -> Bit Word32 takeBits n | n < 0 = error "takeBits: n < 0" | otherwise = takeBits' 0 n takeBits' :: Word32 -> Int -> Bit Word32 takeBits' acc 0 = return acc takeBits' acc n = do b <- fmap toBit takeBit takeBits' (shiftL acc 1 .|. b) (n-1) where toBit False = 0 toBit True = 1 takeBit :: Bit Bool takeBit = do i <- validIndex w <- gets lastByte increment return $! testBit w $ 7-i where validIndex = do i <- gets lastIndex if i > 7 then do w <- lift pU8 put (BitState w 0) return 0 else return i increment = do modify (\s -> s { lastIndex = 1 + lastIndex s})