-- | Utilities for working with binary encoded Shimmer trees.
--
--   The grammar for the binary format is as follows:
--
-- @
-- File    ::= '53' '4d' '52' '31' Seq[Decl]       (Shimmer File: \"SMR1\" in ASCII, then Decls)
--
-- Decl    ::= (dmac)    \'d0\' Name Exp             (Macro declaration)
--          |  (dset)    \'d1\' Name Exp             (Set declaration)
--
-- Var     ::= (var)     \'8N\' Word8^N              (Short Varible,       N <= 15)
--
-- Abs     ::= (abs)     \'9N\' Exp^N                (Short Abstraction,   N <= 15)
--
-- App     ::= (app)     \'aN\' Exp^N                (Packed Application,  N <= 15)
--
-- Exp     ::= (ref)     \'b0\' Ref                  (External reference)
--          |  (key)     \'b1\' Key Exp              (Keyword  application)
--          |  (app)     \'b2\' Exp Seq[Exp]         (Function application)
--          |  (var)     \'b3\' Name Bump            (Variable with bump counter)
--          |  (abs)     \'b4\' Seq[Param] Exp       (Function abstraction)
--          |  (sub)     \'b5\' Seq[Car] Exp         (Substitution train)
--          |            Var                       (Short circuit to Var)
--          |            Abs                       (Short circuit to Abs)
--          |            App                       (Short circuit to App)
--          |            Ref                       (Short circuit to Ref)
--
-- Key     ::= (box)     \'b6\'                      (Box keyword)
--          |  (run)     \'b7\'                      (Run keyword)
--
-- Param   ::= (pval)    \'b8\' Name                 (call-by-value parameter)
--          |  (pnam)    \'b9\' Name                 (call-by-name  parameter)
--
-- Car     ::= (csim)    \'ba\' Seq[SnvBind]         (Simultaneous substitution)
--          |  (crec)    \'bb\' Seq[SnvBind]         (Recursive substitution)
--          |  (cups)    \'bc\' Seq[UpsBump]         (Lifting specifiers)
--
-- SnvBind ::= (svar)    \'bd\' Name Bump Exp        (Substitute for variable)
--          |  (snom)    \'be\' Nom Exp              (Substitute for nominal reference)
--
-- UpsBump ::= (ups)     \'bf\' Name Bump Bump       (Lifting specifier)
--
-- Ref     ::= (sym)     \'c0\' Seq[Word8]           (Symbol reference)
--          |  (prm)     \'c1\' Seq[Word8]           (Primitive reference)
--          |  (txt)     \'c2\' Seq[Word8]           (Text reference)
--          |  (mac)     \'c3\' Seq[Word8]           (Macro reference)
--          |  (set)     \'c4\' Seq[Word8]           (Set reference)
--          |  (nom)     \'c5\' Nom                  (Nominal reference)
--          |            Name                      (Short circuit to Sym Name)
--
-- Prim    ::= (unit)    \'e0\'                      (Unit value)
--          |  (list)    \'e1\'                      (List constructor tag)
--          |  (true)    \'e2\'                      (True value)
--          |  (false)   \'e3\'                      (False value)
--
--          |  (word8)   \'e4\' Word8                ( 8-bit word value)
--          |  (word16)  \'e5\' Word16               (16-bit word value)
--          |  (word32)  \'e6\' Word32               (32-bit word value)
--          |  (word64)  \'e7\' Word64               (64-bit word value)
--
--          |  (int8)    \'e8\' Int8                 ( 8-bit  int value)
--          |  (int16)   \'e9\' Int16                (16-bit  int value)
--          |  (int32)   \'ea\' Int32                (32-bit  int value)
--          |  (int64)   \'eb\' Int64                (64-bit  int value)
--
--          |  (float32) \'ec\' Float32              (32-bit float value)
--          |  (float64) \'ed\' Float64              (64-bit float value)
--
--          |  (named)   \'ee\' Name                 (Named primitive)
--          |  (words)   \'ef\' Name Seq[Word8]      (Packed raw words with type name)
--
-- Seq[A]  ::= (seqN)    \'fN\' A*                   (N-count then sequence of A things, N <= 13)
--          |  (seq8)    \'fd\' Word8  A*            ( 8-bit count then sequence of A things)
--          |  (seq16)   \'fe\' Word16 A*            (16-bit count then sequence of A things)
--          |  (seq32)   \'ff\' Word32 A*            (32-bit count then sequence of A things)
--
-- Name    ::= Seq[Word8]                          (Name)
--
-- Bump    ::= Word16                              (Bump counter)
--
-- Nom     ::= Word32                              (Nominal constant)
--
-- @
module SMR.Core.Codec
        ( -- * Pack
          packFileDecls
        , packDecl
        , packExp
        , packRef

          -- * Unpack
        , unpackFileDecls
        , unpackDecl
        , unpackExp
        , unpackRef

          -- * Raw Size
        , sizeOfFileDecls
        , sizeOfDecl, sizeOfExp, sizeOfRef

          -- * Raw Poke
        , Poke
        , pokeFileDecls
        , pokeDecl, pokeExp, pokeRef

          -- * Raw Peek
        , Peek
        , peekFileDecls
        , peekDecl, peekExp, peekRef)
where
import SMR.Core.Codec.Size
import SMR.Core.Codec.Poke
import SMR.Core.Codec.Peek
import SMR.Core.Exp
import SMR.Prim.Name
import Data.Text                        (Text)
import qualified Foreign.Marshal.Utils  as F
import qualified Foreign.Marshal.Alloc  as F
import qualified Foreign.Ptr            as F
import qualified System.IO.Unsafe       as System
import qualified Data.ByteString        as BS
import qualified Data.ByteString.Unsafe as BS


-- Pack -------------------------------------------------------------------------------------------
-- | Pack a list of `Decl` into a `ByteString`, including the file header.
packFileDecls :: [Decl Text Prim] -> BS.ByteString
packFileDecls decls
 = System.unsafePerformIO
 $ do   let nBytes = sizeOfFileDecls decls
        buf     <- F.mallocBytes nBytes
        _       <- pokeFileDecls decls (F.castPtr buf)
        BS.unsafePackMallocCStringLen (buf, nBytes)
{-# NOINLINE packFileDecls #-}


-- | Pack a `Decl` into a `ByteString`.
packDecl :: Decl Text Prim -> BS.ByteString
packDecl xx
 = System.unsafePerformIO
 $ do   let nBytes = sizeOfDecl xx
        buf     <- F.mallocBytes nBytes
        _       <- pokeDecl xx (F.castPtr buf)
        BS.unsafePackMallocCStringLen (buf, nBytes)
{-# NOINLINE packDecl #-}


-- | Pack an `Exp` into a `ByteString`.
packExp :: Exp Text Prim -> BS.ByteString
packExp xx
 = System.unsafePerformIO
 $ do   let nBytes = sizeOfExp xx
        buf     <- F.mallocBytes nBytes
        _       <- pokeExp xx (F.castPtr buf)
        BS.unsafePackMallocCStringLen (buf, nBytes)
{-# NOINLINE packExp #-}


-- | Pack a `Ref` into a `ByteString`.
packRef :: Ref Text Prim -> BS.ByteString
packRef xx
 = System.unsafePerformIO
 $ do   let nBytes = sizeOfRef xx
        buf     <- F.mallocBytes nBytes
        _       <- pokeRef xx (F.castPtr buf)
        BS.unsafePackMallocCStringLen (buf, nBytes)
{-# NOINLINE packRef #-}


-- Unpack -----------------------------------------------------------------------------------------
-- | Unpack a list of `Decl` into a ByteString, including the file header.
--
--   If the packed data is malformed then `error`.
unpackFileDecls :: BS.ByteString -> [Decl Text Prim]
unpackFileDecls bs
 = System.unsafePerformIO
 $ BS.unsafeUseAsCStringLen bs $ \(buf, nBytes)
 -> do  (decls, _, _) <- peekFileDecls (F.castPtr buf) nBytes
        return decls
{-# NOINLINE unpackFileDecls #-}


-- | Unpack a `Decl` from a ByteString.
--
--   If the packed data is malformed then `error`.
unpackDecl :: BS.ByteString -> Decl Text Prim
unpackDecl bs
 = System.unsafePerformIO
 $ BS.unsafeUseAsCStringLen bs $ \(buf, nBytes)
 -> do  (decl, _, _) <- peekDecl (F.castPtr buf) nBytes
        return decl
{-# NOINLINE unpackDecl #-}


-- | Unpack an `Exp` into a ByteString.
--
--   If the packed data is malformed then `error`.
unpackExp :: BS.ByteString -> Exp Text Prim
unpackExp bs
 = System.unsafePerformIO
 $ BS.unsafeUseAsCStringLen bs $ \(buf, nBytes)
 -> do  (exp, _, _) <- peekExp (F.castPtr buf) nBytes
        return exp
{-# NOINLINE unpackExp #-}


-- | Unpack a `Ref` from a ByteString.
--
--   If the packed data is malformed then `error`.
unpackRef :: BS.ByteString -> Ref Text Prim
unpackRef bs
 = System.unsafePerformIO
 $ BS.unsafeUseAsCStringLen bs $ \(buf, nBytes)
 -> do  (ref, _, _) <- peekRef (F.castPtr buf) nBytes
        return ref
{-# NOINLINE unpackRef #-}