Skip to main content

gdnative_bindings_generator/
lib.rs

1//! Internal API bindings generator for the godot-rust bindings.
2//!
3//! # Creating custom binding crates
4//!
5//! It's possible to create custom binding crates without forking the repository by passing
6//! custom `api.json` data to `Api::new()`. The JSON data can be generated from Godot using
7//! the following command:
8//!
9//! `/path/to/godot --gdnative-generate-json-api /path/to/api.json`
10//!
11//! *Please note that The generator is an internal dependency.* As such, it is not covered
12//! by semver guarantees of the main `gdnative` crate. When using custom binding crates, care
13//! must be taken to ensure that the version of the generator matches the one specified in
14//! the `Cargo.toml` of the `gdnative` crate exactly, even for updates that are considered
15//! non-breaking in the `gdnative` crate.
16
17mod 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    // types and methods
99    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    // traits
124    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        // Instantiable
138        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    // method table for classes with functions
161    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    // Keywords obtained from https://doc.rust-lang.org/reference/keywords.html
177    match name {
178        // Lexer 2015
179        "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        // Lexer 2018
185        "async" | "await" | "dyn" |
186        
187        // Lexer 2018+
188        "try" |
189        
190        // Reserved words
191        "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        // Tests whether each generated snippet individually constitutes a valid AST representation of Rust code
225
226        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            // traits
259            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            // Instantiable
274            if class.instantiable {
275                let code = generate_instantiable_impl(&class);
276                write!(buffer, "{}", code).unwrap();
277                validate_and_clear_buffer!(buffer);
278            }
279
280            // icalls and method table
281            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}