emacs-module-0.2: src/Data/Emacs/Module/SymbolName/Internal.hs
----------------------------------------------------------------------------
-- |
-- Module : Data.Emacs.Module.SymbolName.Internal
-- Copyright : (c) Sergey Vinokurov 2018
-- License : Apache-2.0 (see LICENSE)
-- Maintainer : serg.foo@gmail.com
----------------------------------------------------------------------------
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UnliftedNewtypes #-}
{-# OPTIONS_HADDOCK not-home #-}
module Data.Emacs.Module.SymbolName.Internal
( Static(..)
, Dynamic(..)
, SymbolName(..)
, mkSymbolName
, mkSymbolNameString
, mkSymbolNameShortByteString
, mkSymbolNameUnsafe
, mkSymbolNameCache
, mkCachedSymbolName
, reifySymbolRaw
, reifySymbolUnknown
, reifySymbol
) where
import Data.ByteString.Internal qualified as BS
import Data.ByteString.Short qualified as BSS
import Data.Char
import Data.Coerce
import Data.IORef
import Data.String
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import Data.Text.Encoding.Error qualified as TE
import Data.Text.Foreign qualified as T
import Foreign.C.Types
import Foreign.Storable
import GHC.Exts (Addr#, unpackCString#)
import GHC.Ptr
import Prettyprinter
import System.IO.Unsafe
import Data.Emacs.Module.NonNullPtr
import Data.Emacs.Module.Raw.Env qualified as Raw
import Data.Emacs.Module.Raw.Env.Internal
import Data.Emacs.Module.Raw.Value
import Emacs.Module.Assert
import Data.Emacs.Module.SymbolName.Predefined.Funcall
-- | Symbols that are known at compile time.
--
-- Will just pass pointer to 0-terminated statically-allocated string
-- to Emacs API when used.
newtype Static = Static { unStatic :: Ptr CChar }
deriving (Eq, Ord, Show)
newtype Dynamic = Dynamic { unDynamic :: Text }
deriving (Eq, Ord, Show, Pretty)
data SymbolName
= StaticSymbol {-# UNPACK #-} !(Ptr CChar)
| DynamicSymbol {-# UNPACK #-} !Text
| CachedSymbol (IORef (Env -> IO (RawValue 'Pinned))) SymbolName
deriving (Eq)
instance Show SymbolName where
show = \case
StaticSymbol (Ptr addr)
-> show $ TE.decodeUtf8With TE.lenientDecode $ BS.unsafePackLiteral addr
DynamicSymbol str -> show str
CachedSymbol _ sym -> show sym
instance Pretty SymbolName where
pretty = \case
StaticSymbol (Ptr addr)
-> pretty $ TE.decodeUtf8With TE.lenientDecode $ BS.unsafePackLiteral addr
DynamicSymbol str -> pretty str
CachedSymbol _ sym -> pretty sym
mkSymbolNameCache :: SymbolName -> IO (IORef (Env -> IO (RawValue 'Pinned)))
mkSymbolNameCache = go
where
go :: SymbolName -> IO (IORef (Env -> IO (RawValue 'Pinned)))
go !name =
unsafeFixIO $ \ ref ->
newIORef $ \env -> do
!global <- Raw.makeGlobalRef env =<< reifySymbolRaw env name
writeIORef ref $ \_env -> pure global
pure global
{-# INLINE mkCachedSymbolName #-}
mkCachedSymbolName :: IORef (Env -> IO (RawValue 'Pinned)) -> SymbolName -> SymbolName
mkCachedSymbolName = CachedSymbol
-- | Should be applied to unboxed string literals like this
--
-- @
-- mkSymbolNameUnsafe "foo"#
-- @
--
-- Can be safely applied to non-literals (e.g. arbitrary pointers) if
-- it's guaranteed that address points to a null-terminated strings.
-- Otherwise behaviour is undefined.
--
-- The string literal must only contain ASCII symbols. This condition
-- is required by the Emacs API and results in undefined behaviour if
-- violated.
{-# INLINE mkSymbolNameUnsafe #-}
mkSymbolNameUnsafe :: Addr# -> SymbolName
mkSymbolNameUnsafe addr = StaticSymbol (Ptr addr)
{-# INLINE mkSymbolName #-}
mkSymbolName :: Text -> SymbolName
mkSymbolName = DynamicSymbol
{-# INLINE mkSymbolNameShortByteString #-}
mkSymbolNameShortByteString :: BSS.ShortByteString -> SymbolName
mkSymbolNameShortByteString = DynamicSymbol . TE.decodeUtf8With TE.lenientDecode . BSS.fromShort
{-# INLINE [0] mkSymbolNameString #-}
mkSymbolNameString :: String -> SymbolName
mkSymbolNameString = mkSymbolName . T.pack
instance IsString SymbolName where
{-# INLINE fromString #-}
fromString = mkSymbolNameString
{-# RULES
"SymbolName string literal" forall s .
mkSymbolNameString (unpackCString# s) = mkSymbolNameUnsafe s
#-}
{-# INLINE reifySymbolRaw #-}
reifySymbolRaw :: Env -> SymbolName -> IO (RawValue 'Regular)
reifySymbolRaw env sym = reifySymbol env sym id coerce
{-# INLINE reifySymbolUnknown #-}
reifySymbolUnknown :: Env -> SymbolName -> IO (RawValue 'Unknown)
reifySymbolUnknown env sym = reifySymbol env sym coerce coerce
{-# INLINE reifySymbol #-}
reifySymbol
:: WithCallStack
=> Env -> SymbolName -> (RawValue 'Regular -> a) -> (RawValue 'Pinned -> a) -> IO a
reifySymbol env sym f g = case sym of
StaticSymbol addr ->
f <$> Raw.intern env addr
DynamicSymbol str
-- If it's only ASCII then can run FFI intern, otherwise have to go via funcall
-- TODO: cache this check
| T.all (\c -> ord c < 128) str ->
f <$> T.withCString str (Raw.intern env)
| otherwise ->
T.withCStringLen str $ \(ptr, len) -> do
str' <- emacsAssert (len >= 0) "Symbol text length must be non-negative" $
Raw.makeString env ptr (fromIntegral len)
funcall' <- reifySymbolUnknown env funcall
allocaNonNull $ \args -> do
poke (unNonNullPtr args) str'
f <$> Raw.funcallPrimitive env funcall' 1 args
CachedSymbol ref _ ->
g <$> ((\k -> k env) =<< readIORef ref)