gdnative_bindings_generator/
lib.rs1mod class_docs;
18mod classes;
19mod documentation;
20mod methods;
21mod special_methods;
22
23#[cfg(feature = "custom-godot")]
24mod godot_api_json;
25mod godot_version;
26
27pub mod api;
28pub mod dependency;
29
30use crate::classes::*;
31use crate::documentation::*;
32use crate::methods::*;
33use crate::special_methods::*;
34use proc_macro2::TokenStream;
35use quote::{format_ident, quote};
36use std::collections::HashMap;
37use std::io;
38
39pub use api::*;
40pub use class_docs::*;
41pub use dependency::*;
42
43#[cfg(feature = "custom-godot")]
44pub use godot_api_json::*;
45pub use godot_version::*;
46
47#[cfg(not(feature = "custom-godot"))]
48pub fn generate_json_if_needed() -> bool {
49 false
50}
51
52pub type GeneratorResult<T = ()> = Result<T, io::Error>;
53
54pub struct BindingResult<'a> {
55 pub class_bindings: Vec<(&'a GodotClass, TokenStream)>,
56 pub icalls: TokenStream,
57}
58
59pub fn generate_bindings<'a>(api: &'a Api, docs: Option<&GodotXmlDocs>) -> BindingResult<'a> {
60 let mut icalls = HashMap::new();
61
62 let class_bindings = api
63 .classes
64 .iter()
65 .map(|class| {
66 (
67 class,
68 generate_class_bindings(api, class, &mut icalls, docs),
69 )
70 })
71 .collect();
72
73 let icalls = icalls
74 .into_iter()
75 .map(|(name, sig)| generate_icall(name, sig))
76 .collect();
77
78 BindingResult {
79 class_bindings,
80 icalls,
81 }
82}
83
84pub fn generate_imports() -> TokenStream {
85 quote! {
86 use std::os::raw::c_char;
87 use std::ptr;
88 use std::mem;
89 }
90}
91
92fn generate_class_bindings(
93 api: &Api,
94 class: &GodotClass,
95 icalls: &mut HashMap<String, MethodSig>,
96 docs: Option<&GodotXmlDocs>,
97) -> TokenStream {
98 let types_and_methods = {
100 let module_doc = generate_module_doc(class);
101 let class_doc = generate_class_documentation(api, class);
102 let class_struct = generate_class_struct(class, class_doc);
103
104 let enums = generate_enums(class);
105
106 let constants = if !class.constants.is_empty() {
107 generate_class_constants(class)
108 } else {
109 Default::default()
110 };
111
112 let class_impl = generate_class_impl(class, icalls, docs);
113
114 quote! {
115 #module_doc
116 #class_struct
117 #enums
118 #constants
119 #class_impl
120 }
121 };
122
123 let traits = {
125 let object_impl = generate_godot_object_impl(class);
126
127 let free_impl = generate_queue_free_impl(api, class);
128
129 let base_class = if !class.base_class.is_empty() {
130 generate_deref_impl(class)
131 } else {
132 Default::default()
133 };
134
135 let sub_class = generate_sub_class_impls(api, class);
136
137 let instantiable = if class.instantiable {
139 generate_instantiable_impl(class)
140 } else {
141 Default::default()
142 };
143
144 let send_sync = if class.singleton && class.is_singleton_thread_safe() {
145 generate_send_sync_impls(class)
146 } else {
147 Default::default()
148 };
149
150 quote! {
151 #object_impl
152 #free_impl
153 #base_class
154 #sub_class
155 #instantiable
156 #send_sync
157 }
158 };
159
160 let method_table = if class.instantiable || !class.methods.is_empty() {
162 generate_method_table(api, class)
163 } else {
164 Default::default()
165 };
166
167 quote! {
168 #types_and_methods
169 #traits
170 #method_table
171 }
172}
173
174#[rustfmt::skip]
175fn rust_safe_name(name: &str) -> proc_macro2::Ident {
176 match name {
178 "as" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern" | "false" | "fn" | "for" |
180 "if" | "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut" | "pub" | "ref" | "return" |
181 "self" | "Self" | "static" | "struct" | "super" | "trait" | "true" | "type" | "unsafe" | "use" |
182 "where" | "while" |
183
184 "async" | "await" | "dyn" |
186
187 "try" |
189
190 "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv" | "typeof" |
192 "unsized" | "virtual" | "yield"
193 => format_ident!("{}_", name),
194
195 _ => format_ident!("{}", name)
196 }
197}
198
199#[cfg(feature = "debug")]
200#[cfg(test)]
201pub(crate) mod test_prelude {
202 use super::*;
203 use std::io::{BufWriter, Write};
204
205 macro_rules! validate_and_clear_buffer {
206 ($buffer:ident) => {
207 $buffer.flush().unwrap();
208 let content = std::str::from_utf8($buffer.get_ref()).unwrap();
209 if syn::parse_file(&content).is_err() {
210 let mut code_file = std::env::temp_dir();
211 code_file.set_file_name("bad_code.rs");
212 std::fs::write(&code_file, &content).unwrap();
213 panic!(
214 "Could not parse generated code. Check {}",
215 code_file.display()
216 );
217 }
218 $buffer.get_mut().clear();
219 };
220 }
221
222 #[test]
223 fn sanity_test_generated_code() {
224 let api = Api::new(include_str!("../../gdnative-bindings/api.json"));
227 let mut buffer = BufWriter::new(Vec::with_capacity(16384));
228 for class in &api.classes {
229 let mut icalls = HashMap::new();
230
231 let code = generate_module_doc(&class);
232 write!(buffer, "{}", code).unwrap();
233 validate_and_clear_buffer!(buffer);
234
235 let class_doc = generate_class_documentation(&api, &class);
236 write!(buffer, "{}", code).unwrap();
237 write!(buffer, "{}", quote! { struct StructWithDocs {} }).unwrap();
238 validate_and_clear_buffer!(buffer);
239
240 let code = generate_class_struct(&class, class_doc);
241 write!(buffer, "{}", code).unwrap();
242 validate_and_clear_buffer!(buffer);
243
244 let code = generate_enums(&class);
245 write!(buffer, "{}", code).unwrap();
246 validate_and_clear_buffer!(buffer);
247
248 if !class.constants.is_empty() {
249 let code = generate_class_constants(&class);
250 write!(buffer, "{}", code).unwrap();
251 validate_and_clear_buffer!(buffer);
252 }
253
254 let code = generate_class_impl(&class, &mut icalls, None);
255 write!(buffer, "{}", code).unwrap();
256 validate_and_clear_buffer!(buffer);
257
258 let code = generate_godot_object_impl(&class);
260 write!(buffer, "{}", code).unwrap();
261 validate_and_clear_buffer!(buffer);
262
263 let code = generate_queue_free_impl(&api, &class);
264 write!(buffer, "{}", code).unwrap();
265 validate_and_clear_buffer!(buffer);
266
267 if !class.base_class.is_empty() {
268 let code = generate_deref_impl(&class);
269 write!(buffer, "{}", code).unwrap();
270 validate_and_clear_buffer!(buffer);
271 }
272
273 if class.instantiable {
275 let code = generate_instantiable_impl(&class);
276 write!(buffer, "{}", code).unwrap();
277 validate_and_clear_buffer!(buffer);
278 }
279
280 let code = generate_method_table(&api, &class);
282 write!(buffer, "{}", code).unwrap();
283 validate_and_clear_buffer!(buffer);
284
285 for (name, sig) in icalls {
286 let code = generate_icall(name, sig);
287 write!(buffer, "{}", code).unwrap();
288 validate_and_clear_buffer!(buffer);
289 }
290 }
291 }
292}