use std::fs;
use std::path::Path;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
use regex::Regex;
use crate::SubmitFn;
use crate::util::{ident, make_load_safety_doc};
pub fn generate_sys_interface_file(
h_path: &Path,
sys_gen_path: &Path,
is_godot_4_0: bool,
submit_fn: &mut SubmitFn,
) {
let code = if is_godot_4_0 {
TokenStream::new()
} else {
generate_proc_address_funcs(h_path)
};
submit_fn(sys_gen_path.join("interface.rs"), code);
}
struct GodotFuncPtr {
name: Ident,
func_ptr_ty: Ident,
doc: String,
}
fn generate_proc_address_funcs(h_path: &Path) -> TokenStream {
let header_code = fs::read_to_string(h_path)
.expect("failed to read gdextension_interface.h for header parsing");
let func_ptrs = parse_function_pointers(&header_code);
let mut fptr_decls = vec![];
let mut fptr_inits = vec![];
for fptr in func_ptrs {
let GodotFuncPtr {
name,
func_ptr_ty,
doc,
} = fptr;
let name_str = Literal::byte_string(format!("{name}\0").as_bytes());
let decl = quote! {
#[doc = #doc]
pub #name: crate::#func_ptr_ty,
};
let init = quote! {
#name: std::mem::transmute::<
crate::GDExtensionInterfaceFunctionPtr,
crate::#func_ptr_ty
>(get_proc_address(crate::c_str(#name_str))),
};
fptr_decls.push(decl);
fptr_inits.push(init);
}
let safety_doc = make_load_safety_doc();
let code = quote! {
pub struct GDExtensionInterface {
#( #fptr_decls )*
}
impl GDExtensionInterface {
#safety_doc
pub(crate) unsafe fn load(
get_proc_address: crate::GDExtensionInterfaceGetProcAddress,
) -> Self {
let get_proc_address = get_proc_address.expect("invalid get_proc_address function pointer");
Self {
#( #fptr_inits )*
}
}
}
};
code
}
fn parse_function_pointers(header_code: &str) -> Vec<GodotFuncPtr> {
let regex = Regex::new(
r"(?xms)
# x: ignore whitespace and allow line comments (starting with `#`)
# m: multi-line mode, ^ and $ match start and end of line
# s: . matches newlines; would otherwise require (:?\n|\r\n|\r)
^
# Start of comment /**
/\*\*
# followed by any characters
[^*].*?
# Identifier @name variant_can_convert
@name\s(?P<name>[a-z0-9_]+)
(?P<doc>
.+?
)
#(?:@param\s([a-z0-9_]+))*?
#(?:\n|.)+?
# End of comment */
\*/
.+?
# Return type: typedef GDExtensionBool
# or pointers with space: typedef void *
#typedef\s[A-Za-z0-9_]+?\s\*?
typedef\s[^(]+?
# Function pointer: (*GDExtensionInterfaceVariantCanConvert)
\(\*(?P<type>[A-Za-z0-9_]+?)\)
# Parameters: (GDExtensionVariantType p_from, GDExtensionVariantType p_to);
.+?;
# $ omitted, because there can be comments after `;`
",
)
.unwrap();
let mut func_ptrs = vec![];
for cap in regex.captures_iter(header_code) {
let name = cap.name("name");
let funcptr_ty = cap.name("type");
let doc = cap.name("doc");
let (Some(name), Some(funcptr_ty), Some(doc)) = (name, funcptr_ty, doc) else {
continue;
};
func_ptrs.push(GodotFuncPtr {
name: ident(name.as_str()),
func_ptr_ty: ident(funcptr_ty.as_str()),
doc: doc.as_str().replace("\n *", "\n").trim().to_string(),
});
}
func_ptrs
}
#[test]
fn test_parse_function_pointers() {
let header_code = r#"
/* INTERFACE: ClassDB Extension */
/**
* @name classdb_register_extension_class
*
* Registers an extension class in the ClassDB.
*
* Provided struct can be safely freed once the function returns.
*
* @param p_library A pointer the library received by the GDExtension's entry point function.
* @param p_class_name A pointer to a StringName with the class name.
* @param p_parent_class_name A pointer to a StringName with the parent class name.
* @param p_extension_funcs A pointer to a GDExtensionClassCreationInfo struct.
*/
typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs);
"#;
let func_ptrs = parse_function_pointers(header_code);
assert_eq!(func_ptrs.len(), 1);
let func_ptr = &func_ptrs[0];
assert_eq!(
func_ptr.name.to_string(),
"classdb_register_extension_class"
);
assert_eq!(
func_ptr.func_ptr_ty.to_string(),
"GDExtensionInterfaceClassdbRegisterExtensionClass"
);
assert_eq!(
func_ptr.doc,
r#"
Registers an extension class in the ClassDB.
Provided struct can be safely freed once the function returns.
@param p_library A pointer the library received by the GDExtension's entry point function.
@param p_class_name A pointer to a StringName with the class name.
@param p_parent_class_name A pointer to a StringName with the parent class name.
@param p_extension_funcs A pointer to a GDExtensionClassCreationInfo struct.
"#
.trim()
);
}