Skip to content

Commit

Permalink
Merge pull request #373 from AlexDarigan/double_default_arguments
Browse files Browse the repository at this point in the history
Automatically Double Function Keywords, Engine-Defined Default Arguments & User-Defined Default Arguments
  • Loading branch information
AlexDarigan authored May 7, 2023
2 parents 750fabc + 011cd29 commit 64fa717
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 393 deletions.
127 changes: 123 additions & 4 deletions N.gd
Original file line number Diff line number Diff line change
@@ -1,9 +1,128 @@
extends Node

signal custom
var a = [5, 2, 10]
var b = [5, 2, 10]
var methods: Dictionary = {} # name: {}
var is_built_in = false
var inner_klass = ""
#var klass = "res://N.gd"
var klass = "res://OldExamples/Scripts/user.gd"
var username

func _init(_username: String = "Hello"):
username = _username




func _ready():
print(a == b)
do()
# parse_for_methods()
# var n = NotBase.new("")

master func do():
print("done")

func append_function(function: Dictionary) -> void:
if methods.has(function["name"]):
return # already parsed in a subclasses
methods[function["name"]] = function

func parse_for_methods() -> void:
var script: GDScript
var engine_methods: Array = []
if not is_built_in and inner_klass == "":
script = load(klass)
engine_methods = ClassDB.class_get_method_list(script.get_instance_base_type())
while script != null:
parse_script(script.source_code)
script = script.get_base_script()
else:
engine_methods = ClassDB.class_get_method_list(klass)
for method in methods:
print(methods[method])
parse_builtins(engine_methods)


func parse_builtins(engine_methods: Array) -> void:
for method in engine_methods:
var function = {
"keyword": "",
"name": method.name,
"args": "",
"return_type": "",
}

function["args"] = parse_engine_method_arguments(method.args, method.default_args)
append_function(function)

func parse_engine_method_arguments(args: Array, default_args: Array) -> String:
var stringArgs = ""
var non_defaults: Array = args.slice(0, args.size() - default_args.size())
var defaults: Array = args.slice(args.size() - default_args.size(), args.size())
for arg in non_defaults:
stringArgs += "%s, " % arg.name
for idx in default_args.size():
stringArgs += "%s = %s, " % [defaults[idx].name, default_args[idx]]
stringArgs = stringArgs.rstrip(", ")
return stringArgs

func parse_script(source_code: String):
var function: Dictionary = {}
for line in source_code.split("\n"):
if line.begins_with("func"):
append_function(parse_function(line))
elif line.begins_with("static func"):
append_function(parse_function(line, "static "))
elif line.begins_with("remote func"):
append_function(parse_function(line, "remote "))
elif line.begins_with("master func"):
append_function(parse_function(line, "master "))
elif line.begins_with("puppet func"):
append_function(parse_function(line, "puppet "))
elif line.begins_with("slave func"):
append_function(parse_function(line, "slave "))

func parse_function(line: String, keyword: String = "") -> Dictionary:
var function = {
"keyword": keyword,
"name": "",
"args": "",
"return_type": ""
}

# Get Name
var start: int = line.find("func") + 5
var length: int = line.find("(") - start
function["name"] = line.substr(start, length)

# Get Return Type
var Rstart = line.find("-> ")
var Rlength = line.find_last(":") - Rstart
if "->" in line:
function["return_type"] = line.substr(Rstart, Rlength)

var argStart = line.find("(") + 1
var argLength = line.find(")") - argStart
function["args"] = line.substr(argStart, argLength)

return function

func simple_func() -> void:
pass

func simple_func_args(a, b: int, c = "Hello", d: int = 100) -> void:
pass

static func static_func() -> void:
pass

remote func remote_func() -> void:
pass

master func master_func() -> void:
pass

puppet func puppet_func() -> void:
pass

