Skip to content

Add very basic support for Python slices. #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions python3-sys/src/sliceobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/objects/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -132,6 +133,7 @@ mod tuple;
mod list;
mod num;
mod sequence;
mod slice;
pub mod exc;

#[cfg(feature="python27-sys")]
Expand Down
163 changes: 163 additions & 0 deletions src/objects/slice.rs
Original file line number Diff line number Diff line change
@@ -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<T, U, V>(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<T> ToPyObject for ops::Range<T> 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<T> ToPyObject for ops::RangeFrom<T> 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<T> ToPyObject for ops::RangeTo<T> 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<Self> {
let obj = try!(obj.cast_as::<PySlice>(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::<exc::ValueError>(),
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<T>; $($none_part:ident),*; $($rust_part:ident $py_part:ident),*) => {
impl<'a, T> FromPyObject<'a> for ops::$Range<T> 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<T>; step; start start, end stop);
range_from_slice!(RangeFrom<T>; stop, step; start start);
range_from_slice!(RangeFull; start, stop, step; );
range_from_slice!(RangeTo<T>; 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<String> = ("a".."z").to_py_object(py).as_object().extract(py).unwrap();
assert_eq!(x, "a".to_owned().."z".to_owned());
}
}