{-| Module : Binary.Neko.Module Description : Emit and parse Binary.Neko bytecode Copyright : (c) Petr Penzin, 2015 License : BSD2 Maintainer : penzin.dev@gmail.com Stability : experimental Portability : cross-platform Primitives to emit and parse Binary.Neko bytecode, including instruction definitions. -} module Binary.Neko.Module where import Control.Applicative import Data.ByteString.Lazy as BS import Data.ByteString.Lazy.Char8 as BSChar import Data.Binary.Get import Data.Binary.Put import Data.Maybe import Data.Either import Data.Word import Data.Int import Binary.Neko.Hashtbl as H import Binary.Neko.Globals import Binary.Neko.Instructions -- | A Binary.Neko module. Consists of global entities and a list of instructions data Module = N {globals::[Global], fields::Hashtbl, code::[Instruction]} deriving (Show, Eq) -- | Parse module from ByteString. -- Return module or return an error string readModule :: ByteString -> Either String Module readModule bs = if (isRight res) then if (BS.null rest) then (Right m) else Left "Trailing bytes" else Left err where res = runGetOrFail getModule bs Right (rest, _, m) = res Left (_, _, err) = res -- | Internal type for module header (counts of entities in the module) data ModuleHeader = ModuleHeader { numGlobals :: Word32, -- ^ Number of globals numFields :: Word32, -- ^ Number of fields codeSize :: Word32 -- ^ Code size (number of instructions+arguments) } -- | Pick module header fields from a bytestring. Requires bytestring to start with the first field. getModuleHeader :: Get ModuleHeader getModuleHeader = ModuleHeader <$> getWord32le <*> getWord32le <*> getWord32le -- | Get a full module from a bytestring getModule :: Get Module getModule = getMagicCheck >>= \good -> if (not good) then fail "Invalid magic value" else getModuleHeader >>= \h -> getModuleContents (numGlobals h) (numFields h) (codeSize h) -- | Parse insides of a module from a bytestring. -- Bytesting is expected to start with the first section of the module. getModuleContents :: Word32 -- ^ number of globals -> Word32 -- ^ number of fields -> Word32 -- ^ code size -> Get Module -- ^ decode module getModuleContents globals fields code = if (globals > 0xFFFF) then fail "Number of globals not between 0 and 0xFFFF" else if (fields > 0xFFFF) then fail "Number of fields not between 0 and 0xFFFF" else if (code > 0xFFFFFF) then fail "Code size not between 0 and 0xFFFFFF" else getGlobals globals >>= \g -> getFields fields >>= \f -> getInstructions code f >>= \i -> return (N {globals = g, fields = f, code = i}) -- | A check for next four bytes matching neko magic value getMagicCheck :: Get Bool getMagicCheck = getLazyByteString 4 >>= \b -> return (b == BSChar.pack "NEKO") -- | Grab a global field from a bytestring getField :: Get String getField = getLazyByteStringNul >>= \b -> return (BSChar.unpack b) -- | Get a list of fields into a hashtable indexed by their hash values getFields :: Word32 -> Get Hashtbl getFields 0 = return H.empty getFields n = getField >>= \s -> getFields (n - 1) >>= \h -> if (memberString s h) then fail ("Duplicate field " ++ s) else return (H.insertString s h) -- | Produce a sequence of null-terminated strings prepStrings :: [String] -> ByteString prepStrings [] = BS.empty prepStrings (s:ss) = BS.append (BS.snoc (BSChar.pack s) 0x0) (prepStrings ss) -- | Generate binary for fields putFields :: Hashtbl -> Put putFields = putLazyByteString . prepStrings . H.elems -- | Generate binary for a module -- TODO: we are not running any checks on sizes of header fields (like we do -- while reading), that needs to be implemented, otherwise user will get surprised -- by Binary.Neko runtime. putModule :: Module -> Put putModule m = putLazyByteString (BSChar.pack "NEKO") -- put magic value >> putWord32le (fromIntegral $ Prelude.length $ globals m) -- put number of globals >> putWord32le (fromIntegral $ Prelude.length $ H.elems $ fields m) -- put number of fields >> putWord32le (sum $ Prelude.map (\x -> if (hasParam x) then 2 else 1) $ code m) -- put code size {- Contents of the module -} >> putGlobals (globals m) >> putFields (fields m) >> putInstructions (code m)