packages feed

inline-python-0.3.0.0: src/Python/Inline/Eval.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
-- |
-- Interface to python's @eval@ and @exec@ which gives programmer
-- control over local and global variables.
module Python.Inline.Eval
  ( -- * Python execution
    eval
  , exec
  , evalPyFunction
  , createModule
    -- * Source code
  , PyQuote(..)
  , PyQuoteFun(..)
  , Code
  , codeFromText
  , codeFromString
  , DictBinder
  , bindVar
    -- * Variable namespaces
  , Namespace(..)
  , Main(..)
  , Temp(..)
  , Dict(..)
  , Module(..)
  ) where

import Data.ByteString            qualified as BS
import Data.Text                  (Text)
import Data.Text                  qualified as T
import Data.Text.Encoding         qualified as T
import Language.C.Inline          qualified as C
import Language.C.Inline.Unsafe   qualified as CU

import Python.Internal.Types
import Python.Internal.Eval
import Python.Internal.Program
import Python.Inline.Literal

----------------------------------------------------------------
C.context (C.baseCtx <> pyCtx)
C.include "<inline-python.h>"
----------------------------------------------------------------

-- | Bind variable in dictionary
bindVar
  :: (ToPy a)
  => Text -- ^ Variable name
  -> a    -- ^ Variable value
  -> DictBinder
bindVar name a = DictBinder $ \p_dict -> runProgram $ do
  p_key <- progIOBracket $ BS.useAsCString (T.encodeUtf8 name)
  p_obj <- takeOwnership =<< progPy (throwOnNULL =<< basicToPy a)
  progPy $ do
    r <- Py [CU.block| int {
      PyObject* p_obj = $(PyObject* p_obj);
      return PyDict_SetItemString($(PyObject* p_dict), $(char* p_key), p_obj);
      } |]
    case r of
      0 -> pure ()
      _ -> mustThrowPyError


-- | Create python module from given quote. This function creates new
--   module using @importlib@ machinery and executes quote in context
--   of this module.
createModule
  :: Text
  -> PyQuote
  -> Py Module
createModule name quote = do
  -- It would be nice to use quasiquotes but linker chokes on them.
  -- We have to this hard way
  mdl <- Module <$> evalPyFunction Temp PyQuoteFun
    { code   = codeFromText $ T.unlines
        [ "def _inline_python_(name):"
        , "    import types"
        , "    return types.ModuleType(name)"
        ]
    , binder = bindVar "name" name
    }
  mdl <$ exec mdl mdl quote