use std::{env, sync::LazyLock};
use quote::quote;
use syn::{Item, parse_macro_input};
use crate::{
enums::{expand_c_enum, expand_enum, is_c_compatible_enum},
functions::{WrapMethods, expand_fn, expand_impl, expand_wrap_methods},
structs::{expand_c_struct, expand_struct, is_c_compatible_struct},
};
mod config;
mod enums;
mod functions;
mod namer;
mod structs;
mod type_resolver;
mod typemap;
use config::CONFIG;
use namer::Namer;
use type_resolver::FFITypeResolver;
static PKG_NAME: LazyLock<String> = LazyLock::new(|| env::var("CARGO_PKG_NAME").unwrap());
static MANIFEST_DIR: LazyLock<String> = LazyLock::new(|| env::var("CARGO_MANIFEST_DIR").unwrap());
const EZFFI_CODEGEN_VERSION: u32 = 1;
const MIN_COMPATIBLE_CODEGEN: u32 = 1;
const CODEGEN_INTRODUCED_IN: &[(u32, &str)] = &[(1, "0.1.0")];
fn crate_version_for_codegen(v: u32) -> &'static str {
CODEGEN_INTRODUCED_IN
.iter()
.find(|(cv, _)| *cv == v)
.map(|(_, ver)| *ver)
.unwrap_or("?")
}
#[proc_macro_attribute]
pub fn export(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
export_impl(attr, item, false)
}
#[proc_macro]
pub fn export_extern_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let path = parse_macro_input!(input as syn::Path);
let dummy_struct = quote! { struct #path {} }.into();
export_impl(proc_macro::TokenStream::new(), dummy_struct, true)
}
#[proc_macro]
pub fn wrap_methods(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parsed = parse_macro_input!(input as WrapMethods);
let result = FFITypeResolver::check_registry().and_then(|()| expand_wrap_methods(&parsed));
match result {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn export_impl(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
skip_input: bool,
) -> proc_macro::TokenStream {
let item = parse_macro_input!(item as Item);
match expand_item(&item, skip_input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn expand_item(item: &Item, mut skip_input: bool) -> syn::Result<proc_macro2::TokenStream> {
FFITypeResolver::check_registry()?;
let output = match item {
Item::Struct(item) => {
if item.generics.gt_token.is_none() && is_c_compatible_struct(item) {
skip_input = true;
expand_c_struct(item)
} else {
expand_struct(item)?
}
}
Item::Enum(item) => {
if is_c_compatible_enum(item) {
skip_input = true;
expand_c_enum(item)
} else {
expand_enum(item)?
}
}
Item::Fn(item) => expand_fn(item)?,
Item::Impl(item) => expand_impl(item)?,
other => {
return Err(syn::Error::new_spanned(
other,
format!(
"#[ezffi::export] doesn't support this item: `{}`",
quote!(#other)
),
));
}
};
if skip_input {
Ok(output)
} else {
Ok(quote! {
#item
#output
})
}
}