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
data Module = N {globals::[Global], fields::Hashtbl, code::[Instruction]} deriving (Show, Eq)
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
data ModuleHeader = ModuleHeader {
numGlobals :: Word32,
numFields :: Word32,
codeSize :: Word32
}
getModuleHeader :: Get ModuleHeader
getModuleHeader = ModuleHeader <$> getWord32le <*> getWord32le <*> getWord32le
getModule :: Get Module
getModule = getMagicCheck
>>= \good -> if (not good) then fail "Invalid magic value" else getModuleHeader
>>= \h -> getModuleContents (numGlobals h) (numFields h) (codeSize h)
getModuleContents :: Word32
-> Word32
-> Word32
-> Get 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})
getMagicCheck :: Get Bool
getMagicCheck = getLazyByteString 4 >>= \b -> return (b == BSChar.pack "NEKO")
getField :: Get String
getField = getLazyByteStringNul >>= \b -> return (BSChar.unpack b)
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)
prepStrings :: [String] -> ByteString
prepStrings [] = BS.empty
prepStrings (s:ss) = BS.append (BS.snoc (BSChar.pack s) 0x0) (prepStrings ss)
putFields :: Hashtbl -> Put
putFields = putLazyByteString . prepStrings . H.elems
putModule :: Module -> Put
putModule m = putLazyByteString (BSChar.pack "NEKO")
>> putWord32le (fromIntegral $ Prelude.length $ globals m)
>> putWord32le (fromIntegral $ Prelude.length $ H.elems $ fields m)
>> putWord32le (sum $ Prelude.map (\x -> if (hasParam x) then 2 else 1) $ code m)
>> putGlobals (globals m)
>> putFields (fields m)
>> putInstructions (code m)