feat(treewide): Add broken Vec<E>, Result<T,E> and Option<T> types to c api

These are implemented right now by simply casting the generic arguments
to void pointers and providing a `type_id` field in every struct
denoting the original type.

This implementation, whilst being extremely unwieldy to work with on
the c side, also fails in a lot of fundamental ways:
   1. The `type_id` enum *can* never really support user defined
      types because we would already need it to provide the c to rust
      value conversion.

   2. Even without custom user types the type conversion is extremely
      hard to correctly implement in a somewhat performant way: A vector
      passed from c code to rust would need to completely reallocated
      *one element at a time*. And this only works if the c side has
      correctly cast the void pointer to the vectors data before accessing
      it, as any other way would have lead to possible unaligned data
      (which the rust side had to account for).

   3. The c api is just simply bad in this state:
      You have to always look at the Trixy file to even be able to deal
      with the data the api returns (that is: There is no mention of
      a results generics in the c header).  Additionally the question
      arises if these types should even be leaked into the c code because
      than c just becomes a worse version of rust, which undermines the
      whole reason of providing a c api in the first place.

One way to fix all these issues would be to change the way generics are
handled by using unions instead of the void pointer and trying to avoid
leaking these rust types in c as far as possible.
This approach would require a lot less binding code (both on the c and
rust side), but also would make the rust based c-header-gen-code harder,
as it would then be required to turn a `Vec<String>` to a `char **` (and
obviously a whole wrapper struct with size and string length), whilst
turning a `Vec<char>` to a `char*` differentiating it from a `string_t`.
This commit is contained in:
Benedikt Peetz 2023-12-28 10:28:58 +01:00
parent b9e26418ad
commit b3c6a4c1a1
Signed by: bpeetz
GPG Key ID: A5E94010C3A642AD
38 changed files with 1531 additions and 478 deletions

View File

@ -5,3 +5,70 @@ pub mod types {
pub mod macros {
pub use trixy_macros::*;
}
pub mod oneshot {
use std::{
mem,
sync::mpsc::{self, RecvError, SendError, TryRecvError},
};
#[derive(Clone, Debug)]
pub struct Sender<T> {
channel: mpsc::Sender<T>,
}
#[derive(Debug)]
pub struct Receiver<T> {
channel: mpsc::Receiver<T>,
last_value: Option<T>,
}
pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let (tx, rx) = mpsc::channel();
(
Sender { channel: tx },
Receiver {
channel: rx,
last_value: None,
},
)
}
impl<T> Sender<T> {
pub fn send(&self, input: T) -> Result<(), SendError<T>> {
self.channel.send(input)
}
}
impl<T> Receiver<T> {
pub fn try_recv(&mut self) -> Result<bool, TryRecvError> {
match self.channel.try_recv() {
Ok(ok) => {
self.close();
self.last_value = Some(ok);
Ok(true)
}
Err(err) => Err(err),
}
}
pub fn close(&mut self) {
let (_, recv) = mpsc::channel();
let channel = mem::replace(&mut self.channel, recv);
drop(channel);
}
pub fn recv(mut self) -> Result<T, RecvError> {
match self.channel.recv() {
Ok(ok) => {
self.close();
Ok(ok)
}
Err(err) => {
if let Some(val) = self.last_value {
Ok(val)
} else {
Err(err)
}
}
}
}
}
}

View File

@ -25,7 +25,7 @@ edition = "2021"
[dependencies]
convert_case = "0.6.0"
prettyplease = "0.2.15"
proc-macro2 = "1.0.70"
proc-macro2 = {version = "1.0.70", features = [ ]}
quote = "1.0.33"
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
trixy-parser = { path = "../trixy-parser" }

View File

@ -1 +0,0 @@
/home/soispha/repos/rust/trinitrix/trixy/trixy-lang_parser/example/full.tri

View File

