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 6e72b7bcf1
commit 5fe757f829
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 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<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(
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<TokenStream2> = 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
}

View File

@ -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));
Variant::Primitive => match identifier.name.to_case(Case::Snake).as_str() {
"string" => {
quote! {
#ident
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
let type_name = trixy_build_in_types
.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! {
trixy::types:: #type_name
*const #value
}
}
}
fn namespaces_to_path(namespaces: &[&Identifier]) -> TokenStream2 {
namespaces
_ => {
let ident = identifier_to_rust(&r#type.identifier);
let generics: TokenStream2 = {
let generics: Vec<TokenStream2> = r#type
.generic_args
.iter()
.fold(TokenStream2::default(), |acc, nasp| {
let ident = format_ident!("{}", nasp.name);
if acc.is_empty() {
.map(|val| type_to_c_equivalent(val))
.collect();
quote! {
#ident
<#(#generics),*>
}
} else {
};
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[..])
}