bliplib 0.1.0 → 0.2.0
raw patch · 2 files changed
+141/−31 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Blip.Marshal: Long :: !Integer -> PyObject
+ Blip.Marshal: co_generator :: CodeObjectFlagMask
+ Blip.Marshal: co_nested :: CodeObjectFlagMask
+ Blip.Marshal: co_newlocals :: CodeObjectFlagMask
+ Blip.Marshal: co_nofree :: CodeObjectFlagMask
+ Blip.Marshal: co_optimized :: CodeObjectFlagMask
+ Blip.Marshal: co_varargs :: CodeObjectFlagMask
+ Blip.Marshal: co_varkeywords :: CodeObjectFlagMask
+ Blip.Marshal: long_value :: PyObject -> !Integer
+ Blip.Marshal: type CodeObjectFlagMask = Word32
- Blip.Marshal: Code :: Word32 -> Word32 -> Word32 -> Word32 -> Word32 -> PyObject -> PyObject -> PyObject -> PyObject -> PyObject -> PyObject -> PyObject -> PyObject -> Word32 -> PyObject -> PyObject
+ Blip.Marshal: Code :: !Word32 -> !Word32 -> !Word32 -> !Word32 -> !Word32 -> !PyObject -> !PyObject -> !PyObject -> !PyObject -> !PyObject -> !PyObject -> !PyObject -> !PyObject -> !Word32 -> !PyObject -> PyObject
- Blip.Marshal: Complex :: Double -> Double -> PyObject
+ Blip.Marshal: Complex :: !Double -> !Double -> PyObject
- Blip.Marshal: Float :: Double -> PyObject
+ Blip.Marshal: Float :: !Double -> PyObject
- Blip.Marshal: Int :: Word32 -> PyObject
+ Blip.Marshal: Int :: !Word32 -> PyObject
- Blip.Marshal: String :: ByteString -> PyObject
+ Blip.Marshal: String :: !ByteString -> PyObject
- Blip.Marshal: Tuple :: [PyObject] -> PyObject
+ Blip.Marshal: Tuple :: ![PyObject] -> PyObject
- Blip.Marshal: Unicode :: String -> PyObject
+ Blip.Marshal: Unicode :: !String -> PyObject
- Blip.Marshal: argcount :: PyObject -> Word32
+ Blip.Marshal: argcount :: PyObject -> !Word32
- Blip.Marshal: cellvars :: PyObject -> PyObject
+ Blip.Marshal: cellvars :: PyObject -> !PyObject
- Blip.Marshal: code :: PyObject -> PyObject
+ Blip.Marshal: code :: PyObject -> !PyObject
- Blip.Marshal: consts :: PyObject -> PyObject
+ Blip.Marshal: consts :: PyObject -> !PyObject
- Blip.Marshal: elements :: PyObject -> [PyObject]
+ Blip.Marshal: elements :: PyObject -> ![PyObject]
- Blip.Marshal: filename :: PyObject -> PyObject
+ Blip.Marshal: filename :: PyObject -> !PyObject
- Blip.Marshal: firstlineno :: PyObject -> Word32
+ Blip.Marshal: firstlineno :: PyObject -> !Word32
- Blip.Marshal: flags :: PyObject -> Word32
+ Blip.Marshal: flags :: PyObject -> !Word32
- Blip.Marshal: float_value :: PyObject -> Double
+ Blip.Marshal: float_value :: PyObject -> !Double
- Blip.Marshal: freevars :: PyObject -> PyObject
+ Blip.Marshal: freevars :: PyObject -> !PyObject
- Blip.Marshal: imaginary :: PyObject -> Double
+ Blip.Marshal: imaginary :: PyObject -> !Double
- Blip.Marshal: int_value :: PyObject -> Word32
+ Blip.Marshal: int_value :: PyObject -> !Word32
- Blip.Marshal: kwonlyargcount :: PyObject -> Word32
+ Blip.Marshal: kwonlyargcount :: PyObject -> !Word32
- Blip.Marshal: lnotab :: PyObject -> PyObject
+ Blip.Marshal: lnotab :: PyObject -> !PyObject
- Blip.Marshal: name :: PyObject -> PyObject
+ Blip.Marshal: name :: PyObject -> !PyObject
- Blip.Marshal: names :: PyObject -> PyObject
+ Blip.Marshal: names :: PyObject -> !PyObject
- Blip.Marshal: nlocals :: PyObject -> Word32
+ Blip.Marshal: nlocals :: PyObject -> !Word32
- Blip.Marshal: real :: PyObject -> Double
+ Blip.Marshal: real :: PyObject -> !Double
- Blip.Marshal: stacksize :: PyObject -> Word32
+ Blip.Marshal: stacksize :: PyObject -> !Word32
- Blip.Marshal: string :: PyObject -> ByteString
+ Blip.Marshal: string :: PyObject -> !ByteString
- Blip.Marshal: unicode :: PyObject -> String
+ Blip.Marshal: unicode :: PyObject -> !String
- Blip.Marshal: varnames :: PyObject -> PyObject
+ Blip.Marshal: varnames :: PyObject -> !PyObject
Files
- bliplib.cabal +1/−1
- src/Blip/Marshal.hs +140/−30
bliplib.cabal view
@@ -1,5 +1,5 @@ Name: bliplib-Version: 0.1.0+Version: 0.2.0 Synopsis: Support code for Blip. Homepage: https://github.com/bjpop/blip License: BSD3
src/Blip/Marshal.hs view
@@ -12,25 +12,30 @@ -- ----------------------------------------------------------------------------- -module Blip.Marshal (readPyc, writePyc, PycFile (..), PyObject (..)) where+module Blip.Marshal+ ( readPyc, writePyc, PycFile (..), PyObject (..), CodeObjectFlagMask+ , co_optimized, co_newlocals, co_varargs, co_varkeywords+ , co_nested, co_generator, co_nofree )+ where import Blip.MarshalDouble (bytesToDouble, doubleToBytes) import Blip.Bytecode (decode, BytecodeSeq (..)) import Blip.Pretty (Pretty (..), prettyList, prettyTuple) import Control.Applicative ((<$>), (<*>))-import Data.Map as Map hiding (map, size)-import Data.Word (Word8, Word32)+import Data.Map as Map hiding (map, size, empty)+import Data.Word (Word8, Word16, Word32) import Control.Monad.Error (ErrorT (..), lift, replicateM) import System.IO (Handle) import qualified Data.ByteString.Lazy as B (ByteString, hGetContents, unpack, hPutStr, length) import Data.ByteString.Lazy.UTF8 as UTF8 (toString, fromString) -import Data.Binary.Get (Get, runGet, getLazyByteString, getWord32le, getWord8)-import Data.Binary.Put (PutM, putWord32le, putLazyByteString, runPutM, putWord8)-import Data.Int (Int64)+import Data.Binary.Get (Get, runGet, getLazyByteString, getWord32le, getWord8, getWord16le)+import Data.Binary.Put (PutM, putWord32le, putLazyByteString, runPutM, putWord8, putWord16le)+import Data.Bits ((.&.))+import Data.Int (Int64, Int32) import Data.Char (chr, ord) import Text.PrettyPrint- (text, (<+>), ($$), (<>), Doc , vcat, int, equals, doubleQuotes)+ (text, (<+>), ($$), (<>), Doc , vcat, int, equals, doubleQuotes, hsep, empty) data PycFile = PycFile@@ -50,38 +55,40 @@ data PyObject = Code- { argcount :: Word32 -- #arguments, except *args- , kwonlyargcount :: Word32 -- #keyword only arguments - , nlocals :: Word32 -- #local variables- , stacksize :: Word32 -- #entries needed for evaluation stack- , flags :: Word32 -- CO_..., see below- , code :: PyObject -- instruction opcodes (a string)- , consts :: PyObject -- list (constants used) XXX seems to be a tuple- , names :: PyObject -- list of strings (names used)- , varnames :: PyObject -- tuple of strings (local variable names)- , freevars :: PyObject -- tuple of strings (free variable names)- , cellvars :: PyObject -- tuple of strings (cell variable names)- , filename :: PyObject -- unicode (where it was loaded from)- , name :: PyObject -- unicode (name, for reference)- , firstlineno :: Word32 -- first source line number- , lnotab :: PyObject -- string (encoding addr<->lineno mapping)+ { argcount :: !Word32 -- #arguments, except *args+ , kwonlyargcount :: !Word32 -- #keyword only arguments + , nlocals :: !Word32 -- #local variables+ , stacksize :: !Word32 -- #entries needed for evaluation stack+ , flags :: !Word32 -- CO_..., see below+ , code :: !PyObject -- instruction opcodes (a string)+ , consts :: !PyObject -- list (constants used) XXX seems to be a tuple+ , names :: !PyObject -- list of strings (names used)+ , varnames :: !PyObject -- tuple of strings (local variable names)+ , freevars :: !PyObject -- tuple of strings (free variable names)+ , cellvars :: !PyObject -- tuple of strings (cell variable names)+ , filename :: !PyObject -- unicode (where it was loaded from)+ , name :: !PyObject -- unicode (name, for reference)+ , firstlineno :: !Word32 -- first source line number+ , lnotab :: !PyObject -- string (encoding addr<->lineno mapping) }- | String { string :: B.ByteString }- | Tuple { elements :: [PyObject] }- | Int { int_value :: Word32 }- | Float { float_value :: Double }+ | String { string :: !B.ByteString }+ | Tuple { elements :: ![PyObject] }+ | Int { int_value :: !Word32 } -- XXX should that be Int32?+ | Float { float_value :: !Double } | None | Ellipsis- | Unicode { unicode :: String } -- should be decoded into a String+ | Unicode { unicode :: !String } -- should be decoded into a String | TrueObj | FalseObj- | Complex { real :: Double, imaginary :: Double }+ | Complex { real :: !Double, imaginary :: !Double }+ | Long { long_value :: !Integer } deriving (Eq, Ord, Show) instance Pretty PyObject where pretty (String {..}) = doubleQuotes $ pretty string pretty (Tuple {..}) = prettyTuple $ map pretty elements pretty (Int {..}) = pretty int_value+ pretty (Long {..}) = pretty long_value pretty (Float {..}) = pretty float_value pretty None = text "None" pretty Ellipsis = text "..."@@ -93,7 +100,7 @@ text "kwonlyargcount =" <+> pretty kwonlyargcount $$ text "nlocals =" <+> pretty nlocals $$ text "stacksize =" <+> pretty stacksize $$- text "flags =" <+> pretty flags $$+ text "flags =" <+> prettyFlags flags $$ text "varnames =" <+> pretty varnames $$ text "freevars =" <+> pretty freevars $$ text "cellvars =" <+> pretty cellvars $$@@ -119,8 +126,13 @@ prettyLnotab :: PyObject -> Doc prettyLnotab obj = case obj of- String {..} -> prettyList $ map pretty $ B.unpack string+ String {..} -> prettyList $ map pretty $ pairs $ B.unpack string _other -> text ("lnotab not a string: " ++ show obj)+ where+ pairs :: [Word8] -> [(Word8, Word8)]+ pairs [] = []+ pairs [_] = error $ "Odd numbered linenotab"+ pairs (nextCode:nextLine:rest) = (nextCode, nextLine) : pairs rest readPyc :: Handle -> IO PycFile readPyc handle = do@@ -157,6 +169,7 @@ UNICODE -> readUnicodeObject BINARY_FLOAT -> readFloatObject BINARY_COMPLEX -> readComplexObject+ LONG -> readLongObject _other -> error ("readObject: unsupported object type: " ++ show object_type) writeObject :: PyObject -> PutData@@ -173,6 +186,7 @@ FalseObj -> putU8 $ encodeObjectType FALSE Float {..} -> writeFloatObject object Complex {..} -> writeComplexObject object+ Long {..} -> writeLongObject object writeObjectType :: ObjectType -> PutData writeObjectType = putU8 . encodeObjectType@@ -256,6 +270,53 @@ putBS uc writeUnicodeObject other = error $ "writeUnicodeObject called on non unicode object: " ++ show other +longDigitBase :: Integer+longDigitBase = 2^(15::Integer)++readLongObject :: GetData PyObject+readLongObject = do+ -- Read the len as a signed 32bit integer.+ -- The sign tells us whether the number is positive or negative.+ -- The magnitude tells us how many digits are in the number.+ -- Digits are stored in base 2^15, in 16 bit chunks.+ len <- getI32 + if len == 0+ then return $ Long 0+ else do+ -- read 'len' digits+ base15digits <- replicateM (fromIntegral (abs len)) getU16+ -- pair each digit with its exponent+ -- [(d0, 0), (d1, 1), ... (dn, n)]+ let digitsExponents = zip (map fromIntegral base15digits) [(0::Integer) ..]+ -- (d0 * base^0) + (d1 * base^1) + ... + ... (dn * base^n)+ val = sum [(longDigitBase ^ exp) * digit | (digit, exp) <- digitsExponents]+ -- Check if the result is positive or negative based on the sign of len.+ if len < 0+ then return $! Long $! negate val+ else return $! Long val++writeLongObject :: PyObject -> PutData+writeLongObject (Long {..}) = do+ writeObjectType LONG + case compare long_value 0 of+ EQ -> putI32 0+ GT -> do + putI32 numDigits + mapM_ putU16 digits+ LT -> do+ putI32 $ negate numDigits + mapM_ putU16 digits+ where+ digits :: [Word16]+ digits = getDigits (abs long_value) longDigitBase+ numDigits :: Int32 + numDigits = fromIntegral $ length digits+ -- We assume n > 0+ getDigits :: Integer -> Integer -> [Word16]+ getDigits 0 _base = []+ getDigits n base = (fromIntegral (n `mod` base)) : getDigits (n `div` base) base+writeLongObject other = error $ "writeLongObject called on non long object: " ++ show other+ data ObjectType = NULL -- '0' | NONE -- 'N'@@ -350,6 +411,12 @@ getU32 :: GetData Word32 getU32 = lift getWord32le +getI32 :: GetData Int32+getI32 = fromIntegral `fmap` lift getWord32le++getU16 :: GetData Word16+getU16 = lift getWord16le+ runGetData :: GetData a -> B.ByteString -> Either String a runGetData = runGet . runErrorT @@ -373,11 +440,18 @@ putU8 :: Word8 -> PutData putU8 = lift . putWord8 +-- write an unsigned 16 bit word+putU16 :: Word16 -> PutData+putU16 = lift . putWord16le+ -- XXX is it always little endian? -- write an unsigned 32 bit word putU32 :: Word32 -> PutData putU32 = lift . putWord32le +putI32 :: Int32 -> PutData+putI32 = putU32 . fromIntegral+ runPutData :: PutData -> Either String B.ByteString runPutData comp = case runPutM (runErrorT comp) of@@ -389,3 +463,39 @@ case runPutData comp of Left e -> fail e Right bs -> return bs++-- masks for the code object flags+type CodeObjectFlagMask = Word32++co_optimized :: CodeObjectFlagMask+co_optimized = 0x0001+co_newlocals :: CodeObjectFlagMask +co_newlocals = 0x0002+co_varargs :: CodeObjectFlagMask +co_varargs = 0x0004+co_varkeywords :: CodeObjectFlagMask +co_varkeywords = 0x0008+co_nested :: CodeObjectFlagMask +co_nested = 0x0010+co_generator :: CodeObjectFlagMask +co_generator = 0x0020+co_nofree :: CodeObjectFlagMask +co_nofree = 0x0040++prettyFlags :: Word32 -> Doc+prettyFlags bits = + hsep $ map (uncurry showFlag) masks+ where+ checkFlag :: CodeObjectFlagMask -> Bool+ checkFlag mask = (bits .&. mask) /= 0+ showFlag :: CodeObjectFlagMask -> String -> Doc+ showFlag mask name+ | checkFlag mask = text name+ | otherwise = empty+ masks = [ (co_optimized, "CO_OPTIMIZED")+ , (co_newlocals, "CO_NEWLOCALS")+ , (co_varargs, "CO_VARARGS") + , (co_varkeywords, "CO_VARKEYWORDS")+ , (co_nested, "CO_NESTED") + , (co_generator, "CO_GENERATOR") + , (co_nofree, "CO_NOFREE") ]