From 5fe757f82908890f17f4b31ac9032849297c0126 Mon Sep 17 00:00:00 2001 From: Soispha Date: Sun, 24 Mar 2024 21:05:10 +0100 Subject: [PATCH] fix(macros/c_api): Add host generation support for structs and enums --- trixy-macros/src/generate/c_api/host.rs | 119 +++++++++++++++-- trixy-macros/src/generate/c_api/mod.rs | 171 ++++++++++++++++++------ 2 files changed, 241 insertions(+), 49 deletions(-) diff --git a/trixy-macros/src/generate/c_api/host.rs b/trixy-macros/src/generate/c_api/host.rs index 1ca44c3..95385c1 100644 --- a/trixy-macros/src/generate/c_api/host.rs +++ b/trixy-macros/src/generate/c_api/host.rs @@ -22,13 +22,21 @@ use convert_case::{Case, Casing}; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use trixy_parser::command_spec::{CommandSpec, Function, Identifier, NamedType, Namespace}; +use trixy_parser::command_spec::{ + CommandSpec, Enumeration, Function, Identifier, NamedType, Namespace, Structure, +}; use crate::{ config::TrixyConfig, generate::{ - c_api::{mangle_c_function_ident, namespaces_to_path, type_to_c_equalivalent}, - function_identifier_to_rust, identifier_to_rust, type_to_rust, + attribute_to_rust, + c_api::{ + doc_named_type_to_c_equivalent, mangle_c_function_identifier, mangle_c_type_identifier, + type_to_c_equivalent, + }, + convertible_derive::c_enumeration_into_impl, + doc_identifier_to_rust, function_identifier_to_rust, identifier_to_rust, + namespaces_to_path, type_variant_rust_path, }, }; @@ -68,17 +76,98 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 { .iter() .map(|nasp| namespace_to_c(&nasp, &config, &vec![])) .collect(); + let structures: TokenStream2 = trixy + .structures + .iter() + .map(|nasp| structure_to_c(&nasp)) + .collect(); + let enumerations: TokenStream2 = trixy + .enumerations + .iter() + .map(|nasp| enumeration_to_c(&nasp)) + .collect(); quote! { + #enumerations + #structures #functions #namespaced_functions } } +fn enumeration_to_c(enumeration: &Enumeration) -> TokenStream2 { + let ident = mangle_c_type_identifier(&enumeration.identifier); + let doc_comments: TokenStream2 = enumeration + .attributes + .iter() + .map(|attr| attribute_to_rust(&enumeration.identifier, attr)) + .collect(); + + let states: Vec = enumeration + .states + .iter() + .map(doc_identifier_to_rust) + .collect(); + let paired_type = { + let path = type_variant_rust_path(&enumeration.identifier.variant) + .expect("This should always be some for enums"); + + let ident = identifier_to_rust(&enumeration.identifier); + if path.is_empty() { + quote! { + crate :: #ident + } + } else { + quote! { + #path :: #ident + } + } + }; + + let convertible = c_enumeration_into_impl(&enumeration, &paired_type); + quote! { + #doc_comments + #[allow(non_camel_case_types)] + #[repr(C)] + #[derive(Debug)] + pub enum #ident { + #(#states),* + } + #convertible + } +} + +fn structure_to_c(structure: &Structure) -> TokenStream2 { + let ident = mangle_c_type_identifier(&structure.identifier); + let doc_comments: TokenStream2 = structure + .attributes + .iter() + .map(|attr| attribute_to_rust(&structure.identifier, attr)) + .collect(); + + let contents: Vec = structure + .contents + .iter() + .map(doc_named_type_to_c_equivalent) + .collect(); + + // TODO: Convertible <2024-03-08> + quote! { + #doc_comments + #[allow(non_camel_case_types)] + #[repr(C)] + #[derive(Debug)] + pub struct #ident { + #(#contents),* + } + } +} + fn namespace_to_c( namespace: &Namespace, config: &TrixyConfig, namespaces: &Vec<&Identifier>, ) -> TokenStream2 { + let ident = mangle_c_type_identifier(&namespace.name); let mut namespaces = namespaces.clone(); namespaces.push(&namespace.name); @@ -92,7 +181,21 @@ fn namespace_to_c( .iter() .map(|nasp| namespace_to_c(&nasp, &config, &namespaces)) .collect(); + let structures: TokenStream2 = namespace + .structures + .iter() + .map(|nasp| structure_to_c(&nasp)) + .collect(); + let enumerations: TokenStream2 = namespace + .enumerations + .iter() + .map(|nasp| enumeration_to_c(&nasp)) + .collect(); quote! { + pub mod #ident { + #enumerations + #structures + } #functions #additional_functions } @@ -103,11 +206,11 @@ fn function_to_c( config: &TrixyConfig, namespaces: &Vec<&Identifier>, ) -> TokenStream2 { - let ident = mangle_c_function_ident(function, namespaces); + let ident = mangle_c_function_identifier(&function.identifier, namespaces); let inputs: Vec = function .inputs .iter() - .map(|named_type| named_type_to_rust_trixy(named_type, &namespaces)) + .map(|named_type| named_type_to_rust_trixy(named_type)) .collect(); let callback_function = format_ident!("{}", config.callback_function); @@ -115,7 +218,7 @@ fn function_to_c( let command_value: TokenStream2 = function_path_to_rust(&namespaces, &function); if let Some(r#type) = &function.output { - let output_ident = type_to_c_equalivalent(&r#type, &namespaces); + let output_ident = type_to_c_equivalent(&r#type); quote! { #[no_mangle] pub extern "C" fn #ident(output: *mut #output_ident, #(#inputs),*) -> core::ffi::c_int { @@ -142,9 +245,9 @@ fn function_to_c( } } -fn named_type_to_rust_trixy(named_type: &NamedType, namespaces: &[&Identifier]) -> TokenStream2 { +fn named_type_to_rust_trixy(named_type: &NamedType) -> TokenStream2 { let ident = identifier_to_rust(&named_type.name); - let type_ident = type_to_c_equalivalent(&named_type.r#type, &namespaces); + let type_ident = type_to_c_equivalent(&named_type.r#type); quote! { #ident : #type_ident } diff --git a/trixy-macros/src/generate/c_api/mod.rs b/trixy-macros/src/generate/c_api/mod.rs index 484a3b1..b94135a 100644 --- a/trixy-macros/src/generate/c_api/mod.rs +++ b/trixy-macros/src/generate/c_api/mod.rs @@ -22,14 +22,16 @@ 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, Variant}; +use trixy_parser::command_spec::{DocNamedType, Identifier, NamedType, Type, Variant}; + +use crate::generate::{attribute_to_rust, namespaces_to_path_expansive, type_to_rust}; use super::identifier_to_rust; pub mod header; pub mod host; -pub fn mangle_c_function_ident(function: &Function, namespaces: &[&Identifier]) -> Ident { +pub fn mangle_c_function_identifier(identifier: &Identifier, namespaces: &[&Identifier]) -> Ident { let namespace_str = namespaces.iter().fold(String::default(), |acc, nasp| { if acc.is_empty() { nasp.name.clone() @@ -39,11 +41,15 @@ pub fn mangle_c_function_ident(function: &Function, namespaces: &[&Identifier]) }); if namespace_str.is_empty() { - format_ident!("{}", &function.identifier.name) + format_ident!("{}", &identifier.name) } else { - format_ident!("{}_{}", namespace_str, &function.identifier.name) + format_ident!("{}_{}", namespace_str, &identifier.name) } } +pub fn mangle_c_type_identifier(identifier: &Identifier) -> Ident { + format_ident!("{}_c", &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 { @@ -58,32 +64,36 @@ pub fn type_to_c(r#type: &Type, is_output: bool) -> TokenStream2 { } } pub fn identifier_to_c(identifier: &Identifier) -> TokenStream2 { - match identifier.variant { - Variant::Structure => { + match &identifier.variant { + Variant::Structure { .. } => { let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal)); quote! { struct #ident } } - Variant::Enumeration => { + Variant::Enumeration { .. } => { let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal)); quote! { enum #ident } } - Variant::Primitive => { - let ident = format_ident!("{}_t", identifier.name.to_case(Case::Snake)); - quote! { - #ident + Variant::Primitive => match identifier.name.to_case(Case::Snake).as_str() { + "string" => { + quote! { + const char* + } } - } + other => { + todo!("'{}' is not yet supported", other) + } + }, other => { unimplemented!("{:#?}", other) } } } -pub fn type_to_c_equalivalent(r#type: &Type, namespaces: &[&Identifier]) -> TokenStream2 { - let ident = identifier_to_rust(&r#type.identifier); + +pub fn type_to_c_equivalent(r#type: &Type) -> TokenStream2 { let trixy_build_in_types: Vec<&str> = trixy_types::BASE_TYPES .iter() .filter_map(|(name, _)| { @@ -94,13 +104,21 @@ pub fn type_to_c_equalivalent(r#type: &Type, namespaces: &[&Identifier]) -> Toke } }) .collect(); + if trixy_build_in_types.is_empty() { - let nasp_path = if namespaces.is_empty() { - TokenStream2::default() - } else { - let path = namespaces_to_path(&namespaces); + // The types was specified in the api.tri file + + let ident = mangle_c_type_identifier(&r#type.identifier); + + let namespaces_path = type_variant_c_path(&r#type.identifier.variant); + let nasp_path = if namespaces_path.is_empty() { quote! { - Commands :: #path :: + crate :: + } + } else { + let path = namespaces_path; + quote! { + #path :: } }; quote! { @@ -108,30 +126,101 @@ pub fn type_to_c_equalivalent(r#type: &Type, namespaces: &[&Identifier]) -> Toke } } 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 + + let type_name = trixy_build_in_types + .first() + .expect("The names should not be dublicated, this should be the only value"); + + match *type_name { + "Result" => { + let ident_ok = + type_to_c_equivalent(&r#type.generic_args.first().expect("This is a result")); + let ident_err = + type_to_c_equivalent(&r#type.generic_args.last().expect("This is a result")); + quote! { + // as Convertible>::Ptr, + as Convertible>::Ptr + } + } + "Option" => { + let value = type_to_rust( + r#type + .generic_args + .first() + .expect("An option does only have one arg"), + ); + quote! { + *const #value + } + } + _ => { + let ident = identifier_to_rust(&r#type.identifier); + let generics: TokenStream2 = { + let generics: Vec = r#type + .generic_args + .iter() + .map(|val| type_to_c_equivalent(val)) + .collect(); + quote! { + <#(#generics),*> + } + }; + + quote! { + trixy::types:: #ident #generics + } + } } } } -fn namespaces_to_path(namespaces: &[&Identifier]) -> TokenStream2 { - namespaces +fn doc_named_type_to_c_equivalent(doc_named_type: &DocNamedType) -> TokenStream2 { + let doc_comments: TokenStream2 = doc_named_type + .attributes .iter() - .fold(TokenStream2::default(), |acc, nasp| { - let ident = format_ident!("{}", nasp.name); - if acc.is_empty() { - quote! { - #ident - } - } else { - quote! { - #acc :: #ident - } - } - }) + .map(|attr| attribute_to_rust(&&doc_named_type.name, attr)) + .collect(); + let named_type = named_type_to_c_equivalent(&doc_named_type.into()); + quote! { + #doc_comments + pub #named_type + } +} +fn named_type_to_c_equivalent(named_type: &NamedType) -> TokenStream2 { + let ident = identifier_to_rust(&named_type.name); + let r#type = type_to_c_equivalent(&named_type.r#type); + quote! { + #ident : #r#type + } +} +pub fn type_variant_c_path(variant: &Variant) -> TokenStream2 { + fn mangle_namespace_name(vec: &Vec) -> Vec { + vec.into_iter() + .map(|ident| { + if "" == &ident.name && ident.variant == Variant::RootNamespace { + // The [`namespaces_to_path_expansive`] function already deals with + // [`RootNamespace`] variants, so we just leave that as is + ident.clone() + } else { + Identifier { + // TODO(@soispha): This should use [`mangle_c_type_name`] + // to ensure same mangling as the others <2024-03-05> + name: format!("{}_c", ident.name), + variant: ident.variant.clone(), + } + } + }) + .collect() + } + + let main_namespace; + match variant { + Variant::Structure { namespace } => { + main_namespace = mangle_namespace_name(namespace); + } + Variant::Enumeration { namespace } => { + main_namespace = mangle_namespace_name(namespace); + } + _ => unreachable!("This should never be called"), + } + namespaces_to_path_expansive(&main_namespace[..]) }