fix(macros/c_api): Add host generation support for structs and enums

This commit is contained in:
Benedikt Peetz 2024-03-24 21:05:10 +01:00
parent 9ec0c22e4e
commit fa26bdfc6c
Signed by: bpeetz
GPG Key ID: A5E94010C3A642AD
2 changed files with 241 additions and 49 deletions

View File

@ -22,13 +22,21 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote}; 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::{ use crate::{
config::TrixyConfig, config::TrixyConfig,
generate::{ generate::{
c_api::{mangle_c_function_ident, namespaces_to_path, type_to_c_equalivalent}, attribute_to_rust,
function_identifier_to_rust, identifier_to_rust, type_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() .iter()
.map(|nasp| namespace_to_c(&nasp, &config, &vec![])) .map(|nasp| namespace_to_c(&nasp, &config, &vec![]))
.collect(); .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! { quote! {
#enumerations
#structures
#functions #functions
#namespaced_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<TokenStream2> = 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<TokenStream2> = 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( fn namespace_to_c(
namespace: &Namespace, namespace: &Namespace,
config: &TrixyConfig, config: &TrixyConfig,
namespaces: &Vec<&Identifier>, namespaces: &Vec<&Identifier>,
) -> TokenStream2 { ) -> TokenStream2 {
let ident = mangle_c_type_identifier(&namespace.name);
let mut namespaces = namespaces.clone(); let mut namespaces = namespaces.clone();
namespaces.push(&namespace.name); namespaces.push(&namespace.name);
@ -92,7 +181,21 @@ fn namespace_to_c(
.iter() .iter()
.map(|nasp| namespace_to_c(&nasp, &config, &namespaces)) .map(|nasp| namespace_to_c(&nasp, &config, &namespaces))
.collect(); .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! { quote! {
pub mod #ident {
#enumerations
#structures
}
#functions #functions
#additional_functions #additional_functions
} }
@ -103,11 +206,11 @@ fn function_to_c(
config: &TrixyConfig, config: &TrixyConfig,
namespaces: &Vec<&Identifier>, namespaces: &Vec<&Identifier>,
) -> TokenStream2 { ) -> TokenStream2 {
let ident = mangle_c_function_ident(function, namespaces); let ident = mangle_c_function_identifier(&function.identifier, namespaces);
let inputs: Vec<TokenStream2> = function let inputs: Vec<TokenStream2> = function
.inputs .inputs
.iter() .iter()
.map(|named_type| named_type_to_rust_trixy(named_type, &namespaces)) .map(|named_type| named_type_to_rust_trixy(named_type))
.collect(); .collect();
let callback_function = format_ident!("{}", config.callback_function); 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); let command_value: TokenStream2 = function_path_to_rust(&namespaces, &function);
if let Some(r#type) = &function.output { 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! { quote! {
#[no_mangle] #[no_mangle]
pub extern "C" fn #ident(output: *mut #output_ident, #(#inputs),*) -> core::ffi::c_int { 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 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! { quote! {
#ident : #type_ident #ident : #type_ident
} }

View File

@ -22,14 +22,16 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro2::{Ident, TokenStream as TokenStream2}; use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{format_ident, quote}; 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; use super::identifier_to_rust;
pub mod header; pub mod header;
pub mod host; 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| { let namespace_str = namespaces.iter().fold(String::default(), |acc, nasp| {
if acc.is_empty() { if acc.is_empty() {
nasp.name.clone() nasp.name.clone()
@ -39,11 +41,15 @@ pub fn mangle_c_function_ident(function: &Function, namespaces: &[&Identifier])
}); });
if namespace_str.is_empty() { if namespace_str.is_empty() {
format_ident!("{}", &function.identifier.name) format_ident!("{}", &identifier.name)
} else { } 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 { pub fn type_to_c(r#type: &Type, is_output: bool) -> TokenStream2 {
let ident = identifier_to_c(&r#type.identifier); let ident = identifier_to_c(&r#type.identifier);
let output = if is_output { 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 { pub fn identifier_to_c(identifier: &Identifier) -> TokenStream2 {
match identifier.variant { match &identifier.variant {
Variant::Structure => { Variant::Structure { .. } => {
let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal)); let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal));
quote! { quote! {
struct #ident struct #ident
} }
} }
Variant::Enumeration => { Variant::Enumeration { .. } => {
let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal)); let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal));
quote! { quote! {
enum #ident enum #ident
} }
} }
Variant::Primitive => { Variant::Primitive => match identifier.name.to_case(Case::Snake).as_str() {
let ident = format_ident!("{}_t", identifier.name.to_case(Case::Snake)); "string" => {
quote! { quote! {
#ident const char*
} }
} }
other => {
todo!("'{}' is not yet supported", other)
}
},
other => { other => {
unimplemented!("{:#?}", 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 let trixy_build_in_types: Vec<&str> = trixy_types::BASE_TYPES
.iter() .iter()
.filter_map(|(name, _)| { .filter_map(|(name, _)| {
@ -94,13 +104,21 @@ pub fn type_to_c_equalivalent(r#type: &Type, namespaces: &[&Identifier]) -> Toke
} }
}) })
.collect(); .collect();
if trixy_build_in_types.is_empty() { if trixy_build_in_types.is_empty() {
let nasp_path = if namespaces.is_empty() { // The types was specified in the api.tri file
TokenStream2::default()
} else { let ident = mangle_c_type_identifier(&r#type.identifier);
let path = namespaces_to_path(&namespaces);
let namespaces_path = type_variant_c_path(&r#type.identifier.variant);
let nasp_path = if namespaces_path.is_empty() {
quote! { quote! {
Commands :: #path :: crate ::
}
} else {
let path = namespaces_path;
quote! {
#path ::
} }
}; };
quote! { quote! {
@ -108,30 +126,101 @@ pub fn type_to_c_equalivalent(r#type: &Type, namespaces: &[&Identifier]) -> Toke
} }
} else { } else {
debug_assert_eq!(trixy_build_in_types.len(), 1); debug_assert_eq!(trixy_build_in_types.len(), 1);
let type_name = format_ident!(
"{}", let type_name = trixy_build_in_types
trixy_build_in_types
.first() .first()
.expect("The names should not be dublicated, this should be the only value") .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! {
// <Result<TrainedDog, TrainingMistake> as Convertible>::Ptr,
<Result<#ident_ok, #ident_err> as Convertible>::Ptr
}
}
"Option" => {
let value = type_to_rust(
r#type
.generic_args
.first()
.expect("An option does only have one arg"),
); );
quote! { quote! {
trixy::types:: #type_name *const #value
} }
} }
} _ => {
fn namespaces_to_path(namespaces: &[&Identifier]) -> TokenStream2 { let ident = identifier_to_rust(&r#type.identifier);
namespaces let generics: TokenStream2 = {
let generics: Vec<TokenStream2> = r#type
.generic_args
.iter() .iter()
.fold(TokenStream2::default(), |acc, nasp| { .map(|val| type_to_c_equivalent(val))
let ident = format_ident!("{}", nasp.name); .collect();
if acc.is_empty() {
quote! { quote! {
#ident <#(#generics),*>
} }
} else { };
quote! { quote! {
#acc :: #ident trixy::types:: #ident #generics
}
}
}
}
}
fn doc_named_type_to_c_equivalent(doc_named_type: &DocNamedType) -> TokenStream2 {
let doc_comments: TokenStream2 = doc_named_type
.attributes
.iter()
.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<Identifier>) -> Vec<Identifier> {
vec.into_iter()
.map(|ident| {
if "<root>" == &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[..])
} }