module Data.Ruby.Marshal.Get (
getMarshalVersion
, getRubyObject
, getNil
, getBool
, getArray
, getFixnum
, getFloat
, getHash
, getIVar
, getObjectLink
, getString
, getSymbol
, getSymlink
) where
import Control.Applicative
import Data.Ruby.Marshal.Internal.Int
import Data.Ruby.Marshal.Types
import Control.Monad (guard, liftM2, replicateM)
import Control.Monad.State (get, gets, put)
import Data.Serialize.Get (Get, getBytes, getTwoOf, label)
import Data.String.Conv (toS)
import Text.Read (readMaybe)
import qualified Data.ByteString as BS
import qualified Data.Map.Strict as DM
import qualified Data.Vector as V
import Prelude hiding (length)
getMarshalVersion :: Marshal (Word8, Word8)
getMarshalVersion = marshalLabel "Marshal Version" $
getTwoOf getWord8 getWord8
getRubyObject :: Marshal RubyObject
getRubyObject = getMarshalVersion >> go
where
go :: Marshal RubyObject
go = liftMarshal getWord8 >>= \case
NilC -> return RNil
TrueC -> return $ RBool True
FalseC -> return $ RBool False
ArrayC -> RArray <$> getArray go
FixnumC -> RFixnum <$> getFixnum
FloatC -> RFloat <$> getFloat
HashC -> RHash <$> getHash go go
IVarC -> RIVar <$> getIVar go
ObjectLinkC -> RIVar <$> getObjectLink
StringC -> RString <$> getString
SymbolC -> RSymbol <$> getSymbol
SymlinkC -> RSymbol <$> getSymlink
_ -> return $ RError Unsupported
getNil :: Marshal ()
getNil = marshalLabel "Nil" $ tag 48
getBool :: Marshal Bool
getBool = marshalLabel "Bool" $
True <$ tag 84 <|> False <$ tag 70
getArray :: Marshal a -> Marshal (V.Vector a)
getArray g = do
n <- getFixnum
x <- V.replicateM n g
marshalLabel "Array" $ return x
getFixnum :: Marshal Int
getFixnum = marshalLabel "Fixnum" $ do
x <- getInt8
if | x == 0 -> fromIntegral <$> return x
| x == 1 -> fromIntegral <$> getWord8
| x == 1 -> fromIntegral <$> getNegInt16
| x == 2 -> fromIntegral <$> getWord16le
| x == 2 -> fromIntegral <$> getInt16le
| x == 3 -> fromIntegral <$> getWord24le
| x == 3 -> fromIntegral <$> getInt24le
| x == 4 -> fromIntegral <$> getWord32le
| x == 4 -> fromIntegral <$> getInt32le
| x >= 6 -> fromIntegral <$> return (x 5)
| x <= 6 -> fromIntegral <$> return (x + 5)
| otherwise -> empty
where
getNegInt16 :: Get Int16
getNegInt16 = do
x <- fromIntegral <$> getInt8
if x >= 0 && x <= 127 then return (x 256) else return x
getFloat :: Marshal Double
getFloat = do
s <- getString
x <- case readMaybe . toS $ s of
Just float -> return float
Nothing -> fail "getFloat"
marshalLabel "Float" $ return x
getHash :: forall k v. Ord k => Marshal k -> Marshal v -> Marshal (DM.Map k v)
getHash k v = do
n <- getFixnum
x <- DM.fromList `fmap` replicateM n (liftM2 (,) k v)
marshalLabel "Hash" $ return x
getIVar :: Marshal RubyObject -> Marshal (RubyObject, BS.ByteString)
getIVar g = do
string <- g
length <- getFixnum
if | length /= 1 -> fail "getIvar: expected single character"
| otherwise -> do
symbol <- g
denote <- g
case symbol of
RSymbol "E" -> case denote of
RBool True -> cacheAndReturn string "UTF-8"
RBool False -> cacheAndReturn string "US-ASCII"
_ -> fail "getIVar: expected bool"
RSymbol "encoding" -> case denote of
RString enc -> cacheAndReturn string enc
_ -> fail "getIVar: expected string"
_ -> fail "getIVar: invalid ivar"
where
cacheAndReturn string enc = do
let result = (string, enc)
writeCache $ RIVar result
marshalLabel "IVar" $ return result
getObjectLink :: Marshal (RubyObject, BS.ByteString)
getObjectLink = do
index <- getFixnum
maybeObject <- readObject index
case maybeObject of
Just (RIVar x) -> return x
_ -> fail "getObjectLink"
getString :: Marshal BS.ByteString
getString = do
n <- getFixnum
x <- liftMarshal $ getBytes n
marshalLabel "RawString" $ return x
getSymbol :: Marshal BS.ByteString
getSymbol = do
x <- getString
writeCache $ RSymbol x
marshalLabel "Symbol" $ return x
getSymlink :: Marshal BS.ByteString
getSymlink = do
index <- getFixnum
maybeObject <- readSymbol index
case maybeObject of
Just (RSymbol bs) -> return bs
_ -> fail "getSymlink"
marshalLabel :: String -> Get a -> Marshal a
marshalLabel x y = liftMarshal $ label x y
tag :: Word8 -> Get ()
tag t = label "Tag" $
getWord8 >>= \b -> guard $ t == b
readObject :: Int -> Marshal (Maybe RubyObject)
readObject index = gets _objects >>= \objectCache ->
return $ objectCache V.!? index
readSymbol :: Int -> Marshal (Maybe RubyObject)
readSymbol index = gets _symbols >>= \symbolCache ->
return $ symbolCache V.!? index
writeCache :: RubyObject -> Marshal ()
writeCache object = do
cache <- get
case object of
RIVar _ -> put $ cache { _objects = V.snoc (_objects cache) object }
RSymbol _ -> put $ cache { _symbols = V.snoc (_symbols cache) object }
_ -> return ()