fix(macros/c_api): Add host generation support for structs and enums
This commit is contained in:
parent
6e72b7bcf1
commit
5fe757f829
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
quote! {
|
"Result" => {
|
||||||
trixy::types:: #type_name
|
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 {
|
fn doc_named_type_to_c_equivalent(doc_named_type: &DocNamedType) -> TokenStream2 {
|
||||||
namespaces
|
let doc_comments: TokenStream2 = doc_named_type
|
||||||
|
.attributes
|
||||||
.iter()
|
.iter()
|
||||||
.fold(TokenStream2::default(), |acc, nasp| {
|
.map(|attr| attribute_to_rust(&&doc_named_type.name, attr))
|
||||||
let ident = format_ident!("{}", nasp.name);
|
.collect();
|
||||||
if acc.is_empty() {
|
let named_type = named_type_to_c_equivalent(&doc_named_type.into());
|
||||||
quote! {
|
quote! {
|
||||||
#ident
|
#doc_comments
|
||||||
}
|
pub #named_type
|
||||||
} else {
|
}
|
||||||
quote! {
|
}
|
||||||
#acc :: #ident
|
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