From b3c6a4c1a13e91f665d7704b53da8ee92457473a Mon Sep 17 00:00:00 2001 From: Soispha Date: Thu, 28 Dec 2023 10:28:58 +0100 Subject: [PATCH] feat(treewide): Add broken Vec, Result and Option 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` to a `char **` (and obviously a whole wrapper struct with size and string length), whilst turning a `Vec` to a `char*` differentiating it from a `string_t`. --- src/lib.rs | 67 ++++ trixy-macros/Cargo.toml | 2 +- trixy-macros/example/main/src/full.tri | 1 - trixy-macros/src/generate/c_api/header.rs | 292 ------------------ trixy-macros/src/generate/c_api/header/mod.rs | 123 ++++++++ .../src/generate/c_api/header/pure_header.rs | 159 ++++++++++ .../src/generate/c_api/header/structs_init.rs | 65 ++++ .../src/generate/c_api/header/typedef.rs | 93 ++++++ trixy-macros/src/generate/c_api/host.rs | 80 ++--- trixy-macros/src/generate/c_api/mod.rs | 81 ++++- trixy-macros/src/generate/host/mod.rs | 86 ++++-- trixy-macros/src/generate/mod.rs | 58 +++- trixy-macros/src/lib.rs | 71 ++++- .../example/failing_types_generic.tri | 30 ++ trixy-parser/src/bin/trixy-parser.rs | 8 +- trixy-parser/src/command_spec/checked.rs | 22 +- trixy-parser/src/parsing/checked/error.rs | 19 ++ trixy-parser/src/parsing/checked/mod.rs | 56 +++- trixy-parser/src/parsing/unchecked/mod.rs | 1 + trixy-types/Cargo.toml | 2 + trixy-types/src/c_headers/errno.h | 21 ++ trixy-types/src/c_headers/option.h | 12 + trixy-types/src/c_headers/result.h | 24 ++ trixy-types/src/c_headers/result_dec.h | 14 + trixy-types/src/c_headers/string.h | 12 + trixy-types/src/c_headers/type_id.h | 35 +++ trixy-types/src/c_headers/type_id_dec.h | 17 + trixy-types/src/c_headers/vec.h | 28 ++ trixy-types/src/c_headers/vec_dec.h | 18 ++ trixy-types/src/error/mod.rs | 3 + trixy-types/src/lib.rs | 65 ++-- trixy-types/src/traits/convert_trait.rs | 108 +++++++ trixy-types/src/traits/errno.rs | 21 +- trixy-types/src/traits/mod.rs | 188 ++++++++++- trixy-types/src/types_list.rs | 70 +++++ trixy-types/trixy-types-derive/.gitignore | 6 + trixy-types/trixy-types-derive/Cargo.toml | 13 + trixy-types/trixy-types-derive/src/lib.rs | 38 +++ 38 files changed, 1531 insertions(+), 478 deletions(-) delete mode 120000 trixy-macros/example/main/src/full.tri delete mode 100644 trixy-macros/src/generate/c_api/header.rs create mode 100644 trixy-macros/src/generate/c_api/header/mod.rs create mode 100644 trixy-macros/src/generate/c_api/header/pure_header.rs create mode 100644 trixy-macros/src/generate/c_api/header/structs_init.rs create mode 100644 trixy-macros/src/generate/c_api/header/typedef.rs create mode 100644 trixy-parser/example/failing_types_generic.tri create mode 100644 trixy-types/src/c_headers/errno.h create mode 100644 trixy-types/src/c_headers/option.h create mode 100644 trixy-types/src/c_headers/result.h create mode 100644 trixy-types/src/c_headers/result_dec.h create mode 100644 trixy-types/src/c_headers/string.h create mode 100644 trixy-types/src/c_headers/type_id.h create mode 100644 trixy-types/src/c_headers/type_id_dec.h create mode 100644 trixy-types/src/c_headers/vec.h create mode 100644 trixy-types/src/c_headers/vec_dec.h create mode 100644 trixy-types/src/traits/convert_trait.rs create mode 100644 trixy-types/src/types_list.rs create mode 100644 trixy-types/trixy-types-derive/.gitignore create mode 100644 trixy-types/trixy-types-derive/Cargo.toml create mode 100644 trixy-types/trixy-types-derive/src/lib.rs diff --git a/src/lib.rs b/src/lib.rs index 6eccc4f..d54c53c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 { + channel: mpsc::Sender, + } + #[derive(Debug)] + pub struct Receiver { + channel: mpsc::Receiver, + last_value: Option, + } + + pub fn channel() -> (Sender, Receiver) { + let (tx, rx) = mpsc::channel(); + ( + Sender { channel: tx }, + Receiver { + channel: rx, + last_value: None, + }, + ) + } + impl Sender { + pub fn send(&self, input: T) -> Result<(), SendError> { + self.channel.send(input) + } + } + impl Receiver { + pub fn try_recv(&mut self) -> Result { + 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 { + match self.channel.recv() { + Ok(ok) => { + self.close(); + Ok(ok) + } + Err(err) => { + if let Some(val) = self.last_value { + Ok(val) + } else { + Err(err) + } + } + } + } + } +} diff --git a/trixy-macros/Cargo.toml b/trixy-macros/Cargo.toml index 32cc0de..24cb43e 100644 --- a/trixy-macros/Cargo.toml +++ b/trixy-macros/Cargo.toml @@ -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" } diff --git a/trixy-macros/example/main/src/full.tri b/trixy-macros/example/main/src/full.tri deleted file mode 120000 index 84464ec..0000000 --- a/trixy-macros/example/main/src/full.tri +++ /dev/null @@ -1 +0,0 @@ -/home/soispha/repos/rust/trinitrix/trixy/trixy-lang_parser/example/full.tri \ No newline at end of file diff --git a/trixy-macros/src/generate/c_api/header.rs b/trixy-macros/src/generate/c_api/header.rs deleted file mode 100644 index 8ef871d..0000000 --- a/trixy-macros/src/generate/c_api/header.rs +++ /dev/null @@ -1,292 +0,0 @@ -/* -* Copyright (C) 2023 The Trinitrix Project -* -* 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 . -*/ - -//! 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::>() - .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::(); - let ident = mangle_c_function_ident(function, namespaces); - let inputs: Vec = 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::(); - - 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::>() - .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 = 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::>() - .join("\n"); - let namespaces: String = nasp - .namespaces - .iter() - .map(|nasp| namespace_to_header(nasp, &nasps)) - .collect(); - - format! {"{}\n{}", functions, namespaces} -} diff --git a/trixy-macros/src/generate/c_api/header/mod.rs b/trixy-macros/src/generate/c_api/header/mod.rs new file mode 100644 index 0000000..5c1786a --- /dev/null +++ b/trixy-macros/src/generate/c_api/header/mod.rs @@ -0,0 +1,123 @@ +/* +* Copyright (C) 2023 The Trinitrix Project +* +* 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 . +*/ + +//! 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 + } +} diff --git a/trixy-macros/src/generate/c_api/header/pure_header.rs b/trixy-macros/src/generate/c_api/header/pure_header.rs new file mode 100644 index 0000000..9219f4e --- /dev/null +++ b/trixy-macros/src/generate/c_api/header/pure_header.rs @@ -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::(); + let ident = identifier_to_c(&structure.identifier); + let contents = structure + .contents + .iter() + .map(doc_named_type_to_header) + .collect::(); + 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::(); + 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::(); + let ident = identifier_to_c(&enumeration.identifier); + let states = enumeration + .states + .iter() + .map(doc_identifier_to_header) + .collect::(); + 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::(); + 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::(); + let ident = mangle_c_function_ident(function, namespaces); + let inputs: Vec = 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::>() + .join("\n"); + let namespaces: String = nasp + .namespaces + .iter() + .map(|nasp| namespace_to_header(nasp, &nasps)) + .collect(); + + format! {"{}\n{}", functions, namespaces} +} diff --git a/trixy-macros/src/generate/c_api/header/structs_init.rs b/trixy-macros/src/generate/c_api/header/structs_init.rs new file mode 100644 index 0000000..3f25c3b --- /dev/null +++ b/trixy-macros/src/generate/c_api/header/structs_init.rs @@ -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 , + } +} + diff --git a/trixy-macros/src/generate/c_api/header/typedef.rs b/trixy-macros/src/generate/c_api/header/typedef.rs new file mode 100644 index 0000000..611c534 --- /dev/null +++ b/trixy-macros/src/generate/c_api/header/typedef.rs @@ -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::>() + .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 = 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::(); + + 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::>() + .join("\n"); + + let namespace = quote! { + typedef struct { + #functions + #namespaces + } #ident; + }; + format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace} +} diff --git a/trixy-macros/src/generate/c_api/host.rs b/trixy-macros/src/generate/c_api/host.rs index a70257b..8f9df40 100644 --- a/trixy-macros/src/generate/c_api/host.rs +++ b/trixy-macros/src/generate/c_api/host.rs @@ -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 = 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 = 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 = - 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 - } - } - }) -} diff --git a/trixy-macros/src/generate/c_api/mod.rs b/trixy-macros/src/generate/c_api/mod.rs index 13f60ee..c46b8d5 100644 --- a/trixy-macros/src/generate/c_api/mod.rs +++ b/trixy-macros/src/generate/c_api/mod.rs @@ -18,9 +18,12 @@ * If not, see . */ -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 + } + } + }) +} diff --git a/trixy-macros/src/generate/host/mod.rs b/trixy-macros/src/generate/host/mod.rs index 0ff1dc8..89d3623 100644 --- a/trixy-macros/src/generate/host/mod.rs +++ b/trixy-macros/src/generate/host/mod.rs @@ -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 = OnceCell::new();} /// This function turns, for example, the following trixy input into this rust code: @@ -53,10 +55,8 @@ thread_local! {static DEBUG: OnceCell = 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 = 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 = 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 +/// }, /// } /// } /// ``` @@ -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 = trixy.functions.iter().map(function_to_rust).collect(); + let functions: Vec = trixy + .functions + .iter() + .map(|r#fn| function_to_rust(r#fn, &[])) + .collect(); let namespace_modules: Vec = 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 = namespace.functions.iter().map(function_to_rust).collect(); + let functions: Vec = namespace + .functions + .iter() + .map(|r#fn| function_to_rust(r#fn, &namespaces)) + .collect(); let namespace_modules: Vec = 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 = 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),* } } diff --git a/trixy-macros/src/generate/mod.rs b/trixy-macros/src/generate/mod.rs index f745a39..2db5ac7 100644 --- a/trixy-macros/src/generate/mod.rs +++ b/trixy-macros/src/generate/mod.rs @@ -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( + 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 = 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 = r#type.generic_args.iter().map(type_to_rust).collect(); diff --git a/trixy-macros/src/lib.rs b/trixy-macros/src/lib.rs index 1ce482e..a6b771a 100644 --- a/trixy-macros/src/lib.rs +++ b/trixy-macros/src/lib.rs @@ -18,9 +18,17 @@ * If not, see . */ -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} + }); + }); } } } diff --git a/trixy-parser/example/failing_types_generic.tri b/trixy-parser/example/failing_types_generic.tri new file mode 100644 index 0000000..ec5993a --- /dev/null +++ b/trixy-parser/example/failing_types_generic.tri @@ -0,0 +1,30 @@ +/* +* Copyright (C) 2023 The Trinitrix Project +* +* 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 . +*/ + +struct A {}; +struct B {}; + +enum Error {}; + +fn execute_callback(callback: String) -> Error; + + +// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: +// vim: syntax=rust diff --git a/trixy-parser/src/bin/trixy-parser.rs b/trixy-parser/src/bin/trixy-parser.rs index 2004949..8dd5ca4 100644 --- a/trixy-parser/src/bin/trixy-parser.rs +++ b/trixy-parser/src/bin/trixy-parser.rs @@ -18,14 +18,12 @@ * If not, see . */ -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)] diff --git a/trixy-parser/src/command_spec/checked.rs b/trixy-parser/src/command_spec/checked.rs index 1bac4b3..4efc6a8 100644 --- a/trixy-parser/src/command_spec/checked.rs +++ b/trixy-parser/src/command_spec/checked.rs @@ -158,11 +158,11 @@ impl From 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 } - } -} diff --git a/trixy-parser/src/parsing/checked/error.rs b/trixy-parser/src/parsing/checked/error.rs index ae08ff5..28a01fa 100644 --- a/trixy-parser/src/parsing/checked/error.rs +++ b/trixy-parser/src/parsing/checked/error.rs @@ -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), } } } diff --git a/trixy-parser/src/parsing/checked/mod.rs b/trixy-parser/src/parsing/checked/mod.rs index f3c6e18..44a4a14 100644 --- a/trixy-parser/src/parsing/checked/mod.rs +++ b/trixy-parser/src/parsing/checked/mod.rs @@ -259,18 +259,72 @@ impl Parser { .iter() .map(|r#enum| Into::::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, diff --git a/trixy-parser/src/parsing/unchecked/mod.rs b/trixy-parser/src/parsing/unchecked/mod.rs index b40d800..69781bb 100644 --- a/trixy-parser/src/parsing/unchecked/mod.rs +++ b/trixy-parser/src/parsing/unchecked/mod.rs @@ -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![>])?; diff --git a/trixy-types/Cargo.toml b/trixy-types/Cargo.toml index 6ddc508..379ee6d 100644 --- a/trixy-types/Cargo.toml +++ b/trixy-types/Cargo.toml @@ -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" } diff --git a/trixy-types/src/c_headers/errno.h b/trixy-types/src/c_headers/errno.h new file mode 100644 index 0000000..80cd198 --- /dev/null +++ b/trixy-types/src/c_headers/errno.h @@ -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 diff --git a/trixy-types/src/c_headers/option.h b/trixy-types/src/c_headers/option.h new file mode 100644 index 0000000..50560fa --- /dev/null +++ b/trixy-types/src/c_headers/option.h @@ -0,0 +1,12 @@ +#ifndef TRIXY_OPTION_H +#define TRIXY_OPTION_H +#include "type_id_dec.h" +#include + +typedef struct { + type_id_t type_id; + void *value; + bool some; +} option_t; + +#endif // TRIXY_OPTION_H diff --git a/trixy-types/src/c_headers/result.h b/trixy-types/src/c_headers/result.h new file mode 100644 index 0000000..918093d --- /dev/null +++ b/trixy-types/src/c_headers/result.h @@ -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 diff --git a/trixy-types/src/c_headers/result_dec.h b/trixy-types/src/c_headers/result_dec.h new file mode 100644 index 0000000..b08fdcb --- /dev/null +++ b/trixy-types/src/c_headers/result_dec.h @@ -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 diff --git a/trixy-types/src/c_headers/string.h b/trixy-types/src/c_headers/string.h new file mode 100644 index 0000000..65664f0 --- /dev/null +++ b/trixy-types/src/c_headers/string.h @@ -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 diff --git a/trixy-types/src/c_headers/type_id.h b/trixy-types/src/c_headers/type_id.h new file mode 100644 index 0000000..8d2d207 --- /dev/null +++ b/trixy-types/src/c_headers/type_id.h @@ -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 +#include + +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 diff --git a/trixy-types/src/c_headers/type_id_dec.h b/trixy-types/src/c_headers/type_id_dec.h new file mode 100644 index 0000000..0dbc83c --- /dev/null +++ b/trixy-types/src/c_headers/type_id_dec.h @@ -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 diff --git a/trixy-types/src/c_headers/vec.h b/trixy-types/src/c_headers/vec.h new file mode 100644 index 0000000..b9e3b14 --- /dev/null +++ b/trixy-types/src/c_headers/vec.h @@ -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 +#include + +/// 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 diff --git a/trixy-types/src/c_headers/vec_dec.h b/trixy-types/src/c_headers/vec_dec.h new file mode 100644 index 0000000..6359ea2 --- /dev/null +++ b/trixy-types/src/c_headers/vec_dec.h @@ -0,0 +1,18 @@ +#ifndef TRIXY_VEC_DEC_H +#define TRIXY_VEC_DEC_H +#include "type_id_dec.h" +#include + +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 diff --git a/trixy-types/src/error/mod.rs b/trixy-types/src/error/mod.rs index 612b235..884b320 100644 --- a/trixy-types/src/error/mod.rs +++ b/trixy-types/src/error/mod.rs @@ -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, } diff --git a/trixy-types/src/lib.rs b/trixy-types/src/lib.rs index 9804b8c..8165091 100644 --- a/trixy-types/src/lib.rs +++ b/trixy-types/src/lib.rs @@ -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(*const T); - -#[repr(C)] -pub struct Function(*const usize); - -pub type Option = std::option::Option; +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>(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" => 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) + }) } diff --git a/trixy-types/src/traits/convert_trait.rs b/trixy-types/src/traits/convert_trait.rs new file mode 100644 index 0000000..0934416 --- /dev/null +++ b/trixy-types/src/traits/convert_trait.rs @@ -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 Convertible + for Result +{ + fn into_void(self) -> *const c_void { + Into::::into(self).into_void() + } +} + +impl Convertible for &CStr { + fn into_void(self) -> *const c_void { + Into::::into(self).into_void() + } +} + +impl Convertible for CString { + fn into_void(self) -> *const c_void { + Into::::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 TypeInfo for $name { + fn type_of(&self) -> crate::TypeId { + crate::TypeId::$output + } + } + }; + ($name:tt<_,_>, $output:ident) => { + impl TypeInfo for $name { + 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); diff --git a/trixy-types/src/traits/errno.rs b/trixy-types/src/traits/errno.rs index a8ef7b4..67d1e24 100644 --- a/trixy-types/src/traits/errno.rs +++ b/trixy-types/src/traits/errno.rs @@ -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> { 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] diff --git a/trixy-types/src/traits/mod.rs b/trixy-types/src/traits/mod.rs index 8e70861..ac32f2d 100644 --- a/trixy-types/src/traits/mod.rs +++ b/trixy-types/src/traits/mod.rs @@ -18,16 +18,24 @@ * If not, see . */ -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 for &str { +impl<'a> TryFrom<&'a crate::str> for &'a str { type Error = crate::error::TypeConversionError; - fn try_from(value: crate::String) -> Result { + fn try_from(value: &'a crate::str) -> Result { let ptr = value.0; if ptr.is_null() { Err(TypeConversionError::NullPointer) @@ -39,9 +47,11 @@ impl TryFrom 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 for String { type Error = crate::error::TypeConversionError; fn try_from(value: crate::String) -> Result { - 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 TryFrom for Vec { + type Error = crate::error::TypeConversionError; + + fn try_from(value: crate::Vec) -> Result { + match value.type_id { + crate::TypeId::Unknown => Err(Self::Error::UntypedInput), + crate::TypeId::void => Ok(vec![]), + crate::TypeId::str_t => { + let mut output: Vec = 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(&mut self) -> Result { + 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 for crate::String { + fn from(value: CString) -> Self { + let layout = Layout::array::(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::()` 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 From> for crate::Vec +where + T: Convertible + TypeInfo, +{ + fn from(value: Vec) -> 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 From> for crate::Option +where + T: Convertible + TypeInfo, +{ + fn from(value: Option) -> 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 From> for crate::Result +where + T: Convertible + TypeInfo, + E: Convertible + TypeInfo, +{ + fn from(value: Result) -> 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(), + }, + } } } diff --git a/trixy-types/src/types_list.rs b/trixy-types/src/types_list.rs new file mode 100644 index 0000000..b28ead7 --- /dev/null +++ b/trixy-types/src/types_list.rs @@ -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, +} diff --git a/trixy-types/trixy-types-derive/.gitignore b/trixy-types/trixy-types-derive/.gitignore new file mode 100644 index 0000000..20c0ba9 --- /dev/null +++ b/trixy-types/trixy-types-derive/.gitignore @@ -0,0 +1,6 @@ +# build +/target +/result + +# This crate is a library +Cargo.lock diff --git a/trixy-types/trixy-types-derive/Cargo.toml b/trixy-types/trixy-types-derive/Cargo.toml new file mode 100644 index 0000000..af0be22 --- /dev/null +++ b/trixy-types/trixy-types-derive/Cargo.toml @@ -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 diff --git a/trixy-types/trixy-types-derive/src/lib.rs b/trixy-types/trixy-types-derive/src/lib.rs new file mode 100644 index 0000000..800bf32 --- /dev/null +++ b/trixy-types/trixy-types-derive/src/lib.rs @@ -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() +}