From 7ccf5c88f10f5cc063d37611ff85b7782fd5accf Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 5 Jan 2015 17:05:53 +0100 Subject: [PATCH] Initial commit --- .gitignore | 4 + Cargo.toml | 11 +++ Makefile | 20 +++++ src/conversion.rs | 139 +++++++++++++++++++++++++++++ src/err.rs | 82 +++++++++++++++++ src/lib.rs | 39 ++++++++ src/module.rs | 62 +++++++++++++ src/object.rs | 222 ++++++++++++++++++++++++++++++++++++++++++++++ src/pyptr.rs | 164 ++++++++++++++++++++++++++++++++++ src/python.rs | 126 ++++++++++++++++++++++++++ src/pythonrun.rs | 32 +++++++ src/typeobject.rs | 61 +++++++++++++ test.bat | 6 ++ test.sh | 7 ++ testmodule.rs | 29 ++++++ 15 files changed, 1004 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 src/conversion.rs create mode 100644 src/err.rs create mode 100644 src/lib.rs create mode 100644 src/module.rs create mode 100644 src/object.rs create mode 100644 src/pyptr.rs create mode 100644 src/python.rs create mode 100644 src/pythonrun.rs create mode 100644 src/typeobject.rs create mode 100644 test.bat create mode 100755 test.sh create mode 100644 testmodule.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..81c01aa2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/Cargo.lock +/testmodule.so + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..f83486f1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] + +name = "rust-cpython" +version = "0.0.1" +authors = ["Daniel Grunwald "] + +[dependencies.libpython27-sys] +path="../libpython27-sys" + + + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..72472da9 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: build all test testextension debug + +all: test + +test: + cargo test + +testextension: testmodule.so + python -c "import testmodule; print(repr(testmodule.__author__))" + +debug: testmodule.so + gdb --args python -c "import testmodule; print(repr(testmodule.__author__))" + +target/librust-cpython-21cf8ea55e61f78d.rlib: src/*.rs Cargo.toml + cargo build + +testmodule.so: testmodule.rs target/librust-cpython-21cf8ea55e61f78d.rlib Makefile + rustc testmodule.rs -g --extern rust-cpython=target/librust-cpython-21cf8ea55e61f78d.rlib --extern abort_on_panic=target/deps/libabort_on_panic-95c987fec9e5b445.rlib -o testmodule.so + + diff --git a/src/conversion.rs b/src/conversion.rs new file mode 100644 index 00000000..ad13adea --- /dev/null +++ b/src/conversion.rs @@ -0,0 +1,139 @@ +use libc::c_char; +use std; +use ffi; +use err; +use {Python, PyObject, PyResult, PythonObject, PyErr}; +use pyptr::{PyPtr, PythonPointer}; + +/// FromPyObject is implemented by various types that can be extracted from a python object. +pub trait FromPyObject<'p, 'a> { + fn from_py_object(s: &'a PyObject<'p>) -> PyResult<'p, Self>; +} + +/// ToPyObject is implemented for types that can be converted into a python object. +pub trait ToPyObject<'p> for Sized? { + //type PointerType : 'p + PythonPointer + Deref> = PyPtr<'p, PyObject<'p>>; + + //fn to_py_object(&self, py: Python<'p>) -> PyResult<'p, Self::PointerType>; + fn to_py_object(&self, py: Python<'p>) -> PyResult<'p, PyPtr<'p, PyObject<'p>>>; +} + +/// BorrowAsPyObject is implemented for types that can be accessed as a borrowed python object +/// (without having to allocate a temporary python object) +trait BorrowAsPyObject<'p> for Sized? { + fn as_py_object(&self, py: Python<'p>) -> &PyObject<'p>; +} +// Note: I think BorrowAsPyObject is too restricted to be useful, we might as well use &PyObject. +// On the other hand, we might want to optimize ToPyObject so that it doesn't always return a new +// reference: it could return PyResult with associated type A : 'p + PythonPointer + Deref>. +// Then types that can borrow existing python objects can return A=&'p PyObject<'p>, +// while other types can return A=PyPtr<'p, PyObject<'p>>. + +// impl ToPyObject for BorrowAsPyObject +impl <'p, T : BorrowAsPyObject<'p>> ToPyObject<'p> for T { + #[inline] + fn to_py_object(&self, py: Python<'p>) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + Ok(PyPtr::new(self.as_py_object(py))) + } +} + +// PyObject, PyModule etc. +// We support all three traits (FromPyObject, ToPyObject, BorrowAsPyObject) for +// borrowed python references. +// This allows using existing python objects in code that generically expects a value +// convertible to a python object. +impl <'p, T : PythonObject<'p>> BorrowAsPyObject<'p> for T { + #[inline] + fn as_py_object(&self, _: Python<'p>) -> &PyObject<'p> { + self.as_object() + } +} + +impl <'p, 'a, T : PythonObject<'p>> FromPyObject<'p, 'a> for &'a T { + #[inline] + fn from_py_object(s: &'a PyObject<'p>) -> PyResult<'p, &'a T> { + s.downcast() + } +} + +// PyPtr +// We support all three traits (FromPyObject, ToPyObject, BorrowAsPyObject) for +// owned python references. +// This allows using existing python objects in code that generically expects a value +// convertible to a python object. + +impl <'p, T : PythonObject<'p>> BorrowAsPyObject<'p> for PyPtr<'p, T> { + #[inline] + fn as_py_object(&self, _: Python<'p>) -> &PyObject<'p> { + self.as_object() + } +} + +impl <'p, 'a, T : PythonObject<'p>> FromPyObject<'p, 'a> for PyPtr<'p, T> { + #[inline] + fn from_py_object(s : &'a PyObject<'p>) -> PyResult<'p, PyPtr<'p, T>> { + PyPtr::new(s).downcast_into() + } +} + + +// bool +// As the bool instances have lifetime 'p, we can implement BorrowAsPyObject, not just ToPyObject +impl <'p> BorrowAsPyObject<'p> for bool { + #[inline] + fn as_py_object(&self, py: Python<'p>) -> &PyObject<'p> { + if *self { py.True() } else { py.False() } + } +} + +impl <'p, 'a> FromPyObject<'p, 'a> for bool { + fn from_py_object(s: &'a PyObject<'p>) -> PyResult<'p, bool> { + let py = s.python(); + if s == py.True() { + Ok(true) + } else if s == py.False() { + Ok(false) + } else { + unimplemented!() + } + } +} + +// Strings. +// When converting strings to/from python, we need to copy the string data. +// This means we can implement ToPyObject for str, but FromPyObject only for String. +impl <'p> ToPyObject<'p> for str { + fn to_py_object(&self, py : Python<'p>) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + let ptr : *const c_char = self.as_ptr() as *const _; + let len : ffi::Py_ssize_t = std::num::from_uint(self.len()).unwrap(); + unsafe { + use std::ascii::AsciiExt; + let obj = if self.is_ascii() { + ffi::PyString_FromStringAndSize(ptr, len) + } else { + ffi::PyUnicode_FromStringAndSize(ptr, len) + }; + err::result_from_owned_ptr(py, obj) + } + } +} + +impl <'p, 'a> FromPyObject<'p, 'a> for String { + fn from_py_object(s : &'a PyObject<'p>) -> PyResult<'p, String> { + string_as_slice(s).map(|buf| String::from_utf8_lossy(buf).to_string()) + } +} + +pub fn string_as_slice<'a, 'p>(s : &'a PyObject<'p>) -> PyResult<'p, &'a [u8]> { + unsafe { + let mut buffer : *mut c_char = std::mem::uninitialized(); + let mut length : ffi::Py_ssize_t = std::mem::uninitialized(); + if ffi::PyString_AsStringAndSize(s.as_ptr(), &mut buffer, &mut length) == 1 { + Err(PyErr::fetch(s.python())) + } else { + let buffer = buffer as *const u8; + Ok(std::slice::from_raw_buf(std::mem::copy_lifetime(s, &buffer), length as uint)) + } + } +} + diff --git a/src/err.rs b/src/err.rs new file mode 100644 index 00000000..99395ee9 --- /dev/null +++ b/src/err.rs @@ -0,0 +1,82 @@ +use std; +use {PyObject, PythonObject, PyTypeObject, Python, PyPtr}; +use pyptr::PythonPointer; +use ffi; +use libc; + +/// Represents a python exception that was raised. +#[derive(Clone, Show)] +pub struct PyErr<'p> { + ptype : Option>>, + pvalue : Option>>, + ptraceback : Option>> +} + + +/// Represents the result of a python call. +pub type PyResult<'p, T> = Result>; +pub type PyPtrResult<'p, T> = PyResult<'p, PyPtr<'p, T>>; + +impl <'p> PyErr<'p> { + /// Gets whether an error is present in the python interpreter's global state. + pub fn occurred(_ : Python<'p>) -> bool { + unsafe { !ffi::PyErr_Occurred().is_null() } + } + + /// Retrieves the current error from the python interpreter's global state. + /// The error is cleared from the python interpreter. + pub fn fetch(py : Python<'p>) -> PyErr<'p> { + unsafe { + let mut ptype : *mut ffi::PyObject = std::mem::uninitialized(); + let mut pvalue : *mut ffi::PyObject = std::mem::uninitialized(); + let mut ptraceback : *mut ffi::PyObject = std::mem::uninitialized(); + ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback); + PyErr { + ptype: PyPtr::from_owned_ptr_opt(py, ptype), + pvalue: PyPtr::from_owned_ptr_opt(py, pvalue), + ptraceback: PyPtr::from_owned_ptr_opt(py, pvalue) + } + } + } + + /// Restores the error by writing it to the python interpreter's global state. + pub fn restore(self) { + let PyErr { ptype, pvalue, ptraceback } = self; + unsafe { + ffi::PyErr_Restore(ptype.steal_ptr(), pvalue.steal_ptr(), ptraceback.steal_ptr()) + } + } + + #[allow(unused_variables)] + pub fn type_error(obj : &PyObject<'p>, expected_type : &PyTypeObject<'p>) -> PyErr<'p> { + let py = obj.python(); + PyErr { + ptype: Some(unsafe { PyPtr::from_borrowed_ptr(py, ffi::PyExc_TypeError) }), + pvalue: None, + ptraceback: None + } + } +} + +/// Construct PyObject from the result of a python FFI call that returns a new reference (owned pointer). +/// Returns Err(PyErr) if the pointer is null. +/// Unsafe because the pointer might be invalid. +#[inline] +pub unsafe fn result_from_owned_ptr(py : Python, p : *mut ffi::PyObject) -> PyResult> { + if p.is_null() { + Err(PyErr::fetch(py)) + } else { + Ok(PyPtr::from_owned_ptr(py, p)) + } +} + +/// Returns Ok if the error code is 0. +#[inline] +pub fn result_from_error_code(py : Python, result : libc::c_int) -> PyResult<()> { + if result == 0 { + Ok(()) + } else { + Err(PyErr::fetch(py)) + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..b6ff8c1a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,39 @@ +#![feature(unsafe_destructor)] +#![allow(unused_imports, dead_code, unused_variables)] +#![feature(associated_types)] + +extern crate libc; +extern crate "libpython27-sys" as ffi; +pub use ffi::Py_ssize_t; +pub use err::{PyErr, PyResult}; +pub use python::Python; +pub use pythonrun::PythonInterpreter; +pub use object::{PythonObject, PyObject, ObjectProtocol}; +pub use typeobject::PyTypeObject; +pub use pyptr::PyPtr; +pub use module::PyModule; +pub use conversion::{FromPyObject, ToPyObject}; + +// Fundamentals: +mod python; +mod object; +mod pyptr; +mod err; + +// Object Types: +mod typeobject; +mod module; + +mod pythonrun; +mod conversion; + +#[test] +fn it_works() { + let interpreter = unsafe { PythonInterpreter::new() }; + let py = interpreter.python(); + let sys = PyModule::import(py, "sys").unwrap(); + let path = sys.as_object().getattr("path").unwrap(); + println!("{0}", path); + panic!() +} + diff --git a/src/module.rs b/src/module.rs new file mode 100644 index 00000000..04bc6e40 --- /dev/null +++ b/src/module.rs @@ -0,0 +1,62 @@ +use std; +use ffi; +//use ffi::CStringAsPtr; +//use {PyResult, PyErr, FromPyObject, PyTypeObject, ToPyObject, PyObject, Python, PyPtr, PythonObject}; +use {Python, PyPtr, PyResult, PyObject, PythonObject, PyTypeObject}; +use err; + +pub struct PyModule<'p>(PyObject<'p>); + +impl <'p> PythonObject<'p> for PyModule<'p> { + fn from_object<'a>(obj : &'a PyObject<'p>) -> Option<&'a PyModule<'p>> { + unsafe { + if ffi::PyModule_Check(obj.as_ptr()) { + Some(std::mem::transmute(obj)) + } else { + None + } + } + } + + fn as_object<'a>(&'a self) -> &'a PyObject<'p> { + &self.0 + } + + fn type_object(_ : Option<&Self>) -> &'p PyTypeObject<'p> { + panic!() + } +} + +impl <'p> PyModule<'p> { + pub fn import(py : Python<'p>, name : N) -> PyResult>> { + let result = name.with_c_str(|name| unsafe { + err::result_from_owned_ptr(py, ffi::PyImport_ImportModule(name)) + }); + try!(result).downcast_into() + } +} + +/* +pub fn as_module<'p>(py : &'p Python, obj : PyPtr) -> PyResult<'p, PyPtr<'p, PyModule>> { + if py.module_type().is_instance(obj.deref()) { + Ok(unsafe { PyPtr::from_owned_ptr(py, obj.steal_ptr()) }) + } else { + Err(PyErr::type_error(py, obj.deref(), py.module_type())) + } +} + +impl PyModule { + + pub fn add_object + (&self, name : &S, value : &T) -> PyResult<()> + { + let value = try!(value.to_py_object(self.python())).steal_ptr(); + let rc = name.with_c_str(|name| unsafe { + ffi::PyModule_AddObject(self.as_ptr(), name, value) + }); + err::result_from_error_code(self.python(), rc) + } + +} +*/ + diff --git a/src/object.rs b/src/object.rs new file mode 100644 index 00000000..5d434d18 --- /dev/null +++ b/src/object.rs @@ -0,0 +1,222 @@ +//use {PythonObject, PyPtr, PyResult, PyTypeObject, Python}; +use std; +use std::cmp::Ordering; +use libc; +use ffi; +use {Python, Py_ssize_t, PyResult, PyErr, PyPtr, ToPyObject}; +use typeobject::PyTypeObject; +use err; + +/// Trait implemented by all python object types. +pub trait PythonObject<'p> { + /// Upcast from PyObject to a concrete python object type. + /// Returns None if the python object is not of the specified type. + fn from_object<'a>(&'a PyObject<'p>) -> Option<&'a Self>; + + /// Casts the python object to PyObject. + fn as_object<'a>(&'a self) -> &'a PyObject<'p>; + + /// Retrieves the underlying FFI pointer associated with this python object. + fn as_ptr(&self) -> *mut ffi::PyObject { + self.as_object().as_ptr() + } + + /// Retrieves the type object for this python object type. + /// unused_self is necessary until UFCS is implemented. + fn type_object(unused_self : Option<&Self>) -> &'p PyTypeObject<'p>; + + /// Retrieve python instance from an existing python object. + fn python(&self) -> Python<'p> { + self.as_object().python() + } +} + +pub struct PyObject<'p> { + cell : std::cell::UnsafeCell, + py : Python<'p> +} + +impl <'p> PythonObject<'p> for PyObject<'p> { + #[inline] + fn from_object<'a>(obj : &'a PyObject<'p>) -> Option<&'a PyObject<'p>> { + Some(obj) + } + + #[inline] + fn as_object<'a>(&'a self) -> &'a PyObject<'p> { + self + } + + /// Retrieves the underlying FFI pointer associated with this python object. + #[inline] + fn as_ptr(&self) -> *mut ffi::PyObject { + self.cell.get() + } + + #[inline] + fn python(&self) -> Python<'p> { + self.py + } + + fn type_object(_ : Option<&Self>) -> &'p PyTypeObject<'p> { + panic!() + } +} + +impl <'p> PyObject<'p> { + + + /// Retrieves the PyObject instance for the given FFI pointer. + /// Undefined behavior if the pointer is NULL or invalid. + /// Also, the output lifetime 'a is unconstrained, make sure to use a lifetime + /// appropriate for the underlying FFI pointer. + #[inline] + pub unsafe fn from_ptr<'a>(_ : Python<'p>, p : *mut ffi::PyObject) -> &'a PyObject<'p> { + debug_assert!(!p.is_null()); + &*(p as *mut PyObject) + } + + /// Retrieves the reference count of this python object. + #[inline] + pub fn get_refcnt(&self) -> Py_ssize_t { + unsafe { ffi::Py_REFCNT(self.as_ptr()) } + } + + /*pub fn get_type(&self) -> &PyTypeObject { + unsafe { PyTypeObject::from_type_ptr(self.python(), ffi::Py_TYPE(self.as_ptr())) } + }*/ + + /// Casts the PyObject to a concrete python object type. + /// Returns a python TypeError if the object is not of the expected type. + pub fn downcast>(&self) -> PyResult<'p, &T> { + let obj_opt : Option<&T> = PythonObject::from_object(self); + match obj_opt { + Some(obj) => Ok(obj), + None => Err(PyErr::type_error(self, PythonObject::type_object(obj_opt))) + } + } +} + +impl <'p> std::fmt::Show for PyObject<'p> { + fn fmt(&self, f : &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + let rep = try!(self.repr().map_err(|_| std::fmt::Error)); + let slice = try!(::conversion::string_as_slice(&*rep).map_err(|_| std::fmt::Error)); + f.write_str(try!(std::str::from_utf8(slice).map_err(|_| std::fmt::Error))) + } +} + +impl <'p> PartialEq for PyObject<'p> { + #[inline] + fn eq(&self, o : &PyObject<'p>) -> bool { + self.as_ptr() == o.as_ptr() + } +} + +pub trait ObjectProtocol<'p> : PythonObject<'p> { + /// Determines whether this object has the given attribute. + /// This is equivalent to the Python expression 'hasattr(self, attr_name)'. + #[inline] + fn hasattr>(&self, attr_name: &N) -> PyResult<'p, bool> { + let py = self.python(); + let attr_name = try!(attr_name.to_py_object(py)); + unsafe { + Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name.as_ptr()) != 0) + } + } + + /// Retrieves an attribute value. + /// This is equivalent to the Python expression 'self.attr_name'. + #[inline] + fn getattr>(&self, attr_name: &N) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + let py = self.python(); + let attr_name = try!(attr_name.to_py_object(py)); + unsafe { + err::result_from_owned_ptr(py, ffi::PyObject_GetAttr(self.as_ptr(), attr_name.as_ptr())) + } + } + + /// Sets an attribute value. + /// This is equivalent to the Python expression 'self.attr_name = value'. + #[inline] + fn setattr, Sized? V: ToPyObject<'p>> + (&self, attr_name: &N, value: &V) -> PyResult<'p, ()> { + let py = self.python(); + let attr_name = try!(attr_name.to_py_object(py)); + let value = try!(value.to_py_object(py)); + unsafe { + err::result_from_error_code(py, + ffi::PyObject_SetAttr(self.as_ptr(), attr_name.as_ptr(), value.as_ptr())) + } + } + + /// Deletes an attribute. + /// This is equivalent to the Python expression 'del self.attr_name'. + #[inline] + fn delattr>(&self, attr_name: &N) -> PyResult<'p, ()> { + let py = self.python(); + let attr_name = try!(attr_name.to_py_object(py)); + unsafe { + err::result_from_error_code(py, + ffi::PyObject_DelAttr(self.as_ptr(), attr_name.as_ptr())) + } + } + + /// Compares two python objects. + /// This is equivalent to the python expression 'cmp(self, other)'. + fn compare(&self, other : &PyObject<'p>) -> PyResult<'p, Ordering> { + unsafe { + let mut result : libc::c_int = std::mem::uninitialized(); + try!(err::result_from_error_code(self.python(), + ffi::PyObject_Cmp(self.as_ptr(), other.as_ptr(), &mut result))); + Ok(if result < 0 { + Ordering::Less + } else if result > 0 { + Ordering::Greater + } else { + Ordering::Equal + }) + } + } + + /// Compute the string representation of self. + /// This is equivalent to the python expression 'repr(self)'. + fn repr(&self) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + unsafe { + err::result_from_owned_ptr(self.python(), ffi::PyObject_Repr(self.as_ptr())) + } + } + + /// Compute the string representation of self. + /// This is equivalent to the python expression 'str(self)'. + fn str(&self) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + unsafe { + err::result_from_owned_ptr(self.python(), ffi::PyObject_Str(self.as_ptr())) + } + } + + /// Compute the unicode string representation of self. + /// This is equivalent to the python expression 'unistr(self)'. + fn unistr(&self) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + unsafe { + err::result_from_owned_ptr(self.python(), ffi::PyObject_Unicode(self.as_ptr())) + } + } + + /// Determines whether this object is callable. + fn is_callable(&self) -> bool { + unsafe { + ffi::PyCallable_Check(self.as_ptr()) != 0 + } + } + + fn call(&self, args: &PyObject<'p>, kw: Option<&PyObject<'p>>) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + unimplemented!() + } + + fn call_method>(&self, name: &N, args: &PyObject<'p>, kw: Option<&PyObject<'p>>) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> { + try!(self.getattr(name)).call(args, kw) + } +} + +impl <'p> ObjectProtocol<'p> for PyObject<'p> {} + diff --git a/src/pyptr.rs b/src/pyptr.rs new file mode 100644 index 00000000..84eafcfe --- /dev/null +++ b/src/pyptr.rs @@ -0,0 +1,164 @@ +use std; +use std::ops::Deref; +use ffi; +use object::{PythonObject, PyObject}; +use err::{PyResult, PyErr}; +use python::Python; +//use conversion::{FromPyObject, ToPyObject}; +//use PyResult; + + +/// Owned pointer to python object. +/// The PyPtr owns one reference to a python object. +/// Python objects are reference-counted, so it is possible to have +/// multiple PyPtr objects pointing to the same object, like Rc. +pub struct PyPtr<'p, T : 'p + PythonObject<'p>>(&'p T); + +// impl Deref for PyPtr +impl <'p, T : 'p + PythonObject<'p>> Deref for PyPtr<'p, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + debug_assert!(self.0.as_object().get_refcnt() > 0); + self.0 + } +} + +// impl Drop for PyPtr +#[unsafe_destructor] +impl<'p, T : 'p + PythonObject<'p>> Drop for PyPtr<'p, T> { + #[inline] + fn drop(&mut self) { + debug_assert!(self.0.as_object().get_refcnt() > 0); + unsafe { ffi::Py_DECREF(self.as_ptr()) } + } +} + +// impl Clone for PyPtr +impl<'p, T : 'p + PythonObject<'p>> Clone for PyPtr<'p, T> { + #[inline] + fn clone(&self) -> PyPtr<'p, T> { + unsafe { ffi::Py_INCREF(self.as_ptr()) }; + PyPtr(self.0) + } +} + +// impl Show for PyPtr +impl<'p, T : 'p + PythonObject<'p> + std::fmt::Show> std::fmt::Show for PyPtr<'p, T> { + #[inline] + fn fmt(&self, f : &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.deref().fmt(f) + } +} + +// impl PyPtr +impl<'p, T : 'p + PythonObject<'p>> PyPtr<'p, T> { + /// Creates a new PyPtr instance from a borrowed reference. + /// This increments the reference count. + #[inline] + pub fn new(obj : &T) -> PyPtr<'p, T> { + debug_assert!(obj.as_object().get_refcnt() > 0); + let obj_extended_life : &T = unsafe { + ffi::Py_INCREF(obj.as_ptr()); + // transmuting from &T to &'p T is safe because we just incremented the reference count, + // and the &'p T is used only within the PyPtr -- the reference returned by Deref has + // the lifetime restricted to the PyPtr's lifetime. + std::mem::transmute(obj) + }; + PyPtr(obj_extended_life) + } +} + +/// The PythonPointer trait allows extracting an FFI pointer. +pub trait PythonPointer { + /// Gets the FFI pointer (borrowed reference). + fn as_ptr(&self) -> *mut ffi::PyObject; + /// Gets the FFI pointer (owned reference). + /// If the implementation of this trait is an owned pointer, this steals the reference. + fn steal_ptr(self) -> *mut ffi::PyObject; +} + +impl <'p, T : 'p + PythonObject<'p>> PythonPointer for PyPtr<'p, T> { + #[inline] + fn as_ptr(&self) -> *mut ffi::PyObject { + self.deref().as_ptr() + } + + #[inline] + fn steal_ptr(self) -> *mut ffi::PyObject { + // Destruct the PyPtr without decrementing the reference count + let p = self.deref().as_ptr(); + unsafe { std::mem::forget(self) }; + p + } +} + +// &PyObject (etc.) is also a PythonPointer +// (but steal_ptr increases the reference count) +impl <'p, 'a, T : 'p + PythonObject<'p>> PythonPointer for &'a T { + #[inline] + fn as_ptr(&self) -> *mut ffi::PyObject { + self.deref().as_ptr() + } + + #[inline] + fn steal_ptr(self) -> *mut ffi::PyObject { + PyPtr::new(self).steal_ptr() + } +} + +// Option can be used to extract a nullable FFI pointer. +impl PythonPointer for Option { + #[inline] + fn as_ptr(&self) -> *mut ffi::PyObject { + match *self { + Some(ref p) => p.as_ptr(), + None => std::ptr::null_mut() + } + } + + #[inline] + fn steal_ptr(self) -> *mut ffi::PyObject { + match self { + Some(p) => p.steal_ptr(), + None => std::ptr::null_mut() + } + } +} + +// impl PyPtr +impl<'p> PyPtr<'p, PyObject<'p>> { + #[inline] + pub unsafe fn from_owned_ptr(py : Python<'p>, p : *mut ffi::PyObject) -> PyPtr<'p, PyObject<'p>> { + debug_assert!(!p.is_null() && ffi::Py_REFCNT(p) > 0); + PyPtr(PyObject::from_ptr(py, p)) + } + + + #[inline] + pub unsafe fn from_borrowed_ptr(py : Python<'p>, p : *mut ffi::PyObject) -> PyPtr<'p, PyObject<'p>> { + debug_assert!(!p.is_null() && ffi::Py_REFCNT(p) > 0); + ffi::Py_INCREF(p); + PyPtr(PyObject::from_ptr(py, p)) + } + + #[inline] + pub unsafe fn from_owned_ptr_opt(py : Python<'p>, p : *mut ffi::PyObject) -> Option>> { + if p.is_null() { None } else { Some(PyPtr::from_owned_ptr(py, p)) } + } + + #[inline] + pub unsafe fn from_borrowed_ptr_opt(py : Python<'p>, p : *mut ffi::PyObject) -> Option>> { + if p.is_null() { None } else { Some(PyPtr::from_borrowed_ptr(py, p)) } + } + + /// Casts the PyPtr to a PyPtr of a concrete python object type. + /// Returns a python TypeError if the object is not of the expected type. + #[inline] + pub fn downcast_into>(self) -> PyResult<'p, PyPtr<'p, T>> { + // TODO: avoid unnecessary IncRef/DefRef + self.deref().downcast().map(PyPtr::new) + } +} + diff --git a/src/python.rs b/src/python.rs new file mode 100644 index 00000000..63ed685f --- /dev/null +++ b/src/python.rs @@ -0,0 +1,126 @@ +use std; +use std::kinds::marker::{NoSend, NoCopy, InvariantLifetime}; +use std::ptr; +use ffi; +use std::c_str::CString; +use object::PyObject; + +/// The 'Python' struct is a zero-size marker struct that is required for most python operations. +/// This is used to indicate that the operation accesses/modifies the python interpreter state, +/// and thus can only be called if the python interpreter is initialized and the GIL is acquired. +/// The lifetime 'p represents the lifetime of the python interpreter. +/// For example, python constants like None have the type "&'p PyObject<'p>". +/// You can imagine the GIL to be a giant "Mutex". This makes 'p the lifetime of the +/// python state protected by that mutex. +#[derive(Copy)] +pub struct Python<'p>(NoSend, InvariantLifetime<'p>); + +impl<'p> Python<'p> { + /// Retrieve python instance under the assumption that the GIL is already acquired at this point, + /// and stays acquired for the lifetime 'p. + pub unsafe fn assume_gil_acquired() -> Python<'p> { + Python(NoSend, InvariantLifetime) + } + + /// Retrieves a reference to the special 'None' value. + #[allow(non_snake_case)] // the python keyword starts with uppercase + #[inline] + pub fn None(self) -> &'p PyObject<'p> { + unsafe { PyObject::from_ptr(self, ffi::Py_None()) } + } + + /// Retrieves a reference to the 'True' constant value. + #[allow(non_snake_case)] // the python keyword starts with uppercase + #[inline] + pub fn True(self) -> &'p PyObject<'p> { + unsafe { PyObject::from_ptr(self, ffi::Py_True()) } + } + + /// Retrieves a reference to the 'False' constant value. + #[allow(non_snake_case)] // the python keyword starts with uppercase + #[inline] + pub fn False(self) -> &'p PyObject<'p> { + unsafe { PyObject::from_ptr(self, ffi::Py_False()) } + } + +/* + /// Retrieve python instance from an existing PyObject. + /// This can be used to avoid having to explicitly pass the &Python parameter to each function call. + /// Note that the reference may point to a different memory location than the original &Python used to + /// construct the object -- the Python type is just used as a token to prove that the GIL is currently held. + pub fn from_object(_ : &T) -> &Python { + &STATIC_PYTHON_INSTANCE + } + + /// Acquires the global interpreter lock, which allows access to the Python runtime. + /// This function is unsafe because + /// This function is unsafe because it is possible to recursively acquire the GIL, + /// and thus getting access to multiple '&mut Python' references to the python interpreter. + pub unsafe fn acquire_gil(&self) -> GILGuard { + let gstate = ffi::PyGILState_Ensure(); // acquire GIL + GILGuard { py: NEW_PYTHON_INSTANCE, gstate: gstate } + } + + /// Releases the GIL and allows the use of python on other threads. + pub fn allow_threads(&mut self, f: fn() -> T) -> T { + let save = unsafe { ffi::PyEval_SaveThread() }; + let result = f(); + unsafe { ffi::PyEval_RestoreThread(save); } + result + } + + pub fn module_type(&self) -> &PyTypeObject { + unsafe { PyTypeObject::from_type_ptr(self, &mut ffi::PyModule_Type) } + } + + // Importing Modules + + /// Imports the python with the given name. + pub fn import_module<'s, N : ToCStr>(&'s self, name : N) -> PyResult<'s, PyPtr<'s, PyModule>> { + use module; + name.with_c_str(|name| unsafe { + let m = ffi::PyImport_ImportModule(name); + let m : PyPtr = try!(err::result_from_owned_ptr(self, m)); + module::as_module(self, m) + }) + } + + /// Create a new module object based on a name. + pub fn init_module + (&self, name : &N, doc : Option<&CString>) -> PyResult<&PyModule> + { + let name = name.to_c_str(); + unsafe { + ffi::PyEval_InitThreads(); + let m = ffi::Py_InitModule3(name.as_ptr(), ptr::null_mut(), doc.as_ptr()); + if m.is_null() { + Err(PyErr::fetch(self)) + } else { + Ok(PythonObject::from_ptr(self, m)) + } + } + } + */ +} + +/* +/// RAII type that represents an acquired GIL. +#[must_use] +pub struct GILGuard { + gstate : ffi::PyGILState_STATE, + py : Python +} + +#[unsafe_destructor] +impl Drop for GILGuard { + fn drop(&mut self) { + unsafe { ffi::PyGILState_Release(self.gstate) } + } +} + +impl GILGuard { + pub fn python(&mut self) -> &mut Python { + &mut self.py + } +}*/ + diff --git a/src/pythonrun.rs b/src/pythonrun.rs new file mode 100644 index 00000000..a27c5c8b --- /dev/null +++ b/src/pythonrun.rs @@ -0,0 +1,32 @@ +use ffi; +use std::kinds::marker::{NoSend, NoCopy}; +use python::Python; + +/// Represents the python interpreter instance. +/// The python runtime is initialized using `PythonInterpreter::new()`, +/// and destroyed when the PythonInterpreter is dropped. +pub struct PythonInterpreter(NoSend, NoCopy); + +#[must_use] +impl PythonInterpreter { + /// Initializes the python interpreter. + /// Unsafe because we currently do not prevent multiple initialization, which is not supported. + pub unsafe fn new() -> PythonInterpreter { + ffi::Py_Initialize(); + ffi::PyEval_InitThreads(); + PythonInterpreter(NoSend, NoCopy) + } + + pub fn python<'p>(&'p self) -> Python<'p> { + unsafe { Python::assume_gil_acquired() } + } +} + +#[unsafe_destructor] +impl Drop for PythonInterpreter { + fn drop(&mut self) { + unsafe { ffi::Py_Finalize() } + } +} + + diff --git a/src/typeobject.rs b/src/typeobject.rs new file mode 100644 index 00000000..2980e4ef --- /dev/null +++ b/src/typeobject.rs @@ -0,0 +1,61 @@ +use object::{PythonObject, PyObject}; +use python::Python; +use ffi; +use libc::c_char; +use std; + +pub struct PyTypeObject<'p> { + cell : std::cell::UnsafeCell, + py : Python<'p> +} + +impl <'p> PythonObject<'p> for PyTypeObject<'p> { + #[inline] + fn from_object<'a>(obj : &'a PyObject<'p>) -> Option<&'a PyTypeObject<'p>> { + unsafe { + if ffi::PyType_Check(obj.as_ptr()) { + Some(std::mem::transmute(obj)) + } else { + None + } + } + } + + #[inline] + fn as_object<'a>(&'a self) -> &'a PyObject<'p> { + unsafe { std::mem::transmute(self) } + } + + #[inline] + fn python(&self) -> Python<'p> { + self.py + } + + fn type_object(_ : Option<&Self>) -> &'p PyTypeObject<'p> { + panic!() + } +} +/* +impl PyTypeObject { + pub fn as_type_ptr(&self) -> *mut ffi::PyTypeObjectRaw { + // safe because the PyObject is only accessed while holding the GIL + (unsafe { self.cell.get() }) + } + + pub unsafe fn from_type_ptr(_ : &Python, p : *mut ffi::PyTypeObjectRaw) -> &PyTypeObject { + debug_assert!(p.is_not_null()) + &*(p as *mut PyTypeObject) + } + + /// Return true if self is a subtype of b. + pub fn is_subtype_of(&self, b : &PyTypeObject) -> bool { + unsafe { ffi::PyType_IsSubtype(self.as_type_ptr(), b.as_type_ptr()) != 0 } + } + + /// Return true if obj is an instance of self. + pub fn is_instance(&self, obj : &PyObject) -> bool { + obj.get_type().is_subtype_of(self) + } +} +*/ + diff --git a/test.bat b/test.bat new file mode 100644 index 00000000..4585122e --- /dev/null +++ b/test.bat @@ -0,0 +1,6 @@ +@c:\cygwin\bin\touch src/lib.rs +cargo build +@if errorlevel 1 exit /b 1 +rustc testmodule.rs --extern rust-cpython=target\librust-cpython-211b85e007ec6e28.rlib -L "c:\Program Files\Python27\libs" -o testmodule.pyd +@if errorlevel 1 exit /b 1 +python -c "import testmodule" diff --git a/test.sh b/test.sh new file mode 100755 index 00000000..62ff92a4 --- /dev/null +++ b/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -xeu +#@c:\cygwin\bin\touch src/lib.rs +cargo build +rustc testmodule.rs --extern rust-cpython=target/librust-cpython-21cf8ea55e61f78d.rlib -o testmodule.so +python -c "import testmodule; print(repr(testmodule.__author__))" + diff --git a/testmodule.rs b/testmodule.rs new file mode 100644 index 00000000..df00a7fe --- /dev/null +++ b/testmodule.rs @@ -0,0 +1,29 @@ +#![crate_type = "dylib"] +#![feature(phase)] + +#[phase(plugin, link)] extern crate abort_on_panic; +extern crate "rust-cpython" as cpython; +extern crate rustrt; +extern crate libc; + +use cpython::{PyModule, PyResult, Python}; + +#[no_mangle] +pub extern "C" fn inittestmodule() { + //abort_on_panic!({ + let py = unsafe { Python::assume_gil_acquired() }; + if let Err(e) = init(py) { + println!("Restore error") + e.restore() + } + //}) +} + +fn init(py : Python) -> PyResult<()> { + let m : &PyModule = try!(py.init_module("testmodule", None)); + //println!("init_module done") + try!(m.add_object("__author__", "Daniel Grunwald")); + try!(m.add_object("__version__", "0.0.1")); + Ok(()) +} +