From 37a7b6e2012b9f1aba3d52fc956d95b9b2340cdd Mon Sep 17 00:00:00 2001 From: Ralf Biedert Date: Sat, 1 Feb 2025 17:42:22 +0100 Subject: [PATCH] Split Python interop generation into modules. --- crates/backend_cpython/src/docs.rs | 13 +- crates/backend_cpython/src/interop.rs | 703 +----------------- .../backend_cpython/src/interop/bootstrap.rs | 40 + .../backend_cpython/src/interop/callbacks.rs | 22 + .../backend_cpython/src/interop/constants.rs | 12 + .../backend_cpython/src/interop/functions.rs | 98 +++ crates/backend_cpython/src/interop/imports.rs | 12 + .../backend_cpython/src/interop/patterns.rs | 292 ++++++++ crates/backend_cpython/src/interop/types.rs | 147 ++++ crates/backend_cpython/src/interop/utils.rs | 86 +++ 10 files changed, 746 insertions(+), 679 deletions(-) create mode 100644 crates/backend_cpython/src/interop/bootstrap.rs create mode 100644 crates/backend_cpython/src/interop/callbacks.rs create mode 100644 crates/backend_cpython/src/interop/constants.rs create mode 100644 crates/backend_cpython/src/interop/functions.rs create mode 100644 crates/backend_cpython/src/interop/imports.rs create mode 100644 crates/backend_cpython/src/interop/patterns.rs create mode 100644 crates/backend_cpython/src/interop/types.rs create mode 100644 crates/backend_cpython/src/interop/utils.rs diff --git a/crates/backend_cpython/src/docs.rs b/crates/backend_cpython/src/docs.rs index 39fe74f0..debc4d4e 100644 --- a/crates/backend_cpython/src/docs.rs +++ b/crates/backend_cpython/src/docs.rs @@ -1,3 +1,6 @@ +use crate::interop::functions::write_function; +use crate::interop::patterns::{write_pattern_class_ctor, write_pattern_class_method}; +use crate::interop::types::{write_enum, write_struct}; use crate::Interop; use interoptopus::lang::c::{CType, CompositeType, Function}; use interoptopus::patterns::{LibraryPattern, TypePattern}; @@ -142,7 +145,7 @@ impl<'a> Markdown<'a> { indented!(w, r"#### Definition ")?; indented!(w, r"```python")?; - self.interop.write_struct(w, composite, WriteFor::Docs)?; + write_struct(self.interop, w, composite, WriteFor::Docs)?; indented!(w, r"```")?; Ok(()) @@ -174,7 +177,7 @@ impl<'a> Markdown<'a> { indented!(w, r"#### Definition ")?; indented!(w, r"```python")?; - self.interop.write_enum(w, the_enum, WriteFor::Docs)?; + write_enum(self.interop, w, the_enum, WriteFor::Docs)?; indented!(w, r"```")?; w.newline()?; indented!(w, r"---")?; @@ -206,7 +209,7 @@ impl<'a> Markdown<'a> { indented!(w, r"#### Definition ")?; indented!(w, r"```python")?; - self.interop.write_function(w, function, WriteFor::Docs)?; + write_function(self.interop, w, function, WriteFor::Docs)?; indented!(w, r"```")?; w.newline()?; indented!(w, r"---")?; @@ -254,7 +257,7 @@ impl<'a> Markdown<'a> { indented!(w, r"```python")?; indented!(w, r"class {}:", class_name)?; w.newline()?; - self.interop.write_pattern_class_ctor(w, pattern, x, WriteFor::Docs)?; + write_pattern_class_ctor(self.interop, w, pattern, x, WriteFor::Docs)?; indented!(w, [()()], r"...")?; indented!(w, r"```")?; w.newline()?; @@ -280,7 +283,7 @@ impl<'a> Markdown<'a> { indented!(w, r"```python")?; indented!(w, r"class {}:", class_name)?; w.newline()?; - self.interop.write_pattern_class_method(w, pattern, x, WriteFor::Docs)?; + write_pattern_class_method(self.interop, w, pattern, x, WriteFor::Docs)?; indented!(w, [()()], r"...")?; indented!(w, r"```")?; w.newline()?; diff --git a/crates/backend_cpython/src/interop.rs b/crates/backend_cpython/src/interop.rs index 4aa99555..738f7d7c 100644 --- a/crates/backend_cpython/src/interop.rs +++ b/crates/backend_cpython/src/interop.rs @@ -1,11 +1,25 @@ -use crate::converter::{constant_value_to_value, documentation, fnpointer_to_typename, to_ctypes_name, to_type_hint_in, to_type_hint_out}; +pub mod bootstrap; +pub mod callbacks; +pub mod constants; +pub mod functions; +pub mod imports; +pub mod patterns; +pub mod types; +pub mod utils; + +use crate::converter::to_type_hint_in; +use crate::interop::bootstrap::write_api_load_fuction; +use crate::interop::callbacks::write_callback_helpers; +use crate::interop::constants::write_constants; +use crate::interop::functions::write_function_proxies; +use crate::interop::imports::write_imports; +use crate::interop::patterns::write_patterns; +use crate::interop::types::write_types; +use crate::interop::utils::write_utils; use derive_builder::Builder; -use interoptopus::lang::c::{CType, CompositeType, EnumType, Function, Layout}; -use interoptopus::patterns::service::Service; -use interoptopus::patterns::{LibraryPattern, TypePattern}; -use interoptopus::util::{longest_common_prefix, safe_name, sort_types_by_dependencies}; -use interoptopus::writer::{IndentWriter, WriteFor}; -use interoptopus::{indented, non_service_functions, Bindings, Error, Inventory}; +use interoptopus::lang::c::Function; +use interoptopus::writer::IndentWriter; +use interoptopus::{Bindings, Error, Inventory}; /// Generates Python `ctypes` files, **get this with [`InteropBuilder`]**.🐙 #[derive(Clone, Debug, Default, Builder)] @@ -20,583 +34,6 @@ pub struct Interop { #[allow(clippy::unused_self)] impl Interop { - fn write_imports(&self, w: &mut IndentWriter) -> Result<(), Error> { - indented!(w, r"from __future__ import annotations")?; - indented!(w, r"import ctypes")?; - indented!(w, r"import typing")?; - w.newline()?; - indented!(w, r#"T = typing.TypeVar("T")"#)?; - Ok(()) - } - - fn write_api_load_fuction(&self, w: &mut IndentWriter) -> Result<(), Error> { - indented!(w, r"c_lib = None")?; - w.newline()?; - indented!(w, r"def init_lib(path):")?; - indented!(w, [()], r#""""Initializes the native library. Must be called at least once before anything else.""""#)?; - indented!(w, [()], r"global c_lib")?; - indented!(w, [()], r"c_lib = ctypes.cdll.LoadLibrary(path)")?; - - w.newline()?; - for f in self.inventory.functions() { - let args = f.signature().params().iter().map(|x| to_ctypes_name(x.the_type(), false)).collect::>(); - - indented!(w, [()], r"c_lib.{}.argtypes = [{}]", f.name(), args.join(", "))?; - } - - w.newline()?; - for f in self.inventory.functions() { - let rtype = to_ctypes_name(f.signature().rval(), false); - if !rtype.is_empty() { - indented!(w, [()], r"c_lib.{}.restype = {}", f.name(), rtype)?; - } - } - - w.newline()?; - for f in self.inventory.functions() { - if let CType::Pattern(TypePattern::FFIErrorEnum(e)) = f.signature().rval() { - let value = e.success_variant().value(); - indented!(w, [()], r"c_lib.{}.errcheck = lambda rval, _fptr, _args: _errcheck(rval, {})", f.name(), value)?; - } - } - - Ok(()) - } - - fn write_constants(&self, w: &mut IndentWriter) -> Result<(), Error> { - for c in self.inventory.constants() { - indented!(w, r"{} = {}", c.name(), constant_value_to_value(c.value()))?; - } - - Ok(()) - } - - fn write_types(&self, w: &mut IndentWriter) -> Result<(), Error> { - let all_types = self.inventory.ctypes().to_vec(); - let sorted_types = sort_types_by_dependencies(all_types); - - for t in &sorted_types { - match t { - CType::Composite(c) => self.write_struct(w, c, WriteFor::Code)?, - CType::Enum(e) => self.write_enum(w, e, WriteFor::Code)?, - CType::Pattern(p) => match p { - TypePattern::FFIErrorEnum(e) => self.write_enum(w, e.the_enum(), WriteFor::Code)?, - TypePattern::Slice(c) => self.write_slice(w, c, false)?, - TypePattern::SliceMut(c) => self.write_slice(w, c, true)?, - TypePattern::Option(c) => { - self.write_option(w, c)?; - } - _ => continue, - }, - _ => continue, - } - - w.newline()?; - w.newline()?; - } - - Ok(()) - } - - pub(crate) fn write_struct(&self, w: &mut IndentWriter, c: &CompositeType, write_for: WriteFor) -> Result<(), Error> { - let documentation = c.meta().documentation().lines().join("\n"); - - indented!(w, r"class {}(ctypes.Structure):", c.rust_name())?; - if !documentation.is_empty() && write_for == WriteFor::Code { - indented!(w, [()], r#""""{}""""#, documentation)?; - } - - if c.repr().layout() == Layout::Packed { - indented!(w, [()], r"_pack_ = 1")?; - } - - let alignment = c.repr().alignment(); - if let Some(align) = alignment { - indented!(w, [()], r"_align_ = {}", align)?; - } - - w.newline()?; - if write_for == WriteFor::Code { - indented!(w, [()], r"# These fields represent the underlying C data layout")?; - } - indented!(w, [()], r"_fields_ = [")?; - for f in c.fields() { - let type_name = to_ctypes_name(f.the_type(), true); - indented!(w, [()()], r#"("{}", {}),"#, f.name(), type_name)?; - } - indented!(w, [()], r"]")?; - - // Ctor - let extra_args = c - .fields() - .iter() - .map(|x| { - let type_hint_in = to_type_hint_in(x.the_type(), false); - - format!("{}{} = None", x.name(), type_hint_in) - }) - .collect::>() - .join(", "); - - if !c.fields().is_empty() { - w.newline()?; - indented!(w, [()], r"def __init__(self, {}):", extra_args)?; - - if write_for == WriteFor::Code { - for field in c.fields() { - indented!(w, [()()], r"if {} is not None:", field.name())?; - indented!(w, [()()()], r"self.{} = {}", field.name(), field.name())?; - } - } else { - indented!(w, [()()], r"...")?; - } - } - - if write_for == WriteFor::Docs { - return Ok(()); - } - - // Fields - for f in c.fields() { - let documentation = f.documentation().lines().join("\n"); - - w.newline()?; - - let hint_in = to_type_hint_in(f.the_type(), false); - let hint_out = to_type_hint_out(f.the_type()); - - indented!(w, [()], r"@property")?; - indented!(w, [()], r"def {}(self){}:", f.name(), hint_out)?; - - if !documentation.is_empty() { - indented!(w, [()()], r#""""{}""""#, documentation)?; - } - - match f.the_type() { - CType::Pattern(_) => indented!(w, [()()], r#"return ctypes.Structure.__get__(self, "{}")"#, f.name())?, - _ => indented!(w, [()()], r#"return ctypes.Structure.__get__(self, "{}")"#, f.name())?, - } - - w.newline()?; - - indented!(w, [()], r"@{}.setter", f.name())?; - indented!(w, [()], r"def {}(self, value{}):", f.name(), hint_in)?; - if !documentation.is_empty() { - indented!(w, [()()], r#""""{}""""#, documentation)?; - } - indented!(w, [()()], r#"return ctypes.Structure.__set__(self, "{}", value)"#, f.name())?; - } - - Ok(()) - } - - pub(crate) fn write_enum(&self, w: &mut IndentWriter, e: &EnumType, write_for: WriteFor) -> Result<(), Error> { - let documentation = e.meta().documentation().lines().join("\n"); - - indented!(w, r"class {}:", e.rust_name())?; - if !documentation.is_empty() && write_for == WriteFor::Code { - indented!(w, [()], r#""""{}""""#, documentation)?; - } - - for v in e.variants() { - if write_for == WriteFor::Code { - for line in v.documentation().lines() { - indented!(w, [()], r"# {}", line)?; - } - } - indented!(w, [()], r"{} = {}", v.name(), v.value())?; - } - - Ok(()) - } - - fn write_callback_helpers(&self, w: &mut IndentWriter) -> Result<(), Error> { - indented!(w, r"class {}:", self.callback_namespace)?; - indented!(w, [()], r#""""Helpers to define callbacks.""""#)?; - - for callback in self.inventory.ctypes().iter().filter_map(|x| match x { - CType::FnPointer(x) => Some(x), - CType::Pattern(TypePattern::NamedCallback(x)) => Some(x.fnpointer()), - _ => None, - }) { - indented!(w, [()], r"{} = {}", safe_name(&callback.internal_name()), fnpointer_to_typename(callback))?; - } - - Ok(()) - } - - fn write_function_proxies(&self, w: &mut IndentWriter) -> Result<(), Error> { - for function in non_service_functions(&self.inventory) { - self.write_function(w, function, WriteFor::Code)?; - } - - Ok(()) - } - - pub(crate) fn write_function(&self, w: &mut IndentWriter, function: &Function, write_for: WriteFor) -> Result<(), Error> { - let rval_sig = to_type_hint_out(function.signature().rval()); - let args = self.function_args_to_string(function, true, false); - let documentation = function.meta().documentation().lines().join("\n"); - - indented!(w, r"def {}({}){}:", function.name(), args, rval_sig)?; - - if write_for == WriteFor::Code { - if !documentation.is_empty() { - indented!(w, [()], r#""""{}""""#, documentation)?; - } - - self.write_param_helpers(w, function)?; - self.write_library_call(w, function, None)?; - w.newline()?; - } else { - indented!(w, [()], r"...")?; - } - - Ok(()) - } - - fn write_param_helpers(&self, w: &mut IndentWriter, function: &Function) -> Result<(), Error> { - for arg in function.signature().params() { - match arg.the_type() { - CType::FnPointer(x) => { - indented!(w, [()], r#"if not hasattr({}, "__ctypes_from_outparam__"):"#, arg.name())?; - indented!(w, [()()], r"{} = callbacks.{}({})", arg.name(), safe_name(&x.internal_name()), arg.name())?; - w.newline()?; - } - CType::Pattern(pattern) => match pattern { - TypePattern::NamedCallback(x) => { - let x = x.fnpointer(); - indented!(w, [()], r#"if not hasattr({}, "__ctypes_from_outparam__"):"#, arg.name())?; - indented!(w, [()()], r"{} = callbacks.{}({})", arg.name(), safe_name(&x.internal_name()), arg.name())?; - w.newline()?; - } - TypePattern::CStrPointer => { - indented!(w, [()], r#"if not hasattr({}, "__ctypes_from_outparam__"):"#, arg.name())?; - indented!(w, [()()], r"{} = ctypes.cast({}, ctypes.POINTER(ctypes.c_char))", arg.name(), arg.name())?; - } - TypePattern::Slice(t) | TypePattern::SliceMut(t) => { - let inner = to_ctypes_name( - t.fields() - .iter() - .find(|i| i.name().eq_ignore_ascii_case("data")) - .expect("slice must have a data field") - .the_type() - .try_deref_pointer() - .expect("data must be a pointer type"), - false, - ); - - indented!( - w, - [()], - r#"if hasattr({}, "_length_") and getattr({}, "_type_", "") == {}:"#, - arg.name(), - arg.name(), - inner - )?; - - indented!( - w, - [()()], - r"{} = {}(data=ctypes.cast({}, ctypes.POINTER({})), len=len({}))", - arg.name(), - arg.the_type().name_within_lib(), - arg.name(), - inner, - arg.name() - )?; - w.newline()?; - } - _ => {} - }, - _ => {} - } - } - Ok(()) - } - - fn write_slice(&self, w: &mut IndentWriter, c: &CompositeType, mutable: bool) -> Result<(), Error> { - let data_type = c - .fields() - .iter() - .find(|x| x.name().contains("data")) - .expect("Slice must contain field called 'data'.") - .the_type() - .try_deref_pointer() - .expect("data must be a pointer type"); - - let data_type_python = to_ctypes_name(data_type, true); - let hint_in = to_type_hint_in(data_type, false); - let hint_out = to_type_hint_out(data_type); - - indented!(w, r"class {}(ctypes.Structure):", c.rust_name())?; - indented!(w, [()], r"# These fields represent the underlying C data layout")?; - indented!(w, [()], r"_fields_ = [")?; - indented!(w, [()], r#" ("data", ctypes.POINTER({})),"#, data_type_python)?; - indented!(w, [()], r#" ("len", ctypes.c_uint64),"#)?; - indented!(w, [()], r"]")?; - w.newline()?; - indented!(w, [()], r"def __len__(self):")?; - indented!(w, [()()], r"return self.len")?; - w.newline()?; - indented!(w, [()], r"def __getitem__(self, i){}:", hint_out)?; - indented!(w, [()()], r"if i < 0:")?; - indented!(w, [()()()], r"index = self.len+i")?; - indented!(w, [()()], r"else:")?; - indented!(w, [()()()], r"index = i")?; - w.newline()?; - indented!(w, [()()], r"if index >= self.len:")?; - indented!(w, [()()()], r#"raise IndexError("Index out of range")"#)?; - w.newline()?; - indented!(w, [()()], r"return self.data[index]")?; - - if mutable { - w.newline()?; - indented!(w, [()], r"def __setitem__(self, i, v{}):", hint_in)?; - indented!(w, [()()], r"if i < 0:")?; - indented!(w, [()()()], r"index = self.len+i")?; - indented!(w, [()()], r"else:")?; - indented!(w, [()()()], r"index = i")?; - w.newline()?; - indented!(w, [()()], r"if index >= self.len:")?; - indented!(w, [()()()], r#"raise IndexError("Index out of range")"#)?; - w.newline()?; - indented!(w, [()()], r"self.data[index] = v")?; - } - - w.newline()?; - indented!(w, [()], r"def copied(self) -> {}:", c.rust_name())?; - indented!( - w, - [()()], - r#""""Returns a shallow, owned copy of the underlying slice. - - The returned object owns the immediate data, but not the targets of any contained - pointers. In other words, if your struct contains any pointers the returned object - may only be used as long as these pointers are valid. If the struct did not contain - any pointers the returned object is valid indefinitely.""""# - )?; - indented!(w, [()()], r"array = ({} * len(self))()", data_type_python)?; - indented!(w, [()()], r"ctypes.memmove(array, self.data, len(self) * ctypes.sizeof({}))", data_type_python)?; - indented!( - w, - [()()], - r"rval = {}(data=ctypes.cast(array, ctypes.POINTER({})), len=len(self))", - c.rust_name(), - data_type_python - )?; - indented!(w, [()()], r"rval.owned = array # Store array in returned slice to prevent memory deallocation")?; - indented!(w, [()()], r"return rval")?; - w.newline()?; - indented!(w, [()], r"def __iter__(self) -> typing.Iterable[{}]:", data_type_python)?; - indented!(w, [()()], r"return _Iter(self)")?; - w.newline()?; - indented!(w, [()], r"def iter(self) -> typing.Iterable[{}]:", data_type_python)?; - indented!(w, [()()], r#""""Convenience method returning a value iterator.""""#)?; - indented!(w, [()()], r"return iter(self)")?; - w.newline()?; - indented!(w, [()], r"def first(self){}:", hint_out)?; - indented!(w, [()()], r#""""Returns the first element of this slice.""""#)?; - indented!(w, [()()], r"return self[0]")?; - w.newline()?; - indented!(w, [()], r"def last(self){}:", hint_out)?; - indented!(w, [()()], r#""""Returns the last element of this slice.""""#)?; - indented!(w, [()()], r"return self[len(self)-1]")?; - - // Only write this for byte-like types right now - if data_type.size_of() == 1 { - w.newline()?; - indented!(w, [()], r"def bytearray(self):")?; - indented!(w, [()()], r#""""Returns a bytearray with the content of this slice.""""#)?; - indented!(w, [()()], r"rval = bytearray(len(self))")?; - indented!(w, [()()], r"for i in range(len(self)):")?; - indented!(w, [()()()], r"rval[i] = self[i]")?; - indented!(w, [()()], r"return rval")?; - } - - Ok(()) - } - - fn write_option(&self, w: &mut IndentWriter, c: &CompositeType) -> Result<(), Error> { - let data_type = c - .fields() - .iter() - .find(|x| x.name().contains('t')) - .expect("Slice must contain field called 't'.") - .the_type(); - - let data_type_python = to_ctypes_name(data_type, true); - - indented!(w, r"class {}(ctypes.Structure):", c.rust_name())?; - indented!(w, [()], r#""""May optionally hold a value.""""#)?; - w.newline()?; - indented!(w, [()], r"_fields_ = [")?; - indented!(w, [()], r#" ("_t", {}),"#, data_type_python)?; - indented!(w, [()], r#" ("_is_some", ctypes.c_uint8),"#)?; - indented!(w, [()], r"]")?; - w.newline()?; - indented!(w, [()], r"@property")?; - indented!(w, [()], r"def value(self) -> {}:", data_type_python)?; - indented!(w, [()()], r#""""Returns the value if it exists, or None.""""#)?; - indented!(w, [()()], r"if self._is_some == 1:")?; - indented!(w, [()()()], r"return self._t")?; - indented!(w, [()()], r"else:")?; - indented!(w, [()()()], r"return None")?; - w.newline()?; - indented!(w, [()], r"def is_some(self) -> bool:")?; - indented!(w, [()()], r#""""Returns true if the value exists.""""#)?; - indented!(w, [()()], r"return self._is_some == 1")?; - w.newline()?; - indented!(w, [()], r"def is_none(self) -> bool:")?; - indented!(w, [()()], r#""""Returns true if the value does not exist.""""#)?; - indented!(w, [()()], r"return self._is_some != 0")?; - - Ok(()) - } - - fn write_patterns(&self, w: &mut IndentWriter) -> Result<(), Error> { - for pattern in self.inventory.patterns() { - match pattern { - LibraryPattern::Service(x) => self.write_pattern_class(w, x)?, - _ => panic!("Pattern not explicitly handled"), - } - } - - Ok(()) - } - - fn write_pattern_class(&self, w: &mut IndentWriter, class: &Service) -> Result<(), Error> { - let context_type_name = class.the_type().rust_name(); - - let mut all_functions = class.constructors().to_vec(); - all_functions.extend_from_slice(class.methods()); - all_functions.push(class.destructor().clone()); - - let _common_prefix = longest_common_prefix(&all_functions); - let documentation = class.the_type().meta().documentation().lines().join("\n"); - - indented!(w, r"class {}:", context_type_name)?; - if !documentation.is_empty() { - indented!(w, [()], r#""""{}""""#, documentation)?; - } - indented!(w, [()], r"__api_lock = object()")?; - w.newline()?; - indented!(w, [()], r"def __init__(self, api_lock, ctx):")?; - indented!( - w, - [()()], - r#"assert(api_lock == {}.__api_lock), "You must create this with a static constructor." "#, - context_type_name - )?; - indented!(w, [()()], r"self._ctx = ctx")?; - w.newline()?; - indented!(w, [()], r"@property")?; - indented!(w, [()], r"def _as_parameter_(self):")?; - indented!(w, [()()], r"return self._ctx")?; - w.newline()?; - - for ctor in class.constructors() { - self.write_pattern_class_ctor(w, class, ctor, WriteFor::Code)?; - } - - // Dtor - indented!(w, [()], r"def __del__(self):")?; - // indented!(w, [_ _], r#"global _api, ffi"#)?; - w.indent(); - self.write_success_enum_aware_rval(w, class.destructor(), &self.get_method_args(class.destructor(), "self._ctx"), false)?; - w.unindent(); - - for function in class.methods() { - self.write_pattern_class_method(w, class, function, WriteFor::Code)?; - } - - w.newline()?; - w.newline()?; - - Ok(()) - } - - pub(crate) fn write_pattern_class_ctor(&self, w: &mut IndentWriter, class: &Service, ctor: &Function, write_for: WriteFor) -> Result<(), Error> { - let context_type_name = class.the_type().rust_name(); - let mut all_functions = class.constructors().to_vec(); - all_functions.extend_from_slice(class.methods()); - all_functions.push(class.destructor().clone()); - - let common_prefix = longest_common_prefix(&all_functions); - - let ctor_args = self.function_args_to_string(ctor, true, true); - indented!(w, [()], r"@staticmethod")?; - indented!(w, [()], r"def {}({}) -> {}:", ctor.name().replace(&common_prefix, ""), ctor_args, context_type_name)?; - - if write_for == WriteFor::Docs { - return Ok(()); - } - - indented!(w, [()()], r"{}", documentation(ctor.meta().documentation()))?; - indented!(w, [()()], r"ctx = ctypes.c_void_p()")?; - w.indent(); - self.write_param_helpers(w, ctor)?; - self.write_success_enum_aware_rval(w, ctor, &self.get_method_args(ctor, "ctx"), false)?; - w.unindent(); - indented!(w, [()()], r"self = {}({}.__api_lock, ctx)", context_type_name, context_type_name)?; - indented!(w, [()()], r"return self")?; - w.newline()?; - - Ok(()) - } - - pub(crate) fn write_pattern_class_method(&self, w: &mut IndentWriter, class: &Service, function: &Function, write_for: WriteFor) -> Result<(), Error> { - let mut all_functions = class.constructors().to_vec(); - all_functions.extend_from_slice(class.methods()); - all_functions.push(class.destructor().clone()); - - let common_prefix = longest_common_prefix(&all_functions); - - let args = self.function_args_to_string(function, true, true); - let type_hint_out = to_type_hint_out(function.signature().rval()); - - indented!(w, [()], r"def {}(self, {}){}:", function.name().replace(&common_prefix, ""), &args, type_hint_out)?; - - if write_for == WriteFor::Docs { - return Ok(()); - } - - indented!(w, [()()], r"{}", documentation(function.meta().documentation()))?; - - w.indent(); - self.write_param_helpers(w, function)?; - w.unindent(); - - self.write_library_call(w, function, Some("self._ctx"))?; - w.newline()?; - - Ok(()) - } - - fn write_library_call(&self, w: &mut IndentWriter, function: &Function, class_str: Option<&str>) -> Result<(), Error> { - let args = match class_str { - None => self.function_args_to_string(function, false, false), - Some(class) => { - w.indent(); - self.get_method_args(function, class) - } - }; - - match function.signature().rval() { - CType::Pattern(TypePattern::CStrPointer) => { - indented!(w, [()], r"rval = c_lib.{}({})", function.name(), &args)?; - indented!(w, [()], r"return ctypes.string_at(rval)")?; - } - _ => self.write_success_enum_aware_rval(w, function, &args, true)?, - } - - if class_str.is_some() { - w.unindent(); - } - - Ok(()) - } - #[must_use] fn function_args_to_string(&self, function: &Function, type_hints: bool, skip_first: bool) -> String { let skip = usize::from(skip_first); @@ -613,15 +50,6 @@ impl Interop { .join(", ") } - fn write_success_enum_aware_rval(&self, w: &mut IndentWriter, function: &Function, args: &str, ret: bool) -> Result<(), Error> { - if ret { - indented!(w, [()], r"return c_lib.{}({})", function.name(), &args)?; - } else { - indented!(w, [()], r"c_lib.{}({})", function.name(), &args)?; - } - Ok(()) - } - #[must_use] fn get_method_args(&self, function: &Function, ctx: &str) -> String { let mut args = self.function_args_to_string(function, false, true); @@ -629,103 +57,30 @@ impl Interop { args } - fn write_utils(&self, w: &mut IndentWriter) -> Result<(), Error> { - // indented!(w, r#"class Slice(ctypes.Structure, typing.Generic[T]):"#)?; - // indented!(w, [_], r#"# These fields represent the underlying C data layout"#)?; - // indented!(w, [_], r#"_fields_ = ["#)?; - // indented!(w, [_], r#" ("data", ctypes.c_void_p),"#)?; - // indented!(w, [_], r#" ("len", ctypes.c_uint64),"#)?; - // indented!(w, [_], r#"]"#)?; - // w.newline()?; - // indented!(w, [_], r#"def cast(self, x):"#)?; - // indented!(w, [_ _], r#"return ctypes.cast(self.data, ctypes.POINTER(x))"#)?; - // w.newline()?; - // w.newline()?; - // - // indented!(w, r#"class SliceMut(ctypes.Structure, typing.Generic[T]):"#)?; - // indented!(w, [_], r#"# These fields represent the underlying C data layout"#)?; - // indented!(w, [_], r#"_fields_ = ["#)?; - // indented!(w, [_], r#" ("data", ctypes.c_void_p),"#)?; - // indented!(w, [_], r#" ("len", ctypes.c_uint64),"#)?; - // indented!(w, [_], r#"]"#)?; - // w.newline()?; - // indented!(w, [_], r#"def cast(self, x):"#)?; - // indented!(w, [_ _], r#"return ctypes.cast(self.data, ctypes.POINTER(x))"#)?; - // w.newline()?; - // w.newline()?; - - indented!(w, r"TRUE = ctypes.c_uint8(1)")?; - indented!(w, r"FALSE = ctypes.c_uint8(0)")?; - w.newline()?; - w.newline()?; - - indented!(w, r"def _errcheck(returned, success):")?; - indented!(w, [()], r#""""Checks for FFIErrors and converts them to an exception.""""#)?; - indented!(w, [()], r"if returned == success: return")?; - indented!(w, [()], r#"else: raise Exception(f"Function returned error: {{returned}}")"#)?; - w.newline()?; - w.newline()?; - - indented!(w, r"class CallbackVars(object):")?; - indented!( - w, - [()], - r#""""Helper to be used `lambda x: setattr(cv, "x", x)` when getting values from callbacks.""""# - )?; - indented!(w, [()], r"def __str__(self):")?; - indented!(w, [()()], r#"rval = """#)?; - indented!(w, [()()], r#"for var in filter(lambda x: "__" not in x, dir(self)):"#)?; - indented!(w, [()()()], r#"rval += f"{{var}}: {{getattr(self, var)}}""#)?; - indented!(w, [()()], r"return rval")?; - w.newline()?; - w.newline()?; - - indented!(w, r"class _Iter(object):")?; - indented!(w, [()], r#""""Helper for slice iterators.""""#)?; - indented!(w, [()], r"def __init__(self, target):")?; - indented!(w, [()()], r"self.i = 0")?; - indented!(w, [()()], r"self.target = target")?; - w.newline()?; - indented!(w, [()], r"def __iter__(self):")?; - indented!(w, [()()], r"self.i = 0")?; - indented!(w, [()()], r"return self")?; - w.newline()?; - indented!(w, [()], r"def __next__(self):")?; - indented!(w, [()()], r"if self.i >= self.target.len:")?; - indented!(w, [()()()], r"raise StopIteration()")?; - indented!(w, [()()], r"rval = self.target[self.i]")?; - indented!(w, [()()], r"self.i += 1")?; - indented!(w, [()()], r"return rval")?; - w.newline()?; - w.newline()?; - - Ok(()) - } - fn write_to(&self, w: &mut IndentWriter) -> Result<(), Error> { - self.write_imports(w)?; - self.write_api_load_fuction(w)?; + write_imports(self, w)?; + write_api_load_fuction(self, w)?; w.newline()?; w.newline()?; - self.write_function_proxies(w)?; + write_function_proxies(self, w)?; w.newline()?; w.newline()?; - self.write_constants(w)?; + write_constants(self, w)?; w.newline()?; w.newline()?; - self.write_utils(w)?; - self.write_types(w)?; + write_utils(self, w)?; + write_types(self, w)?; w.newline()?; w.newline()?; - self.write_callback_helpers(w)?; + write_callback_helpers(self, w)?; w.newline()?; w.newline()?; - self.write_patterns(w)?; + write_patterns(self, w)?; Ok(()) } diff --git a/crates/backend_cpython/src/interop/bootstrap.rs b/crates/backend_cpython/src/interop/bootstrap.rs new file mode 100644 index 00000000..4ba80630 --- /dev/null +++ b/crates/backend_cpython/src/interop/bootstrap.rs @@ -0,0 +1,40 @@ +use crate::converter::to_ctypes_name; +use crate::Interop; +use interoptopus::lang::c::CType; +use interoptopus::patterns::TypePattern; +use interoptopus::writer::IndentWriter; +use interoptopus::{indented, Error}; + +pub fn write_api_load_fuction(i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + indented!(w, r"c_lib = None")?; + w.newline()?; + indented!(w, r"def init_lib(path):")?; + indented!(w, [()], r#""""Initializes the native library. Must be called at least once before anything else.""""#)?; + indented!(w, [()], r"global c_lib")?; + indented!(w, [()], r"c_lib = ctypes.cdll.LoadLibrary(path)")?; + + w.newline()?; + for f in i.inventory.functions() { + let args = f.signature().params().iter().map(|x| to_ctypes_name(x.the_type(), false)).collect::>(); + + indented!(w, [()], r"c_lib.{}.argtypes = [{}]", f.name(), args.join(", "))?; + } + + w.newline()?; + for f in i.inventory.functions() { + let rtype = to_ctypes_name(f.signature().rval(), false); + if !rtype.is_empty() { + indented!(w, [()], r"c_lib.{}.restype = {}", f.name(), rtype)?; + } + } + + w.newline()?; + for f in i.inventory.functions() { + if let CType::Pattern(TypePattern::FFIErrorEnum(e)) = f.signature().rval() { + let value = e.success_variant().value(); + indented!(w, [()], r"c_lib.{}.errcheck = lambda rval, _fptr, _args: _errcheck(rval, {})", f.name(), value)?; + } + } + + Ok(()) +} diff --git a/crates/backend_cpython/src/interop/callbacks.rs b/crates/backend_cpython/src/interop/callbacks.rs new file mode 100644 index 00000000..48ad3e1c --- /dev/null +++ b/crates/backend_cpython/src/interop/callbacks.rs @@ -0,0 +1,22 @@ +use crate::converter::fnpointer_to_typename; +use crate::Interop; +use interoptopus::lang::c::CType; +use interoptopus::patterns::TypePattern; +use interoptopus::util::safe_name; +use interoptopus::writer::IndentWriter; +use interoptopus::{indented, Error}; + +pub fn write_callback_helpers(i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + indented!(w, r"class {}:", i.callback_namespace)?; + indented!(w, [()], r#""""Helpers to define callbacks.""""#)?; + + for callback in i.inventory.ctypes().iter().filter_map(|x| match x { + CType::FnPointer(x) => Some(x), + CType::Pattern(TypePattern::NamedCallback(x)) => Some(x.fnpointer()), + _ => None, + }) { + indented!(w, [()], r"{} = {}", safe_name(&callback.internal_name()), fnpointer_to_typename(callback))?; + } + + Ok(()) +} diff --git a/crates/backend_cpython/src/interop/constants.rs b/crates/backend_cpython/src/interop/constants.rs new file mode 100644 index 00000000..bc716961 --- /dev/null +++ b/crates/backend_cpython/src/interop/constants.rs @@ -0,0 +1,12 @@ +use crate::converter::constant_value_to_value; +use crate::Interop; +use interoptopus::writer::IndentWriter; +use interoptopus::{indented, Error}; + +pub fn write_constants(i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + for c in i.inventory.constants() { + indented!(w, r"{} = {}", c.name(), constant_value_to_value(c.value()))?; + } + + Ok(()) +} diff --git a/crates/backend_cpython/src/interop/functions.rs b/crates/backend_cpython/src/interop/functions.rs new file mode 100644 index 00000000..3cf1ab33 --- /dev/null +++ b/crates/backend_cpython/src/interop/functions.rs @@ -0,0 +1,98 @@ +use crate::converter::{to_ctypes_name, to_type_hint_out}; +use crate::interop::patterns::write_library_call; +use crate::Interop; +use interoptopus::lang::c::{CType, Function}; +use interoptopus::patterns::TypePattern; +use interoptopus::util::safe_name; +use interoptopus::writer::{IndentWriter, WriteFor}; +use interoptopus::{indented, non_service_functions, Error}; + +pub fn write_function_proxies(i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + for function in non_service_functions(&i.inventory) { + write_function(i, w, function, WriteFor::Code)?; + } + + Ok(()) +} + +pub fn write_function(i: &Interop, w: &mut IndentWriter, function: &Function, write_for: WriteFor) -> Result<(), Error> { + let rval_sig = to_type_hint_out(function.signature().rval()); + let args = i.function_args_to_string(function, true, false); + let documentation = function.meta().documentation().lines().join("\n"); + + indented!(w, r"def {}({}){}:", function.name(), args, rval_sig)?; + + if write_for == WriteFor::Code { + if !documentation.is_empty() { + indented!(w, [()], r#""""{}""""#, documentation)?; + } + + write_param_helpers(i, w, function)?; + write_library_call(i, w, function, None)?; + w.newline()?; + } else { + indented!(w, [()], r"...")?; + } + + Ok(()) +} + +pub fn write_param_helpers(_i: &Interop, w: &mut IndentWriter, function: &Function) -> Result<(), Error> { + for arg in function.signature().params() { + match arg.the_type() { + CType::FnPointer(x) => { + indented!(w, [()], r#"if not hasattr({}, "__ctypes_from_outparam__"):"#, arg.name())?; + indented!(w, [()()], r"{} = callbacks.{}({})", arg.name(), safe_name(&x.internal_name()), arg.name())?; + w.newline()?; + } + CType::Pattern(pattern) => match pattern { + TypePattern::NamedCallback(x) => { + let x = x.fnpointer(); + indented!(w, [()], r#"if not hasattr({}, "__ctypes_from_outparam__"):"#, arg.name())?; + indented!(w, [()()], r"{} = callbacks.{}({})", arg.name(), safe_name(&x.internal_name()), arg.name())?; + w.newline()?; + } + TypePattern::CStrPointer => { + indented!(w, [()], r#"if not hasattr({}, "__ctypes_from_outparam__"):"#, arg.name())?; + indented!(w, [()()], r"{} = ctypes.cast({}, ctypes.POINTER(ctypes.c_char))", arg.name(), arg.name())?; + } + TypePattern::Slice(t) | TypePattern::SliceMut(t) => { + let inner = to_ctypes_name( + t.fields() + .iter() + .find(|i| i.name().eq_ignore_ascii_case("data")) + .expect("slice must have a data field") + .the_type() + .try_deref_pointer() + .expect("data must be a pointer type"), + false, + ); + + indented!( + w, + [()], + r#"if hasattr({}, "_length_") and getattr({}, "_type_", "") == {}:"#, + arg.name(), + arg.name(), + inner + )?; + + indented!( + w, + [()()], + r"{} = {}(data=ctypes.cast({}, ctypes.POINTER({})), len=len({}))", + arg.name(), + arg.the_type().name_within_lib(), + arg.name(), + inner, + arg.name() + )?; + w.newline()?; + } + _ => {} + }, + _ => {} + } + } + Ok(()) +} diff --git a/crates/backend_cpython/src/interop/imports.rs b/crates/backend_cpython/src/interop/imports.rs new file mode 100644 index 00000000..863dfba6 --- /dev/null +++ b/crates/backend_cpython/src/interop/imports.rs @@ -0,0 +1,12 @@ +use crate::Interop; +use interoptopus::writer::IndentWriter; +use interoptopus::{indented, Error}; + +pub fn write_imports(_i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + indented!(w, r"from __future__ import annotations")?; + indented!(w, r"import ctypes")?; + indented!(w, r"import typing")?; + w.newline()?; + indented!(w, r#"T = typing.TypeVar("T")"#)?; + Ok(()) +} diff --git a/crates/backend_cpython/src/interop/patterns.rs b/crates/backend_cpython/src/interop/patterns.rs new file mode 100644 index 00000000..54e11fc0 --- /dev/null +++ b/crates/backend_cpython/src/interop/patterns.rs @@ -0,0 +1,292 @@ +use crate::converter::{documentation, to_ctypes_name, to_type_hint_in, to_type_hint_out}; +use crate::interop::functions::write_param_helpers; +use crate::interop::utils::write_success_enum_aware_rval; +use crate::Interop; +use interoptopus::lang::c::{CType, CompositeType, Function}; +use interoptopus::patterns::service::Service; +use interoptopus::patterns::{LibraryPattern, TypePattern}; +use interoptopus::util::longest_common_prefix; +use interoptopus::writer::{IndentWriter, WriteFor}; +use interoptopus::{indented, Error}; + +pub fn write_slice(_i: &Interop, w: &mut IndentWriter, c: &CompositeType, mutable: bool) -> Result<(), Error> { + let data_type = c + .fields() + .iter() + .find(|x| x.name().contains("data")) + .expect("Slice must contain field called 'data'.") + .the_type() + .try_deref_pointer() + .expect("data must be a pointer type"); + + let data_type_python = to_ctypes_name(data_type, true); + let hint_in = to_type_hint_in(data_type, false); + let hint_out = to_type_hint_out(data_type); + + indented!(w, r"class {}(ctypes.Structure):", c.rust_name())?; + indented!(w, [()], r"# These fields represent the underlying C data layout")?; + indented!(w, [()], r"_fields_ = [")?; + indented!(w, [()], r#" ("data", ctypes.POINTER({})),"#, data_type_python)?; + indented!(w, [()], r#" ("len", ctypes.c_uint64),"#)?; + indented!(w, [()], r"]")?; + w.newline()?; + indented!(w, [()], r"def __len__(self):")?; + indented!(w, [()()], r"return self.len")?; + w.newline()?; + indented!(w, [()], r"def __getitem__(self, i){}:", hint_out)?; + indented!(w, [()()], r"if i < 0:")?; + indented!(w, [()()()], r"index = self.len+i")?; + indented!(w, [()()], r"else:")?; + indented!(w, [()()()], r"index = i")?; + w.newline()?; + indented!(w, [()()], r"if index >= self.len:")?; + indented!(w, [()()()], r#"raise IndexError("Index out of range")"#)?; + w.newline()?; + indented!(w, [()()], r"return self.data[index]")?; + + if mutable { + w.newline()?; + indented!(w, [()], r"def __setitem__(self, i, v{}):", hint_in)?; + indented!(w, [()()], r"if i < 0:")?; + indented!(w, [()()()], r"index = self.len+i")?; + indented!(w, [()()], r"else:")?; + indented!(w, [()()()], r"index = i")?; + w.newline()?; + indented!(w, [()()], r"if index >= self.len:")?; + indented!(w, [()()()], r#"raise IndexError("Index out of range")"#)?; + w.newline()?; + indented!(w, [()()], r"self.data[index] = v")?; + } + + w.newline()?; + indented!(w, [()], r"def copied(self) -> {}:", c.rust_name())?; + indented!( + w, + [()()], + r#""""Returns a shallow, owned copy of the underlying slice. + + The returned object owns the immediate data, but not the targets of any contained + pointers. In other words, if your struct contains any pointers the returned object + may only be used as long as these pointers are valid. If the struct did not contain + any pointers the returned object is valid indefinitely.""""# + )?; + indented!(w, [()()], r"array = ({} * len(self))()", data_type_python)?; + indented!(w, [()()], r"ctypes.memmove(array, self.data, len(self) * ctypes.sizeof({}))", data_type_python)?; + indented!( + w, + [()()], + r"rval = {}(data=ctypes.cast(array, ctypes.POINTER({})), len=len(self))", + c.rust_name(), + data_type_python + )?; + indented!(w, [()()], r"rval.owned = array # Store array in returned slice to prevent memory deallocation")?; + indented!(w, [()()], r"return rval")?; + w.newline()?; + indented!(w, [()], r"def __iter__(self) -> typing.Iterable[{}]:", data_type_python)?; + indented!(w, [()()], r"return _Iter(self)")?; + w.newline()?; + indented!(w, [()], r"def iter(self) -> typing.Iterable[{}]:", data_type_python)?; + indented!(w, [()()], r#""""Convenience method returning a value iterator.""""#)?; + indented!(w, [()()], r"return iter(self)")?; + w.newline()?; + indented!(w, [()], r"def first(self){}:", hint_out)?; + indented!(w, [()()], r#""""Returns the first element of this slice.""""#)?; + indented!(w, [()()], r"return self[0]")?; + w.newline()?; + indented!(w, [()], r"def last(self){}:", hint_out)?; + indented!(w, [()()], r#""""Returns the last element of this slice.""""#)?; + indented!(w, [()()], r"return self[len(self)-1]")?; + + // Only write this for byte-like types right now + if data_type.size_of() == 1 { + w.newline()?; + indented!(w, [()], r"def bytearray(self):")?; + indented!(w, [()()], r#""""Returns a bytearray with the content of this slice.""""#)?; + indented!(w, [()()], r"rval = bytearray(len(self))")?; + indented!(w, [()()], r"for i in range(len(self)):")?; + indented!(w, [()()()], r"rval[i] = self[i]")?; + indented!(w, [()()], r"return rval")?; + } + + Ok(()) +} + +pub fn write_option(_i: &Interop, w: &mut IndentWriter, c: &CompositeType) -> Result<(), Error> { + let data_type = c + .fields() + .iter() + .find(|x| x.name().contains('t')) + .expect("Slice must contain field called 't'.") + .the_type(); + + let data_type_python = to_ctypes_name(data_type, true); + + indented!(w, r"class {}(ctypes.Structure):", c.rust_name())?; + indented!(w, [()], r#""""May optionally hold a value.""""#)?; + w.newline()?; + indented!(w, [()], r"_fields_ = [")?; + indented!(w, [()], r#" ("_t", {}),"#, data_type_python)?; + indented!(w, [()], r#" ("_is_some", ctypes.c_uint8),"#)?; + indented!(w, [()], r"]")?; + w.newline()?; + indented!(w, [()], r"@property")?; + indented!(w, [()], r"def value(self) -> {}:", data_type_python)?; + indented!(w, [()()], r#""""Returns the value if it exists, or None.""""#)?; + indented!(w, [()()], r"if self._is_some == 1:")?; + indented!(w, [()()()], r"return self._t")?; + indented!(w, [()()], r"else:")?; + indented!(w, [()()()], r"return None")?; + w.newline()?; + indented!(w, [()], r"def is_some(self) -> bool:")?; + indented!(w, [()()], r#""""Returns true if the value exists.""""#)?; + indented!(w, [()()], r"return self._is_some == 1")?; + w.newline()?; + indented!(w, [()], r"def is_none(self) -> bool:")?; + indented!(w, [()()], r#""""Returns true if the value does not exist.""""#)?; + indented!(w, [()()], r"return self._is_some != 0")?; + + Ok(()) +} + +pub fn write_patterns(i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + for pattern in i.inventory.patterns() { + match pattern { + LibraryPattern::Service(x) => write_pattern_class(i, w, x)?, + _ => panic!("Pattern not explicitly handled"), + } + } + + Ok(()) +} + +pub fn write_pattern_class(i: &Interop, w: &mut IndentWriter, class: &Service) -> Result<(), Error> { + let context_type_name = class.the_type().rust_name(); + + let mut all_functions = class.constructors().to_vec(); + all_functions.extend_from_slice(class.methods()); + all_functions.push(class.destructor().clone()); + + let _common_prefix = longest_common_prefix(&all_functions); + let documentation = class.the_type().meta().documentation().lines().join("\n"); + + indented!(w, r"class {}:", context_type_name)?; + if !documentation.is_empty() { + indented!(w, [()], r#""""{}""""#, documentation)?; + } + indented!(w, [()], r"__api_lock = object()")?; + w.newline()?; + indented!(w, [()], r"def __init__(self, api_lock, ctx):")?; + indented!( + w, + [()()], + r#"assert(api_lock == {}.__api_lock), "You must create this with a static constructor." "#, + context_type_name + )?; + indented!(w, [()()], r"self._ctx = ctx")?; + w.newline()?; + indented!(w, [()], r"@property")?; + indented!(w, [()], r"def _as_parameter_(self):")?; + indented!(w, [()()], r"return self._ctx")?; + w.newline()?; + + for ctor in class.constructors() { + write_pattern_class_ctor(i, w, class, ctor, WriteFor::Code)?; + } + + // Dtor + indented!(w, [()], r"def __del__(self):")?; + // indented!(w, [_ _], r#"global _api, ffi"#)?; + w.indent(); + write_success_enum_aware_rval(i, w, class.destructor(), &i.get_method_args(class.destructor(), "self._ctx"), false)?; + w.unindent(); + + for function in class.methods() { + write_pattern_class_method(i, w, class, function, WriteFor::Code)?; + } + + w.newline()?; + w.newline()?; + + Ok(()) +} + +pub fn write_pattern_class_ctor(i: &Interop, w: &mut IndentWriter, class: &Service, ctor: &Function, write_for: WriteFor) -> Result<(), Error> { + let context_type_name = class.the_type().rust_name(); + let mut all_functions = class.constructors().to_vec(); + all_functions.extend_from_slice(class.methods()); + all_functions.push(class.destructor().clone()); + + let common_prefix = longest_common_prefix(&all_functions); + + let ctor_args = i.function_args_to_string(ctor, true, true); + indented!(w, [()], r"@staticmethod")?; + indented!(w, [()], r"def {}({}) -> {}:", ctor.name().replace(&common_prefix, ""), ctor_args, context_type_name)?; + + if write_for == WriteFor::Docs { + return Ok(()); + } + + indented!(w, [()()], r"{}", documentation(ctor.meta().documentation()))?; + indented!(w, [()()], r"ctx = ctypes.c_void_p()")?; + w.indent(); + write_param_helpers(i, w, ctor)?; + write_success_enum_aware_rval(i, w, ctor, &i.get_method_args(ctor, "ctx"), false)?; + w.unindent(); + indented!(w, [()()], r"self = {}({}.__api_lock, ctx)", context_type_name, context_type_name)?; + indented!(w, [()()], r"return self")?; + w.newline()?; + + Ok(()) +} + +pub fn write_pattern_class_method(i: &Interop, w: &mut IndentWriter, class: &Service, function: &Function, write_for: WriteFor) -> Result<(), Error> { + let mut all_functions = class.constructors().to_vec(); + all_functions.extend_from_slice(class.methods()); + all_functions.push(class.destructor().clone()); + + let common_prefix = longest_common_prefix(&all_functions); + + let args = i.function_args_to_string(function, true, true); + let type_hint_out = to_type_hint_out(function.signature().rval()); + + indented!(w, [()], r"def {}(self, {}){}:", function.name().replace(&common_prefix, ""), &args, type_hint_out)?; + + if write_for == WriteFor::Docs { + return Ok(()); + } + + indented!(w, [()()], r"{}", documentation(function.meta().documentation()))?; + + w.indent(); + write_param_helpers(i, w, function)?; + w.unindent(); + + write_library_call(i, w, function, Some("self._ctx"))?; + w.newline()?; + + Ok(()) +} + +pub fn write_library_call(i: &Interop, w: &mut IndentWriter, function: &Function, class_str: Option<&str>) -> Result<(), Error> { + let args = match class_str { + None => i.function_args_to_string(function, false, false), + Some(class) => { + w.indent(); + i.get_method_args(function, class) + } + }; + + match function.signature().rval() { + CType::Pattern(TypePattern::CStrPointer) => { + indented!(w, [()], r"rval = c_lib.{}({})", function.name(), &args)?; + indented!(w, [()], r"return ctypes.string_at(rval)")?; + } + _ => write_success_enum_aware_rval(i, w, function, &args, true)?, + } + + if class_str.is_some() { + w.unindent(); + } + + Ok(()) +} diff --git a/crates/backend_cpython/src/interop/types.rs b/crates/backend_cpython/src/interop/types.rs new file mode 100644 index 00000000..dc59603f --- /dev/null +++ b/crates/backend_cpython/src/interop/types.rs @@ -0,0 +1,147 @@ +use crate::converter::{to_ctypes_name, to_type_hint_in, to_type_hint_out}; +use crate::interop::patterns::{write_option, write_slice}; +use crate::Interop; +use interoptopus::lang::c::{CType, CompositeType, EnumType, Layout}; +use interoptopus::patterns::TypePattern; +use interoptopus::util::sort_types_by_dependencies; +use interoptopus::writer::{IndentWriter, WriteFor}; +use interoptopus::{indented, Error}; + +pub fn write_types(i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + let all_types = i.inventory.ctypes().to_vec(); + let sorted_types = sort_types_by_dependencies(all_types); + + for t in &sorted_types { + match t { + CType::Composite(c) => write_struct(i, w, c, WriteFor::Code)?, + CType::Enum(e) => write_enum(i, w, e, WriteFor::Code)?, + CType::Pattern(p) => match p { + TypePattern::FFIErrorEnum(e) => write_enum(i, w, e.the_enum(), WriteFor::Code)?, + TypePattern::Slice(c) => write_slice(i, w, c, false)?, + TypePattern::SliceMut(c) => write_slice(i, w, c, true)?, + TypePattern::Option(c) => { + write_option(i, w, c)?; + } + _ => continue, + }, + _ => continue, + } + + w.newline()?; + w.newline()?; + } + + Ok(()) +} + +pub fn write_struct(_i: &Interop, w: &mut IndentWriter, c: &CompositeType, write_for: WriteFor) -> Result<(), Error> { + let documentation = c.meta().documentation().lines().join("\n"); + + indented!(w, r"class {}(ctypes.Structure):", c.rust_name())?; + if !documentation.is_empty() && write_for == WriteFor::Code { + indented!(w, [()], r#""""{}""""#, documentation)?; + } + + if c.repr().layout() == Layout::Packed { + indented!(w, [()], r"_pack_ = 1")?; + } + + let alignment = c.repr().alignment(); + if let Some(align) = alignment { + indented!(w, [()], r"_align_ = {}", align)?; + } + + w.newline()?; + if write_for == WriteFor::Code { + indented!(w, [()], r"# These fields represent the underlying C data layout")?; + } + indented!(w, [()], r"_fields_ = [")?; + for f in c.fields() { + let type_name = to_ctypes_name(f.the_type(), true); + indented!(w, [()()], r#"("{}", {}),"#, f.name(), type_name)?; + } + indented!(w, [()], r"]")?; + + // Ctor + let extra_args = c + .fields() + .iter() + .map(|x| { + let type_hint_in = to_type_hint_in(x.the_type(), false); + + format!("{}{} = None", x.name(), type_hint_in) + }) + .collect::>() + .join(", "); + + if !c.fields().is_empty() { + w.newline()?; + indented!(w, [()], r"def __init__(self, {}):", extra_args)?; + + if write_for == WriteFor::Code { + for field in c.fields() { + indented!(w, [()()], r"if {} is not None:", field.name())?; + indented!(w, [()()()], r"self.{} = {}", field.name(), field.name())?; + } + } else { + indented!(w, [()()], r"...")?; + } + } + + if write_for == WriteFor::Docs { + return Ok(()); + } + + // Fields + for f in c.fields() { + let documentation = f.documentation().lines().join("\n"); + + w.newline()?; + + let hint_in = to_type_hint_in(f.the_type(), false); + let hint_out = to_type_hint_out(f.the_type()); + + indented!(w, [()], r"@property")?; + indented!(w, [()], r"def {}(self){}:", f.name(), hint_out)?; + + if !documentation.is_empty() { + indented!(w, [()()], r#""""{}""""#, documentation)?; + } + + match f.the_type() { + CType::Pattern(_) => indented!(w, [()()], r#"return ctypes.Structure.__get__(self, "{}")"#, f.name())?, + _ => indented!(w, [()()], r#"return ctypes.Structure.__get__(self, "{}")"#, f.name())?, + } + + w.newline()?; + + indented!(w, [()], r"@{}.setter", f.name())?; + indented!(w, [()], r"def {}(self, value{}):", f.name(), hint_in)?; + if !documentation.is_empty() { + indented!(w, [()()], r#""""{}""""#, documentation)?; + } + indented!(w, [()()], r#"return ctypes.Structure.__set__(self, "{}", value)"#, f.name())?; + } + + Ok(()) +} + +pub fn write_enum(_i: &Interop, w: &mut IndentWriter, e: &EnumType, write_for: WriteFor) -> Result<(), Error> { + let documentation = e.meta().documentation().lines().join("\n"); + + indented!(w, r"class {}:", e.rust_name())?; + if !documentation.is_empty() && write_for == WriteFor::Code { + indented!(w, [()], r#""""{}""""#, documentation)?; + } + + for v in e.variants() { + if write_for == WriteFor::Code { + for line in v.documentation().lines() { + indented!(w, [()], r"# {}", line)?; + } + } + indented!(w, [()], r"{} = {}", v.name(), v.value())?; + } + + Ok(()) +} diff --git a/crates/backend_cpython/src/interop/utils.rs b/crates/backend_cpython/src/interop/utils.rs new file mode 100644 index 00000000..77717946 --- /dev/null +++ b/crates/backend_cpython/src/interop/utils.rs @@ -0,0 +1,86 @@ +use crate::Interop; +use interoptopus::lang::c::Function; +use interoptopus::writer::IndentWriter; +use interoptopus::{indented, Error}; + +pub fn write_success_enum_aware_rval(_i: &Interop, w: &mut IndentWriter, function: &Function, args: &str, ret: bool) -> Result<(), Error> { + if ret { + indented!(w, [()], r"return c_lib.{}({})", function.name(), &args)?; + } else { + indented!(w, [()], r"c_lib.{}({})", function.name(), &args)?; + } + Ok(()) +} + +pub fn write_utils(_i: &Interop, w: &mut IndentWriter) -> Result<(), Error> { + // indented!(w, r#"class Slice(ctypes.Structure, typing.Generic[T]):"#)?; + // indented!(w, [_], r#"# These fields represent the underlying C data layout"#)?; + // indented!(w, [_], r#"_fields_ = ["#)?; + // indented!(w, [_], r#" ("data", ctypes.c_void_p),"#)?; + // indented!(w, [_], r#" ("len", ctypes.c_uint64),"#)?; + // indented!(w, [_], r#"]"#)?; + // w.newline()?; + // indented!(w, [_], r#"def cast(self, x):"#)?; + // indented!(w, [_ _], r#"return ctypes.cast(self.data, ctypes.POINTER(x))"#)?; + // w.newline()?; + // w.newline()?; + // + // indented!(w, r#"class SliceMut(ctypes.Structure, typing.Generic[T]):"#)?; + // indented!(w, [_], r#"# These fields represent the underlying C data layout"#)?; + // indented!(w, [_], r#"_fields_ = ["#)?; + // indented!(w, [_], r#" ("data", ctypes.c_void_p),"#)?; + // indented!(w, [_], r#" ("len", ctypes.c_uint64),"#)?; + // indented!(w, [_], r#"]"#)?; + // w.newline()?; + // indented!(w, [_], r#"def cast(self, x):"#)?; + // indented!(w, [_ _], r#"return ctypes.cast(self.data, ctypes.POINTER(x))"#)?; + // w.newline()?; + // w.newline()?; + + indented!(w, r"TRUE = ctypes.c_uint8(1)")?; + indented!(w, r"FALSE = ctypes.c_uint8(0)")?; + w.newline()?; + w.newline()?; + + indented!(w, r"def _errcheck(returned, success):")?; + indented!(w, [()], r#""""Checks for FFIErrors and converts them to an exception.""""#)?; + indented!(w, [()], r"if returned == success: return")?; + indented!(w, [()], r#"else: raise Exception(f"Function returned error: {{returned}}")"#)?; + w.newline()?; + w.newline()?; + + indented!(w, r"class CallbackVars(object):")?; + indented!( + w, + [()], + r#""""Helper to be used `lambda x: setattr(cv, "x", x)` when getting values from callbacks.""""# + )?; + indented!(w, [()], r"def __str__(self):")?; + indented!(w, [()()], r#"rval = """#)?; + indented!(w, [()()], r#"for var in filter(lambda x: "__" not in x, dir(self)):"#)?; + indented!(w, [()()()], r#"rval += f"{{var}}: {{getattr(self, var)}}""#)?; + indented!(w, [()()], r"return rval")?; + w.newline()?; + w.newline()?; + + indented!(w, r"class _Iter(object):")?; + indented!(w, [()], r#""""Helper for slice iterators.""""#)?; + indented!(w, [()], r"def __init__(self, target):")?; + indented!(w, [()()], r"self.i = 0")?; + indented!(w, [()()], r"self.target = target")?; + w.newline()?; + indented!(w, [()], r"def __iter__(self):")?; + indented!(w, [()()], r"self.i = 0")?; + indented!(w, [()()], r"return self")?; + w.newline()?; + indented!(w, [()], r"def __next__(self):")?; + indented!(w, [()()], r"if self.i >= self.target.len:")?; + indented!(w, [()()()], r"raise StopIteration()")?; + indented!(w, [()()], r"rval = self.target[self.i]")?; + indented!(w, [()()], r"self.i += 1")?; + indented!(w, [()()], r"return rval")?; + w.newline()?; + w.newline()?; + + Ok(()) +}