use std::ffi::{CStr, CString};
use std::panic;
use std::{any, io, marker, mem, ptr};
use crate::conversion::ToPyObject;
use crate::err::{self, PyResult};
use crate::ffi;
use crate::objects::{exc, PyDict, PyObject, PyString, PyTuple};
use crate::python::{PyDrop, Python, PythonObject};
#[macro_export]
#[doc(hidden)]
macro_rules! py_method_def {
($name: expr, $flags: expr, $wrap: expr) => {{
$crate::py_method_def!($name, $flags, $wrap, "")
}};
($name: expr, $flags: expr, $wrap: expr, $doc: expr) => {{
static mut METHOD_DEF: $crate::_detail::ffi::PyMethodDef =
$crate::_detail::ffi::PyMethodDef {
ml_name: 0 as *const $crate::_detail::libc::c_char,
ml_meth: None,
ml_flags: $crate::_detail::ffi::METH_VARARGS
| $crate::_detail::ffi::METH_KEYWORDS
| $flags,
ml_doc: 0 as *const $crate::_detail::libc::c_char,
};
METHOD_DEF.ml_name = $crate::strip_raw!(concat!($name, "\0")).as_ptr() as *const _;
if !$doc.is_empty() {
METHOD_DEF.ml_doc = concat!($doc, "\0").as_ptr() as *const _;
}
METHOD_DEF.ml_meth = Some(std::mem::transmute::<
$crate::_detail::ffi::PyCFunctionWithKeywords,
$crate::_detail::ffi::PyCFunction,
>($wrap));
&mut METHOD_DEF
}};
}
#[macro_export]
macro_rules! py_fn {
($py:expr, $f:ident $plist:tt ) => {
$crate::py_argparse_parse_plist! { py_fn_impl { $py, $f } $plist }
};
($py:ident, $f:ident $plist:tt -> $ret:ty { $($body:tt)* } ) => {
$crate::py_argparse_parse_plist! { py_fn_impl { $py, $f, $ret, { $($body)* } } $plist }
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! py_fn_impl {
{ $py:expr, $f:ident [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ] } => {{
unsafe extern "C" fn wrap(
_slf: *mut $crate::_detail::ffi::PyObject,
args: *mut $crate::_detail::ffi::PyObject,
kwargs: *mut $crate::_detail::ffi::PyObject)
-> *mut $crate::_detail::ffi::PyObject
{
$crate::_detail::handle_callback(
stringify!($f), $crate::_detail::PyObjectCallbackConverter,
|py| {
$crate::py_argparse_raw!(py, Some(stringify!($f)), args, kwargs,
[ $( { $pname : $ptype = $detail } )* ]
{
$f(py $(, $pname )* )
})
})
}
unsafe {
$crate::_detail::py_fn_impl($py,
$crate::py_method_def!(stringify!($f), 0, wrap))
}
}};
{ $py:ident, $f:ident, $ret:ty, {$($body:tt)*} [ $( { $pname:ident : $ptype:ty = $detail:tt } )* ] } => {{
fn $f($py: $crate::Python $( , $pname : $ptype )* ) -> $ret {
let _ = $py;
$($body)*
}
$crate::py_fn_impl!($py, $f [ $( { $pname : $ptype = $detail } )* ])
}}
}
pub unsafe fn py_fn_impl(py: Python, method_def: *mut ffi::PyMethodDef) -> PyObject {
err::from_owned_ptr_or_panic(py, ffi::PyCFunction_New(method_def, ptr::null_mut()))
}
pub trait CallbackConverter<S> {
type R;
fn convert(val: S, py: Python) -> Self::R;
fn error_value() -> Self::R;
}
pub struct PyObjectCallbackConverter;
impl<S> CallbackConverter<S> for PyObjectCallbackConverter
where
S: ToPyObject,
{
type R = *mut ffi::PyObject;
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub struct PythonObjectCallbackConverter<T>(pub marker::PhantomData<T>);
impl<T, S> CallbackConverter<S> for PythonObjectCallbackConverter<T>
where
T: PythonObject,
S: ToPyObject<ObjectType = T>,
{
type R = *mut ffi::PyObject;
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub unsafe fn handle_callback<F, T, C>(_location: &str, _c: C, f: F) -> C::R
where
F: FnOnce(Python) -> PyResult<T>,
F: panic::UnwindSafe,
C: CallbackConverter<T>,
{
let ret = panic::catch_unwind(|| {
let py = Python::assume_gil_acquired();
match f(py) {
Ok(val) => C::convert(val, py),
Err(e) => {
e.restore(py);
C::error_value()
}
}
});
match ret {
Ok(r) => r,
Err(err) => {
let guard = AbortOnDrop("handle_panic() / C::error_value()");
handle_panic(Python::assume_gil_acquired(), err);
let errval = C::error_value();
mem::forget(guard);
errval
}
}
}
fn handle_panic(_py: Python, panic: Box<dyn any::Any>) {
let panic_str = if let Some(s) = panic.downcast_ref::<String>() {
Some(s.as_str())
} else if let Some(s) = panic.downcast_ref::<&'static str>() {
Some(*s)
} else {
None
};
let panic_cstring = panic_str.and_then(|s| {
let result = CString::new(format!("Rust panic: {}", s));
result.ok()
});
let msg = if let Some(s) = &panic_cstring {
s.as_c_str()
} else {
cstr!("Rust panic")
};
unsafe {
ffi::PyErr_SetString(ffi::PyExc_SystemError, msg.as_ptr());
}
}
pub struct AbortOnDrop<'a>(pub &'a str);
impl<'a> Drop for AbortOnDrop<'a> {
fn drop(&mut self) {
use std::io::Write;
let _ = writeln!(&mut io::stderr(), "Cannot unwind out of {}", self.0);
unsafe { libc::abort() }
}
}