diff --git a/python3-sys/src/sliceobject.rs b/python3-sys/src/sliceobject.rs index 6be40b0f..2f60347c 100644 --- a/python3-sys/src/sliceobject.rs +++ b/python3-sys/src/sliceobject.rs @@ -11,6 +11,21 @@ pub unsafe fn Py_Ellipsis() -> *mut PyObject { &mut _Py_EllipsisObject } +#[repr(C)] +#[derive(Copy, Clone)] +#[cfg(not(Py_LIMITED_API))] +pub struct PySliceObject { + #[cfg(py_sys_config="Py_TRACE_REFS")] + pub _ob_next: *mut PyObject, + #[cfg(py_sys_config="Py_TRACE_REFS")] + pub _ob_prev: *mut PyObject, + pub ob_refcnt: Py_ssize_t, + pub ob_type: *mut PyTypeObject, + pub start: *mut PyObject, + pub stop: *mut PyObject, + pub step: *mut PyObject +} + #[cfg_attr(windows, link(name="pythonXY"))] extern "C" { pub static mut PySlice_Type: PyTypeObject; pub static mut PyEllipsis_Type: PyTypeObject; diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 791b10e8..19b6e0fa 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -37,6 +37,7 @@ pub use self::num::PyInt; pub use self::num::PyLong as PyInt; pub use self::num::{PyLong, PyFloat}; pub use self::sequence::PySequence; +pub use self::slice::PySlice; #[macro_export] macro_rules! pyobject_newtype( @@ -132,6 +133,7 @@ mod tuple; mod list; mod num; mod sequence; +mod slice; pub mod exc; #[cfg(feature="python27-sys")] diff --git a/src/objects/slice.rs b/src/objects/slice.rs new file mode 100644 index 00000000..357d12c4 --- /dev/null +++ b/src/objects/slice.rs @@ -0,0 +1,163 @@ +use ffi; +use python::{Python, PythonObject}; +use err::{self, PyResult, PyErr}; +use super::{exc, PyObject}; +use conversion::{FromPyObject, ToPyObject}; +use std::ops; + +/// Represents a Python slice object. +pub struct PySlice(PyObject); + +pyobject_newtype!(PySlice, PySlice_Check, PySlice_Type); + +impl PySlice { + /// Construct a new PySlice with given start, stop, and step. + #[inline] + pub fn new(py: Python, start: T, stop: U, step: V) -> Self + where T: ToPyObject, + U: ToPyObject, + V: ToPyObject + { + start.with_borrowed_ptr(py, |start| + stop.with_borrowed_ptr(py, |stop| + step.with_borrowed_ptr(py, |step| unsafe { + err::cast_from_owned_ptr_or_panic( + py, ffi::PySlice_New(start, stop, step)) + }) + ) + ) + } + + /// Accessor method for the start of the PySlice. + #[inline] + pub fn start(&self) -> &PyObject { + unsafe { + let ptr = self.0.as_ptr() as *mut ffi::PySliceObject; + PyObject::borrow_from_ptr(&(*ptr).start) + } + } + + /// Accessor method for the stop of the PySlice. + #[inline] + pub fn stop(&self) -> &PyObject { + unsafe { + let ptr = self.0.as_ptr() as *mut ffi::PySliceObject; + PyObject::borrow_from_ptr(&(*ptr).stop) + } + } + + /// Accessor method for the step of the PySlice. + #[inline] + pub fn step(&self) -> &PyObject { + unsafe { + let ptr = self.0.as_ptr() as *mut ffi::PySliceObject; + PyObject::borrow_from_ptr(&(*ptr).step) + } + } +} + +/// Converts a rust Range to a Python slice. +impl ToPyObject for ops::Range where T: ToPyObject { + type ObjectType = PySlice; + + #[inline] + fn to_py_object(&self, py: Python) -> Self::ObjectType { + Self::ObjectType::new(py, &self.start, &self.end, py.None()) + } +} + +/// Converts a rust RangeFrom to a Python slice. +impl ToPyObject for ops::RangeFrom where T: ToPyObject { + type ObjectType = PySlice; + + #[inline] + fn to_py_object(&self, py: Python) -> Self::ObjectType { + Self::ObjectType::new(py, &self.start, py.None(), py.None()) + } +} + +/// Converts a rust RangeFull to a Python slice. +impl ToPyObject for ops::RangeFull { + type ObjectType = PySlice; + + #[inline] + fn to_py_object(&self, py: Python) -> Self::ObjectType { + Self::ObjectType::new(py, py.None(), py.None(), py.None()) + } +} + +/// Converts a rust RangeTo to a Python slice. +impl ToPyObject for ops::RangeTo where T: ToPyObject { + type ObjectType = PySlice; + + #[inline] + fn to_py_object(&self, py: Python) -> Self::ObjectType { + Self::ObjectType::new(py, py.None(), &self.end, py.None()) + } +} + +macro_rules! range_from_slice_body { + ($Range:ident; $($none_part:ident),*; $($rust_part:ident $py_part:ident),*) => { + #[inline] + fn extract(py: Python, obj: &'a PyObject) -> PyResult { + let obj = try!(obj.cast_as::(py)); + $( + if obj.$none_part().as_ptr() != unsafe { ffi::Py_None() } { + const MSG: &'static str = + concat!("cannot cast slice with ", stringify!($none_part), + " as ::std::ops::", stringify!($Range)); + return Err(PyErr::new_lazy_init( + py.get_type::(), + Some(MSG.to_py_object(py).into_object()) + )); + } + )* + + Ok(ops::$Range { + $( + $rust_part: try!(T::extract(py, obj.$py_part())), + )* + }) + } + } +} +macro_rules! range_from_slice { + ($Range:ident; $($none_part:ident),*; $($rust_part:ident $py_part:ident),*) => { + impl<'a, T> FromPyObject<'a> for ops::$Range where T: for<'b> FromPyObject<'b> { + range_from_slice_body!($Range; $($none_part),*; $($rust_part $py_part),*); + } + }; + ($Range:ident; $($none_part:ident),*; ) => { + impl<'a> FromPyObject<'a> for ops::$Range { + range_from_slice_body!($Range; $($none_part),*; ); + } + }; +} + +/// Converts a Python slice to rust Range types. +range_from_slice!(Range; step; start start, end stop); +range_from_slice!(RangeFrom; stop, step; start start); +range_from_slice!(RangeFull; start, stop, step; ); +range_from_slice!(RangeTo; start, step; end stop); + +#[cfg(test)] +mod test { + use python::{Python, PythonObject}; + use conversion::ToPyObject; + use super::PySlice; + use std::ops; + + #[test] + fn test_range_slice_conversion() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + assert_eq!(2..9, (2..9).to_py_object(py).as_object().extract(py).unwrap()); + assert_eq!(2.., (2..).to_py_object(py).as_object().extract(py).unwrap()); + assert_eq!(.., (..).to_py_object(py).as_object().extract(py).unwrap()); + assert_eq!(..9, (..9).to_py_object(py).as_object().extract(py).unwrap()); + + let x: ops::Range = ("a".."z").to_py_object(py).as_object().extract(py).unwrap(); + assert_eq!(x, "a".to_owned().."z".to_owned()); + } +}