From de06fc092339e1d5a159077087da74568bfda778 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers <7943856+bschoenmaeckers@users.noreply.github.com> Date: Wed, 20 May 2026 15:18:06 +0200 Subject: [PATCH] Add eval function to c-api (#7927) * Add eval function to c-api * Extract `Py_CompileString` start values into constants --- crates/capi/Cargo.toml | 2 +- crates/capi/src/ceval.rs | 95 ++++++++++++++++++++++++++++++++++++++++ crates/capi/src/lib.rs | 1 + 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 crates/capi/src/ceval.rs diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index e9dc8de00..85f42934a 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -12,7 +12,7 @@ license.workspace = true crate-type = ["cdylib", "rlib"] [dependencies] -rustpython-vm = { workspace = true, features = ["threading"] } +rustpython-vm = { workspace = true, features = ["threading", "compiler"] } rustpython-stdlib = {workspace = true, features = ["threading"] } [dev-dependencies] diff --git a/crates/capi/src/ceval.rs b/crates/capi/src/ceval.rs new file mode 100644 index 000000000..be8d9c48b --- /dev/null +++ b/crates/capi/src/ceval.rs @@ -0,0 +1,95 @@ +use crate::pystate::with_vm; +use core::ffi::{CStr, c_char, c_int}; +use core::ptr::NonNull; +use rustpython_vm::builtins::{PyCode, PyDict}; +use rustpython_vm::compiler::Mode; +use rustpython_vm::function::ArgMapping; +use rustpython_vm::scope::Scope; +use rustpython_vm::{AsObject, PyObject, TryFromObject}; + +const PY_SINGLE_INPUT: c_int = 256; +const PY_FILE_INPUT: c_int = 257; +const PY_EVAL_INPUT: c_int = 258; +const PY_FUNC_TYPE_INPUT: c_int = 345; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn Py_CompileString( + code: *const c_char, + filename: *const c_char, + start: c_int, +) -> *mut PyObject { + with_vm(|vm| { + let code = unsafe { CStr::from_ptr(code) }.to_str().map_err(|_| { + vm.new_system_error("Py_CompileString called with non UTF-8 code string") + })?; + let filename = unsafe { CStr::from_ptr(filename) } + .to_str() + .map_err(|_| vm.new_system_error("Py_CompileString called with non UTF-8 filename"))?; + + let mode = match start { + PY_SINGLE_INPUT => Mode::Single, + PY_FILE_INPUT => Mode::Exec, + PY_EVAL_INPUT => Mode::Eval, + PY_FUNC_TYPE_INPUT => Mode::BlockExpr, + _ => { + return Err( + vm.new_system_error("Invalid start argument passed to Py_CompileString") + ); + } + }; + + vm.compile(code, mode, filename.to_owned()) + .map_err(|err| vm.new_syntax_error(&err, Some(code))) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyEval_EvalCode( + co: *mut PyObject, + globals: *mut PyObject, + locals: *mut PyObject, +) -> *mut PyObject { + with_vm(|vm| { + let code = unsafe { &*co }.try_downcast_ref::(vm)?; + let globals = unsafe { &*globals }.try_downcast_ref::(vm)?; + let locals = NonNull::new(locals) + .map(|ptr| ArgMapping::try_from_object(vm, unsafe { ptr.as_ref() }.to_owned())) + .transpose()?; + + let scope = Scope::with_builtins(locals, globals.to_owned(), vm); + + vm.run_code_obj(code.to_owned(), scope) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyEval_GetBuiltins() -> *mut PyObject { + with_vm(|vm| { + vm.current_frame().map_or_else( + || vm.builtins.as_object().as_raw(), + |frame| frame.builtins.as_object().as_raw(), + ) + }) +} + +#[cfg(false)] +mod tests { + use pyo3::exceptions::PyException; + use pyo3::prelude::*; + + #[test] + fn test_code_eval() { + Python::attach(|py| { + let result = py.eval(c"1 + 1", None, None).unwrap(); + assert_eq!(result.extract::().unwrap(), 2); + }) + } + + #[test] + fn test_code_run_exception() { + Python::attach(|py| { + let err = py.run(c"raise Exception()", None, None).unwrap_err(); + assert!(err.is_instance_of::(py)); + }) + } +} diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index 7196ad233..ac7bdf5ef 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -10,6 +10,7 @@ extern crate alloc; pub mod abstract_; pub mod bytesobject; +pub mod ceval; pub mod import; pub mod longobject; pub mod object;