Skip to content

Commit 8074129

Browse files
committed
Replace regex-based C header parsing with JSON pipeline
Wire JSON-based code generator into the build pipeline, replacing the regex-based C header parser. Remove regex dependency from godot-codegen.
1 parent 44066cc commit 8074129

7 files changed

Lines changed: 1156 additions & 1272 deletions

File tree

godot-codegen/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ heck = { workspace = true }
2828
nanoserde = { workspace = true }
2929
proc-macro2 = { workspace = true }
3030
quote = { workspace = true }
31-
regex = { workspace = true }
3231

3332
[build-dependencies]
3433
godot-bindings = { path = "../godot-bindings", version = "=0.5.1" } # emit_godot_version_cfg

godot-codegen/src/generator/extension_interface.rs

Lines changed: 5 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -5,204 +5,19 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use std::fs;
98
use std::path::Path;
109

11-
use proc_macro2::{Ident, Literal, TokenStream};
12-
use quote::quote;
13-
use regex::Regex;
10+
use proc_macro2::TokenStream;
1411

1512
use crate::SubmitFn;
16-
use crate::util::{ident, make_load_safety_doc};
13+
use crate::generator::header_codegen;
14+
use crate::models::header_json::HeaderJson;
1715

1816
pub fn generate_sys_interface_file(
19-
h_path: &Path,
17+
header: &HeaderJson,
2018
sys_gen_path: &Path,
2119
submit_fn: &mut SubmitFn,
2220
) {
23-
let code = generate_proc_address_funcs(h_path);
21+
let code = header_codegen::generate_sys_interface_from_json(header);
2422
submit_fn(sys_gen_path.join("interface.rs"), code);
2523
}
26-
27-
// ----------------------------------------------------------------------------------------------------------------------------------------------
28-
// Implementation
29-
30-
struct GodotFuncPtr {
31-
name: Ident,
32-
func_ptr_ty: Ident,
33-
doc: String,
34-
}
35-
36-
fn generate_proc_address_funcs(h_path: &Path) -> TokenStream {
37-
let header_code = fs::read_to_string(h_path)
38-
.expect("failed to read gdextension_interface.h for header parsing");
39-
let func_ptrs = parse_function_pointers(&header_code);
40-
41-
let mut fptr_decls = vec![];
42-
let mut fptr_inits = vec![];
43-
for fptr in func_ptrs {
44-
let GodotFuncPtr {
45-
name,
46-
func_ptr_ty,
47-
doc,
48-
} = fptr;
49-
50-
let name_str = Literal::byte_string(format!("{name}\0").as_bytes());
51-
52-
let decl = quote! {
53-
#[doc = #doc]
54-
pub #name: crate::#func_ptr_ty,
55-
};
56-
57-
// SAFETY: transmute relies on Option<F1> and Option<F2> having the same layout.
58-
// It might be better to transmute the raw function pointers, but then we have no type names.
59-
let init = quote! {
60-
#name: std::mem::transmute::<
61-
crate::GDExtensionInterfaceFunctionPtr,
62-
crate::#func_ptr_ty
63-
>(get_proc_address(crate::c_str(#name_str))),
64-
};
65-
66-
fptr_decls.push(decl);
67-
fptr_inits.push(init);
68-
}
69-
70-
// Do not derive Copy -- even though the struct is bitwise-copyable, this is rarely needed and may point to an error.
71-
let safety_doc = make_load_safety_doc();
72-
let code = quote! {
73-
pub struct GDExtensionInterface {
74-
#( #fptr_decls )*
75-
}
76-
77-
impl GDExtensionInterface {
78-
#safety_doc
79-
pub(crate) unsafe fn load(
80-
get_proc_address: crate::GDExtensionInterfaceGetProcAddress,
81-
) -> Self {
82-
let get_proc_address = get_proc_address.expect("invalid get_proc_address function pointer");
83-
84-
// SAFETY: transmute relies on GDExtensionInterfaceFunctionPtr and specific function pointer types
85-
// having the same layout (both are Option<unsafe extern "C" fn(...)>).
86-
Self {
87-
#( #fptr_inits )*
88-
}
89-
}
90-
}
91-
};
92-
93-
code
94-
}
95-
96-
fn parse_function_pointers(header_code: &str) -> Vec<GodotFuncPtr> {
97-
// See https://docs.rs/regex/latest/regex for docs.
98-
let regex = Regex::new(
99-
r"(?xms)
100-
# x: ignore whitespace and allow line comments (starting with `#`)
101-
# m: multi-line mode, ^ and $ match start and end of line
102-
# s: . matches newlines; would otherwise require (:?\n|\r\n|\r)
103-
^
104-
# Start of comment /**
105-
/\*\*
106-
# followed by any characters
107-
[^*].*?
108-
# Identifier @name variant_can_convert
109-
@name\s(?P<name>[a-z0-9_]+)
110-
(?P<doc>
111-
.+?
112-
)
113-
#(?:@param\s([a-z0-9_]+))*?
114-
#(?:\n|.)+?
115-
# End of comment */
116-
\*/
117-
.+?
118-
# Return type: typedef GDExtensionBool
119-
# or pointers with space: typedef void *
120-
#typedef\s[A-Za-z0-9_]+?\s\*?
121-
typedef\s[^(]+?
122-
# Function pointer: (*GDExtensionInterfaceVariantCanConvert)
123-
\(\*(?P<type>[A-Za-z0-9_]+?)\)
124-
# Parameters: (GDExtensionVariantType p_from, GDExtensionVariantType p_to);
125-
.+?;
126-
# $ omitted, because there can be comments after `;`
127-
",
128-
)
129-
.unwrap();
130-
131-
let mut func_ptrs = vec![];
132-
for cap in regex.captures_iter(header_code) {
133-
let name = cap.name("name");
134-
let funcptr_ty = cap.name("type");
135-
let doc = cap.name("doc");
136-
137-
let (Some(name), Some(funcptr_ty), Some(doc)) = (name, funcptr_ty, doc) else {
138-
// Skip unparseable ones, instead of breaking build (could just be a /** */ comment around something else)
139-
continue;
140-
};
141-
142-
func_ptrs.push(GodotFuncPtr {
143-
name: ident(name.as_str()),
144-
func_ptr_ty: ident(funcptr_ty.as_str()),
145-
doc: doc.as_str().replace("\n *", "\n").trim().to_string(),
146-
});
147-
}
148-
149-
func_ptrs
150-
}
151-
152-
// fn doxygen_to_rustdoc(c_doc: &str) -> String {
153-
// // Remove leading stars
154-
// let mut doc = c_doc .replace("\n * ", "\n");
155-
//
156-
// // FIXME only compile once
157-
// let param_regex = Regex::new(r#"@p"#)
158-
// }
159-
160-
#[test]
161-
fn test_parse_function_pointers() {
162-
let header_code = r#"
163-
/* INTERFACE: ClassDB Extension */
164-
165-
/**
166-
* @name classdb_register_extension_class
167-
*
168-
* Registers an extension class in the ClassDB.
169-
*
170-
* Provided struct can be safely freed once the function returns.
171-
*
172-
* @param p_library A pointer the library received by the GDExtension's entry point function.
173-
* @param p_class_name A pointer to a StringName with the class name.
174-
* @param p_parent_class_name A pointer to a StringName with the parent class name.
175-
* @param p_extension_funcs A pointer to a GDExtensionClassCreationInfo struct.
176-
*/
177-
typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs);
178-
"#;
179-
180-
let func_ptrs = parse_function_pointers(header_code);
181-
assert_eq!(func_ptrs.len(), 1);
182-
183-
let func_ptr = &func_ptrs[0];
184-
assert_eq!(
185-
func_ptr.name.to_string(),
186-
"classdb_register_extension_class"
187-
);
188-
189-
assert_eq!(
190-
func_ptr.func_ptr_ty.to_string(),
191-
"GDExtensionInterfaceClassdbRegisterExtensionClass"
192-
);
193-
194-
assert_eq!(
195-
func_ptr.doc,
196-
r#"
197-
Registers an extension class in the ClassDB.
198-
199-
Provided struct can be safely freed once the function returns.
200-
201-
@param p_library A pointer the library received by the GDExtension's entry point function.
202-
@param p_class_name A pointer to a StringName with the class name.
203-
@param p_parent_class_name A pointer to a StringName with the parent class name.
204-
@param p_extension_funcs A pointer to a GDExtensionClassCreationInfo struct.
205-
"#
206-
.trim()
207-
);
208-
}

0 commit comments

Comments
 (0)