From 376ce21f2d09b330ab5f4270638f0e9912ede177 Mon Sep 17 00:00:00 2001 From: Murarth Date: Sun, 2 Aug 2015 09:29:47 -0700 Subject: [PATCH] Implement `eval` and `run` to execute Python code --- python27-sys/src/pythonrun.rs | 4 ++ python3-sys/src/pythonrun.rs | 16 ++++++- src/python.rs | 86 ++++++++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/python27-sys/src/pythonrun.rs b/python27-sys/src/pythonrun.rs index de447077..3be7efd0 100644 --- a/python27-sys/src/pythonrun.rs +++ b/python27-sys/src/pythonrun.rs @@ -12,6 +12,10 @@ pub const PyCF_SOURCE_IS_UTF8 : c_int = 0x0100; pub const PyCF_DONT_IMPLY_DEDENT : c_int = 0x0200; pub const PyCF_ONLY_AST : c_int = 0x0400; +pub const Py_single_input: c_int = 256; +pub const Py_file_input: c_int = 257; +pub const Py_eval_input: c_int = 258; + #[repr(C)] #[derive(Copy, Clone)] pub struct PyCompilerFlags { diff --git a/python3-sys/src/pythonrun.rs b/python3-sys/src/pythonrun.rs index b5c7bef1..7fb8de3f 100644 --- a/python3-sys/src/pythonrun.rs +++ b/python3-sys/src/pythonrun.rs @@ -2,6 +2,16 @@ use libc::{c_char, c_int, wchar_t}; use object::*; use pystate::PyThreadState; +pub const Py_single_input: c_int = 256; +pub const Py_file_input: c_int = 257; +pub const Py_eval_input: c_int = 258; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyCompilerFlags { + cf_flags : c_int +} + extern "C" { pub fn Py_SetProgramName(arg1: *mut wchar_t) -> (); pub fn Py_GetProgramName() -> *mut wchar_t; @@ -40,7 +50,11 @@ extern "C" { // arg3: c_int, // arg4: c_int) // -> *mut _node; - + + pub fn PyRun_StringFlags(code: *const c_char, start: c_int, + globals: *mut PyObject, locals: *mut PyObject, + flags: *mut PyCompilerFlags) -> *mut PyObject; + pub fn Py_CompileString(arg1: *const c_char, arg2: *const c_char, arg3: c_int) -> *mut PyObject; diff --git a/src/python.rs b/src/python.rs index b9085520..015d5bd0 100644 --- a/src/python.rs +++ b/src/python.rs @@ -17,10 +17,12 @@ // DEALINGS IN THE SOFTWARE. use std; +use std::ffi::CString; use std::marker::PhantomData; +use libc::c_int; use ffi; -use objects::{PyObject, PyType, PyBool, PyModule}; -use err::PyResult; +use objects::{PyObject, PyType, PyBool, PyDict, PyModule}; +use err::{self, PyErr, PyResult}; use pythonrun::GILGuard; /// Marker type that indicates that the GIL is currently held. @@ -150,6 +152,62 @@ impl<'p> Python<'p> { result } + /// Evaluates a Python expression in the given context and returns the result. + /// + /// If `globals` is `None`, it defaults to Python module `__main__`. + /// If `locals` is `None`, it defaults to the value of `globals`. + pub fn eval(self, code: &str, globals: Option<&PyDict<'p>>, + locals: Option<&PyDict<'p>>) -> PyResult<'p, PyObject<'p>> { + self.run_code(code, ffi::Py_eval_input, globals, locals) + } + + /// Executes one or more Python statements in the given context. + /// + /// If `globals` is `None`, it defaults to Python module `__main__`. + /// If `locals` is `None`, it defaults to the value of `globals`. + pub fn run(self, code: &str, globals: Option<&PyDict<'p>>, + locals: Option<&PyDict<'p>>) -> PyResult<'p, ()> { + try!(self.run_code(code, ffi::Py_file_input, globals, locals)); + Ok(()) + } + + /// Runs code in the given context. + /// `start` indicates the type of input expected: + /// one of `Py_single_input`, `Py_file_input`, or `Py_eval_input`. + /// + /// If `globals` is `None`, it defaults to Python module `__main__`. + /// If `locals` is `None`, it defaults to the value of `globals`. + fn run_code(self, code: &str, start: c_int, + globals: Option<&PyDict<'p>>, locals: Option<&PyDict<'p>>) + -> PyResult<'p, PyObject<'p>> { + let code = CString::new(code).unwrap(); + + unsafe { + let mptr = ffi::PyImport_AddModule("__main__\0".as_ptr() as *const _); + + if mptr.is_null() { + return Err(PyErr::fetch(self)); + } + + let mdict = ffi::PyModule_GetDict(mptr); + + let globals = match globals { + Some(g) => g.as_ptr(), + None => mdict, + }; + + let locals = match locals { + Some(l) => l.as_ptr(), + None => globals + }; + + let res_ptr = ffi::PyRun_StringFlags(code.as_ptr(), + start, globals, locals, 0 as *mut _); + + err::result_from_owned_ptr(self, res_ptr) + } + } + /// Gets the Python builtin value `None`. #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] @@ -188,3 +246,27 @@ impl <'p> std::fmt::Debug for PythonObjectDowncastError<'p> { } } +#[cfg(test)] +mod test { + use {Python, PyDict}; + + #[test] + fn test_eval() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + // Make sure builtin names are accessible + let v: i32 = py.eval("min(1, 2)", None, None).unwrap().extract().unwrap(); + + assert_eq!(v, 1); + + let d = PyDict::new(py); + + d.set_item("foo", 13).unwrap(); + + // Inject our own local namespace + let v: i32 = py.eval("foo + 29", None, Some(&d)).unwrap().extract().unwrap(); + + assert_eq!(v, 42); + } +}