1use std::{env, sync::LazyLock};
4
5use quote::quote;
6use syn::{Item, parse_macro_input};
7
8use crate::{
9 enums::{expand_c_enum, expand_enum, is_c_compatible_enum},
10 functions::{WrapMethods, expand_fn, expand_impl, expand_wrap_methods},
11 structs::{expand_c_struct, expand_struct, is_c_compatible_struct},
12};
13
14mod config;
15mod enums;
16mod functions;
17mod namer;
18mod structs;
19mod type_resolver;
20mod typemap;
21
22use config::CONFIG;
23use namer::Namer;
24use type_resolver::FFITypeResolver;
25
26static PKG_NAME: LazyLock<String> = LazyLock::new(|| env::var("CARGO_PKG_NAME").unwrap());
27static MANIFEST_DIR: LazyLock<String> = LazyLock::new(|| env::var("CARGO_MANIFEST_DIR").unwrap());
28
29const EZFFI_CODEGEN_VERSION: u32 = 1;
30const MIN_COMPATIBLE_CODEGEN: u32 = 1;
31const CODEGEN_INTRODUCED_IN: &[(u32, &str)] = &[(1, "0.1.0")];
32
33fn crate_version_for_codegen(v: u32) -> &'static str {
34 CODEGEN_INTRODUCED_IN
35 .iter()
36 .find(|(cv, _)| *cv == v)
37 .map(|(_, ver)| *ver)
38 .unwrap_or("?")
39}
40
41#[proc_macro_attribute]
63pub fn export(
64 attr: proc_macro::TokenStream,
65 item: proc_macro::TokenStream,
66) -> proc_macro::TokenStream {
67 export_impl(attr, item, false)
68}
69
70#[proc_macro]
75pub fn export_extern_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
76 let path = parse_macro_input!(input as syn::Path);
77
78 let dummy_struct = quote! { struct #path {} }.into();
79
80 export_impl(proc_macro::TokenStream::new(), dummy_struct, true)
81}
82
83#[proc_macro]
97pub fn wrap_methods(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
98 let parsed = parse_macro_input!(input as WrapMethods);
99
100 let result = FFITypeResolver::check_registry().and_then(|()| expand_wrap_methods(&parsed));
101
102 match result {
103 Ok(ts) => ts.into(),
104 Err(e) => e.to_compile_error().into(),
105 }
106}
107
108fn export_impl(
109 _attr: proc_macro::TokenStream,
110 item: proc_macro::TokenStream,
111 skip_input: bool,
112) -> proc_macro::TokenStream {
113 let item = parse_macro_input!(item as Item);
114
115 match expand_item(&item, skip_input) {
116 Ok(ts) => ts.into(),
117 Err(e) => e.to_compile_error().into(),
118 }
119}
120
121fn expand_item(item: &Item, mut skip_input: bool) -> syn::Result<proc_macro2::TokenStream> {
122 FFITypeResolver::check_registry()?;
123
124 let output = match item {
125 Item::Struct(item) => {
126 if item.generics.gt_token.is_none() && is_c_compatible_struct(item) {
127 skip_input = true;
128 expand_c_struct(item)
129 } else {
130 expand_struct(item)?
131 }
132 }
133 Item::Enum(item) => {
134 if is_c_compatible_enum(item) {
135 skip_input = true;
136 expand_c_enum(item)
137 } else {
138 expand_enum(item)?
139 }
140 }
141 Item::Fn(item) => expand_fn(item)?,
142 Item::Impl(item) => expand_impl(item)?,
143 other => {
144 return Err(syn::Error::new_spanned(
145 other,
146 format!(
147 "#[ezffi::export] doesn't support this item: `{}`",
148 quote!(#other)
149 ),
150 ));
151 }
152 };
153
154 if skip_input {
155 Ok(output)
156 } else {
157 Ok(quote! {
158 #item
159 #output
160 })
161 }
162}