module Data.Repa.Convert.Format.Lists ( -- * ASCII Strings FixAsc (..) , VarAsc (..) , VarString (..)) where import Data.Repa.Convert.Format.Binary import Data.Repa.Convert.Format.Base import Data.Monoid import Data.Word import Data.Char import qualified Foreign.Storable as S import qualified Foreign.Ptr as S import Prelude hiding (fail) --------------------------------------------------------------------------------------------------- -- | Fixed length string. -- -- * When packing, the length of the provided string must match -- the field width, else packing will fail. -- -- * When unpacking, the length of the result will be as set -- by the field width. -- data FixAsc = FixAsc Int deriving (Eq, Show) instance Format FixAsc where type Value (FixAsc) = String fieldCount _ = 1 minSize (FixAsc len) = len fixedSize (FixAsc len) = Just len packedSize (FixAsc len) _ = Just len {-# INLINE minSize #-} {-# INLINE fieldCount #-} {-# INLINE fixedSize #-} {-# INLINE packedSize #-} instance Packable FixAsc where pack (FixAsc len) xs | length xs == len = Packer $ \buf k -> do mapM_ (\(o, x) -> S.pokeByteOff buf o (w8 $ ord x)) $ zip [0 .. len - 1] xs k (S.plusPtr buf len) | otherwise = Packer $ \_ _ -> return Nothing {-# NOINLINE pack #-} unpack (FixAsc len) = Unpacker $ \start end _stop fail eat -> do let lenBuf = S.minusPtr end start if lenBuf < len then fail else do let load_unpackChar o = do x :: Word8 <- S.peekByteOff start o return $ chr $ fromIntegral x {-# INLINE load_unpackChar #-} xs <- mapM load_unpackChar [0 .. len - 1] eat (S.plusPtr start len) xs {-# NOINLINE unpack #-} --------------------------------------------------------------------------------------------------- -- | Variable length raw string (with no quotes). data VarAsc = VarAsc deriving (Eq, Show) instance Format (VarAsc) where type Value VarAsc = String fieldCount _ = 1 minSize _ = 0 fixedSize VarAsc = Nothing packedSize VarAsc xs = Just $ length xs {-# INLINE minSize #-} {-# INLINE fieldCount #-} {-# INLINE fixedSize #-} {-# INLINE packedSize #-} instance Packable VarAsc where pack VarAsc xx = case xx of [] -> mempty (x : xs) -> pack Word8be (w8 $ ord x) <> pack VarAsc xs {-# NOINLINE pack #-} unpack VarAsc = Unpacker $ \start end stop _fail eat -> do (ptr, str) <- unpackAsc start end stop eat ptr str {-# INLINE unpack #-} -- | Unpack a ascii text from the given buffer. unpackAsc :: S.Ptr Word8 -- ^ First byte in buffer. -> S.Ptr Word8 -- ^ First byte after buffer. -> (Word8 -> Bool) -- ^ Detect field deliminator. -> IO (S.Ptr Word8, [Char]) unpackAsc start end stop = go start [] where go !ptr !acc | ptr >= end = return (ptr, reverse acc) | otherwise = do w :: Word8 <- S.peek ptr if stop w then do return (ptr, reverse acc) else do let !ptr' = S.plusPtr ptr 1 go ptr' ((chr $ fromIntegral w) : acc) {-# INLINE unpackAsc #-} --------------------------------------------------------------------------------------------------- -- | Variable length string in double quotes, -- and standard backslash encoding of special characters. data VarString = VarString deriving (Eq, Show) instance Format VarString where type Value VarString = String fieldCount _ = 1 minSize _ = 2 fixedSize _ = Nothing packedSize VarString xs = Just $ length $ show xs {-# INLINE minSize #-} {-# INLINE fieldCount #-} {-# INLINE fixedSize #-} {-# INLINE packedSize #-} instance Packable VarString where -- ISSUE #43: Avoid intermediate lists when packing Ints and Strings. pack VarString xx = pack VarAsc (show xx) {-# INLINE pack #-} unpack VarString = Unpacker $ \start end _stop fail eat -> do r <- unpackString start end case r of Nothing -> fail Just (start', str) -> eat start' str {-# INLINE unpack #-} -- | Unpack a string from the given buffer. unpackString :: S.Ptr Word8 -- ^ First byte in buffer. -> S.Ptr Word8 -- ^ First byte after buffer. -> IO (Maybe (S.Ptr Word8, [Char])) unpackString start end = open start where -- Accept the open quotes. open !ptr | ptr >= end = return $ Nothing | otherwise = do w :: Word8 <- S.peek ptr let !ptr' = S.plusPtr ptr 1 case chr $ fromIntegral w of '"' -> go_body ptr' [] _ -> return Nothing -- Handle the next character in the string. go_body !ptr !acc | ptr >= end = return $ Just (ptr, reverse acc) | otherwise = do w :: Word8 <- S.peek ptr let !ptr' = S.plusPtr ptr 1 case chr $ fromIntegral w of '"' -> return $ Just (ptr', reverse acc) '\\' -> go_escape ptr' acc c -> go_body ptr' (c : acc) -- Handle escaped character. -- The previous character was a '\\' go_escape !ptr !acc | ptr >= end = return Nothing | otherwise = do w :: Word8 <- S.peek ptr let ptr' = S.plusPtr ptr 1 case chr $ fromIntegral w of 'a' -> go_body ptr' ('\a' : acc) 'b' -> go_body ptr' ('\b' : acc) 'f' -> go_body ptr' ('\f' : acc) 'n' -> go_body ptr' ('\n' : acc) 'r' -> go_body ptr' ('\r' : acc) 't' -> go_body ptr' ('\t' : acc) 'v' -> go_body ptr' ('\v' : acc) '\\' -> go_body ptr' ('\\' : acc) '"' -> go_body ptr' ('"' : acc) _ -> return Nothing {-# NOINLINE unpackString #-} --------------------------------------------------------------------------------------------------- w8 :: Integral a => a -> Word8 w8 = fromIntegral {-# INLINE w8 #-}