slave func slave_func() -> void:
pass
21 changes: 18 additions & 3 deletions addons/WAT/double/method.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,36 @@ var name: String = ""
var spying: bool = false
var stubbed: bool = false
var calls_super: bool = false
#var args: String = ""
#var args_with_defaults: String = ""
var args: String = ""
var args_with_defaults: String = ""
var argNames: String = ""
var keyword: String = ""
var retval: String = ""
var calls: Array = []
var stubs: Array = []
var supers: Array = []
var callables: Array = []
var default
var double

func _init(name: String, keyword: String, args: String, defaults: String) -> void:
func _init(name: String, keyword: String, args: String, retval: String = "") -> void:
self.name = name
self.keyword = keyword
self.args = args
self.args_with_defaults = defaults
self.retval = retval
self.argNames = parse_arg_names(args)

func parse_arg_names(parameters):
var _param_names: String = ""
for param in parameters.split(","):
if ":" in param:
_param_names += "%s, " % param.substr(0, param.find(":")).strip_edges()
elif "=" in param:
_param_names += "%s, " % param.substr(0, param.find("=")).strip_edges()
else:
_param_names += "%s, " % param
return _param_names.rstrip(", ")

func dummy() -> Reference:
stubbed = true
Expand Down
170 changes: 110 additions & 60 deletions addons/WAT/double/script_director.gd
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ func _init(_registry, _klass: String, _inner_klass: String, deps: Array = []) ->
is_built_in = ClassDB.class_exists(_klass)
registry = _registry
registry.register(self)
set_methods()
parse_for_methods()

func method(name: String, keyword: String = "") -> Method:
func method(name: String, optKeyword: String = "", optRetval = "") -> Method:
if not methods.has(name):
methods[name] = Method.new(name, keyword, base_methods[name].arguments, base_methods[name].default_arguments)
var base: Dictionary = base_methods[name]
var keyword = base.keyword if optKeyword == "" else optKeyword
var retval = base.return_type if optRetval == "" else optRetval
methods[name] = Method.new(name, keyword, base.args, retval)
return methods[name]

func clear():
Expand All @@ -58,62 +61,109 @@ func found_matching_call(method, expected_args: Array):
func add_call(method: String, args: Array = []) -> void:
methods[method].add_call(args)

func set_methods() -> void:
var params: String = "abcdefghijklmnopqrstuvwxyz"
for m in method_list():
var arguments: String = ""
# m.args.size() causes crashes in release exports
var idx = 0
for i in m.args:
arguments = arguments + params[idx] + ", "
idx += 1
var sanitized = arguments.replace(", ", "")
arguments = arguments.rstrip(", ")
var default_args = arguments
var def_idx = 0
# m.default_args.size() causes crashes in release exports
for def in m.default_args:
def_idx += 1
if def_idx > 0:
default_args = get_args_with_default(sanitized, m.default_args)
base_methods[m.name] = {"arguments": arguments, "default_arguments": default_args}

func get_args_with_default(args: String, base_default_args: Array) -> String:
var retval_args: String
var substr_start = args.length() - base_default_args.size()
var length = args.length() # We're transforming in loop so we capture first
var arg_index = 0
for i in length:
if i < substr_start:
retval_args += "%s, " % args[i]
continue
var letter = args[i]
var arg = base_default_args[arg_index]
if arg is String:
retval_args += '%s = "%s", ' % [letter, str(arg)]
else:
retval_args += '%s = %s, ' % [letter, arg]
arg_index += 1
retval_args = retval_args.rstrip(", ")
return retval_args

func method_list() -> Array:
var list: Array = []
func append_function(function: Dictionary) -> void:
if base_methods.has(function["name"]):
return # already parsed in a subclasses
base_methods[function["name"]] = function

