@@ -23,11 +23,14 @@ var regex_getter_setter := RegEx.create_from_string("(.*?[sg]et\\s*=\\s*)(\\w+)(
2323## returns only the super word, excluding the (, as match to make substitution easier
2424var regex_super_call := RegEx .create_from_string ("\\ bsuper(?=\\ s*\\ ()" )
2525
26- ## matches the indented function body
27- ## needs to start from the : of a function definition to work (offset)
28- ## the body of a function is every line that is empty or starts with an indent or comment
26+ ## Matches the indented function body.
27+ ## Needs to start from the : of a function declaration to work (.search() offset param )
28+ ## The body of a function is every line that is empty or starts with an indent or comment
2929var regex_func_body := RegEx .create_from_string ("(?smn)\\ N*(\\ n^(([\\ t #]+\\ N*)|$))*" )
3030
31+ ## Just await between word boundaries
32+ var regex_keyword_await := RegEx .create_from_string ("\\ bawait\\ b" )
33+
3134
3235var hashmap := {}
3336
@@ -56,7 +59,7 @@ func process_script(path: String, enable_hook_check := false) -> String:
5659 var class_prefix := str (hash (path ))
5760 var method_store : Array [String ] = []
5861
59- var getters_setters := collect_getters_and_setters (source_code , regex_getter_setter )
62+ var getters_setters := collect_getters_and_setters (source_code )
6063
6164 var moddable_methods := current_script .get_script_method_list ().filter (
6265 is_func_moddable .bind (source_code , getters_setters )
@@ -68,6 +71,30 @@ func process_script(path: String, enable_hook_check := false) -> String:
6871
6972 var type_string := get_return_type_string (method .return )
7073 var is_static := true if method .flags == METHOD_FLAG_STATIC + METHOD_FLAG_NORMAL else false
74+
75+ var func_def : RegExMatch = match_func_with_whitespace (method .name , source_code )
76+ if not func_def : # Could not regex match a function with that name
77+ continue # Means invalid Script, should never happen
78+
79+ # Processing does not cover methods in subclasses yet.
80+ # If a function with the same name was found in a subclass,
81+ # try again until we find the top level one
82+ var max_loop := 1000
83+ while not is_top_level_func (source_code , func_def .get_start (), is_static ): # indent before "func"
84+ func_def = match_func_with_whitespace (method .name , source_code , func_def .get_end ())
85+ if not func_def or max_loop <= 0 : # Couldn't match any func like before
86+ break # Means invalid Script, should never happen
87+ max_loop -= 1
88+
89+ var func_body_start_index := get_func_body_start_index (func_def .get_end (), source_code )
90+ if func_body_start_index == - 1 : # The function is malformed, opening ( was not closed by )
91+ continue # Means invalid Script, should never happen
92+
93+ var func_body := match_method_body (method .name , func_body_start_index , source_code )
94+ if not func_body : # No indented lines found
95+ continue # Means invalid Script, should never happen
96+
97+ var is_async := is_func_async (func_body .get_string ())
7198 var method_arg_string_with_defaults_and_types := get_function_parameters (method .name , source_code , is_static )
7299 var method_arg_string_names_only := get_function_arg_name_string (method .args )
73100
@@ -77,13 +104,14 @@ func process_script(path: String, enable_hook_check := false) -> String:
77104 push_error (HASH_COLLISION_ERROR % [hashmap [hook_id ], hook_id_data ])
78105 hashmap [hook_id ] = hook_id_data
79106
80- var mod_loader_hook_string := get_mod_loader_hook (
107+ var mod_loader_hook_string := build_mod_hook_string (
81108 method .name ,
82109 method_arg_string_names_only ,
83110 method_arg_string_with_defaults_and_types ,
84111 type_string ,
85112 method .return .usage ,
86113 is_static ,
114+ is_async ,
87115 hook_id ,
88116 METHOD_PREFIX + class_prefix ,
89117 enable_hook_check
@@ -97,15 +125,14 @@ func process_script(path: String, enable_hook_check := false) -> String:
97125 method_store .push_back (method .name )
98126 source_code = edit_vanilla_method (
99127 method .name ,
100- is_static ,
101128 source_code ,
102- regex_func_body ,
103- regex_super_call ,
129+ func_def ,
130+ func_body ,
104131 METHOD_PREFIX + class_prefix
105132 )
106133 source_code_additions += "\n %s " % mod_loader_hook_string
107134
108- # if we have some additions to the code, append them at the end
135+ # If we have some additions to the code, append them at the end
109136 if source_code_additions != "" :
110137 source_code = "%s \n %s \n %s " % [source_code , MOD_LOADER_HOOKS_START_STRING , source_code_additions ]
111138
@@ -126,6 +153,65 @@ static func is_func_moddable(method: Dictionary, source_code: String, getters_se
126153 return true
127154
128155
156+ func is_func_async (func_body_text : String ) -> bool :
157+ if not func_body_text .contains ("await" ):
158+ return false
159+
160+ var lines := func_body_text .split ("\n " )
161+ var in_multiline_string := false
162+ var current_multiline_delimiter := ""
163+
164+ for line : String in lines :
165+ var char_index := 0
166+ while char_index < line .length ():
167+ if in_multiline_string :
168+ # Check if we are exiting the multiline string
169+ if line .substr (char_index ).begins_with (current_multiline_delimiter ):
170+ in_multiline_string = false
171+ char_index += 3
172+ else :
173+ char_index += 1
174+ continue
175+
176+ # Comments: Skip the rest of the line
177+ if line .substr (char_index ).begins_with ("#" ):
178+ break
179+
180+ # Check for multiline string start
181+ if line .substr (char_index ).begins_with ('"""' ) or line .substr (char_index ).begins_with ("'''" ):
182+ in_multiline_string = true
183+ current_multiline_delimiter = line .substr (char_index , 3 )
184+ char_index += 3
185+ continue
186+
187+ # Check for single-quoted strings
188+ if line [char_index ] == '"' or line [char_index ] == "'" :
189+ var delimiter = line [char_index ]
190+ char_index += 1
191+ while char_index < line .length () and line [char_index ] != delimiter :
192+ # Skip escaped quotes
193+ if line [char_index ] == "\\ " :
194+ char_index += 1
195+ char_index += 1
196+ char_index += 1 # Skip the closing quote
197+ continue
198+
199+ # Check for the "await" keyword
200+ if not line .substr (char_index ).begins_with ("await" ):
201+ char_index += 1
202+ continue
203+
204+ # Ensure "await" is a standalone word
205+ var start := char_index - 1 if char_index > 0 else 0
206+ if regex_keyword_await .search (line .substr (start )):
207+ return true # Just return here, we don't need every occurence
208+ # i += 5 # Normal parser: Skip the keyword
209+ else :
210+ char_index += 1
211+
212+ return false
213+
214+
129215static func get_function_arg_name_string (args : Array ) -> String :
130216 var arg_string := ""
131217 for x in args .size ():
@@ -191,86 +277,69 @@ static func get_closing_paren_index(opening_paren_index: int, text: String) -> i
191277 return closing_paren_index
192278
193279
194- static func edit_vanilla_method (
280+ func edit_vanilla_method (
195281 method_name : String ,
196- is_static : bool ,
197282 text : String ,
198- regex_func_body : RegEx ,
199- regex_super_call : RegEx ,
283+ func_def : RegExMatch ,
284+ func_body : RegExMatch ,
200285 prefix := METHOD_PREFIX ,
201- offset := 0
202286) -> String :
203- var func_def := match_func_with_whitespace (method_name , text , offset )
204-
205- if not func_def :
206- return text
207-
208- if not is_top_level_func (text , func_def .get_start (), is_static ):
209- return edit_vanilla_method (
210- method_name ,
211- is_static ,
212- text ,
213- regex_func_body ,
214- regex_super_call ,
215- prefix ,
216- func_def .get_end ()
217- )
218-
219- text = fix_method_super (method_name , func_def .get_end (), text , regex_func_body , regex_super_call )
287+ text = fix_method_super (method_name , func_body , text )
220288 text = text .erase (func_def .get_start (), func_def .get_end () - func_def .get_start ())
221289 text = text .insert (func_def .get_start (), "func %s _%s (" % [prefix , method_name ])
222290
223291 return text
224292
225293
226- static func fix_method_super (method_name : String , func_def_end : int , text : String , regex_func_body : RegEx , regex_super_call : RegEx , offset := 0 ) -> String :
294+ func fix_method_super (method_name : String , func_body : RegExMatch , text : String ) -> String :
295+ return regex_super_call .sub (
296+ text , "super.%s " % method_name ,
297+ true , func_body .get_start (), func_body .get_end ()
298+ )
299+
300+
301+ static func get_func_body_start_index (func_def_end : int , source_code : String ) -> int :
227302 # Shift the func_def_end index back by one to start on the opening parentheses.
228303 # Because the match_func_with_whitespace().get_end() is the index after the opening parentheses.
229- var closing_paren_index := get_closing_paren_index (func_def_end - 1 , text )
304+ var closing_paren_index := get_closing_paren_index (func_def_end - 1 , source_code )
230305 if closing_paren_index == - 1 :
231- return text
232- var func_body_start_index := text .find (":" , closing_paren_index ) + 1
233-
234- var func_body := regex_func_body .search (text , func_body_start_index )
235- if not func_body :
236- return text
237- var func_body_end_index := func_body .get_end ()
306+ return - 1
307+ return source_code .find (":" , closing_paren_index ) + 1
238308
239- text = regex_super_call .sub (
240- text , "super.%s " % method_name ,
241- true , func_body_start_index , func_body_end_index
242- )
243309
244- return text
310+ func match_method_body (method_name : String , func_body_start_index : int , text : String ) -> RegExMatch :
311+ return regex_func_body .search (text , func_body_start_index )
245312
246313
247314static func match_func_with_whitespace (method_name : String , text : String , offset := 0 ) -> RegExMatch :
315+ # Dynamically create the new regex for that specific name
248316 var func_with_whitespace := RegEx .create_from_string ("func\\ s+%s \\ s*\\ (" % method_name )
249-
250- # Search for the function definition
251317 return func_with_whitespace .search (text , offset )
252318
253319
254- static func get_mod_loader_hook (
320+ static func build_mod_hook_string (
255321 method_name : String ,
256322 method_arg_string_names_only : String ,
257323 method_arg_string_with_defaults_and_types : String ,
258324 method_type : String ,
259325 return_prop_usage : int ,
260326 is_static : bool ,
327+ is_async : bool ,
261328 hook_id : int ,
262329 method_prefix := METHOD_PREFIX ,
263330 enable_hook_check := false ,
264331) -> String :
265332 var type_string := " -> %s " % method_type if not method_type .is_empty () else ""
266333 var static_string := "static " if is_static else ""
334+ var await_string := "await " if is_async else ""
335+ var async_string := "_async" if is_async else ""
267336 var return_var := "var %s = " % "return_var" if not method_type .is_empty () or return_prop_usage == 131072 else ""
268337 var method_return := "return " if not method_type .is_empty () or return_prop_usage == 131072 else ""
269338 var hook_check := 'if ModLoaderStore.get("any_mod_hooked") and ModLoaderStore.any_mod_hooked:\n\t\t ' if enable_hook_check else ""
270339
271340 return """
272341{STATIC} func {METHOD_NAME} ({METHOD_PARAMS} ){RETURN_TYPE_STRING} :
273- {HOOK_CHECK}{METHOD_RETURN} _ModLoaderHooks.call_hooks({METHOD_PREFIX} _{METHOD_NAME} , [{METHOD_ARGS} ], {HOOK_ID} )
342+ {HOOK_CHECK}{METHOD_RETURN}{AWAIT} _ModLoaderHooks.call_hooks{ASYNC} ({METHOD_PREFIX} _{METHOD_NAME} , [{METHOD_ARGS} ], {HOOK_ID} )
274343""" .format ({
275344 "METHOD_PREFIX" : method_prefix ,
276345 "METHOD_NAME" : method_name ,
@@ -280,6 +349,8 @@ static func get_mod_loader_hook(
280349 "METHOD_RETURN_VAR" : return_var ,
281350 "METHOD_RETURN" : method_return ,
282351 "STATIC" : static_string ,
352+ "AWAIT" : await_string ,
353+ "ASYNC" : async_string ,
283354 "HOOK_ID" : hook_id ,
284355 "HOOK_CHECK" : hook_check ,
285356 })
@@ -354,7 +425,7 @@ static func get_return_type_string(return_data: Dictionary) -> String:
354425 return "%s%s " % [type_base , type_hint ]
355426
356427
357- static func collect_getters_and_setters (text : String , regex_getter_setter : RegEx ) -> Dictionary :
428+ func collect_getters_and_setters (text : String ) -> Dictionary :
358429 var result := {}
359430 # a valid match has 2 or 4 groups, split into the method names and the rest of the line
360431 # (var example: set = )(example_setter)(, get = )(example_getter)
0 commit comments