packages feed

asil-1.2: src/ParseUtil.hs

-- | 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})