|
5 | 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. |
6 | 6 | */ |
7 | 7 |
|
8 | | -use std::fs; |
9 | 8 | use std::path::Path; |
10 | 9 |
|
11 | | -use proc_macro2::{Ident, Literal, TokenStream}; |
12 | | -use quote::quote; |
13 | | -use regex::Regex; |
| 10 | +use proc_macro2::TokenStream; |
14 | 11 |
|
15 | 12 | 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; |
17 | 15 |
|
18 | 16 | pub fn generate_sys_interface_file( |
19 | | - h_path: &Path, |
| 17 | + header: &HeaderJson, |
20 | 18 | sys_gen_path: &Path, |
21 | 19 | submit_fn: &mut SubmitFn, |
22 | 20 | ) { |
23 | | - let code = generate_proc_address_funcs(h_path); |
| 21 | + let code = header_codegen::generate_sys_interface_from_json(header); |
24 | 22 | submit_fn(sys_gen_path.join("interface.rs"), code); |
25 | 23 | } |
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