-
Notifications
You must be signed in to change notification settings - Fork 2
/
py_callable.gd
142 lines (117 loc) · 3.92 KB
/
py_callable.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
##
## @author: Christoph Haas
##
## @desc: A [PyCallable] may be used to model a python
## - function/constructor call: "<caller>.<callable>(<arguments>)"
## - function object: "<caller>.<callable>"
## - partial function: "partial(<caller>.<callable>, <arguments>)"
##
## This is loosely comparable to python's Callable type (https://docs.python.org/3/library/typing.html#callable).
## However, return values of a python Callable are not modelled by [PyCallable].
## Instead, assignments are handeled via [PyStatement].
##
extends "res://addons/sofa_godot_plugin/sofa_python/python/arguments/py_argument_set.gd"
func get_class() -> String:
return "PyCallable"
func is_class(clazz: String) -> bool:
return .is_class(clazz) or (clazz == get_class())
const PyContext = preload("res://addons/sofa_godot_plugin/sofa_python/sofa_python_context.gd")
const PyArgumentSet = preload("res://addons/sofa_godot_plugin/sofa_python/python/arguments/py_argument_set.gd")
var _partial = false
var _imports: Array = []
var _callable: String
var _caller: String = ""
func _init(name: String, partial: bool = false, arguments: Array = []):
_callable = name
set_partial(partial)
#_args.add_arguments(arguments)
add_arguments(arguments)
func arguments() -> PyArgumentSet:
return self
func set_partial(partial: bool):
if _partial == partial:
return
else:
_partial = partial
if _partial:
add_import("functools", "partial")
else:
_imports.erase(PyContext.make_module_import("functools", "partial"))
func is_partial() -> bool:
return _partial
func set_callable(name: String):
_callable = name
func get_callable() -> String:
return _callable
func set_caller(caller: String):
_caller = caller
func get_caller() -> String:
return _caller
func has_caller() -> bool:
return not get_caller().empty()
func clear_caller():
set_caller("")
func _get_arguments_to_assign(sort_arguments: bool = true) -> Array:
var args_to_assign = []
for arg in get_arguments():
if not arg.is_enabled():
continue
# if callable is wrapped in partial then ignore required arguments that are unchanged, i.e. default-valued
if is_partial():
if not arg.is_default():
args_to_assign.append(arg)
else:
if arg.is_required() or not arg.is_default():
args_to_assign.append(arg)
if sort_arguments:
# sort args in order of ascending position
args_to_assign.sort_custom(PyArgument, "compare_position_ascending")
return args_to_assign
## [code]from <module> import <name>[/code]
func add_import(module, name: String = ""):
_add_import(PyContext.make_module_import(module, name))
func _add_import(import: String):
if not import in _imports:
_imports.append(import)
func clear_imports():
_imports.clear()
if is_partial():
add_import("functools", "partial")
func get_imports() -> Array:
var imports = _imports.duplicate(true)
# append imports of arguments if not already present in own imports
for arg in _get_arguments_to_assign():
for import in arg.get_imports():
if not import in imports:
imports.append(import)
return imports
func generate_python_code(indent_depth: int, context: Dictionary = {}) -> String:
var args_to_assign = _get_arguments_to_assign(true)
# caller.callable(args) vs. callable(args)
var call: String
if has_caller():
call = get_caller() + "." + get_callable()
else:
call = get_callable()
# return if there are no arguments to assign
if args_to_assign.empty():
return call if is_partial() else call + "()"
# partial(caller.callable, args) vs. caller.callable(args)
var code: String
if is_partial():
code = "partial(" + call + ",\n"
else:
code = call + "(\n"
# add arguments to call
var idx = 0
for arg in args_to_assign:
code += "\t".repeat(indent_depth+1)
if not arg.is_name_omitted():
code += arg.get_name() + "="
code += arg.generate_python_code(indent_depth+1, context)
if idx < args_to_assign.size() - 1:
code += ","
code += "\n"
idx += 1
code += "\t".repeat(indent_depth) + ")"
return code