fix(trixy-types): Conform to the api provided by the headers

See the previous commit for an explanation
This commit is contained in:
Benedikt Peetz 2024-02-18 13:35:40 +01:00
parent f699ca24a9
commit 86b946b540
Signed by: bpeetz
GPG Key ID: A5E94010C3A642AD
13 changed files with 202 additions and 457 deletions

View File

@ -36,21 +36,21 @@ mod pure_header;
mod structs_init;
mod typedef;
const BEGIN_HEADER_GUARD: &'static str = r"#ifndef TRIXY_MAIN_HEADER
const BEGIN_HEADER_GUARD: &'static str = r"#if !defined TRIXY_MAIN_HEADER
#define TRIXY_MAIN_HEADER";
const END_HEADER_GUARD: &'static str = r"#endif // ifndef TRIXY_MAIN_HEADER";
const END_HEADER_GUARD: &'static str = r"#endif // if !defined TRIXY_MAIN_HEADER";
/// This function acts as the core transformative aspect, turning this trixy code into the
/// This function acts as the core transformative aspect, turning this Trixy code into the
/// following c header:
///
/// *Trixy:*
/// ```text
/// fn fn_alone();
/// nasp one {
/// mod one {
/// fn fn_one();
/// nasp two {
/// mod two {
/// fn fn_two();
/// nasp three {
/// mod three {
/// fn fn_three();
/// }
/// }
@ -58,7 +58,7 @@ const END_HEADER_GUARD: &'static str = r"#endif // ifndef TRIXY_MAIN_HEADER";
/// ```
/// *C header:*
/// ```text
/// #ifndef TRIXY_MAIN_HEADER
/// #if !defined TRIXY_MAIN_HEADER
/// #define TRIXY_MAIN_HEADER
///
/// extern int fn_alone();
@ -66,30 +66,30 @@ const END_HEADER_GUARD: &'static str = r"#endif // ifndef TRIXY_MAIN_HEADER";
/// extern int one_two_fn_two();
/// extern int one_two_three_fn_three();
///
/// typedef struct {
/// struct three {
/// void (*fn_three)(void);
/// } three_t;
/// typedef struct {
/// };
/// struct two {
/// void (*fn_two)(void);
/// three_t three;
/// } two_t;
/// typedef struct {
/// };
/// struct one {
/// void (*fn_one)(void);
/// two_t two;
/// } one_t;
/// } ;
///
/// const three_t three = {
/// const struct three three = {
/// .fn_three = one_two_three_fn_three,
/// };
/// const two_t two = {
/// const struct two two = {
/// .fn_two = one_two_fn_two,
/// .three = three,
/// };
/// const one_t one = {
/// const struct one one = {
/// .fn_one = one_fn_one,
/// .two = two,
/// };
/// #endif // ifndef TRIXY_MAIN_HEADER
/// #endif // if !defined TRIXY_MAIN_HEADER
/// ```
pub fn generate(trixy: &CommandSpec, _config: &TrixyConfig) -> String {
let pure_header = pure_header::generate(&trixy);

View File

@ -54,10 +54,10 @@ fn structure_to_header(structure: &Structure) -> String {
format!(
"
{}
typedef struct {{
struct {} {{
{}
}} {};",
doc_comments, contents, ident
}};",
doc_comments, ident, contents
)
}
fn doc_named_type_to_header(doc_named_type: &DocNamedType) -> String {
@ -94,10 +94,10 @@ fn enumeration_to_header(enumeration: &Enumeration) -> String {
format!(
"
{}
typedef enum {{
enum {} {{
{}
}} {};",
doc_comments, states, ident
}};",
doc_comments, ident, states
)
}
fn doc_identifier_to_header(doc_identifier: &DocIdentifier) -> String {

View File

@ -51,15 +51,15 @@ fn function_to_typedef(function: &Function) -> TokenStream2 {
fn namespace_to_typedef(namespace: &Namespace) -> TokenStream2 {
let ident = identifier_to_rust(&namespace.name);
let type_ident = format_ident!("{}_t", ident.to_string());
let type_ident = format_ident!("{}", ident.to_string());
quote! {
#type_ident #ident ;
struct #type_ident #ident;
}
}
fn namespace_to_full_typedef(nasp: &Namespace) -> String {
let ident = format_ident!("{}_t", nasp.name.name);
let ident = format_ident!("{}", nasp.name.name);
let doc_comments = nasp
.attributes
.iter()
@ -84,10 +84,10 @@ fn namespace_to_full_typedef(nasp: &Namespace) -> String {
.join("\n");
let namespace = quote! {
typedef struct {
struct #ident {
#functions
#namespaces
} #ident;
};
};
format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace}
}

View File

@ -27,7 +27,7 @@ use crate::{
config::TrixyConfig,
generate::{
c_api::{mangle_c_function_ident, namespaces_to_path, type_to_c_equalivalent},
function_identifier_to_rust, identifier_to_rust, named_type_to_rust,
function_identifier_to_rust, identifier_to_rust,
},
};

View File

@ -30,4 +30,3 @@ proc-macro2 = "1.0.70"
quote = "1.0.33"
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
thiserror = "1.0.51"
trixy-types-derive = { path = "./trixy-types-derive" }

View File

@ -32,4 +32,10 @@ pub enum TypeConversionError {
#[error("You passed a untyped (type_id == Unknown) value to the conversion function!")]
UntypedInput,
#[error("You passed an unaligned pointer to a function expecting a pointer aligned to it's required value!")]
NotAligned,
#[error("The returned Result was an error: {original_message}")]
ResultWasErr { original_message: String },
}

View File

@ -18,7 +18,7 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
//! Trixy contains the types used by the [trixy-macros] crate to provide ffi safe types
//! Trixy contains the types used by the [`trixy-macros`] crate to provide ffi safe types
pub mod error;
pub mod traits;
pub mod types_list;
@ -33,28 +33,32 @@ macro_rules! header {
/// These are the "primitive" types used in Trixy, you can use any of them to create new structures
/// The str is the name, the index represents the expected generic arguments
pub const BASE_TYPES: [(&'static std::primitive::str, usize); 5] = [
("str", 0),
///
// NOTE: Every type here must have the [`Convertible`] implemented on it (@soispha)
pub const BASE_TYPES: [(&'static std::primitive::str, usize); 14] = [
// Unsigned
("u8", 0),
("u16", 0),
("u32", 0),
("u64", 0),
// Signed
("i8", 0),
("i16", 0),
("i32", 0),
("i64", 0),
// Float
("f32", 0),
("f64", 0),
// Other
("String", 0),
("Result", 2),
("Option", 1),
("Vec", 1),
("Result", 2),
];
/// The first value is the file name, the second it's contents
pub const C_TYPE_HEADER: [(&'static std::primitive::str, &'static std::primitive::str); 9] = [
header!("errno.h"),
// These must be *before* "type_id.h"
header!("type_id_dec.h"),
header!("result_dec.h"),
header!("option.h"),
header!("string.h"),
header!("vec_dec.h"),
header!("type_id.h"),
header!("result.h"),
header!("vec.h"),
];
pub const C_TYPE_HEADER: [(&'static std::primitive::str, &'static std::primitive::str); 3] =
[header!("errno.h"), header!("string.h"), header!("vec.h")];
pub fn header_names() -> std::string::String {
C_TYPE_HEADER

View File

@ -1,108 +1,166 @@
use crate::error::{self, TypeConversionError};
use std::{
any::Any,
ffi::{CStr, CString},
os::raw::c_void,
error::Error,
ffi::{c_char, CString},
mem::ManuallyDrop,
ptr,
};
/// Convert a value
use super::errno;
/// Convert a value into a data structure that we can send directly to c.
pub trait Convertible {
/// Turn the value into a c void pointer.
/// It should try its best to return a value which c can understand.
/// If this however is not possible, it should still return the underlying raw data
fn into_void(self) -> *const c_void;
type Ptr;
/// Turn the value into a c void pointer or a data structure that c can understand.
fn into_ptr(self) -> Self::Ptr;
/// If the data returned by [`into_ptr`] needs to be dropped this function should do it.
fn drop_ptr(ptr: Self::Ptr) -> Result<(), error::TypeConversionError>;
}
impl Convertible for crate::String {
fn into_void(self) -> *const c_void {
self.0 as *const _ as *const c_void
macro_rules! primitive_convert {
[$name:ty] => {
impl Convertible for $name {
type Ptr = $name;
fn into_ptr(self) -> Self::Ptr {
self
}
fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> {
unreachable!("Theres is no need to drop a {}", stringify!($name))
}
}
}
}
impl Convertible for crate::str {
fn into_void(self) -> *const c_void {
self.0 as *const _ as *const c_void
}
}
// Unsigned
primitive_convert!(u8);
primitive_convert!(u16);
primitive_convert!(u32);
primitive_convert!(u64);
impl Convertible for crate::Option {
fn into_void(mut self) -> *const c_void {
&mut self as *const _ as *const c_void
}
}
// Signed
primitive_convert!(i8);
primitive_convert!(i16);
primitive_convert!(i32);
primitive_convert!(i64);
impl Convertible for crate::Vec {
fn into_void(mut self) -> *const c_void {
&mut self as *const _ as *const c_void
}
}
impl Convertible for crate::Result {
fn into_void(mut self) -> *const c_void {
&mut self as *const _ as *const c_void
}
}
impl<T: Convertible + Any + TypeInfo, E: Convertible + Any + TypeInfo> Convertible
for Result<T, E>
{
fn into_void(self) -> *const c_void {
Into::<crate::Result>::into(self).into_void()
}
}
impl Convertible for &CStr {
fn into_void(self) -> *const c_void {
Into::<crate::str>::into(self).into_void()
}
}
// Float
primitive_convert!(f32);
primitive_convert!(f64);
impl Convertible for CString {
fn into_void(self) -> *const c_void {
Into::<crate::String>::into(self).into_void()
type Ptr = *const *const c_char;
fn into_ptr(self) -> Self::Ptr {
self.into_raw() as *const *const c_char
}
fn drop_ptr(ptr: Self::Ptr) -> Result<(), error::TypeConversionError> {
unsafe {
if ptr.is_null() {
return Err(TypeConversionError::NullPointer);
}
// TODO(@soispha): Add this, when this feature is stable <2024-02-17>
// if !ptr.is_aligned() {
// return Err(TypeConversionError::NotAligned);
// }
// SAFETY:
// Checked that the ptr is not null and that it is aligned (to c_char).
// This should hopefully be enough to avoid constructing a CString from a pointer not
// constructed by a us earlier in the [`into_ptr`] function.
// However:
// This still builds on the hope that c passes the pointer with the length unchanged.
let string = CString::from_raw(ptr as *mut i8);
drop(string);
}
Ok(())
}
}
impl Convertible for () {
fn into_void(self) -> *const c_void {
std::ptr::null()
impl<T: Convertible> Convertible for Option<T> {
type Ptr = *const T;
fn into_ptr(self) -> Self::Ptr {
if let Some(inner) = self {
let mut_ref = &mut inner.into_ptr();
mut_ref as *mut _ as *const T
} else {
ptr::null()
}
}
fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> {
unreachable!(
"
This should never be called, as this option type resolves
to a null pointer or to a pointer to the real value
(e. g. a `crate::Vec<T>`)
"
);
}
}
extern crate trixy_types_derive;
pub use trixy_types_derive::{Convertible, TypeInfo};
impl<T: Convertible, E: Error> Convertible for Result<T, E> {
type Ptr = *const T;
/// Similar to [`std::any::type_name`] but only really works for *some* types.
/// If the type is unknown it will just return [`TypeId::Unknown`]
pub trait TypeInfo {
fn type_of(&self) -> crate::TypeId;
fn into_ptr(self) -> Self::Ptr {
match self {
Ok(ok) => {
let mut_ref = &mut ok.into_ptr();
mut_ref as *mut _ as *const T
}
Err(err) => {
errno::set(TypeConversionError::ResultWasErr {
original_message: err.to_string(),
});
ptr::null()
}
}
}
fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> {
unreachable!(
"
This should never be called, as this result type resolves
to a null pointer (and a set error) or to a pointer to
the real value (e. g. a `crate::Vec<T>`)
"
);
}
}
macro_rules! type_info {
($name:tt<_>, $output:ident) => {
impl<T> TypeInfo for $name<T> {
fn type_of(&self) -> crate::TypeId {
crate::TypeId::$output
impl<T> Convertible for Vec<T> {
type Ptr = crate::Vec<T>;
fn into_ptr(self) -> Self::Ptr {
let mut me = ManuallyDrop::new(self);
Self::Ptr {
data: me.as_mut_ptr() as *const T,
length: me.len(),
capacity: me.capacity(),
}
}
fn drop_ptr(ptr: Self::Ptr) -> Result<(), error::TypeConversionError> {
let vec = unsafe {
if ptr.data.is_null() {
return Err(TypeConversionError::NullPointer);
}
// TODO(@soispha): Add this, when this feature is stable <2024-02-17>
// if !ptr.data.is_aligned() {
// return Err(TypeConversionError::NotAligned);
// }
// SAFETY:
// We simply hope that c treated our vector as read-only and didn't modify it.
// See the SAFETY section of the String implementation for more detail.
Vec::from_raw_parts(ptr.data as *mut T, ptr.length, ptr.capacity)
};
($name:tt<_,_>, $output:ident) => {
impl<T, E> TypeInfo for $name<T, E> {
fn type_of(&self) -> crate::TypeId {
crate::TypeId::$output
drop(vec);
Ok(())
}
}
};
($name:ty, $output:ident) => {
impl TypeInfo for $name {
fn type_of(&self) -> crate::TypeId {
crate::TypeId::$output
}
}
};
}
type_info!((), void);
type_info!(&CStr, str_t);
type_info!(CString, string_t);
type_info!(Result<_, _>, result_t);
type_info!(Vec<_>, vec_t);
type_info!(Option<_>, option_t);

View File

@ -18,212 +18,5 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
use std::{
alloc::Layout,
ffi::{c_char, CStr, CString},
mem::ManuallyDrop,
ptr,
};
use crate::error::TypeConversionError;
use self::convert_trait::{Convertible, TypeInfo};
pub mod convert_trait;
pub mod errno;
impl<'a> TryFrom<&'a crate::str> for &'a str {
type Error = crate::error::TypeConversionError;
fn try_from(value: &'a crate::str) -> Result<Self, Self::Error> {
let ptr = value.0;
if ptr.is_null() {
Err(TypeConversionError::NullPointer)
} else {
// SAFETY: We checked for null, which is a huge upside other things that we simply hope
// for:
// - null terminated
// - actually be a valid pointer (`(void*) 2` is *valid* c but not a *valid* pointer)
// - be contained in a single allocated object
// - the memory must obviously not be mutated, while this &str exists
// - the null terminator must be within `isize::MAX` from the start position
let str = unsafe { CStr::from_ptr(*ptr as *const c_char) };
str.to_str()
.map_err(|_err| crate::error::TypeConversionError::String {
got: value.0 as *const c_char,
})
}
}
}
impl TryFrom<crate::String> for String {
type Error = crate::error::TypeConversionError;
fn try_from(value: crate::String) -> Result<Self, Self::Error> {
let ptr = value.0;
if ptr.is_null() {
Err(TypeConversionError::NullPointer)
} else {
// SAFETY: (exactly the same as above applies)
let string = unsafe { CStr::from_ptr(ptr) }.to_owned();
Ok(string
.into_string()
.map_err(|err| TypeConversionError::String {
got: err.into_cstring().into_raw(),
})?)
}
}
}
impl<T: Convertible + TypeInfo> TryFrom<crate::Vec> for Vec<T> {
type Error = crate::error::TypeConversionError;
fn try_from(value: crate::Vec) -> Result<Self, Self::Error> {
match value.type_id {
crate::TypeId::Unknown => Err(Self::Error::UntypedInput),
crate::TypeId::void => Ok(vec![]),
crate::TypeId::str_t => {
let mut output: Vec<T> = Vec::with_capacity(value.capacity);
for i in 0..value.size {
// output.push(value.data.add())
}
Ok(output)
},
crate::TypeId::string_t => todo!(),
crate::TypeId::result_t => todo!(),
crate::TypeId::vec_t => todo!(),
crate::TypeId::option_t => todo!(),
}
}
}
impl crate::Vec {
pub fn pop<T: Convertible + TypeInfo>(&mut self) -> Result<T, TypeConversionError> {
match self.type_id {
crate::TypeId::Unknown => Err(TypeConversionError::UntypedInput),
crate::TypeId::void => Ok(() as T),
crate::TypeId::str_t => todo!(),
crate::TypeId::string_t => todo!(),
crate::TypeId::result_t => todo!(),
crate::TypeId::vec_t => todo!(),
crate::TypeId::option_t => todo!(),
}
}
}
impl From<CString> for crate::String {
fn from(value: CString) -> Self {
let layout = Layout::array::<u8>(value.as_bytes_with_nul().len()).expect(
"We don't handle n > isize::MAX, in the other types. \
Thus, this convertion will fail",
);
// SAFETY:
// - The layout should never have zero size (at least 1)
// - The memory blocks are copied from the string, thus initialization is irrelevant
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
// TODO(@soispha): How do we handle this? <2023-12-27>
panic!("While preparing a string for c failed to allocate memory, the pointer is null");
}
// SAFETY:
// - Both are valid for reads of `count * size_of::<char>()` bytes because we checked
// their length
// - They are properly aligned for types of [`c_char`]
unsafe {
ptr::copy(
value.as_ptr(),
ptr as *mut i8,
value.as_bytes_with_nul().len(),
)
};
Self(ptr as *const c_char)
}
}
impl<'a> From<&'a CStr> for crate::str {
fn from(value: &'a CStr) -> Self {
crate::str(value.as_ptr() as *const u8)
}
}
impl<T> From<Vec<T>> for crate::Vec
where
T: Convertible + TypeInfo,
{
fn from(value: Vec<T>) -> Self {
if value.is_empty() {
Self {
type_id: crate::TypeId::Unknown,
data: ptr::null(),
size: 0,
capacity: 0,
}
} else {
let value_type = value.first().expect("We checked the length").type_of();
let value: Vec<_> = value.into_iter().map(|val| val.into_void()).collect();
// We simply tell rust, that c should drop the value
let vec = ManuallyDrop::new(value);
Self {
type_id: value_type,
data: *vec.first().expect("This exists"),
size: vec.len(),
capacity: vec.capacity(),
}
}
}
}
impl<T> From<Option<T>> for crate::Option
where
T: Convertible + TypeInfo,
{
fn from(value: Option<T>) -> Self {
match value {
Some(value) => Self {
type_id: value.type_of(),
value: value.into_void(),
some: true,
},
None => Self {
type_id: crate::TypeId::Unknown,
value: ptr::null(),
some: false,
},
}
}
}
// impl<'a> From<&'a str> for crate::str {
// fn from(value: &'a str) -> Self {
// let ptr = value.as_ptr();
// // This is taken from std's CStr
//
// // SAFETY: We do not handle any strings larger than `isize::MAX` thus this call works.
// //
// // The cast from c_char to i8 is ok because a c_char is always one byte.
// unsafe { CStr::from_ptr(ptr as *const i8) }.into()
// }
// }
impl<T, E> From<Result<T, E>> for crate::Result
where
T: Convertible + TypeInfo,
E: Convertible + TypeInfo,
{
fn from(value: Result<T, E>) -> Self {
match value {
Ok(ok) => Self {
type_id: ok.type_of(),
tag: crate::ResultTag::Ok,
value: ok.into_void(),
},
Err(err) => Self {
type_id: err.type_of(),
tag: crate::ResultTag::Err,
value: err.into_void(),
},
}
}
}

View File

@ -1,70 +1,12 @@
// NOTE(@soispha): All types specified here *MUST* be include in the BASE_TYPES constant, otherwise
// they are not usable from Trixy code <2023-12-25>
use std::ffi::{c_char, c_void};
#[derive(Debug)]
#[repr(C)]
pub struct String(pub(crate) *const c_char);
#[derive(Debug)]
#[repr(C)]
#[allow(non_camel_case_types)]
pub struct str(pub(crate) *const u8);
#[derive(Debug)]
#[repr(C)]
pub enum TypeId {
/// We simply don't know which type this is
Unknown,
#[allow(non_camel_case_types)]
void,
#[allow(non_camel_case_types)]
str_t,
#[allow(non_camel_case_types)]
string_t,
#[allow(non_camel_case_types)]
result_t,
#[allow(non_camel_case_types)]
vec_t,
#[allow(non_camel_case_types)]
option_t,
}
#[derive(Debug)]
#[repr(C)]
pub enum ResultTag {
Ok,
Err,
}
#[derive(Debug)]
#[repr(C)]
pub struct Result {
pub(crate) type_id: TypeId,
pub(crate) tag: ResultTag,
pub(crate) value: *const c_void,
}
#[derive(Debug)]
#[repr(C)]
pub struct Vec {
pub(crate) type_id: TypeId,
pub(crate) data: *const c_void,
pub(crate) size: usize,
pub struct Vec<T> {
/// You should cast this value to it's supposed type (readable from the Trixy api definition
/// file)
pub(crate) data: *const T,
pub(crate) length: usize,
pub(crate) capacity: usize,
}
#[derive(Debug)]
#[repr(C)]
pub struct Option {
pub(crate) type_id: TypeId,
pub(crate) value: *const c_void,
pub(crate) some: bool,
}

View File

@ -1,6 +0,0 @@
# build
/target
/result
# This crate is a library
Cargo.lock

View File

@ -1,13 +0,0 @@
[package]
name = "trixy-types-derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
syn = "1.0"
quote = "1.0"
[lib]
proc-macro = true

View File

@ -1,38 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_derive(Convertible)]
pub fn convertible_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast: syn::DeriveInput = syn::parse(input).unwrap();
// Build the trait implementation
let name = &ast.ident;
let gen = quote! {
impl trixy::types::traits::convert_trait::Convertible for #name {
fn into_void(mut self) -> *const core::ffi::c_void {
&mut self as *const _ as *const core::ffi::c_void
}
}
};
gen.into()
}
#[proc_macro_derive(TypeInfo)]
pub fn type_info_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast: syn::DeriveInput = syn::parse(input).unwrap();
// Build the trait implementation
let name = &ast.ident;
let gen = quote! {
impl trixy::types::traits::convert_trait::TypeInfo for #name {
fn type_of(&self) -> trixy::types::TypeId {
trixy::types::TypeId::Unknown
}
}
};
gen.into()
}