@ -1,292 +0,0 @@
/*
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
//! This module generates the c header
//! It works by firstly listing the functions and then by grouping them into structures, effectively
//! simulating namespaces in c.
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use trixy_parser::command_spec::{
Attribute, CommandSpec, Function, Identifier, NamedType, Namespace,
};
use crate::{
config::TrixyConfig,
generate::{c_api::mangle_c_function_ident, identifier_to_rust, type_to_rust},
};
const BEGIN_HEADER_GUARD: &'static str = r"#ifndef TRIXY_MAIN_HEADER
#define TRIXY_MAIN_HEADER";
const END_HEADER_GUARD: &'static str = r"#endif // ifndef TRIXY_MAIN_HEADER";
/// This function acts as the core transformative aspect, turning this trixy code into the
/// following c header:
///
/// *Trixy:*
/// ```text
/// fn fn_alone();
/// nasp one {
/// fn fn_one();
/// nasp two {
/// fn fn_two();
/// nasp three {
/// fn fn_three();
/// }
/// }
/// }
/// ```
/// *C header:*
/// ```text
/// #ifndef TRIXY_MAIN_HEADER
/// #define TRIXY_MAIN_HEADER
///
/// extern int fn_alone();
/// extern int one_fn_one();
/// extern int one_two_fn_two();
/// extern int one_two_three_fn_three();
///
/// typedef struct {
/// void (*fn_three)(void);
/// } three_t;
/// typedef struct {
/// void (*fn_two)(void);
/// three_t three;
/// } two_t;
/// typedef struct {
/// void (*fn_one)(void);
/// two_t two;
/// } one_t;
///
/// const three_t three = {
/// .fn_three = one_two_three_fn_three,
/// };
/// const two_t two = {
/// .fn_two = one_two_fn_two,
/// .three = three,
/// };
/// const one_t one = {
/// .fn_one = one_fn_one,
/// .two = two,
/// };
/// #endif // ifndef TRIXY_MAIN_HEADER
/// ```
pub fn generate(trixy: &CommandSpec, _config: &TrixyConfig) -> String {
let functions: String = trixy
.functions
.iter()
.map(|r#fn| function_to_header(r#fn, &[]))
.collect();
let namespaces: String = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_header(nasp, &vec![]))
.collect();
let type_defs: String = trixy
.namespaces
.iter()
.rev()
.map(|nasp| namespace_to_full_typedef(nasp))
.collect::<Vec<String>>()
.join("\n");
let struct_initializer: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_full_struct_init(nasp, &vec![]))
.collect();
let output = quote! {
#struct_initializer
}
.to_string();
format!(
"{}\n\n{}\n\n{}\n{}\n{}\n{}\n\n{}",
BEGIN_HEADER_GUARD,
trixy_types::traits::errno::ERROR_FUNCTIONS,
functions,
namespaces,
type_defs,
output,
END_HEADER_GUARD
)
}
fn function_to_header(function: &Function, namespaces: &[&Identifier]) -> String {
let doc_comments: String = function
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = mangle_c_function_ident(function, namespaces);
let inputs: Vec<TokenStream2> = function.inputs.iter().map(named_type_to_c).collect();
let output = quote! {
extern int #ident(#(#inputs),*);
};
format!("{}{}\n", doc_comments, output)
}
fn attribute_to_doc_comment(attribute: &Attribute) -> String {
let Attribute::doc(doc_comment) = attribute;
format!("/// {}\n", doc_comment)
}
fn named_type_to_c(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
let r#type = type_to_rust(&named_type.r#type);
let c_type = trixy_types::to_c_name(r#type.to_string());
quote! {
#c_type #ident
}
}
fn namespace_to_full_struct_init(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> TokenStream2 {
let mut input_namespaces = namespaces.clone();
input_namespaces.push(&nasp.name);
let ident = identifier_to_rust(&nasp.name);
let type_ident = format_ident!("{}_t", ident.to_string());
let functions: TokenStream2 = nasp
.functions
.iter()
.map(|r#fn| function_to_struct_init(r#fn, &input_namespaces))
.collect();
let namespaces: TokenStream2 = nasp
.namespaces
.iter()
.map(namespace_to_struct_init)
.collect();
let next_namespace: TokenStream2 = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_full_struct_init(nasp, &input_namespaces))
.collect();
quote! {
#next_namespace
const #type_ident #ident = {
#functions
#namespaces
};
}
}
fn function_to_struct_init(function: &Function, namespaces: &[&Identifier]) -> TokenStream2 {
let ident = identifier_to_rust(&function.identifier);
let full_ident = mangle_c_function_ident(function, namespaces);
quote! {
. #ident = #full_ident,
}
}
fn namespace_to_struct_init(namespace: &Namespace) -> TokenStream2 {
let ident = identifier_to_rust(&namespace.name);
quote! {
. #ident = #ident ,
}
}
fn namespace_to_full_typedef(nasp: &Namespace) -> String {
let ident = format_ident!("{}_t", nasp.name.name);
let doc_comments = nasp
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let functions: TokenStream2 = nasp
.functions
.iter()
.map(|r#fn| function_to_typedef(r#fn))
.collect();
let namespaces: TokenStream2 = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_typedef(nasp))
.collect();
let next_namespace: String = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_full_typedef(nasp))
.collect::<Vec<String>>()
.join("\n");
let namespace = quote! {
typedef struct {
#functions
#namespaces
} #ident;
};
format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace}
}
fn function_to_typedef(function: &Function) -> TokenStream2 {
let ident = identifier_to_rust(&function.identifier);
let output = if let Some(output) = &function.output {
let output = output.to_string();
quote! { #output, }
} else {
TokenStream2::default()
};
let inputs: Vec<TokenStream2> = if function.inputs.is_empty() {
vec![quote! { void }]
} else {
todo!()
};
quote! {
int (* #ident ) (#output #(#inputs),*);
}
}
fn namespace_to_typedef(namespace: &Namespace) -> TokenStream2 {
let ident = identifier_to_rust(&namespace.name);
let type_ident = format_ident!("{}_t", ident.to_string());
quote! {
#type_ident #ident ;
}
}
fn namespace_to_header(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> String {
let mut nasps = namespaces.clone();
nasps.push(&nasp.name);
let functions: String = nasp
.functions
.iter()
.map(|r#fn| function_to_header(r#fn, &nasps))
.collect::<Vec<String>>()
.join("\n");
let namespaces: String = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_header(nasp, &nasps))
.collect();
format! {"{}\n{}", functions, namespaces}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
//! This module generates the c header
//! It works by firstly listing the functions and then by grouping them into structures, effectively
//! simulating namespaces in c.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use trixy_parser::command_spec::{Attribute, CommandSpec, NamedType};
use trixy_types::header_names;
use crate::{
config::TrixyConfig,
generate::{c_api::type_to_c, identifier_to_rust},
};
mod pure_header;
mod structs_init;
mod typedef;
const BEGIN_HEADER_GUARD: &'static str = r"#ifndef TRIXY_MAIN_HEADER
#define TRIXY_MAIN_HEADER";
const END_HEADER_GUARD: &'static str = r"#endif // ifndef TRIXY_MAIN_HEADER";
/// This function acts as the core transformative aspect, turning this trixy code into the
/// following c header:
///
/// *Trixy:*
/// ```text
/// fn fn_alone();
/// nasp one {
/// fn fn_one();
/// nasp two {
/// fn fn_two();
/// nasp three {
/// fn fn_three();
/// }
/// }
/// }
/// ```
/// *C header:*
/// ```text
/// #ifndef TRIXY_MAIN_HEADER
/// #define TRIXY_MAIN_HEADER
///
/// extern int fn_alone();
/// extern int one_fn_one();
/// extern int one_two_fn_two();
/// extern int one_two_three_fn_three();
///
/// typedef struct {
/// void (*fn_three)(void);
/// } three_t;
/// typedef struct {
/// void (*fn_two)(void);
/// three_t three;
/// } two_t;
/// typedef struct {
/// void (*fn_one)(void);
/// two_t two;
/// } one_t;
///
/// const three_t three = {
/// .fn_three = one_two_three_fn_three,
/// };
/// const two_t two = {
/// .fn_two = one_two_fn_two,
/// .three = three,
/// };
/// const one_t one = {
/// .fn_one = one_fn_one,
/// .two = two,
/// };
/// #endif // ifndef TRIXY_MAIN_HEADER
/// ```
pub fn generate(trixy: &CommandSpec, _config: &TrixyConfig) -> String {
let pure_header = pure_header::generate(&trixy);
let type_defs = typedef::generate(&trixy);
let structs_init = structs_init::generate(&trixy);
format!(
"{}\n\n{}\n\n{}\n{}\n{}\n\n{}",
BEGIN_HEADER_GUARD,
header_names(),
pure_header,
type_defs,
structs_init,
END_HEADER_GUARD
)
}
fn attribute_to_doc_comment(attribute: &Attribute) -> String {
let Attribute::doc(doc_comment) = attribute;
format!("/// {}\n", doc_comment)
}
fn named_type_to_c(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
let c_type = type_to_c(&named_type.r#type, false);
quote! {
#c_type #ident
}
}

View File

@ -0,0 +1,159 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use trixy_parser::command_spec::{
CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, Namespace,
Structure,
};
use crate::generate::{
c_api::{
header::{attribute_to_doc_comment, named_type_to_c, type_to_c},
identifier_to_c, mangle_c_function_ident,
},
identifier_to_rust,
};
pub fn generate(trixy: &CommandSpec) -> String {
let functions: String = trixy
.functions
.iter()
.map(|r#fn| function_to_header(r#fn, &[]))
.collect();
let namespaces: String = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_header(nasp, &vec![]))
.collect();
let structures: String = trixy
.structures
.iter()
.map(|r#struct| structure_to_header(r#struct))
.collect();
let enumerations: String = trixy
.enumerations
.iter()
.map(|r#enum| enumeration_to_header(r#enum))
.collect();
format!(
"{}\n{}\n{}\n{}",
enumerations, structures, functions, namespaces
)
}
fn structure_to_header(structure: &Structure) -> String {
let doc_comments: String = structure
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = identifier_to_c(&structure.identifier);
let contents = structure
.contents
.iter()
.map(doc_named_type_to_header)
.collect::<String>();
format!(
"
{}
typedef struct {{
{}
}} {};",
doc_comments, contents, ident
)
}
fn doc_named_type_to_header(doc_named_type: &DocNamedType) -> String {
let doc_comments: String = doc_named_type
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = identifier_to_rust(&doc_named_type.name);
let r#type = type_to_c(&doc_named_type.r#type, false);
format!("{}{} {};", doc_comments, r#type, ident)
}
fn enumeration_to_header(enumeration: &Enumeration) -> String {
let doc_comments: String = enumeration
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = identifier_to_c(&enumeration.identifier);
let states = enumeration
.states
.iter()
.map(doc_identifier_to_header)
.collect::<String>();
let states = if enumeration.states.is_empty() {
"/// This enum does not have variants on the rust side
/// to work around c limitiation with variant-less enums
/// we added a `__never` variant:
__never,"
.to_owned()
} else {
states
};
format!(
"
{}
typedef enum {{
{}
}} {};",
doc_comments, states, ident
)
}
fn doc_identifier_to_header(doc_identifier: &DocIdentifier) -> String {
let doc_comments: String = doc_identifier
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = &doc_identifier.name;
format!("{}{},", doc_comments, ident)
}
fn function_to_header(function: &Function, namespaces: &[&Identifier]) -> String {
let doc_comments: String = function
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let ident = mangle_c_function_ident(function, namespaces);
let inputs: Vec<TokenStream2> = function.inputs.iter().map(named_type_to_c).collect();
let function_output = if let Some(out) = &function.output {
let type_name = type_to_c(&out, true);
let comma = if !inputs.is_empty() {
quote! {
,
}
} else {
TokenStream2::default()
};
quote! {
#type_name trixy_output #comma
}
} else {
TokenStream2::default()
};
let output = quote! {
extern int #ident(#function_output #(#inputs),*);
};
format!("{}{}\n", doc_comments, output)
}
fn namespace_to_header(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> String {
let mut nasps = namespaces.clone();
nasps.push(&nasp.name);
let functions: String = nasp
.functions
.iter()
.map(|r#fn| function_to_header(r#fn, &nasps))
.collect::<Vec<String>>()
.join("\n");
let namespaces: String = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_header(nasp, &nasps))
.collect();
format! {"{}\n{}", functions, namespaces}
}

View File

@ -0,0 +1,65 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::format_ident;
use trixy_parser::command_spec::{CommandSpec, Namespace, Identifier, Function};
use quote::quote;
use crate::generate::{identifier_to_rust, c_api::mangle_c_function_ident};
pub fn generate(trixy: &CommandSpec) -> String {
let struct_initializer: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_full_struct_init(nasp, &vec![]))
.collect();
struct_initializer.to_string()
}
fn namespace_to_full_struct_init(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> TokenStream2 {
let mut input_namespaces = namespaces.clone();
input_namespaces.push(&nasp.name);
let ident = identifier_to_rust(&nasp.name);
let type_ident = format_ident!("{}_t", ident.to_string());
let functions: TokenStream2 = nasp
.functions
.iter()
.map(|r#fn| function_to_struct_init(r#fn, &input_namespaces))
.collect();
let namespaces: TokenStream2 = nasp
.namespaces
.iter()
.map(namespace_to_struct_init)
.collect();
let next_namespace: TokenStream2 = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_full_struct_init(nasp, &input_namespaces))
.collect();
quote! {
#next_namespace
const #type_ident #ident = {
#functions
#namespaces
};
}
}
fn function_to_struct_init(function: &Function, namespaces: &[&Identifier]) -> TokenStream2 {
let ident = identifier_to_rust(&function.identifier);
let full_ident = mangle_c_function_ident(function, namespaces);
quote! {
. #ident = #full_ident,
}
}
fn namespace_to_struct_init(namespace: &Namespace) -> TokenStream2 {
let ident = identifier_to_rust(&namespace.name);
quote! {
. #ident = #ident ,
}
}

View File

@ -0,0 +1,93 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use trixy_parser::command_spec::{CommandSpec, Function, Namespace};
use crate::generate::{
c_api::{header::attribute_to_doc_comment, type_to_c},
identifier_to_rust,
};
pub fn generate(trixy: &CommandSpec) -> String {
let type_defs: String = trixy
.namespaces
.iter()
.rev()
.map(|nasp| namespace_to_full_typedef(nasp))
.collect::<Vec<String>>()
.join("\n");
type_defs.to_string()
}
fn function_to_typedef(function: &Function) -> TokenStream2 {
let ident = identifier_to_rust(&function.identifier);
let (output, output_comma) = if let Some(output) = &function.output {
let output = type_to_c(output, true);
(quote! { #output }, quote! {,})
} else {
(TokenStream2::default(), TokenStream2::default())
};
let inputs: TokenStream2 = if function.inputs.is_empty() && output.is_empty() {
quote! { void }
} else if !function.inputs.is_empty() && !output.is_empty() {
let inputs: Vec<TokenStream2> = function
.inputs
.iter()
.map(|named_type| &named_type.r#type)
.map(|r#type| type_to_c(&r#type, false))
.collect();
quote! {
#output_comma #(#inputs),*
}
} else {
TokenStream2::default()
};
quote! {
int (* #ident ) (#output #inputs);
}
}
fn namespace_to_typedef(namespace: &Namespace) -> TokenStream2 {
let ident = identifier_to_rust(&namespace.name);
let type_ident = format_ident!("{}_t", ident.to_string());
quote! {
#type_ident #ident ;
}
}
fn namespace_to_full_typedef(nasp: &Namespace) -> String {
let ident = format_ident!("{}_t", nasp.name.name);
let doc_comments = nasp
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect::<String>();
let functions: TokenStream2 = nasp
.functions
.iter()
.map(|r#fn| function_to_typedef(r#fn))
.collect();
let namespaces: TokenStream2 = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_typedef(nasp))
.collect();
let next_namespace: String = nasp
.namespaces
.iter()
.map(|nasp| namespace_to_full_typedef(nasp))
.collect::<Vec<String>>()
.join("\n");
let namespace = quote! {
typedef struct {
#functions
#namespaces
} #ident;
};
format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace}
}

View File

@ -26,7 +26,8 @@ use trixy_parser::command_spec::{CommandSpec, Function, Identifier, NamedType, N
use crate::{
config::TrixyConfig,
generate::{
c_api::mangle_c_function_ident, identifier_to_rust, named_type_to_rust, type_to_rust,
c_api::{mangle_c_function_ident, namespaces_to_path, type_to_c_equalivalent},
function_identifier_to_rust, identifier_to_rust, named_type_to_rust,
},
};
@ -105,7 +106,7 @@ fn function_to_c(
let inputs: Vec<TokenStream2> = function
.inputs
.iter()
.map(named_type_to_rust_trixy)
.map(|named_type| named_type_to_rust_trixy(named_type, &namespaces))
.collect();
let callback_function = format_ident!("{}", config.callback_function);
@ -113,29 +114,35 @@ fn function_to_c(
let command_value: TokenStream2 = function_path_to_rust(&namespaces, &function);
if let Some(r#type) = &function.output {
let output = type_to_rust(&r#type);
let output_ident = type_to_c_equalivalent(&r#type, &namespaces);
quote! {
#[no_mangle]
pub unsafe extern "C" fn #ident(output: *mut #output, #(#inputs),*) -> core::ffi::c_int {
#callback_function ! (#command_value)
pub unsafe extern "C" fn #ident(output: *mut #output_ident, #(#inputs),*) -> core::ffi::c_int {
let output_val: #output_ident = {
let (tx, rx) = trixy::oneshot::channel();
#callback_function (#command_value);
rx.recv().expect("The channel should not be close until this value is received").into()
};
output.write(output_val);
return 1;
}
}
} else {
quote! {
#[no_mangle]
pub extern "C" fn #ident(#(#inputs),*) -> core::ffi::c_int {
#callback_function ! (#command_value);
#callback_function (#command_value);
return 1;
}
}
}
}
fn named_type_to_rust_trixy(named_type: &NamedType) -> TokenStream2 {
fn named_type_to_rust_trixy(named_type: &NamedType, namespaces: &[&Identifier]) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
let type_ident = type_to_rust(&named_type.r#type);
let type_ident = type_to_c_equalivalent(&named_type.r#type, &namespaces);
quote! {
#ident : trixy:: #type_ident
#ident : #type_ident
}
}
@ -169,23 +176,13 @@ fn named_type_to_rust_trixy(named_type: &NamedType) -> TokenStream2 {
/// ))))
/// ```
fn function_path_to_rust(namespaces: &Vec<&Identifier>, function: &Function) -> TokenStream2 {
let function_ident = {
let ident = format_ident!("{}", function.identifier.name);
if function.inputs.is_empty() {
let function_ident =
function_identifier_to_rust(&function, named_type_to_rust_assignment, |_| {
quote! {
#ident
// This is defined in the outer call
tx
}
} else {
let inputs: Vec<TokenStream2> = function
.inputs
.iter()
.map(named_type_to_rust_assignment)
.collect();
quote! {
#ident { #(#inputs),* }
}
}
};
});
if namespaces.is_empty() {
quote! {
Commands:: #function_ident
@ -200,18 +197,10 @@ fn function_path_to_rust(namespaces: &Vec<&Identifier>, function: &Function) ->
.to_case(Case::Pascal)
);
let namespace_path = nasps_to_path(namespaces);
let namespace_path = namespaces_to_path(namespaces);
let function_call = if !function.inputs.is_empty() {
let inputs: Vec<TokenStream2> =
function.inputs.iter().map(named_type_to_rust).collect();
quote! {
#namespace_path :: #nasp_pascal_ident :: #function_ident { #(#inputs),* }
}
} else {
quote! {
#namespace_path :: #nasp_pascal_ident :: #function_ident
}
let function_call = quote! {
#namespace_path :: #nasp_pascal_ident :: #function_ident
};
let output: TokenStream2 = namespaces
@ -229,7 +218,7 @@ fn function_path_to_rust(namespaces: &Vec<&Identifier>, function: &Function) ->
fn named_type_to_rust_assignment(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
quote! {
#ident : trixy::convert!(#ident)
#ident : trixy::types::convert!(#ident)
}
}
@ -265,26 +254,9 @@ fn nasp_path_one_part(
.name
.to_case(Case::Pascal)
);
let namespace_path = nasps_to_path(namespaces_to_do);
let namespace_path = namespaces_to_path(namespaces_to_do);
quote! {
#namespace_path :: #ident_pascal_next :: #ident_pascal ( #input )
}
}
}
fn nasps_to_path(namespaces: &[&Identifier]) -> TokenStream2 {
namespaces
.iter()
.fold(TokenStream2::default(), |acc, nasp| {
let ident = format_ident!("{}", nasp.name);
if acc.is_empty() {
quote! {
#ident
}
} else {
quote! {
#acc :: #ident
}
}
})
}

View File

@ -18,9 +18,12 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
use proc_macro2::Ident;
use quote::format_ident;
use trixy_parser::command_spec::{Function, Identifier};
use convert_case::{Case, Casing};
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use trixy_parser::command_spec::{Function, Identifier, Type};
use super::identifier_to_rust;
pub mod header;
pub mod host;
@ -40,3 +43,75 @@ pub fn mangle_c_function_ident(function: &Function, namespaces: &[&Identifier])
format_ident!("{}_{}", namespace_str, &function.identifier.name)
}
}
pub fn type_to_c(r#type: &Type, is_output: bool) -> TokenStream2 {
let ident = identifier_to_c(&r#type.identifier);
let output = if is_output {
quote! {
*
}
} else {
TokenStream2::default()
};
quote! {
#ident #output
}
}
pub fn identifier_to_c(identifier: &Identifier) -> TokenStream2 {
let ident = format_ident!("{}_t", identifier.name.to_case(Case::Snake));
quote! {
#ident
}
}
pub fn type_to_c_equalivalent(r#type: &Type, namespaces: &[&Identifier]) -> TokenStream2 {
let ident = identifier_to_rust(&r#type.identifier);
let trixy_build_in_types: Vec<&str> = trixy_types::BASE_TYPES
.iter()
.filter_map(|(name, _)| {
if name == &r#type.identifier.name {
Some(*name)
} else {
None
}
})
.collect();
if trixy_build_in_types.is_empty() {
let nasp_path = if namespaces.is_empty() {
TokenStream2::default()
} else {
let path = namespaces_to_path(&namespaces);
quote! {
Commands :: #path ::
}
};
quote! {
#nasp_path #ident
}
} else {
debug_assert_eq!(trixy_build_in_types.len(), 1);
let type_name = format_ident!(
"{}",
trixy_build_in_types
.first()
.expect("The names should not be dublicated, this should be the only value")
);
quote! {
trixy::types:: #type_name
}
}
}
fn namespaces_to_path(namespaces: &[&Identifier]) -> TokenStream2 {
namespaces
.iter()
.fold(TokenStream2::default(), |acc, nasp| {
let ident = format_ident!("{}", nasp.name);
if acc.is_empty() {
quote! {
#ident
}
} else {
quote! {
#acc :: #ident
}
}
})
}

View File

@ -28,8 +28,8 @@ use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use trixy_parser::command_spec::{
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Namespace,
Structure,
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
Namespace, Structure,
};
use crate::{
@ -37,6 +37,8 @@ use crate::{
generate::{identifier_to_rust, named_type_to_rust},
};
use super::{c_api::type_to_c_equalivalent, function_identifier_to_rust};
thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
/// This function turns, for example, the following trixy input into this rust code:
@ -53,10 +55,8 @@ thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
/// Low,
/// };
///
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
/// fn execute_callback(callback: Callback, priority: CallbackPriority) -> String;
/// }
/// // wrong but helps with syntax highlight:
/// // vim: syntax=rust
/// ```
/// ```no_run
/// #[derive(Debug)]
@ -65,13 +65,13 @@ thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
/// }
/// pub mod trinitrix {
/// #[allow(non_camel_case_types)]
/// #[derive(Debug)]
/// #[derive(Debug, Convertible)]
/// struct Callback {
/// func: String,
/// timeout: String,
/// }
/// #[allow(non_camel_case_types)]
/// #[derive(Debug)]
/// #[derive(Debug, Convertible)]
/// enum CallbackPriority {
/// High,
/// Medium,
@ -80,7 +80,11 @@ thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
/// #[derive(Debug)]
/// pub enum Trinitrix {
/// #[allow(non_camel_case_types)]
/// execute_callback { callback: Callback, priority: CallbackPriority },
/// execute_callback {
/// callback: Callback,
/// priority: CallbackPriority,
/// trixy_output: trixy::oneshot::channel<String>
/// },
/// }
/// }
/// ```
@ -96,10 +100,18 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
.expect("The cell should always be empty at this point");
});
let modules: TokenStream2 = trixy.namespaces.iter().map(namespace_to_module).collect();
let modules: TokenStream2 = trixy
.namespaces
.iter()
.map(|nasp| namespace_to_module(nasp, &vec![]))
.collect();
let structures: TokenStream2 = trixy.structures.iter().map(structure_to_rust).collect();
let enumerations: TokenStream2 = trixy.enumerations.iter().map(enumeration_to_rust).collect();
let functions: Vec<TokenStream2> = trixy.functions.iter().map(function_to_rust).collect();
let functions: Vec<TokenStream2> = trixy
.functions
.iter()
.map(|r#fn| function_to_rust(r#fn, &[]))
.collect();
let namespace_modules: Vec<TokenStream2> = trixy
.namespaces
.iter()
@ -109,6 +121,9 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
let debug = get_debug_sate();
quote! {
#[allow(unused_imports)]
use trixy::types::traits::convert_trait::*;
#structures
#enumerations
#debug
@ -120,7 +135,9 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
}
}
fn namespace_to_module(namespace: &Namespace) -> TokenStream2 {
fn namespace_to_module(namespace: &Namespace, namespaces: &Vec<&Identifier>) -> TokenStream2 {
let mut namespaces = namespaces.clone();
namespaces.push(&namespace.name);
let ident = identifier_to_rust(&namespace.name);
let enum_ident = format_ident!("{}", &namespace.name.name.to_case(Case::Pascal));
@ -135,7 +152,11 @@ fn namespace_to_module(namespace: &Namespace) -> TokenStream2 {
.iter()
.map(enumeration_to_rust)
.collect();
let functions: Vec<TokenStream2> = namespace.functions.iter().map(function_to_rust).collect();
let functions: Vec<TokenStream2> = namespace
.functions
.iter()
.map(|r#fn| function_to_rust(r#fn, &namespaces))
.collect();
let namespace_modules: Vec<TokenStream2> = namespace
.namespaces
.iter()
@ -144,13 +165,16 @@ fn namespace_to_module(namespace: &Namespace) -> TokenStream2 {
let namespaces: TokenStream2 = namespace
.namespaces
.iter()
.map(namespace_to_module)
.map(|nasp| namespace_to_module(nasp, &namespaces))
.collect();
let debug = get_debug_sate();
quote! {
#doc_comments
pub mod #ident {
#[allow(unused_imports)]
use trixy::types::traits::convert_trait::*;
#structures
#enumerations
#debug
@ -188,28 +212,24 @@ fn get_debug_sate() -> TokenStream2 {
debug
}
fn function_to_rust(function: &Function) -> TokenStream2 {
fn function_to_rust(function: &Function, namespaces: &[&Identifier]) -> TokenStream2 {
let doc_comments: TokenStream2 = function
.attributes
.iter()
.map(attribute_to_doc_comment)
.collect();
let ident = identifier_to_rust(&function.identifier);
let function_ident =
function_identifier_to_rust(&function, named_type_to_rust, move |r#type| {
let ident = type_to_c_equalivalent(r#type, namespaces);
quote! {
trixy::oneshot::Sender<#ident>
}
});
let inputs: Vec<TokenStream2> = function.inputs.iter().map(named_type_to_rust).collect();
if inputs.is_empty() {
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#ident
}
} else {
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#ident {#(#inputs),*}
}
quote! {
#doc_comments
#[allow(non_camel_case_types)]
#function_ident
}
}
@ -232,7 +252,9 @@ fn enumeration_to_rust(enumeration: &Enumeration) -> TokenStream2 {
#doc_comments
#[allow(non_camel_case_types)]
#debug
enum #ident {
#[repr(C)]
#[derive(Convertible, TypeInfo)]
pub enum #ident {
#(#states),*
}
}
@ -257,7 +279,9 @@ fn structure_to_rust(structure: &Structure) -> TokenStream2 {
#doc_comments
#[allow(non_camel_case_types)]
#debug
struct #ident {
#[repr(C)]
#[derive(Convertible, TypeInfo)]
pub struct #ident {
#(#contents),*
}
}

View File

@ -20,7 +20,7 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use trixy_parser::command_spec::{CommandSpec, Identifier, NamedType, Type};
use trixy_parser::command_spec::{CommandSpec, Function, Identifier, NamedType, Type};
use crate::config::TrixyConfig;
@ -42,11 +42,51 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
}
fn identifier_to_rust(identifier: &Identifier) -> TokenStream2 {
let ident = format_ident!("{}", &identifier.name);
quote! {
#ident
if &identifier.name == "()" {
quote! {
()
}
} else {
let ident = format_ident!("{}", &identifier.name);
quote! {
#ident
}
}
}
fn function_identifier_to_rust<F>(
function: &Function,
input_fmt_fn: fn(&NamedType) -> TokenStream2,
output_fmt_fn: F,
) -> TokenStream2
where
F: Fn(&Type) -> TokenStream2,
{
let ident = identifier_to_rust(&function.identifier);
let inputs: Vec<TokenStream2> = function.inputs.iter().map(input_fmt_fn).collect();
let output = &function.output;
if inputs.is_empty() && output.is_none() {
quote! {
#ident
}
} else if output.is_some() {
let output = output_fmt_fn(&output.as_ref().expect("We checked"));
quote! {
#ident {
trixy_output: #output ,
#(#inputs),*
}
}
} else if output.is_none() && !inputs.is_empty() {
quote! {
#ident { #(#inputs),* }
}
} else {
unreachable!("All other conditions should be met")
}
}
fn named_type_to_rust(named_type: &NamedType) -> TokenStream2 {
let ident = identifier_to_rust(&named_type.name);
let r#type = type_to_rust(&named_type.r#type);
@ -57,8 +97,14 @@ fn named_type_to_rust(named_type: &NamedType) -> TokenStream2 {
fn type_to_rust(r#type: &Type) -> TokenStream2 {
let ident = identifier_to_rust(&r#type.identifier);
if r#type.generic_args.is_empty() {
quote! {
#ident
if r#type.identifier.name == "str" {
quote! {
&str
}
} else {
quote! {
#ident
}
}
} else {
let generics: Vec<TokenStream2> = r#type.generic_args.iter().map(type_to_rust).collect();

View File

@ -18,9 +18,17 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
use std::{env, fs, io::Write, path::PathBuf, process::Command};
use std::{
env,
fs::{self, File},
io::Write,
iter,
path::{Path, PathBuf},
process::Command,
};
use trixy_parser::parse_trixy_lang;
use trixy_types::C_TYPE_HEADER;
use crate::config::TrixyConfig;
@ -46,9 +54,10 @@ impl TrixyConfig {
});
// host code
let tokens = generate::generate(&trixy_code, &self);
eprintln!("{}", tokens);
let host_code = prettyplease::unparse(
&syn::parse2(generate::generate(&trixy_code, &self))
.expect("This code was generated, it should also be parsable"),
&syn::parse2(tokens).expect("This code was generated, it should also be parsable"),
);
let mut host_code_out = fs::File::create(PathBuf::from(format!(
"{}/{}",
@ -87,19 +96,53 @@ impl TrixyConfig {
dist_dir.display(), err}
});
}
if self.check_dist_dir {
if dist_dir.read_dir().iter().count() != 1 {
panic!("Your specified dist dir already has something in it! Set `check_dist_dir` to `false` to override this check");
}
}
let c_header_dist = PathBuf::from(format!(
"{}/{}",
dist_dir.display(),
self.c_header_name.display()
));
let c_header_dist = PathBuf::from(format!("{}/{}", dist_dir.display(), "generated.h"));
fs::copy(c_header_path, c_header_dist).unwrap_or_else(
|err| panic! {"Failed to copy the c header to the dist dir because of: `{}`", err},
|err| panic! {"Failed to copy the c header ('generated.h') to the dist dir because of: `{}`", err},
);
let (interface_name, interface_content) = {
let interface_header = format!(
"\
/* This file is automatcially generated by Trixy */ \n\
#ifndef TRIXY_INTERFACE_H \n\
#define TRIXY_INTERFACE_H \n\
#include \"generated.h\" \n\
#endif // TRIXY_INTERFACE_H \n\
"
);
("interface.h", interface_header)
};
C_TYPE_HEADER
.iter()
.chain(iter::once(&(interface_name, &interface_content[..])))
.for_each(|(name, content)| {
let path: &Path = &Path::new(name);
if self.check_dist_dir {
if path.exists() {
panic! {
"The file ('{}') already exists in your dist dir ('{}')!
If you want to silence this check set `check_dist_dir` to false",
path.display(), dist_dir.display()
}
}
}
let header_path =
PathBuf::from(format!("{}/{}", dist_dir.display(), path.display()));
let mut file = File::create(&header_path).unwrap_or_else(|err| {
panic! {
"Failed to create the file at '{}' because of: '{}'",
header_path.display(),
err
}
});
write!(file, "{}", content).unwrap_or_else(|err| {
panic! {
"Failed to copy the c header ('{}') to the dist dir because of: `{}`",
path.display(),
err}
});
});
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
struct A {};
struct B {};
enum Error {};
fn execute_callback(callback: String<A, B>) -> Error<String, Error>;
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
// vim: syntax=rust

