-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathProgram.hs
More file actions
151 lines (123 loc) · 4.19 KB
/
Copy pathProgram.hs
File metadata and controls
151 lines (123 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
-- |
module Python.Internal.Program
( Program(..)
, runProgram
, progPy
, progIO
, progPyBracket
, progIOBracket
-- * Control flow
, abort
, abortM
, abortOnNull
, checkNull
, finallyProg
, onExceptionProg
, takeOwnership
-- * Allocators
, withPyAlloca
, withPyAllocaArray
, withPyCString
, withPyCStringLen
, withPyWCString
-- * Helpers
, pyobjectStrAsHask
) where
import Control.Monad
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Class
import Control.Monad.Catch
import Data.Coerce
import Foreign.Ptr
import Foreign.Marshal.Array
import Foreign.Marshal
import Foreign.C.String
import Foreign.C.Types
import Foreign.Storable
import Language.C.Inline qualified as C
import Python.Internal.Types
import Python.Internal.Util
import Python.Internal.CAPI
----------------------------------------------------------------
C.context (C.baseCtx <> pyCtx)
C.include "<inline-python.h>"
----------------------------------------------------------------
-- | This monad wraps 'Py' into 'ContT' in order get early exit,
-- applying @finally@ while avoiding building huge ladders.
newtype Program r a = Program (ContT r Py a)
deriving newtype (Functor, Applicative, Monad)
runProgram :: Program a a -> Py a
runProgram (Program m) = evalContT m
-- | Does not change masking state
progIO :: IO a -> Program r a
progIO = Program . lift . pyIO
progPy :: Py a -> Program r a
progPy = Program . lift
progIOBracket :: ((a -> IO r) -> IO r) -> Program r a
progIOBracket = coerce
progPyBracket :: ((a -> Py r) -> Py r) -> Program r a
progPyBracket = coerce
-- | Early exit from continuation monad.
abort :: r -> Program r a
abort r = Program $ ContT $ \_ -> pure r
-- | Early exit from continuation monad.
abortM :: Py r -> Program r a
abortM m = Program $ ContT $ \_ -> m
-- | Perform early exit if pointer is null
abortOnNull :: r -> Py (Ptr a) -> Program r (Ptr a)
abortOnNull r action = Program $ ContT $ \cnt -> action >>= \case
NULL -> pure r
p -> cnt p
-- | If result of computation is NULL return NULL immediately.
checkNull :: Py (Ptr a) -> Program (Ptr a) (Ptr a)
checkNull = abortOnNull nullPtr
-- | Evaluate finalizer even if exception is thrown.
finallyProg
:: Py b -- ^ Finalizer
-> Program r ()
finallyProg fini = Program $ ContT $ \c -> c () `finally` fini
-- | Evaluate finalizer if exception is thrown.
onExceptionProg
:: Py b -- ^ Finalizer
-> Program r ()
onExceptionProg fini = Program $ ContT $ \c -> c () `onException` fini
-- | Decrement reference counter at end of ContT block
takeOwnership :: Ptr PyObject -> Program r (Ptr PyObject)
takeOwnership p = Program $ ContT $ \c -> c p `finally` decref p
----------------------------------------------------------------
-- Allocation in context of `ContT _ Py`
----------------------------------------------------------------
withPyAlloca :: forall a r. Storable a => Program r (Ptr a)
withPyAlloca = coerce (alloca @a @r)
withPyAllocaArray :: forall a r. Storable a => Int -> Program r (Ptr a)
withPyAllocaArray = coerce (allocaArray @a @r)
withPyCString :: forall r. String -> Program r CString
withPyCString = coerce (withCString @r)
withPyWCString :: forall r. String -> Program r (Ptr CWchar)
withPyWCString = coerce (withWCString @r)
withPyCStringLen :: forall r. String -> Program r CStringLen
withPyCStringLen = coerce (withCStringLen @r)
----------------------------------------------------------------
-- More complicated helpers
----------------------------------------------------------------
-- | Call @__str__@ method of object and return haskell
-- string. Returns Nothing if exception was raisede
pyobjectStrAsHask :: Ptr PyObject -> Py (Maybe String)
pyobjectStrAsHask p_obj = runProgram $ do
p_str <- takeOwnership <=< abortOnNull Nothing $ Py [C.block| PyObject* {
PyObject *s = PyObject_Str($(PyObject *p_obj));
if( PyErr_Occurred() ) {
PyErr_Clear();
}
return s;
} |]
c_str <- abortOnNull Nothing $ Py [C.block| const char* {
const char* s = PyUnicode_AsUTF8($(PyObject *p_str));
if( PyErr_Occurred() ) {
PyErr_Clear();
}
return s;
} |]
progIO $ Just <$> peekCString c_str