packages feed

extism-pdk-1.2.0.0: src/Extism/PDK/Memory.hs

{-# LANGUAGE FlexibleInstances #-}

-- |
-- Extism.PDK.Memory implements a low-level interface for interacting with Extism memory
module Extism.PDK.Memory
  ( Memory (..),
    MemoryOffset,
    MemoryLength,
    FromBytes (..),
    ToBytes (..),
    JSON (..),
    MsgPack (..),
    load,
    loadString,
    loadByteString,
    outputMemory,
    memAlloc,
    free,
    alloc,
    allocString,
    allocByteString,
    memoryOffset,
    memoryLength,
    findMemory,
  )
where

import Data.Binary.Get
import Data.Binary.Put
import qualified Data.ByteString as B
import Data.ByteString.Internal (c2w, w2c)
import Data.Int
import Data.Word
import Extism.PDK.Bindings
import qualified Extism.PDK.MsgPack (MsgPack, decode, encode)
import Extism.PDK.Util
import qualified Text.JSON (JSON, Result (..), decode, encode)
import qualified Text.JSON.Generic

-- | Represents a block of memory by offset and length
data Memory = Memory MemoryOffset MemoryLength

-- | Load data from 'Memory' block
load :: (FromBytes a) => Memory -> IO (Either String a)
load (Memory offs len) = do
  x <- readBytes offs len
  return $ fromBytes x

-- | Store data into a 'Memory' block
store :: (ToBytes a) => Memory -> a -> IO ()
store (Memory offs len) a =
  writeBytes offs len $ toBytes a

-- | Set plugin output to the provided 'Memory' block
outputMemory :: Memory -> IO ()
outputMemory (Memory offs len) =
  extismSetOutput offs len

-- | Load ByteString from 'Memory' block
loadByteString :: Memory -> IO B.ByteString
loadByteString (Memory offs len) = do
  readBytes offs len

-- | Load string from 'Memory' block
loadString :: Memory -> IO String
loadString (Memory offs len) =
  fromByteString <$> readBytes offs len

-- | Store string in 'Memory' block
storeString :: Memory -> String -> IO ()
storeString mem s =
  storeByteString mem $ toByteString s

-- | Store byte string in 'Memory' block
storeByteString :: Memory -> B.ByteString -> IO ()
storeByteString (Memory offs len) =
  writeBytes offs len

-- | Encode a value and copy it into Extism memory, returning the Memory block
alloc :: (ToBytes a) => a -> IO Memory
alloc x = do
  Memory offs len <- memAlloc (B.length bs)
  writeBytes offs len bs
  return $ Memory offs len
  where
    bs = toBytes x

-- | Allocate a new 'Memory' block
memAlloc :: Int -> IO Memory
memAlloc n = do
  offs <- extismAlloc len
  return $ Memory offs len
  where
    len = fromIntegral n

-- | Free a 'Memory' block
free :: Memory -> IO ()
free (Memory 0 _) = return ()
free (Memory _ 0) = return ()
free (Memory offs _) =
  extismFree offs

-- | Allocate a new 'Memory' block and copy the encoded value
allocByteString :: B.ByteString -> IO Memory
allocByteString bs = do
  Memory offs len <- memAlloc (B.length bs)
  writeBytes offs len bs
  return (Memory offs len)

-- | Allocate a new 'Memory' block and copy the contents of the provided 'String'
allocString :: String -> IO Memory
allocString = allocByteString . toByteString

-- | Get the offset of a 'Memory' block
memoryOffset :: Memory -> MemoryOffset
memoryOffset (Memory offs _) = offs

-- | Get the length of a 'Memory' block
memoryLength :: Memory -> MemoryLength
memoryLength (Memory _ len) = len

-- | Find 'Memory' block by offset
findMemory :: MemoryOffset -> IO Memory
findMemory offs = do
  len <- extismLength offs
  return $ Memory offs len

-- | A class used to convert values from bytes read from linear memory
class FromBytes a where
  fromBytes :: B.ByteString -> Either String a

-- | A class used to convert values to bytes to be written into linear memory
class ToBytes a where
  toBytes :: a -> B.ByteString

-- | A wrapper type for JSON encoded values
newtype JSON a = JSON a

-- | A wrapper type for MsgPack encoded values
newtype MsgPack a = MsgPack a

instance FromBytes B.ByteString where
  fromBytes = Right

instance ToBytes B.ByteString where
  toBytes = id

instance FromBytes String where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x -> Right $ fromByteString x

instance ToBytes String where
  toBytes = toByteString

instance (Text.JSON.Generic.Data a) => FromBytes (JSON a) where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case Text.JSON.decode x of
          Text.JSON.Error e -> Left e
          Text.JSON.Ok y ->
            case Text.JSON.Generic.fromJSON y of
              Text.JSON.Error e -> Left e
              Text.JSON.Ok z -> Right (JSON z)

instance (Text.JSON.Generic.Data a) => ToBytes (JSON a) where
  toBytes (JSON x) = toBytes (Text.JSON.Generic.encodeJSON x)

instance (Extism.PDK.MsgPack.MsgPack a) => FromBytes (MsgPack a) where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case Extism.PDK.MsgPack.decode x of
          Left e -> Left e
          Right y -> Right (MsgPack y)

instance (Extism.PDK.MsgPack.MsgPack a) => ToBytes (MsgPack a) where
  toBytes (MsgPack x) = toBytes $ Extism.PDK.MsgPack.encode x

instance ToBytes Int32 where
  toBytes i = toBytes $ B.toStrict (runPut (putInt32le i))

instance FromBytes Int32 where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case runGetOrFail getInt32le (B.fromStrict x) of
          Left (_, _, e) -> Left e
          Right (_, _, x) -> Right x

instance ToBytes Int64 where
  toBytes i = toBytes $ B.toStrict (runPut (putInt64le i))

instance FromBytes Int64 where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case runGetOrFail getInt64le (B.fromStrict x) of
          Left (_, _, e) -> Left e
          Right (_, _, x) -> Right x

instance ToBytes Word32 where
  toBytes i = toBytes $ B.toStrict (runPut (putWord32le i))

instance FromBytes Word32 where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case runGetOrFail getWord32le (B.fromStrict x) of
          Left (_, _, e) -> Left e
          Right (_, _, x) -> Right x

instance ToBytes Word64 where
  toBytes i = toBytes $ B.toStrict (runPut (putWord64le i))

instance FromBytes Word64 where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case runGetOrFail getWord64le (B.fromStrict x) of
          Left (_, _, e) -> Left e
          Right (_, _, x) -> Right x

instance ToBytes Float where
  toBytes i = toBytes $ B.toStrict (runPut (putFloatle i))

instance FromBytes Float where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case runGetOrFail getFloatle (B.fromStrict x) of
          Left (_, _, e) -> Left e
          Right (_, _, x) -> Right x

instance ToBytes Double where
  toBytes i = toBytes $ B.toStrict (runPut (putDoublele i))

instance FromBytes Double where
  fromBytes mem =
    case fromBytes mem of
      Left e -> Left e
      Right x ->
        case runGetOrFail getDoublele (B.fromStrict x) of
          Left (_, _, e) -> Left e
          Right (_, _, x) -> Right x