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() +}