fix(macros/c_api): Add host generation support for structs and enums
This commit is contained in:
parent
9ec0c22e4e
commit
fa26bdfc6c
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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! {
|
||||
// <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! {
|
||||
*const #value
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let ident = identifier_to_rust(&r#type.identifier);
|
||||
let generics: TokenStream2 = {
|
||||
let generics: Vec<TokenStream2> = 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<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[..])
|
||||
}
|
||||
|
|
Reference in New Issue