Skip to content

Commit 8343f52

Browse files
Qubus0KANAjetzt
authored andcommitted
feat: 🚧 replace super calls
1 parent 088d379 commit 8343f52

File tree

1 file changed

+82
-37
lines changed

1 file changed

+82
-37
lines changed

addons/mod_loader/internal/mod_hook_preprocessor.gd

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,31 @@ extends Object
22

33
const REQUIRE_EXPLICIT_ADDITION := false
44
const METHOD_PREFIX := "vanilla_"
5-
const HASH_COLLISION_ERROR := "MODDING EXPORT ERROR: Hash collision between %s and %s. The collision can be resolved by renaming one of the methods or changing their script's path."
5+
const HASH_COLLISION_ERROR := \
6+
"MODDING EXPORT ERROR: Hash collision between %s and %s. The collision can be resolved by renaming one of the methods or changing their script's path."
7+
const MOD_LOADER_HOOKS_START_STRING := \
8+
"\n# ModLoader Hooks - The following code has been automatically added by the Godot Mod Loader export plugin."
9+
10+
11+
## finds function names used as setters and getters (excluding inline definitions)
12+
## group 2 and 4 contain the xetter names
13+
static var regex_getter_setter := RegEx.create_from_string("(.*?[sg]et\\s*=\\s*)(\\w+)(\\g<1>)?(\\g<2>)?")
14+
15+
## finds every instance where super() is called
16+
## returns only the super word, excluding the (, as match to make substitution easier
17+
static var regex_super_call := RegEx.create_from_string("\\bsuper(?=\\s*\\()")
18+
19+
## matches the indented function body
20+
## needs to start from the : of a function definition to work (offset)
21+
## the body of a function is every line that is empty or starts with an indent or comment
22+
static var regex_func_body := RegEx.create_from_string("(?smn)\\N*(\\n^(([\\t #]+\\N*)|$))*")
623

7-
static var regex_getter_setter: RegEx
824

925
var hashmap := {}
1026

1127

1228
func process_begin() -> void:
1329
hashmap.clear()
14-
regex_getter_setter = RegEx.new()
15-
regex_getter_setter.compile("(.*?[sg]et\\s*=\\s*)(\\w+)(\\g<1>)?(\\g<2>)?")
1630

1731

1832
func process_script(path: String) -> String:
@@ -24,20 +38,26 @@ func process_script(path: String) -> String:
2438
# since the generated methods will fulfill inheritance requirements
2539
var class_prefix := str(hash(path))
2640
var method_store: Array[String] = []
27-
var mod_loader_hooks_start_string := \
28-
"\n# ModLoader Hooks - The following code has been automatically added by the Godot Mod Loader export plugin."
2941

3042
var getters_setters := collect_getters_and_setters(source_code)
3143

32-
for method in current_script.get_script_method_list():
33-
var method_first_line_start := get_index_at_method_start(method.name, source_code)
34-
if method_first_line_start == -1 or method.name in method_store:
35-
continue
44+
var moddable_methods := current_script.get_script_method_list().filter(
45+
func is_func_moddable(method: Dictionary):
46+
if getters_setters.has(method.name):
47+
return false
3648

37-
if getters_setters.has(method.name):
38-
continue
49+
var method_first_line_start := get_index_at_method_start(method.name, source_code)
50+
if method_first_line_start == -1:
51+
return false
52+
53+
if not is_func_marked_moddable(method_first_line_start, source_code):
54+
return false
3955

40-
if not is_func_moddable(method_first_line_start, source_code):
56+
return true
57+
)
58+
59+
for method in moddable_methods:
60+
if method.name in method_store:
4161
continue
4262

4363
var type_string := get_return_type_string(method.return)
@@ -75,12 +95,12 @@ func process_script(path: String) -> String:
7595
# including the methods from the scripts it extends,
7696
# which leads to multiple entries in the list if they are overridden by the child script.
7797
method_store.push_back(method.name)
78-
source_code = prefix_method_name(method.name, is_static, source_code, METHOD_PREFIX + class_prefix)
98+
source_code = edit_vanilla_method(method.name, is_static, source_code, METHOD_PREFIX + class_prefix)
7999
source_code_additions += "\n%s" % mod_loader_hook_string
80100

81101
#if we have some additions to the code, append them at the end
82102
if source_code_additions != "":
83-
source_code = "%s\n%s\n%s" % [source_code,mod_loader_hooks_start_string, source_code_additions]
103+
source_code = "%s\n%s\n%s" % [source_code, MOD_LOADER_HOOKS_START_STRING, source_code_additions]
84104

85105
return source_code
86106