View File

@ -18,14 +18,12 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
use std::{fs, process::exit};
use trixy_lang_parser::{lexing::TokenStream, parse_trixy_lang};
use std::path::PathBuf;
use std::{fs, path::PathBuf, process::exit};
use clap::{Parser, Subcommand};
use trixy_parser::{lexing::TokenStream, parse_trixy_lang};
/// A helper command for the trixy-lang_parser crate
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]

View File

@ -158,11 +158,11 @@ impl From<unchecked::Attribute> for Attribute {
}
/// An Identifier
/// These include
/// - Variable names
/// - Function names
/// - Namespace names
/// - Type names
/// These include:
/// - Variable names
/// - Function names
/// - Namespace names
/// - Type names
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Identifier {
pub name: String,
@ -182,20 +182,8 @@ impl From<&DocIdentifier> for Identifier {
}
}
/// A const version of [Identifier]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstIdentifier {
pub name: &'static str,
}
impl Display for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.name)
}
}
impl Identifier {
const fn from(value: &'static str) -> ConstIdentifier {
ConstIdentifier { name: value }
}
}

View File

@ -47,6 +47,21 @@ pub enum ParsingError {
enum_span: TokenSpan,
namespace_span: TokenSpan,
},
#[error("Your provided type ('{r#type}') has {got} generic args, but I expected at least: {expected_min}")]
NotEnoughGenericArgs {
expected_min: usize,
got: usize,
r#type: Identifier,
span: TokenSpan,
},
#[error("Your provided type ('{r#type}') has {got} generic args, but I expected at most: {expected_max}")]
TooManyGenericArgs {
expected_max: usize,
got: usize,
r#type: Identifier,
span: TokenSpan,
},
}
impl ParsingError {
@ -56,6 +71,8 @@ impl ParsingError {
ParsingError::PreParseError(err) => err.source.span(),
ParsingError::EnumWithNamespaceName { enum_span, .. } => enum_span,
ParsingError::EnumWithNamespaceNamePascal { enum_span, .. } => enum_span,
ParsingError::NotEnoughGenericArgs { span, .. } => span,
ParsingError::TooManyGenericArgs { span, .. } => span,
}
}
}
@ -67,6 +84,8 @@ impl AdditionalHelp for ParsingError {
ParsingError::PreParseError(err) => ErrorContextDisplay::source(err).additional_help(),
ParsingError::EnumWithNamespaceNamePascal {..}
| ParsingError::EnumWithNamespaceName {..} => "Change the name of this Enumeration as the generation process in trixy-macros needs to use this name".to_owned(),
ParsingError::NotEnoughGenericArgs { got, expected_min, .. } => format!("Add generic args until you have gone from {} to {}", got, expected_min),
ParsingError::TooManyGenericArgs { got, expected_max, .. } => format!("Remove generic args until you have gone from {} to {}", got, expected_max),
}
}
}

