gdnative_bindings_generator/
api.rs

1use super::classes::generate_enum_name;
2use miniserde::Deserialize;
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use std::collections::{HashMap, HashSet};
6
7miniserde::make_place!(Place);
8
9pub struct Api {
10    pub classes: Vec<GodotClass>,
11    pub api_underscore: HashSet<String>,
12}
13
14impl Api {
15    /// Construct an `Api` instance from JSON data.
16    ///
17    /// The JSON data can be generated from Godot using the following command:
18    ///
19    /// `/path/to/godot --gdnative-generate-json-api /path/to/api.json`
20    ///
21    /// # Panics
22    ///
23    /// If the `data` is not valid JSON data the function will panic.
24    pub fn new(data: &str) -> Self {
25        let mut api = Self {
26            classes: miniserde::json::from_str(data).expect("Invalid JSON data"),
27            api_underscore: Default::default(),
28        };
29
30        api.strip_leading_underscores();
31        api.generate_module_names();
32
33        api.classes
34            .iter_mut()
35            .flat_map(|class| class.enums.iter_mut())
36            .for_each(|e| e.strip_common_prefix());
37
38        api
39    }
40
41    pub fn find_class(&self, name: &str) -> Option<&GodotClass> {
42        self.classes.iter().find(|&class| class.name == name)
43    }
44
45    pub fn class_inherits(&self, class: &GodotClass, base_class_name: &str) -> bool {
46        if class.base_class == base_class_name {
47            return true;
48        }
49
50        if let Some(parent) = self.find_class(&class.base_class) {
51            return self.class_inherits(parent, base_class_name);
52        }
53
54        false
55    }
56
57    fn strip_leading_underscores(&mut self) {
58        for class in &mut self.classes {
59            if class.name.starts_with('_') {
60                class.name = class.name[1..].to_string();
61                self.api_underscore.insert(class.name.clone());
62            }
63            for method in &mut class.methods {
64                if method.return_type.starts_with('_') {
65                    method.return_type = method.return_type[1..].to_string();
66                }
67                for arg in &mut method.arguments {
68                    if arg.ty.starts_with('_') {
69                        arg.ty = arg.ty[1..].to_string();
70                    }
71                }
72            }
73        }
74    }
75
76    fn generate_module_names(&mut self) {
77        self.classes
78            .iter_mut()
79            .for_each(|class| class.generate_module_name());
80    }
81}
82
83#[derive(Deserialize, Debug)]
84pub struct GodotClass {
85    pub name: String,
86    pub base_class: String,
87    pub api_type: String,
88    pub singleton: bool,
89    pub is_reference: bool,
90    #[serde(rename = "instanciable")]
91    pub instantiable: bool,
92
93    pub properties: Vec<Property>,
94    pub methods: Vec<GodotMethod>,
95    pub enums: Vec<Enum>,
96    pub constants: HashMap<ConstantName, ConstantValue>,
97
98    module_name: Option<String>,
99    base_class_module_name: Option<String>,
100}
101
102impl GodotClass {
103    pub fn generate_module_name(&mut self) {
104        let module_name = module_name_from_class_name(&self.name);
105        self.module_name.replace(module_name);
106
107        let base_class_module_name = module_name_from_class_name(&self.base_class);
108        self.base_class_module_name.replace(base_class_module_name);
109    }
110
111    pub fn module(&self) -> &str {
112        self.module_name
113            .as_ref()
114            .expect("Module Names should have been generated.")
115    }
116
117    pub fn base_class_module(&self) -> &str {
118        self.base_class_module_name
119            .as_ref()
120            .expect("Module Names should have been generated.")
121    }
122
123    /// Returns the name of the base class if `base_class` is not empty. Returns `None` otherwise.
124    pub fn base_class_name(&self) -> Option<&str> {
125        if self.base_class.is_empty() {
126            None
127        } else {
128            Some(&self.base_class)
129        }
130    }
131
132    pub fn is_singleton_thread_safe(&self) -> bool {
133        assert!(self.singleton, "class is not a singleton");
134        !matches!(
135            self.name.as_str(),
136            "VisualServer" | "PhysicsServer" | "Physics3DServer" | "Physics2DServer"
137        )
138    }
139
140    /// Returns the base class from `api` if `base_class` is not empty. Returns `None` otherwise.
141    pub fn base_class<'a>(&self, api: &'a Api) -> Option<&'a Self> {
142        self.base_class_name()
143            .map(|name| api.find_class(name).expect("base class should exist"))
144    }
145
146    pub fn is_refcounted(&self) -> bool {
147        self.is_reference || &self.name == "Reference"
148    }
149
150    pub fn is_pointer_safe(&self) -> bool {
151        self.is_refcounted() || self.singleton
152    }
153
154    pub fn is_getter(&self, name: &str) -> bool {
155        self.properties.iter().any(|p| p.getter == name)
156    }
157
158    /// Whether there is a snake_case module containing related symbols (nested types in C++)
159    pub fn has_related_module(&self) -> bool {
160        !self.enums.is_empty()
161    }
162}
163
164pub type ConstantName = String;
165pub type ConstantValue = i64;
166
167#[derive(PartialEq, Eq, Deserialize, Debug)]
168pub struct Enum {
169    pub name: String,
170    pub values: HashMap<String, i64>,
171}
172
173impl core::cmp::Ord for Enum {
174    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
175        core::cmp::Ord::cmp(&self.name, &other.name)
176    }
177}
178
179impl core::cmp::PartialOrd for Enum {
180    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
181        core::cmp::PartialOrd::partial_cmp(&self.name, &other.name)
182    }
183}
184
185impl Enum {
186    pub fn strip_common_prefix(&mut self) {
187        // If there is only 1 variant, there are no 'common' prefixes.
188        if self.values.len() <= 1 {
189            return;
190        }
191
192        let mut variants: Vec<_> = self.values.iter().map(|v| (&v.0[..], *v.1)).collect();
193
194        // Build a map of prefix to occurrence
195        loop {
196            let underscore_index = variants[0].0.chars().enumerate().find_map(|(index, ch)| {
197                if ch == '_' {
198                    Some(index)
199                } else {
200                    None
201                }
202            });
203
204            let underscore_index = match underscore_index {
205                Some(index) if index > 0 => index,
206                Some(_) | None => break,
207            };
208
209            // Get a slice up to and including the `_`
210            let prefix = &variants[0].0[..=underscore_index];
211
212            if !variants.iter().all(|v| v.0.starts_with(prefix)) {
213                break;
214            }
215
216            // remove common prefix from variants
217            variants.iter_mut().for_each(|(ref mut name, _)| {
218                *name = &name[prefix.len()..];
219            });
220        }
221
222        let new_variants: HashMap<_, _> = variants
223            .into_iter()
224            .map(|(name, value)| {
225                let starts_with_number = name
226                    .chars()
227                    .next()
228                    .expect("name should not be empty")
229                    .is_numeric();
230
231                let capacity = if starts_with_number {
232                    name.len() + 1
233                } else {
234                    name.len()
235                };
236
237                let mut n = String::with_capacity(capacity);
238
239                // prefix numeric enum variants with `_`
240                if starts_with_number {
241                    n.push('_');
242                }
243
244                n.push_str(name);
245                (n, value)
246            })
247            .collect();
248
249        self.values = new_variants;
250    }
251}
252
253#[derive(Deserialize, Debug)]
254pub struct Property {
255    pub name: String,
256    #[serde(rename = "type")]
257    pub type_: String,
258    pub getter: String,
259    pub setter: String,
260    pub index: i64,
261}
262
263#[derive(Deserialize, Debug)]
264pub struct GodotMethod {
265    pub name: String,
266    pub return_type: String,
267
268    pub is_editor: bool,
269    pub is_noscript: bool,
270    pub is_const: bool,
271    pub is_reverse: bool,
272    pub is_virtual: bool,
273    pub has_varargs: bool,
274
275    pub arguments: Vec<GodotArgument>,
276}
277
278#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
279pub struct MethodName<'a> {
280    pub rust_name: &'a str,
281    pub original_name: &'a str,
282}
283
284impl GodotMethod {
285    pub fn get_name(&self) -> MethodName {
286        // GDScript and NativeScript have ::new methods but we want to reserve
287        // the name for the constructors.
288        if &self.name == "new" {
289            return MethodName {
290                rust_name: "_new",
291                original_name: "new",
292            };
293        }
294
295        MethodName {
296            rust_name: &self.name,
297            original_name: &self.name,
298        }
299    }
300
301    pub fn get_return_type(&self) -> Ty {
302        Ty::from_src(&self.return_type)
303    }
304}
305
306#[derive(Deserialize, Debug)]
307pub struct GodotArgument {
308    pub name: String,
309    #[serde(rename = "type")]
310    pub ty: String,
311    pub has_default_value: bool,
312    pub default_value: String,
313}
314
315impl GodotArgument {
316    pub fn get_type(&self) -> Ty {
317        Ty::from_src(&self.ty)
318    }
319}
320
321#[derive(Clone, Eq, PartialEq)]
322pub enum Ty {
323    Void,
324    String,
325    F64,
326    I64,
327    Bool,
328    Vector2,
329    Vector3,
330    Vector3Axis,
331    Quat,
332    Transform,
333    Transform2D,
334    Rect2,
335    Plane,
336    Basis,
337    Color,
338    NodePath,
339    Variant,
340    Aabb,
341    Rid,
342    VariantArray,
343    Dictionary,
344    ByteArray,
345    StringArray,
346    Vector2Array,
347    Vector3Array,
348    ColorArray,
349    Int32Array,
350    Float32Array,
351    Result,
352    VariantType,
353    VariantOperator,
354    Enum(syn::TypePath),
355    Object(syn::TypePath),
356}
357
358impl Ty {
359    // Note: there is some code duplication with GodotXmlDocs::translate_type() in class_docs.rs
360    pub fn from_src(src: &str) -> Self {
361        match src {
362            "void" => Ty::Void,
363            "String" => Ty::String,
364            "float" => Ty::F64,
365            "int" => Ty::I64,
366            "bool" => Ty::Bool,
367            "Vector2" => Ty::Vector2,
368            "Vector3" => Ty::Vector3,
369            "Quat" => Ty::Quat,
370            "Transform" => Ty::Transform,
371            "Transform2D" => Ty::Transform2D,
372            "Rect2" => Ty::Rect2,
373            "Plane" => Ty::Plane,
374            "Basis" => Ty::Basis,
375            "Color" => Ty::Color,
376            "NodePath" => Ty::NodePath,
377            "Variant" => Ty::Variant,
378            "AABB" => Ty::Aabb,
379            "RID" => Ty::Rid,
380            "Array" => Ty::VariantArray,
381            "Dictionary" => Ty::Dictionary,
382            "PoolByteArray" => Ty::ByteArray,
383            "PoolStringArray" => Ty::StringArray,
384            "PoolVector2Array" => Ty::Vector2Array,
385            "PoolVector3Array" => Ty::Vector3Array,
386            "PoolColorArray" => Ty::ColorArray,
387            "PoolIntArray" => Ty::Int32Array,
388            "PoolRealArray" => Ty::Float32Array,
389            "enum.Error" => Ty::Result,
390            "enum.Variant::Type" => Ty::VariantType,
391            "enum.Variant::Operator" => Ty::VariantOperator,
392            "enum.Vector3::Axis" => Ty::Vector3Axis,
393            ty if ty.starts_with("enum.") => {
394                // Enums may reference known types (above list), check if it's a known type first
395                let mut split = ty[5..].split("::");
396                let class_name = split.next().unwrap();
397                let enum_raw_name = split.next().unwrap();
398                let name = format_ident!("{}", generate_enum_name(class_name, enum_raw_name));
399                let module = format_ident!("{}", module_name_from_class_name(class_name));
400                // Is it a known type?
401                match Ty::from_src(class_name) {
402                    Ty::Enum(_) | Ty::Object(_) => {
403                        Ty::Enum(syn::parse_quote! { crate::generated::#module::#name })
404                    }
405                    _ => Ty::Enum(syn::parse_quote! { #module::#name }),
406                }
407            }
408            ty => {
409                let ty = format_ident!("{}", ty);
410                Ty::Object(syn::parse_quote! { crate::generated::#ty })
411            }
412        }
413    }
414
415    pub fn to_rust(&self) -> syn::Type {
416        match self {
417            Ty::Void => syn::parse_quote! {()},
418            Ty::String => syn::parse_quote! { GodotString },
419            Ty::F64 => syn::parse_quote! { f64 },
420            Ty::I64 => syn::parse_quote! { i64 },
421            Ty::Bool => syn::parse_quote! { bool },
422            Ty::Vector2 => syn::parse_quote! { Vector2 },
423            Ty::Vector3 => syn::parse_quote! { Vector3 },
424            Ty::Vector3Axis => syn::parse_quote! { Axis },
425            Ty::Quat => syn::parse_quote! { Quat },
426            Ty::Transform => syn::parse_quote! { Transform },
427            Ty::Transform2D => syn::parse_quote! { Transform2D },
428            Ty::Rect2 => syn::parse_quote! { Rect2 },
429            Ty::Plane => syn::parse_quote! { Plane },
430            Ty::Basis => syn::parse_quote! { Basis },
431            Ty::Color => syn::parse_quote! { Color },
432            Ty::NodePath => syn::parse_quote! { NodePath },
433            Ty::Variant => syn::parse_quote! { Variant },
434            Ty::Aabb => syn::parse_quote! { Aabb },
435            Ty::Rid => syn::parse_quote! { Rid },
436            Ty::VariantArray => syn::parse_quote! { VariantArray },
437            Ty::Dictionary => syn::parse_quote! { Dictionary },
438            Ty::ByteArray => syn::parse_quote! { PoolArray<u8> },
439            Ty::StringArray => syn::parse_quote! { PoolArray<GodotString> },
440            Ty::Vector2Array => syn::parse_quote! { PoolArray<Vector2> },
441            Ty::Vector3Array => syn::parse_quote! { PoolArray<Vector3> },
442            Ty::ColorArray => syn::parse_quote! { PoolArray<Color> },
443            Ty::Int32Array => syn::parse_quote! { PoolArray<i32> },
444            Ty::Float32Array => syn::parse_quote! { PoolArray<f32> },
445            Ty::Result => syn::parse_quote! { GodotResult },
446            Ty::VariantType => syn::parse_quote! { VariantType },
447            Ty::VariantOperator => syn::parse_quote! { VariantOperator },
448            Ty::Enum(path) => syn::parse_quote! { #path },
449            Ty::Object(path) => {
450                syn::parse_quote! { Option<Ref<#path, ownership::Shared>> }
451            }
452        }
453    }
454
455    pub fn to_rust_arg(&self) -> syn::Type {
456        match self {
457            Ty::Variant => syn::parse_quote! { impl OwnedToVariant },
458            Ty::NodePath => syn::parse_quote! { impl Into<NodePath> },
459            Ty::String => syn::parse_quote! { impl Into<GodotString> },
460            Ty::Object(ref name) => {
461                syn::parse_quote! { impl AsArg<#name> }
462            }
463            _ => self.to_rust(),
464        }
465    }
466
467    pub fn to_icall_arg(&self) -> syn::Type {
468        match self {
469            Ty::Object(_) => syn::parse_quote! { *mut sys::godot_object },
470            _ => self.to_rust(),
471        }
472    }
473
474    pub fn to_icall_return(&self) -> syn::Type {
475        match self {
476            Ty::Void => syn::parse_quote! { () },
477            Ty::String => syn::parse_quote! { sys::godot_string },
478            Ty::F64 => syn::parse_quote! { f64 },
479            Ty::I64 => syn::parse_quote! { i64 },
480            Ty::Bool => syn::parse_quote! { sys::godot_bool },
481            Ty::Vector2 => syn::parse_quote! { sys::godot_vector2 },
482            Ty::Vector3 => syn::parse_quote! { sys::godot_vector3 },
483
484            Ty::Quat => syn::parse_quote! { sys::godot_quat },
485            Ty::Transform => syn::parse_quote! { sys::godot_transform },
486            Ty::Transform2D => syn::parse_quote! { sys::godot_transform2d },
487            Ty::Rect2 => syn::parse_quote! { sys::godot_rect2 },
488            Ty::Plane => syn::parse_quote! { sys::godot_plane },
489            Ty::Basis => syn::parse_quote! { sys::godot_basis },
490            Ty::Color => syn::parse_quote! { sys::godot_color },
491            Ty::NodePath => syn::parse_quote! { sys::godot_node_path },
492            Ty::Variant => syn::parse_quote! { sys::godot_variant },
493            Ty::Aabb => syn::parse_quote! { sys::godot_aabb },
494            Ty::Rid => syn::parse_quote! { sys::godot_rid },
495            Ty::VariantArray => syn::parse_quote! { sys::godot_array },
496            Ty::Dictionary => syn::parse_quote! { sys::godot_dictionary },
497            Ty::ByteArray => syn::parse_quote! { sys::godot_pool_byte_array },
498            Ty::StringArray => syn::parse_quote! { sys::godot_pool_string_array },
499            Ty::Vector2Array => syn::parse_quote! { sys::godot_pool_vector2_array },
500            Ty::Vector3Array => syn::parse_quote! { sys::godot_pool_vector3_array },
501            Ty::ColorArray => syn::parse_quote! { sys::godot_pool_color_array },
502            Ty::Int32Array => syn::parse_quote! { sys::godot_pool_int_array },
503            Ty::Float32Array => syn::parse_quote! { sys::godot_pool_real_array },
504
505            Ty::Vector3Axis | Ty::Result | Ty::VariantType | Ty::VariantOperator | Ty::Enum(_) => {
506                syn::parse_quote! { i64 }
507            }
508
509            Ty::Object(_) => syn::parse_quote! { *mut sys::godot_object },
510        }
511    }
512
513    pub fn to_sys(&self) -> Option<syn::Type> {
514        match self {
515            Ty::Void => None,
516            Ty::String => Some(syn::parse_quote! { sys::godot_string }),
517            Ty::F64 => Some(syn::parse_quote! { sys::godot_real }),
518            Ty::I64 => Some(syn::parse_quote! { sys::godot_int }),
519            Ty::Bool => Some(syn::parse_quote! { sys::godot_bool }),
520            Ty::Vector2 => Some(syn::parse_quote! { sys::godot_vector2 }),
521            Ty::Vector3 => Some(syn::parse_quote! { sys::godot_vector3 }),
522            Ty::Vector3Axis => None,
523            Ty::Quat => Some(syn::parse_quote! { sys::godot_quat }),
524            Ty::Transform => Some(syn::parse_quote! { sys::godot_transform }),
525            Ty::Transform2D => Some(syn::parse_quote! { sys::godot_transform2d }),
526            Ty::Rect2 => Some(syn::parse_quote! { sys::godot_rect2 }),
527            Ty::Plane => Some(syn::parse_quote! { sys::godot_plane }),
528            Ty::Basis => Some(syn::parse_quote! { sys::godot_basis }),
529            Ty::Color => Some(syn::parse_quote! { sys::godot_color }),
530            Ty::NodePath => Some(syn::parse_quote! { sys::godot_node_path }),
531            Ty::Variant => Some(syn::parse_quote! { sys::godot_variant }),
532            Ty::Aabb => Some(syn::parse_quote! { sys::godot_aabb }),
533            Ty::Rid => Some(syn::parse_quote! { sys::godot_rid }),
534            Ty::VariantArray => Some(syn::parse_quote! { sys::godot_array }),
535            Ty::Dictionary => Some(syn::parse_quote! { sys::godot_dictionary }),
536            Ty::ByteArray => Some(syn::parse_quote! { sys::godot_pool_byte_array }),
537            Ty::StringArray => Some(syn::parse_quote! { sys::godot_pool_string_array }),
538            Ty::Vector2Array => Some(syn::parse_quote! { sys::godot_pool_vector2_array }),
539            Ty::Vector3Array => Some(syn::parse_quote! { sys::godot_pool_vector3_array }),
540            Ty::ColorArray => Some(syn::parse_quote! { sys::godot_pool_color_array }),
541            Ty::Int32Array => Some(syn::parse_quote! { sys::godot_pool_int_array }),
542            Ty::Float32Array => Some(syn::parse_quote! { sys::godot_pool_real_array }),
543            Ty::Result => Some(syn::parse_quote! { sys::godot_error }),
544            Ty::VariantType => Some(syn::parse_quote! { sys::variant_type }),
545            Ty::VariantOperator => Some(syn::parse_quote! { sys::godot_variant_operator }),
546            Ty::Enum(_) => None,
547            Ty::Object(_) => Some(syn::parse_quote! { sys::godot_object }),
548        }
549    }
550
551    pub fn to_return_post(&self) -> TokenStream {
552        match self {
553            Ty::Void => Default::default(),
554            Ty::F64 | Ty::I64 | Ty::Bool => {
555                quote! { ret as _ }
556            }
557            Ty::Enum(path) => {
558                quote! { #path(ret) }
559            }
560
561            Ty::Vector2
562            | Ty::Vector3
563            | Ty::Transform
564            | Ty::Transform2D
565            | Ty::Quat
566            | Ty::Aabb
567            | Ty::Rect2
568            | Ty::Basis
569            | Ty::Plane
570            | Ty::Color => {
571                quote! { mem::transmute(ret) }
572            }
573            Ty::Vector3Axis => {
574                quote! { mem::transmute(ret as u32) }
575            }
576            Ty::Rid => {
577                quote! { Rid::from_sys(ret) }
578            }
579            Ty::String
580            | Ty::NodePath
581            | Ty::VariantArray
582            | Ty::Dictionary
583            | Ty::ByteArray
584            | Ty::StringArray
585            | Ty::Vector2Array
586            | Ty::Vector3Array
587            | Ty::ColorArray
588            | Ty::Int32Array
589            | Ty::Float32Array
590            | Ty::Variant => {
591                let rust_ty = self.to_rust();
592                quote! {
593                    <#rust_ty>::from_sys(ret)
594                }
595            }
596            Ty::Object(ref path) => {
597                quote! {
598                    ptr::NonNull::new(ret)
599                        .map(|sys| <Ref<#path, ownership::Shared>>::move_from_sys(sys))
600                }
601            }
602            Ty::Result => {
603                quote! { GodotError::result_from_sys(ret as _) }
604            }
605            Ty::VariantType => {
606                quote! { VariantType::from_sys(ret as _) }
607            }
608            Ty::VariantOperator => {
609                quote! {
610                    VariantOperator::try_from_sys(ret as _).expect("enum variant should be valid")
611                }
612            }
613        }
614    }
615
616    pub fn to_return_post_variant(&self) -> TokenStream {
617        match self {
618            Ty::Void => Default::default(),
619            Ty::F64 | Ty::I64 | Ty::Bool => {
620                let rust_type = self.to_rust();
621                quote! {
622                    <#rust_type>::coerce_from_variant(&ret)
623                }
624            }
625
626            // The `sys` type aliases here can point to different types depending on the platform.
627            // Do not simplify to (Linux) primitive types.
628            Ty::Result => {
629                quote! { GodotError::result_from_sys(sys::godot_error::from_variant(&ret).expect("Unexpected variant type")) }
630            }
631            Ty::VariantType => {
632                quote! { VariantType::from_sys(sys::godot_variant_type::from_variant(&ret).expect("Unexpected variant type")) }
633            }
634            Ty::VariantOperator => {
635                quote! {
636                    VariantOperator::try_from_sys(sys::godot_variant_operator::from_variant(&ret).expect("Unexpected variant type"))
637                        .expect("enum variant should be valid")
638                }
639            }
640
641            Ty::Vector3Axis => {
642                quote! {
643                    unsafe {
644                        mem::transmute::<u32, Axis>(u32::from_variant(&ret).expect("Unexpected variant type") as _)
645                    }
646                }
647            }
648            _ => {
649                let rust_type = self.to_rust();
650                // always a variant returned, use FromVariant
651                quote! {
652                    <#rust_type>::from_variant(&ret).expect("Unexpected variant type")
653                }
654            }
655        }
656    }
657}
658
659pub fn module_name_from_class_name(class_name: &str) -> String {
660    // Remove underscores and make peekable
661    let mut class_chars = class_name.bytes().filter(|&ch| ch != b'_').peekable();
662
663    // 2-lookbehind
664    let mut previous: [Option<u8>; 2] = [None, None]; // previous-previous, previous
665
666    // None is not upper or number
667    #[inline(always)]
668    fn up_or_num<T>(ch: T) -> bool
669    where
670        T: Into<Option<u8>>,
671    {
672        let ch = ch.into();
673        match ch {
674            Some(ch) => ch.is_ascii_digit() || ch.is_ascii_uppercase(),
675            None => false,
676        }
677    }
678
679    // None is lowercase
680    #[inline(always)]
681    fn is_lowercase_or<'a, T>(ch: T, default: bool) -> bool
682    where
683        T: Into<Option<&'a u8>>,
684    {
685        let ch = ch.into();
686        match ch {
687            Some(ch) => ch.is_ascii_lowercase(),
688            None => default,
689        }
690    }
691
692    let mut result = Vec::with_capacity(class_name.len());
693    while let Some(current) = class_chars.next() {
694        let next = class_chars.peek();
695
696        let [two_prev, one_prev] = previous;
697
698        // See tests for cases covered
699        let caps_to_lowercase = up_or_num(one_prev)
700            && up_or_num(current)
701            && is_lowercase_or(next, false)
702            && !is_lowercase_or(&two_prev, true);
703
704        // Add an underscore for Lowercase folowed by Uppercase|Num
705        // Node2D => node_2d (numbers are considered uppercase)
706        let lower_to_uppercase = is_lowercase_or(&one_prev, false) && up_or_num(current);
707
708        if caps_to_lowercase || lower_to_uppercase {
709            result.push(b'_');
710        }
711        result.push(current.to_ascii_lowercase());
712
713        // Update the look-behind
714        previous = [previous[1], Some(current)];
715    }
716
717    let mut result = String::from_utf8(result).unwrap();
718
719    // There are a few cases where the conversions do not work:
720    // * VisualShaderNodeVec3Uniform => visual_shader_node_vec_3_uniform
721    // * VisualShaderNodeVec3Constant => visual_shader_node_vec_3_constant
722    if let Some(range) = result.find("_vec_3").map(|i| i..i + 6) {
723        result.replace_range(range, "_vec3_")
724    }
725    if let Some(range) = result.find("gd_native").map(|i| i..i + 9) {
726        result.replace_range(range, "gdnative")
727    }
728    if let Some(range) = result.find("gd_script").map(|i| i..i + 9) {
729        result.replace_range(range, "gdscript")
730    }
731
732    // To prevent clobbering `gdnative` during a glob import we rename it to `gdnative_`
733    if result == "gdnative" {
734        return "gdnative_".into();
735    }
736
737    result
738}
739
740#[cfg(test)]
741mod tests {
742    use super::*;
743
744    #[test]
745    fn module_name_generator() {
746        let tests = vec![
747            // A number of test cases to cover some possibilities:
748            // * Underscores are removed
749            // * First character is always lowercased
750            // * lowercase to an uppercase inserts an underscore
751            //   - FooBar => foo_bar
752            // * two capital letter words does not separate the capital letters:
753            //   - FooBBaz => foo_bbaz (lower, cap, cap, lower)
754            // * many-capital letters to lowercase inserts an underscore before the last uppercase letter:
755            //   - FOOBar => boo_bar
756            // underscores
757            ("Ab_Cdefg", "ab_cdefg"),
758            ("_Abcd", "abcd"),
759            ("Abcd_", "abcd"),
760            // first and last
761            ("Abcdefg", "abcdefg"),
762            ("abcdefG", "abcdef_g"),
763            // more than 2 caps
764            ("ABCDefg", "abc_defg"),
765            ("AbcDEFg", "abc_de_fg"),
766            ("AbcdEF10", "abcd_ef10"),
767            ("AbcDEFG", "abc_defg"),
768            ("ABCDEFG", "abcdefg"),
769            ("ABC", "abc"),
770            // Lowercase to an uppercase
771            ("AbcDefg", "abc_defg"),
772            // Only 2 caps
773            ("ABcdefg", "abcdefg"),
774            ("ABcde2G", "abcde_2g"),
775            ("AbcDEfg", "abc_defg"),
776            ("ABcDe2G", "abc_de_2g"),
777            ("abcdeFG", "abcde_fg"),
778            ("AB", "ab"),
779            // Lowercase to an uppercase
780            ("AbcdefG", "abcdef_g"), // PosX => pos_x
781            // text changes
782            ("FooVec3Uni", "foo_vec3_uni"),
783            ("GDNative", "gdnative_"),
784            ("GDScript", "gdscript"),
785        ];
786        tests.iter().for_each(|(class_name, expected)| {
787            let actual = module_name_from_class_name(class_name);
788            assert_eq!(*expected, actual, "Input: {class_name}");
789        });
790    }
791}