@@ -110,6 +130,25 @@ static func get_function_parameters(method_name: String, text: String, is_static
110130
if not is_top_level_func(text, result.get_start(), is_static):
111131
return get_function_parameters(method_name, text, is_static, result.get_end())
112132

133+
var closing_paren_index := get_closing_paren_index(opening_paren_index, text)
134+
if closing_paren_index == -1:
135+
return ""
136+
137+
# Extract the substring between the parentheses
138+
var param_string := text.substr(opening_paren_index + 1, closing_paren_index - opening_paren_index - 1)
139+
140+
# Clean whitespace characters (spaces, newlines, tabs)
141+
param_string = param_string.strip_edges()\
142+
.replace(" ", "")\
143+
.replace("\n", "")\
144+
.replace("\t", "")\
145+
.replace(",", ", ")\
146+
.replace(":", ": ")
147+
148+
return param_string
149+
150+
151+
static func get_closing_paren_index(opening_paren_index: int, text: String) -> int:
113152
# Use a stack to match parentheses
114153
var stack := []
115154
var closing_paren_index := opening_paren_index
@@ -125,40 +164,46 @@ static func get_function_parameters(method_name: String, text: String, is_static
125164

126165
# If the stack is not empty, that means there's no matching closing parenthesis
127166
if stack.size() != 0:
128-
return ""
167+
return -1
129168

130-
# Extract the substring between the parentheses
131-
var param_string := text.substr(opening_paren_index + 1, closing_paren_index - opening_paren_index - 1)
169+
return closing_paren_index
132170

133-
# Clean whitespace characters (spaces, newlines, tabs)
134-
param_string = param_string.strip_edges()\
135-
.replace(" ", "")\
136-
.replace("\n", "")\
137-
.replace("\t", "")\
138-
.replace(",", ", ")\
139-
.replace(":", ": ")
140171

141-
return param_string
172+
static func edit_vanilla_method(method_name: String, is_static: bool, text: String, prefix := METHOD_PREFIX, offset := 0) -> String:
173+
var func_def := match_func_with_whitespace(method_name, text, offset)
142174

175+
if not func_def:
176+
return text
143177

144-
static func prefix_method_name(method_name: String, is_static: bool, text: String, prefix := METHOD_PREFIX, offset := 0) -> String:
145-
var result := match_func_with_whitespace(method_name, text, offset)
178+
if not is_top_level_func(text, func_def.get_start(), is_static):
179+
return edit_vanilla_method(method_name, is_static, text, prefix, func_def.get_end())
146180

147-
if not result:
148-
return text
181+
text = fix_method_super(method_name, func_def.get_end(), text)
182+
text = text.erase(func_def.get_start(), func_def.get_end() - func_def.get_start())
183+
text = text.insert(func_def.get_start(), "func %s_%s(" % [prefix, method_name])
149184

150-
if not is_top_level_func(text, result.get_start(), is_static):
151-
return prefix_method_name(method_name, is_static, text, prefix, result.get_end())
185+
return text
186+
187+
188+
static func fix_method_super(method_name: String, func_def_end: int, text: String, offset := 0) -> String:
189+
var closing_paren_index := get_closing_paren_index(func_def_end, text)
190+
var func_body_start_index := text.find(":", closing_paren_index) +1
191+
192+
var func_body := regex_func_body.search(text, func_body_start_index)
193+
if not func_body:
194+
return text
195+
var func_body_end_index := func_body.get_end()
152196

153-
text = text.erase(result.get_start(), result.get_end() - result.get_start())
154-
text = text.insert(result.get_start(), "func %s_%s(" % [prefix, method_name])
197+
text = regex_super_call.sub(
198+
text, "super.%s" % method_name,
199+
true, func_body_start_index, func_body_end_index
200+
)
155201

156202
return text
157203

158204

159205
static func match_func_with_whitespace(method_name: String, text: String, offset := 0) -> RegExMatch:
160-
var func_with_whitespace := RegEx.new()
161-
func_with_whitespace.compile("func\\s+%s\\s*\\(" % method_name)
206+
var func_with_whitespace := RegEx.create_from_string("func\\s+%s\\s*\\(" % method_name)
162207

163208
# Search for the function definition
164209
return func_with_whitespace.search(text, offset)
@@ -227,7 +272,7 @@ static func get_previous_line_to(text: String, index: int) -> String:
227272
return text.substr(start_index, end_index - start_index + 1)
228273

229274

230-
static func is_func_moddable(method_start_idx, text) -> bool:
275+
static func is_func_marked_moddable(method_start_idx, text) -> bool:
231276
var prevline := get_previous_line_to(text, method_start_idx)
232277

233278
if prevline.contains("@not-moddable"):

0 commit comments

Comments
 (0)