Skip to content

Commit 23d82e8

Browse files
committed
Add From/ToPy instances for 2-tuples
1 parent 59e4a91 commit 23d82e8

File tree

6 files changed

+103
-0
lines changed

6 files changed

+103
-0
lines changed

cbits/python.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,45 @@ PyObject *inline_py_function_wrapper(PyCFunction fun, int flags) {
4343
return f;
4444
}
4545

46+
int inline_py_unpack_iterable(PyObject *iterable, int n, PyObject **out) {
47+
// Fill out with NULL. This way we can call XDECREF on them
48+
for(int i = 0; i < n; i++) {
49+
out[i] = NULL;
50+
}
51+
// Initialize iterator
52+
PyObject* iter = PyObject_GetIter( iterable );
53+
if( PyErr_Occurred() ) {
54+
return -1;
55+
}
56+
if( !PyIter_Check(iter) ) {
57+
goto err_iter;
58+
}
59+
// Fill elements
60+
for(int i = 0; i < n; i++) {
61+
out[i] = PyIter_Next(iter);
62+
if( NULL==out[i] ) {
63+
goto err_elem;
64+
}
65+
}
66+
// End of iteration
67+
PyObject* end = PyIter_Next(iter);
68+
if( NULL != end || PyErr_Occurred() ) {
69+
goto err_end;
70+
}
71+
return 0;
72+
//----------------------------------------
73+
err_end:
74+
Py_XDECREF(end);
75+
err_elem:
76+
for(int i = 0; i < n; i++) {
77+
Py_XDECREF(out[i]);
78+
}
79+
err_iter:
80+
Py_DECREF(iter);
81+
return -1;
82+
}
83+
84+
4685
void inline_py_free_capsule(PyObject* py) {
4786
PyMethodDef *meth = PyCapsule_GetPointer(py, NULL);
4887
// HACK: We want to release wrappers created by wrapper. It

include/inline-python.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#define INLINE_PY_ERR_COMPILE 1
1111
#define INLINE_PY_ERR_EVAL 2
1212

13+
14+
1315
// This macro checks for errors. If python exception is raised it
1416
// clear it and returns 1 otherwise retruns 0
1517
#define INLINE_PY_SIMPLE_ERROR_HANDLING() do { \
@@ -29,6 +31,20 @@ void inline_py_export_exception(
2931
char** p_msg
3032
);
3133

34+
// Unpack iterable into array of PyObjects. Iterable must contain
35+
// exactly N elements.
36+
//
37+
// On success returns 0 and fills `out` with N PyObjects
38+
//
39+
// On failure returns -1. Python exception is not cleared. It's
40+
// responsibility of caller to deal with it. Content of `out` is
41+
// undefined in this case.
42+
int inline_py_unpack_iterable(
43+
PyObject *iterable,
44+
int n,
45+
PyObject **out
46+
);
47+
3248
// Allocate python function object which carrries its own PyMethodDef.
3349
// Returns function object or NULL with error raised.
3450
//

src/Python/Inline/Literal.hs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module Python.Inline.Literal
1313
import Control.Exception
1414
import Control.Monad
1515
import Control.Monad.IO.Class
16+
import Control.Monad.Trans.Class
1617
import Control.Monad.Trans.Cont
1718
import Data.Int
1819
import Data.Word
@@ -28,6 +29,7 @@ import Language.C.Inline.Unsafe qualified as CU
2829
import Python.Types
2930
import Python.Internal.Types
3031
import Python.Internal.Eval
32+
import Python.Internal.Util
3133

3234

3335
----------------------------------------------------------------
@@ -163,6 +165,38 @@ instance FromPy Bool where
163165
1 -> pure $ Just True
164166
_ -> pure $ Nothing
165167

168+
instance (ToPy a, ToPy b) => ToPy (a,b) where
169+
basicToPy (a,b) = do
170+
p_a <- basicToPy a
171+
p_b <- basicToPy b
172+
Py [CU.exp| PyObject* { PyTuple_Pack(2, $(PyObject* p_a), $(PyObject* p_b)) } |]
173+
174+
instance (FromPy a, FromPy b) => FromPy (a,b) where
175+
basicFromPy p_tup = evalContT $ do
176+
-- Unpack 2-tuple.
177+
p_args <- withPyAllocaArray 2
178+
unpack_ok <- liftIO [CU.exp| int {
179+
inline_py_unpack_iterable($(PyObject *p_tup), 2, $(PyObject **p_args))
180+
}|]
181+
-- We may want to extract exception to haskell side later
182+
liftIO [CU.exp| void { PyErr_Clear() } |]
183+
when (unpack_ok /= 0) $ abort $ pure Nothing
184+
-- Unpack 2-elements
185+
lift $ do
186+
p_a <- liftIO $ peekElemOff p_args 0
187+
p_b <- liftIO $ peekElemOff p_args 1
188+
let parse = basicFromPy p_a >>= \case
189+
Nothing -> pure Nothing
190+
Just a -> basicFromPy p_b >>= \case
191+
Nothing -> pure Nothing
192+
Just b -> pure $ Just (a,b)
193+
fini = liftIO [CU.block| void {
194+
Py_XDECREF( $(PyObject* p_a) );
195+
Py_XDECREF( $(PyObject* p_b) );
196+
} |]
197+
parse `finallyPy` fini
198+
199+
166200

167201
----------------------------------------------------------------
168202
-- Functions marshalling

src/Python/Internal/Types.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module Python.Internal.Types
1010
PyObject(..)
1111
, PyError(..)
1212
, Py(..)
13+
, finallyPy
1314
-- * inline-C
1415
, pyCtx
1516
-- * Patterns
@@ -20,6 +21,7 @@ module Python.Internal.Types
2021

2122
import Control.Exception
2223
import Control.Monad.IO.Class
24+
import Data.Coerce
2325
import Data.Map.Strict qualified as Map
2426
import Foreign.ForeignPtr
2527
import Foreign.C.Types
@@ -50,6 +52,8 @@ newtype Py a = Py (IO a)
5052
deriving newtype (Functor,Applicative,Monad,MonadIO,MonadFail)
5153
-- See NOTE: [Python and threading]
5254

55+
finallyPy :: forall a b. Py a -> Py b -> Py a
56+
finallyPy = coerce (finally @a @b)
5357

5458
----------------------------------------------------------------
5559
-- inline-C

src/Python/Internal/Util.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ withWCtring = withArray0 (CWchar 0) . map (fromIntegral . ord)
2525
withPyAlloca :: forall a r. Storable a => ContT r Py (Ptr a)
2626
withPyAlloca = coerce (alloca @a @r)
2727

28+
withPyAllocaArray :: forall a r. Storable a => Int -> ContT r Py (Ptr a)
29+
withPyAllocaArray = coerce (allocaArray @a @r)
30+
2831
withPyCString :: forall r. String -> ContT r Py CString
2932
withPyCString = coerce (withCString @r)
3033

test/TST/FromPy.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ tests = testGroup "FromPy"
3434
-- Segfaults if exception is not cleared
3535
[py_| 1+1 |]
3636
]
37+
, testGroup "Tuple2"
38+
[ testCase "(2)->2" $ eq @(Int,Bool) (Just (2,True)) =<< [pye| (2,2) |]
39+
, testCase "[2]->2" $ eq @(Int,Bool) (Just (2,True)) =<< [pye| [2,2] |]
40+
, testCase "(1)->2" $ eq @(Int,Bool) Nothing =<< [pye| (1) |]
41+
, testCase "(3)->2" $ eq @(Int,Bool) Nothing =<< [pye| (1,2,3) |]
42+
, testCase "X->2" $ eq @(Int,Bool) Nothing =<< [pye| 2 |]
43+
]
3744
]
3845

3946
eq :: (Eq a, Show a, FromPy a) => Maybe a -> PyObject -> IO ()

0 commit comments

Comments
 (0)