View File

@ -259,18 +259,72 @@ impl Parser {
.iter()
.map(|r#enum| Into::<Identifier>::into(r#enum.identifier.kind.clone()))
.any(|ident| ident == identifier)
&& !BASE_TYPES.iter().any(|ident| ident == &identifier.name)
&& !BASE_TYPES
.iter()
.any(|(ident, _)| ident == &identifier.name)
{
return Err(ParsingError::TypeNotDeclared {
r#type: identifier,
span: r#type.identifier.span,
});
}
{
let fitting_types: Vec<&usize> = BASE_TYPES
.iter()
.filter_map(|(ident, generic_number)| {
if ident == &identifier.name {
Some(generic_number)
} else {
None
}
})
.collect();
if !fitting_types.is_empty() {
let min_fitting_type = fitting_types.iter().min().expect("We checked for none");
let max_fitting_type = fitting_types.iter().max().expect("We checked for none");
if !fitting_types
.iter()
.any(|generic_number| *generic_number == &r#type.generic_args.len())
{
if r#type.generic_args.len() < **min_fitting_type {
return Err(ParsingError::NotEnoughGenericArgs {
expected_min: **min_fitting_type,
got: r#type.generic_args.len(),
r#type: identifier,
span: r#type.identifier.span,
});
} else if r#type.generic_args.len() > **max_fitting_type {
return Err(ParsingError::TooManyGenericArgs {
expected_max: **max_fitting_type,
got: r#type.generic_args.len(),
r#type: identifier,
span: r#type.identifier.span,
});
}
}
} else if fitting_types.is_empty() && !r#type.generic_args.is_empty() {
// Self declared types can not have generic arguments
return Err(ParsingError::TooManyGenericArgs {
expected_max: 0,
got: r#type.generic_args.len(),
r#type: identifier,
span: r#type.identifier.span,
});
}
}
let mut generic_args = vec![];
for generic_arg in r#type.generic_args {
generic_args.push(self.process_type(generic_arg)?);
}
let identifier = if &identifier.name == "void" {
Identifier {
name: "()".to_owned(),
}
} else {
identifier
};
Ok(Type {
identifier,
generic_args,

View File

@ -147,6 +147,7 @@ impl Parser {
generic_args.push(self.parse_type()?);
}
while self.expect_peek(token![Comma]) {
self.expect(token![Comma])?;
generic_args.push(self.parse_type()?);
}
self.expect(token![>])?;

View File

@ -24,8 +24,10 @@ edition = "2021"
[dependencies]
convert_case = "0.6.0"
libc = "0.2.151"
log = "0.4.20"
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

@ -0,0 +1,21 @@
#ifndef TRIXY_ERRNO_H
#define TRIXY_ERRNO_H
/// Calculate the number of bytes in the last error's error message **not**
/// including any trailing `null` characters.
extern int last_error_length();
/// Write the most recent error message into a caller-provided buffer as a UTF-8
/// string, returning the number of bytes written.
///
/// # Note
///
/// This writes a **UTF-8** string into the buffer. Windows users may need to
/// convert it to a UTF-16 “Unicode” afterwards.
///
/// If there are no recent errors then this returns `0` (because we wrote 0
/// bytes). `-1` is returned if there are any errors, for example when passed a
/// null pointer or a buffer of insufficient size.
extern int last_error_message(char *buffer, int length);
#endif // TRIXY_ERRNO_H

View File

@ -0,0 +1,12 @@
#ifndef TRIXY_OPTION_H
#define TRIXY_OPTION_H
#include "type_id_dec.h"
#include <stdbool.h>
typedef struct {
type_id_t type_id;
void *value;
bool some;
} option_t;
#endif // TRIXY_OPTION_H

View File

@ -0,0 +1,24 @@
#ifndef TRIXY_RESULT_H
#define TRIXY_RESULT_H
#include "result_dec.h"
#include "type_id_dec.h"
// Function to create an Ok Result variant
result_t ok_result(void *value, type_id_t type_id) {
result_t result;
result.tag = ok;
result.value = value;
result.type_id = type_id;
return result;
}
// Function to create an Err Result variant
result_t err_result(void *value, type_id_t type_id) {
result_t result;
result.tag = err;
result.value = value;
result.type_id = type_id;
return result;
}
#endif // TRIXY_RESULT_H

View File

@ -0,0 +1,14 @@
#ifndef TRIXY_RESULT_DEC_H
#define TRIXY_RESULT_DEC_H
#include "type_id_dec.h"
typedef enum { ok, err } result_tag_t;
typedef struct {
type_id_t type_id;
result_tag_t tag;
/// The type here should be remembered
void *value;
} result_t;
#endif // TRIXY_RESULT_DEC_H

View File

@ -0,0 +1,12 @@
#ifndef TRIXY_STRING_H
#define TRIXY_STRING_H
/// This is an “owned” string, that means that you have not only a reference to the string
/// but are also required to free it yourself.
typedef char *string_t;
/// This type is, in comparison, *not* owned but still owned its source.
/// Thus, it would be undefined behaviour if you free this string slice or mutate it.
typedef const char *str_t;
#endif // TRIXY_STRING_H

View File

@ -0,0 +1,35 @@
#ifndef TRIXY_TYPE_ID_H
#define TRIXY_TYPE_ID_H
#include "option.h"
#include "result_dec.h"
#include "string.h"
#include "type_id_dec.h"
#include "vec_dec.h"
#include <stdio.h>
#include <stdlib.h>
size_t type_id_size(type_id_t type_id) {
switch (type_id) {
case type_unknown:
fputs("Tried to get size of type with type_id 'unknown'!\n", stderr);
exit(1);
case type_void:
return sizeof(size_t);
case type_str_t:
return sizeof(str_t);
case type_string_t:
return sizeof(string_t);
case type_result_t:
return sizeof(result_t);
case type_vec_t:
return sizeof(vec_t);
case type_option_t:
return sizeof(option_t);
}
fputs("This is unreachable, as all variants of the type_id enum are listed "
"above",
stderr);
exit(1);
};
#endif // TRIXY_TYPE_ID_H

View File

@ -0,0 +1,17 @@
#ifndef TRIXY_TYPE_ID_DEC_H
#define TRIXY_TYPE_ID_DEC_H
/// The type of something (option, result, vec, etc.).
typedef enum {
/// We simply don't know which type this is
type_unknown,
type_void,
type_str_t,
type_string_t,
type_result_t,
type_vec_t,
type_option_t,
} type_id_t;
#endif // TRIXY_TYPE_ID_DEC_H

View File

@ -0,0 +1,28 @@
#ifndef TRIXY_VEC_H
#define TRIXY_VEC_H
#include "type_id_dec.h"
#include "type_id.h"
#include "vec_dec.h"
#include <stdlib.h>
#include <string.h>
/// This will abort execution when called with an un-typed vector (that is one
/// of type_id = unknown).
void push_back(vec_t *vec, const void *value) {
if (vec->size >= vec->capacity) {
// If the current size exceeds the capacity, reallocate memory
vec->capacity =
(vec->capacity == 0) ? 1 : vec->capacity * 2; // Double the capacity
vec->data = realloc(vec->data, vec->capacity * type_id_size(vec->type_id));
}
void *ptr = (size_t *)vec->data + (vec->size / type_id_size(vec->type_id));
memcpy(ptr, value, type_id_size(vec->type_id));
}
void free_vector(vec_t *vec) {
free(vec->data);
vec->data = NULL;
vec->size = vec->capacity = 0;
}
#endif // TRIXY_VEC_H

View File

@ -0,0 +1,18 @@
#ifndef TRIXY_VEC_DEC_H
#define TRIXY_VEC_DEC_H
#include "type_id_dec.h"
#include <stdlib.h>
typedef struct vec {
type_id_t type_id;
void *data;
size_t size;
size_t capacity;
} vec_t;
void init_vector(vec_t *vec) {
vec->data = NULL;
vec->size = 0;
vec->capacity = 0;
}
#endif // TRIXY_VEC_DEC_H

View File

@ -29,4 +29,7 @@ pub enum TypeConversionError {
#[error("You passed a null pointer to the conversion function!")]
NullPointer,
#[error("You passed a untyped (type_id == Unknown) value to the conversion function!")]
UntypedInput,
}

View File

@ -19,37 +19,50 @@
*/
//! Trixy contains the types used by the [trixy-macros] crate to provide ffi safe types
use std::{ffi::c_char, ops::Deref};
use proc_macro2::TokenStream;
use quote::quote;
pub mod error;
pub mod traits;
pub mod types_list;
// 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>
pub use types_list::*;
#[repr(C)]
pub struct String(*const c_char);
#[repr(C)]
pub struct Vec<T>(*const T);
#[repr(C)]
pub struct Function(*const usize);
pub type Option<T> = std::option::Option<T>;
macro_rules! header {
($path:expr) => {
($path, include_str!(concat!("./c_headers/", $path)))
};
}
/// These are the "primitive" types used in Trixy, you can use any of them to create new structures
pub const BASE_TYPES: [&'static str; 4] = ["String", "Vec", "Option", "Function"];
/// The str is the name, the index represents the expected generic arguments
pub const BASE_TYPES: [(&'static std::primitive::str, usize); 6] = [
("void", 0),
("str", 0),
("String", 0),
("Result", 2),
("Option", 1),
("Vec", 1),
];
pub fn to_c_name<T: Deref<Target = str>>(rust_type: T) -> TokenStream {
match &*rust_type {
"String" => quote!(const char*),
"Vec" => quote!(const char**),
// TODO(@soispha): This should show that it's optional <2023-12-25>
"Option<String>" => quote!(const char*),
other => panic! {"'{}' is not a vaild type name!", other},
}
/// 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 fn header_names() -> std::string::String {
C_TYPE_HEADER
.iter()
.map(|(name, _)| name)
// .chain(iter::once(&"generated.h"))
.fold(std::string::String::new(), |acc, name| {
format!("{}#include \"{}\"\n", acc, name)
})
}

View File

@ -0,0 +1,108 @@
use std::{
any::Any,
ffi::{CStr, CString},
os::raw::c_void,
};
/// Convert a value
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;
}
impl Convertible for crate::String {
fn into_void(self) -> *const c_void {
self.0 as *const _ as *const c_void
}
}
impl Convertible for crate::str {
fn into_void(self) -> *const c_void {
self.0 as *const _ as *const c_void
}
}
impl Convertible for crate::Option {
fn into_void(mut self) -> *const c_void {
&mut self as *const _ as *const c_void
}
}
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()
}
}
impl Convertible for CString {
fn into_void(self) -> *const c_void {
Into::<crate::String>::into(self).into_void()
}
}
impl Convertible for () {
fn into_void(self) -> *const c_void {
std::ptr::null()
}
}
extern crate trixy_types_derive;
pub use trixy_types_derive::{Convertible, TypeInfo};
/// 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;
}
macro_rules! type_info {
($name:tt<_>, $output:ident) => {
impl<T> TypeInfo for $name<T> {
fn type_of(&self) -> crate::TypeId {
crate::TypeId::$output
}
}
};
($name:tt<_,_>, $output:ident) => {
impl<T, E> TypeInfo for $name<T, E> {
fn type_of(&self) -> crate::TypeId {
crate::TypeId::$output
}
}
};
($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

@ -36,7 +36,7 @@ macro_rules! convert {
match $input.try_into() {
Ok(ok) => ok,
Err(err) => {
trixy::traits::errno::set(err);
trixy::types::traits::errno::set(err);
return 0;
}
}
@ -72,25 +72,6 @@ pub fn take_last_error() -> Option<Box<TypeConversionError>> {
LAST_ERROR.with(|prev| prev.borrow_mut().take())
}
pub const ERROR_FUNCTIONS: &'static str = r#"
/// Calculate the number of bytes in the last error's error message **not**
/// including any trailing `null` characters.
extern int last_error_length();
/// Write the most recent error message into a caller-provided buffer as a UTF-8
/// string, returning the number of bytes written.
///
/// # Note
///
/// This writes a **UTF-8** string into the buffer. Windows users may need to
/// convert it to a UTF-16 "unicode" afterwards.
///
/// If there are no recent errors then this returns `0` (because we wrote 0
/// bytes). `-1` is returned if there are any errors, for example when passed a
/// null pointer or a buffer of insufficient size.
extern int last_error_message(char* buffer, int length);
"#;
/// Calculate the number of bytes in the last error's error message **not**
/// including any trailing `null` characters.
#[no_mangle]

View File

@ -18,16 +18,24 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
use std::ffi::CStr;
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 TryFrom<crate::String> for &str {
impl<'a> TryFrom<&'a crate::str> for &'a str {
type Error = crate::error::TypeConversionError;
fn try_from(value: crate::String) -> Result<Self, Self::Error> {
fn try_from(value: &'a crate::str) -> Result<Self, Self::Error> {
let ptr = value.0;
if ptr.is_null() {
Err(TypeConversionError::NullPointer)
@ -39,9 +47,11 @@ impl TryFrom<crate::String> for &str {
// - 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) };
let str = unsafe { CStr::from_ptr(*ptr as *const c_char) };
str.to_str()
.map_err(|_err| crate::error::TypeConversionError::String { got: value.0 })
.map_err(|_err| crate::error::TypeConversionError::String {
got: value.0 as *const c_char,
})
}
}
}
@ -49,7 +59,171 @@ impl TryFrom<crate::String> for String {
type Error = crate::error::TypeConversionError;
fn try_from(value: crate::String) -> Result<Self, Self::Error> {
let str: &str = value.try_into()?;
Ok(str.to_owned())
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

@ -0,0 +1,70 @@
// 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(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

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

View File

@ -0,0 +1,13 @@
[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

@ -0,0 +1,38 @@
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()
}