{-# LANGUAGE MagicHash, CPP, MultiParamTypeClasses, OverloadedStrings, TypeSynonymInstances , FlexibleInstances, GeneralizedNewtypeDeriving, BangPatterns, TypeOperators, KindSignatures, DefaultSignatures, FlexibleInstances, TypeSynonymInstances, FlexibleContexts, ScopedTypeVariables #-} -- | Handling of Javascript-native binary blobs. -- -- Generics borrowed from the binary package by Lennart Kolmodin (released under BSD3) module Haste.Binary ( module Haste.Binary.Put, module Haste.Binary.Get, MonadBlob (..), Binary (..), getBlobText, Blob, BlobData, blobSize, blobDataSize, toByteString, fromByteString, toBlob, strToBlob, encode, decode, decodeBlob )where import Data.Int import Data.Word import Data.Char import qualified Haste.JSString as J (length) import Haste.Prim import Haste.Concurrent import Haste.Foreign hiding (get) import Haste.Binary.Types import Haste.Binary.Put import Haste.Binary.Get #if __GLASGOW_HASKELL__ < 710 import Control.Applicative #endif import GHC.Generics import Data.Bits #ifndef __HASTE__ import qualified Data.ByteString.Lazy.Char8 as BS (unpack) #endif class Monad m => MonadBlob m where -- | Retrieve the raw data from a blob. getBlobData :: Blob -> m BlobData -- | Interpret a blob as UTF-8 text, as a JSString. getBlobText' :: Blob -> m JSString -- | Interpret a blob as UTF-8 text. getBlobText :: MonadBlob m => Blob -> m String getBlobText b = getBlobText' b >>= return . fromJSStr instance MonadBlob CIO where #ifdef __HASTE__ getBlobData b = do res <- newEmptyMVar liftIO $ convertBlob b (mkBlobData res (blobSize b)) takeMVar res where mkBlobData res len x = concurrent $ do putMVar res (BlobData 0 len x) convertBlob :: Blob -> (JSAny -> IO ()) -> IO () convertBlob = ffi "(function(b,cb){var r=new FileReader();r.onload=function(){cb(new DataView(r.result));};r.readAsArrayBuffer(b);})" getBlobText' b = do res <- newEmptyMVar liftIO $ convertBlob b (concurrent . putMVar res) takeMVar res where convertBlob :: Blob -> (JSString -> IO ()) -> IO () convertBlob = ffi "(function(b,cb){var r=new FileReader();r.onload=function(){cb(r.result);};r.readAsText(b);})" #else getBlobData (Blob b) = return (BlobData b) getBlobText' (Blob b) = return . toJSStr $ BS.unpack b #endif -- | Somewhat efficient serialization/deserialization to/from binary Blobs. -- The layout of the binaries produced/read by get/put and encode/decode may -- change between versions. If you need a stable binary format, you should -- make your own using the primitives in Haste.Binary.Get/Put. class Binary a where get :: Get a put :: a -> Put default put :: (Generic a, GBinary (Rep a)) => a -> Put put = gput . from default get :: (Generic a, GBinary (Rep a)) => Get a get = to `fmap` gget -- | Generic version class GBinary f where gput :: f t -> Put gget :: Get (f t) instance Binary Word8 where put = putWord8 get = getWord8 instance Binary Word16 where put = putWord16le get = getWord16le instance Binary Word32 where put = putWord32le get = getWord32le instance Binary Int8 where put = putInt8 get = getInt8 instance Binary Int16 where put = putInt16le get = getInt16le instance Binary Int32 where put = putInt32le get = getInt32le instance Binary Int where put = putInt32le . fromIntegral get = fromIntegral <$> getInt32le instance Binary Float where put = putFloat32le get = getFloat32le instance Binary Double where put = putFloat64le get = getFloat64le instance (Binary a, Binary b) => Binary (a, b) where put (a, b) = put a >> put b get = do a <- get b <- get return (a, b) instance Binary a => Binary (Maybe a) where put (Just x) = putWord8 1 >> put x put _ = putWord8 0 get = do tag <- getWord8 case tag of 0 -> return Nothing 1 -> Just <$> get _ -> fail "Wrong constructor tag when reading Maybe value!" instance (Binary a, Binary b) => Binary (Either a b) where put (Left x) = putWord8 0 >> put x put (Right x) = putWord8 1 >> put x get = do tag <- getWord8 case tag of 0 -> Left <$> get 1 -> Right <$> get _ -> fail "Wrong constructor tag when reading Either value!" instance Binary () where put _ = return () get = return () instance Binary a => Binary [a] where put xs = do putWord32le (fromIntegral $ length xs) mapM_ put xs get = do len <- getWord32le getList len [] where getList 0 xs = return $ reverse xs getList n xs = get >>= \x -> getList (n-1) (x:xs) instance Binary JSString where {-# NOINLINE put #-} put s = do putWord32le $ fromIntegral $ J.length s putJSString s {-# NOINLINE get #-} get = get >>= getJSString instance Binary Blob where {-# NOINLINE put #-} put b = do put (blobSize b) putBlob b {-# NOINLINE get #-} get = do sz <- get bd <- getBytes sz return $ toBlob bd instance Binary Char where put = put . ord get = get >>= \x -> case chr x of !x' -> return x' -- | Encode any serializable data into a 'Blob'. encode :: Binary a => a -> Blob encode x = runPut (put x) -- | Decode any deserializable data from a 'BlobData'. decode :: Binary a => BlobData -> Either String a decode = runGet get -- | Decode a 'Blob' into some deserializable value, inconveniently locked up -- inside the 'CIO' monad (or any other concurrent monad) due to the somewhat -- special way JavaScript uses to deal with binary data. decodeBlob :: (MonadBlob m, Binary a) => Blob -> m (Either String a) decodeBlob b = getBlobData b >>= return . decode -- Type without constructors instance GBinary V1 where gput _ = return () gget = return undefined -- Constructor without arguments instance GBinary U1 where gput U1 = return () gget = return U1 -- Product: constructor with parameters instance (GBinary a, GBinary b) => GBinary (a :*: b) where gput (x :*: y) = gput x >> gput y gget = (:*:) <$> gget <*> gget -- Metadata (constructor name, etc) instance GBinary a => GBinary (M1 i c a) where gput = gput . unM1 gget = M1 <$> gget -- Constants, additional parameters, and rank-1 recursion instance Binary a => GBinary (K1 i a) where gput = put . unK1 gget = K1 <$> get -- Borrowed from the cereal package. -- The following GBinary instance for sums has support for serializing -- types with up to 2^64-1 constructors. It will use the minimal -- number of bytes needed to encode the constructor. For example when -- a type has 2^8 constructors or less it will use a single byte to -- encode the constructor. If it has 2^16 constructors or less it will -- use two bytes, and so on till 2^64-1. -- -- NB: changed to 2^32-1 constructors #define GUARD(WORD) (size - 1) <= fromIntegral (maxBound :: WORD) #define PUTSUM(WORD) GUARD(WORD) = putSum (0 :: WORD) (fromIntegral size) #define GETSUM(WORD) GUARD(WORD) = (get :: Get WORD) >>= checkGetSum (fromIntegral size) instance ( GSum a, GSum b , GBinary a, GBinary b , SumSize a, SumSize b) => GBinary (a :+: b) where gput | PUTSUM(Word8) | PUTSUM(Word16) | PUTSUM(Word32) -- | PUTSUM(Word64) | otherwise = sizeError "encode" size where size = unTagged (sumSize :: Tagged (a :+: b) Word32) {-# INLINE gput #-} gget | GETSUM(Word8) | GETSUM(Word16) | GETSUM(Word32) -- | GETSUM(Word64) | otherwise = sizeError "decode" size where size = unTagged (sumSize :: Tagged (a :+: b) Word32) {-# INLINE gget #-} sizeError :: Show size => String -> size -> error sizeError s size = error $ "Can't " ++ s ++ " a type with " ++ show size ++ " constructors" ------------------------------------------------------------------------ checkGetSum :: (Ord word, Num word, Bits word, GSum f) => word -> word -> Get (f a) checkGetSum size code | code < size = getSum code size | otherwise = fail "Unknown encoding for constructor" {-# INLINE checkGetSum #-} class GSum f where getSum :: (Ord word, Num word, Bits word) => word -> word -> Get (f a) putSum :: (Num w, Bits w, Binary w) => w -> w -> f a -> Put instance (GSum a, GSum b, GBinary a, GBinary b) => GSum (a :+: b) where getSum !code !size | code < sizeL = L1 <$> getSum code sizeL | otherwise = R1 <$> getSum (code - sizeL) sizeR where sizeL = size `shiftR` 1 sizeR = size - sizeL {-# INLINE getSum #-} putSum !code !size s = case s of L1 x -> putSum code sizeL x R1 x -> putSum (code + sizeL) sizeR x where sizeL = size `shiftR` 1 sizeR = size - sizeL {-# INLINE putSum #-} instance GBinary a => GSum (C1 c a) where getSum _ _ = gget {-# INLINE getSum #-} putSum !code _ x = put code *> gput x {-# INLINE putSum #-} ------------------------------------------------------------------------ class SumSize f where sumSize :: Tagged f Word32 newtype Tagged (s :: * -> *) b = Tagged {unTagged :: b} instance (SumSize a, SumSize b) => SumSize (a :+: b) where sumSize = Tagged $ unTagged (sumSize :: Tagged a Word32) + unTagged (sumSize :: Tagged b Word32) instance SumSize (C1 c a) where sumSize = Tagged 1