1use derive_syn_parse::Parse;
2use proc_macro::TokenStream;
3use proc_macro2::TokenStream as TokenStream2;
4use quote::quote;
5use syn::{
6 Ident, Result, Token,
7 parse::{Parse, ParseStream},
8 parse2,
9};
10
11use std::env;
12use std::fmt;
13use std::fs;
14use std::path::Path;
15
16use rmp_utils::{RMPArg, RMPConfig, RMPFunction};
17
18mod keywords {
19 use syn::custom_keyword;
20
21 custom_keyword!(ty);
22 custom_keyword!(class);
23 custom_keyword!(name);
24 custom_keyword!(static_function);
25 custom_keyword!(constructor);
26 custom_keyword!(destructor);
27 custom_keyword!(method);
28 custom_keyword!(property);
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
32enum RMPFFIType {
33 StaticFunction,
34 Constructor,
35 Destructor,
36 Method,
37 Property,
38}
39
40impl Parse for RMPFFIType {
41 fn parse(input: ParseStream) -> Result<Self> {
42 let lookahead = input.lookahead1();
43 if lookahead.peek(keywords::static_function) {
44 input.parse::<keywords::static_function>()?;
45 Ok(Self::StaticFunction)
46 } else if lookahead.peek(keywords::constructor) {
47 input.parse::<keywords::constructor>()?;
48 Ok(Self::Constructor)
49 } else if lookahead.peek(keywords::destructor) {
50 input.parse::<keywords::destructor>()?;
51 Ok(Self::Destructor)
52 } else if lookahead.peek(keywords::method) {
53 input.parse::<keywords::method>()?;
54 Ok(Self::Method)
55 } else if lookahead.peek(keywords::property) {
56 input.parse::<keywords::property>()?;
57 Ok(Self::Property)
58 } else {
59 Err(lookahead.error())
60 }
61 }
62}
63
64impl fmt::Display for RMPFFIType {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 RMPFFIType::StaticFunction => write!(f, "static_function"),
68 RMPFFIType::Constructor => write!(f, "constructor"),
69 RMPFFIType::Destructor => write!(f, "destructor"),
70 RMPFFIType::Method => write!(f, "method"),
71 RMPFFIType::Property => write!(f, "property"),
72 }
73 }
74}
75
76#[derive(Parse, Clone)]
77struct RMPFFIAttrArgs {
78 pub _ty_keyword: Option<keywords::ty>,
79 #[parse_if(_ty_keyword.is_some())]
80 pub _eq: Option<Token![=]>,
81 #[parse_if(_ty_keyword.is_some())]
82 pub ty: Option<RMPFFIType>,
83 #[parse_if(_ty_keyword.is_some())]
84 pub _comma: Option<Token![,]>,
85
86 pub _class_keyword: Option<keywords::class>,
87 #[parse_if(_class_keyword.is_some())]
88 pub _eq2: Option<Token![=]>,
89 #[parse_if(_class_keyword.is_some())]
90 pub class: Option<Ident>,
91 #[parse_if(_class_keyword.is_some())]
92 pub _comma2: Option<Token![,]>,
93
94 pub _name_keyword: Option<keywords::name>,
95 #[parse_if(_name_keyword.is_some())]
96 pub _eq3: Option<Token![=]>,
97 #[parse_if(_name_keyword.is_some())]
98 pub name: Option<Ident>,
99}
100
101fn update_or_append_function(functions: &mut Option<Vec<RMPFunction>>, function: RMPFunction) {
102 if let Some(funcs) = functions {
103 if let Some(idx) = funcs.iter().position(|f| f.name == function.name) {
104 funcs[idx] = function;
105 } else {
106 funcs.push(function);
107 }
108 } else {
109 *functions = Some(vec![function]);
110 }
111}
112
113fn process_rmp_ffi(attr: TokenStream2, item: TokenStream2) -> Result<TokenStream2> {
114 let args = parse2::<RMPFFIAttrArgs>(attr)?;
115
116 let func = parse2::<syn::ItemFn>(item.clone())?;
117 let func_name = func.sig.ident.to_string();
118 let func_args = func
119 .sig
120 .inputs
121 .iter()
122 .map(|arg| match arg {
123 syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => (quote!( #pat ), quote!( #ty )),
124 _ => (quote!(), quote!()),
125 })
126 .map(|(name, ty)| RMPArg {
127 name: name.to_string(),
128 ty: ty.to_string(),
129 })
130 .collect::<Vec<RMPArg>>();
131
132 let return_type = func.sig.output;
133 let return_type = match return_type {
134 syn::ReturnType::Type(_, ty) => quote!(#ty).to_string(),
135 _ => "void".to_string(),
136 };
137
138 let class = args.class.unwrap().to_string();
139 let docs = func
140 .attrs
141 .iter()
142 .flat_map(|attr| {
143 if let syn::Meta::NameValue(meta) = &attr.meta {
144 if meta.path.is_ident("doc") {
145 if let syn::Expr::Lit(syn::ExprLit {
146 lit: syn::Lit::Str(lit),
147 ..
148 }) = &meta.value
149 {
150 return Some(lit.value().trim().to_string());
151 }
152 }
153 }
154 None
155 })
156 .collect::<Vec<_>>();
157 let function = RMPFunction {
158 name: args.name.unwrap().to_string(),
159 rust_name: func_name,
160 args: func_args,
161 return_type,
162 docs,
163 };
164
165 if let Ok(out_dir) = env::var("CARGO_MANIFEST_DIR") {
166 let bindings_dir = Path::new(&out_dir).join("bindings");
167 fs::create_dir_all(&bindings_dir).expect("Failed to create bindings directory");
168 let yaml_file_path = bindings_dir.join(format!("{}.yaml", class));
169
170 let mut config = if yaml_file_path.exists() {
171 match fs::read_to_string(&yaml_file_path) {
172 Ok(contents) => match serde_yaml::from_str(&contents) {
173 Ok(config) => config,
174 Err(_) => {
175 let _ = fs::remove_file(&yaml_file_path);
176 RMPConfig {
177 class,
178 ..Default::default()
179 }
180 }
181 },
182 Err(_) => RMPConfig {
183 class,
184 ..Default::default()
185 },
186 }
187 } else {
188 RMPConfig {
189 class,
190 ..Default::default()
191 }
192 };
193 match args.ty {
194 Some(RMPFFIType::StaticFunction) => {
195 if let Some(idx) = config
196 .static_functions
197 .iter()
198 .position(|f| f.name == function.name)
199 {
200 config.static_functions[idx] = function;
201 } else {
202 config.static_functions.push(function);
203 }
204 }
205 Some(RMPFFIType::Constructor) => {
206 update_or_append_function(&mut config.constructors, function);
207 }
208 Some(RMPFFIType::Destructor) => {
209 config.destructor = Some(function);
210 }
211 Some(RMPFFIType::Method) => {
212 update_or_append_function(&mut config.methods, function);
213 }
214 Some(RMPFFIType::Property) => {
215 update_or_append_function(&mut config.properties, function);
216 }
217 _ => panic!("Invalid FFI type"),
218 }
219 let yaml_output: String =
220 serde_yaml::to_string(&config).expect("Failed to serialize to YAML");
221 fs::write(&yaml_file_path, yaml_output).expect("Failed to write YAML file");
222 } else {
223 panic!("CARGO_WORKSPACE_DIR is not set");
224 }
225
226 Ok(item)
227}
228
229#[proc_macro_attribute]
230pub fn rmp_ffi(attr: TokenStream, item: TokenStream) -> TokenStream {
231 match process_rmp_ffi(attr.into(), item.into()) {
232 Ok(item) => item.into(),
233 Err(e) => e.to_compile_error().into(),
234 }
235}