func parse_for_methods() -> void:
var script: GDScript
var engine_methods: Array = []
if is_built_in:
return ClassDB.class_get_method_list(klass)
var script = load(klass) if inner_klass == "" else _load_nested_class()
# # We get our script methods first in case there is a custom constructor
# # This way we don't end up reading the empty base constructors of Object
list += script.get_script_method_list()
list += ClassDB.class_get_method_list(script.get_instance_base_type())
var filtered = {}
for m in list:
if m.name in filtered:
continue
filtered[m.name] = m
return filtered.values()
## END METHOD CLASS
engine_methods = ClassDB.class_get_method_list(klass)
elif inner_klass != "":
var inner_script = _load_nested_class()
parse_inner_klass_methods(inner_script.get_script_method_list())
engine_methods = ClassDB.class_get_method_list(klass)
else:
script = load(klass)
engine_methods = ClassDB.class_get_method_list(script.get_instance_base_type())
while script != null:
parse_script(script.source_code)
script = script.get_base_script()
parse_builtins(engine_methods)


func parse_builtins(engine_methods: Array) -> void:
for method in engine_methods:
var function = {
"keyword": "",
"name": method.name,
"args": "",
"return_type": "",
}

function["args"] = parse_engine_method_arguments(method.args, method.default_args)
append_function(function)

func parse_inner_klass_methods(inner_klass_methods: Array) -> void:
var names: String = "abcdefghijklmnopqrstuvwyxz"
for method in inner_klass_methods:
var function = {
"keyword": "",
"name": method.name,
"args": "",
"return_type": "",
}
var argString = ""
for arg in method.args.size():
argString += "%s, " % names[arg]
argString = argString.rstrip(", ")
function["args"] = argString
append_function(function)

func parse_engine_method_arguments(args: Array, default_args: Array) -> String:
var stringArgs = ""
var non_defaults: Array = args.slice(0, args.size() - (default_args.size() + 1))
var defaults: Array = args.slice(args.size() - default_args.size(), args.size())
for arg in non_defaults:
stringArgs += "%s, " % arg.name
for idx in default_args.size():
stringArgs += "%s = %s, " % [defaults[idx].name, default_args[idx]]
stringArgs = stringArgs.rstrip(", ")
return stringArgs

func parse_script(source_code: String):
var function: Dictionary = {}
for line in source_code.split("\n"):
if line.begins_with("func"):
append_function(parse_function(line))
elif line.begins_with("static func"):
append_function(parse_function(line, "static "))
elif line.begins_with("remote func"):
append_function(parse_function(line, "remote "))
elif line.begins_with("master func"):
append_function(parse_function(line, "master "))
elif line.begins_with("puppet func"):
append_function(parse_function(line, "puppet "))
elif line.begins_with("slave func"):
append_function(parse_function(line, "slave "))

func parse_function(line: String, keyword: String = "") -> Dictionary:
var function = {
"keyword": keyword,
"name": "",
"args": "",
"return_type": ""
}

# Get Name
var start: int = line.find("func") + 5
var length: int = line.find("(") - start
function["name"] = line.substr(start, length)

# Get Return Type
var Rstart = line.find("-> ")
var Rlength = line.find_last(":") - Rstart
if "->" in line:
function["return_type"] = line.substr(Rstart, Rlength)

var argStart = line.find("(") + 1
var argLength = line.find(")") - argStart
function["args"] = line.substr(argStart, argLength)

return function

func add_inner_class(klass: Object, name: String) -> void:
klasses.append({"director": klass, "name": name})

Expand All @@ -122,6 +172,7 @@ func script():
for klass in klasses:
klass.director.script()
script.source_code = SCRIPT_WRITER.new().write(self)
# TODO: Log doubled scripts
script.reload() # Necessary to load source code into memory
return script

Expand All @@ -134,16 +185,15 @@ func double(deps: Array = [], show_error = true) -> Object:
_created = true
if not deps.empty() and dependecies.empty():
dependecies = deps
object = script().callv("new", dependecies)
var script = script()
object = script.callv("new", dependecies)
object.WATRegistry.append(registry)
for m in methods.values():
m.double = object
for prop_name in nodepaths:
object.set(prop_name, nodepaths[prop_name])
return object



func _load_nested_class() -> Script:
var expression = Expression.new()
var script = load(klass)
Expand Down
Loading

0 comments on commit 64fa717

Please sign in to comment.