module Codec.Bytable (
Bytable(..), fromByteString, toByteString,
Parsable(..),
BytableM(..), evalBytableM, execBytableM,
head, take, null, list, addLen,
) where
import Prelude hiding (take, head, null)
import Control.Applicative(Applicative(..), (<$>))
import Control.Monad (unless, liftM, ap)
import Data.Word (Word8)
import qualified Data.ByteString as BS
data BytableM a = BytableM {
runBytableM :: BS.ByteString -> Either String (a, BS.ByteString) }
evalBytableM :: BytableM a -> BS.ByteString -> Either String a
evalBytableM m bs = fst <$> runBytableM m bs
execBytableM :: BytableM a -> BS.ByteString -> Either String BS.ByteString
execBytableM m bs = snd <$> runBytableM m bs
instance Monad BytableM where
return x = BytableM $ \bs -> Right (x, bs)
BytableM m1 >>= f = BytableM $ \bs -> do
(x, bs') <- m1 bs
runBytableM (f x) bs'
fail = BytableM . const . Left
instance Functor BytableM where
fmap = liftM
instance Applicative BytableM where
pure = return
(<*>) = ap
class Bytable b where
decode :: BS.ByteString -> Either String b
encode :: b -> BS.ByteString
fromByteString :: Bytable b => BS.ByteString -> Either String b
fromByteString = decode
toByteString :: Bytable b => b -> BS.ByteString
toByteString = encode
instance Bytable Word8 where
decode "" = Right 0
decode bs
| [w] <- BS.unpack bs = Right w
decode _ = Left "Codec.Bytable.BigEndian: Bytable Word8: too large"
encode = BS.pack . (: [])
instance Bytable BS.ByteString where
decode = Right
encode = id
class Parsable p where
parse :: BytableM p
head :: BytableM Word8
head = BytableM $ \bs -> case BS.uncons bs of
Just (h, t) -> Right (h, t)
_ -> Left "Bytable.head: null"
take :: Bytable b => Int -> BytableM b
take n = BytableM $ \bs -> do
unless (BS.length bs >= n) . Left $
"Bytable.take: length shorter than " ++ show n
let (x, bs') = BS.splitAt n bs
(, bs') <$> decode x
null :: BytableM Bool
null = BytableM $ \bs -> Right (BS.null bs, bs)
list :: Int -> BytableM b -> BytableM [b]
list n m = do
bs <- take n
case evalBytableM lst bs of
Right xs -> return xs
Left msg -> fail msg
where
lst = do
e <- null
if e then return [] else (:) <$> m <*> lst
addLen :: (Bytable n, Num n) => n -> BS.ByteString -> BS.ByteString
addLen t bs =
encode (fromIntegral (BS.length bs) `asTypeOf` t) `BS.append` bs