mapFolding 0.3.10__py3-none-any.whl → 0.3.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mapFolding/__init__.py +10 -0
- mapFolding/someAssemblyRequired/__init__.py +0 -1
- mapFolding/someAssemblyRequired/makeJob.py +5 -5
- mapFolding/someAssemblyRequired/synthesizeNumba.py +637 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaHardcoding.py +188 -0
- mapFolding/syntheticModules/numba_countInitialize.py +10 -6
- mapFolding/syntheticModules/numba_countParallel.py +14 -8
- mapFolding/syntheticModules/numba_countSequential.py +37 -32
- mapFolding/syntheticModules/numba_doTheNeedful.py +10 -20
- mapFolding/theDao.py +61 -45
- mapFolding/theSSOT.py +32 -8
- mapFolding/theSSOTnumba.py +2 -1
- {mapFolding-0.3.10.dist-info → mapFolding-0.3.12.dist-info}/METADATA +3 -1
- {mapFolding-0.3.10.dist-info → mapFolding-0.3.12.dist-info}/RECORD +18 -18
- mapFolding/someAssemblyRequired/synthesizeJobNumba.py +0 -383
- mapFolding/someAssemblyRequired/synthesizeModulesNumba.py +0 -533
- {mapFolding-0.3.10.dist-info → mapFolding-0.3.12.dist-info}/LICENSE +0 -0
- {mapFolding-0.3.10.dist-info → mapFolding-0.3.12.dist-info}/WHEEL +0 -0
- {mapFolding-0.3.10.dist-info → mapFolding-0.3.12.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.3.10.dist-info → mapFolding-0.3.12.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
"""I think this module is free of hardcoded values.
|
|
2
|
+
TODO: consolidate the logic in this module."""
|
|
3
|
+
from mapFolding import (
|
|
4
|
+
computationState,
|
|
5
|
+
EnumIndices,
|
|
6
|
+
FREAKOUT,
|
|
7
|
+
getAlgorithmSource,
|
|
8
|
+
getFilenameFoldsTotal,
|
|
9
|
+
getPathFilenameFoldsTotal,
|
|
10
|
+
getPathJobRootDEFAULT,
|
|
11
|
+
getPathSyntheticModules,
|
|
12
|
+
hackSSOTdatatype,
|
|
13
|
+
indexMy,
|
|
14
|
+
indexTrack,
|
|
15
|
+
moduleOfSyntheticModules,
|
|
16
|
+
myPackageNameIs,
|
|
17
|
+
ParametersNumba,
|
|
18
|
+
parametersNumbaDEFAULT,
|
|
19
|
+
parametersNumbaFailEarly,
|
|
20
|
+
parametersNumbaSuperJit,
|
|
21
|
+
parametersNumbaSuperJitParallel,
|
|
22
|
+
setDatatypeElephino,
|
|
23
|
+
setDatatypeFoldsTotal,
|
|
24
|
+
setDatatypeLeavesTotal,
|
|
25
|
+
setDatatypeModule,
|
|
26
|
+
Z0Z_getDatatypeModuleScalar,
|
|
27
|
+
Z0Z_getDecoratorCallable,
|
|
28
|
+
Z0Z_identifierCountFolds,
|
|
29
|
+
Z0Z_setDatatypeModuleScalar,
|
|
30
|
+
Z0Z_setDecoratorCallable,
|
|
31
|
+
)
|
|
32
|
+
from mapFolding.someAssemblyRequired import makeStateJob
|
|
33
|
+
from types import ModuleType
|
|
34
|
+
from typing import Any, Callable, cast, Dict, List, Optional, Sequence, Set, Tuple, Type, Union
|
|
35
|
+
import ast
|
|
36
|
+
import autoflake
|
|
37
|
+
import collections
|
|
38
|
+
import inspect
|
|
39
|
+
import more_itertools
|
|
40
|
+
import numba
|
|
41
|
+
import numpy
|
|
42
|
+
import os
|
|
43
|
+
import pathlib
|
|
44
|
+
import python_minifier
|
|
45
|
+
|
|
46
|
+
youOughtaKnow = collections.namedtuple('youOughtaKnow', ['callableSynthesized', 'pathFilenameForMe', 'astForCompetentProgrammers'])
|
|
47
|
+
|
|
48
|
+
class UniversalImportTracker:
|
|
49
|
+
def __init__(self):
|
|
50
|
+
self.dictionaryImportFrom = collections.defaultdict(set)
|
|
51
|
+
self.setImport = set()
|
|
52
|
+
|
|
53
|
+
def addAst(self, astImport_: Union[ast.Import, ast.ImportFrom]) -> None:
|
|
54
|
+
if isinstance(astImport_, ast.Import):
|
|
55
|
+
for alias in astImport_.names:
|
|
56
|
+
self.setImport.add(alias.name)
|
|
57
|
+
elif isinstance(astImport_, ast.ImportFrom):
|
|
58
|
+
self.dictionaryImportFrom[astImport_.module].update(alias.name for alias in astImport_.names)
|
|
59
|
+
|
|
60
|
+
def addImportFromStr(self, module: str, name: str) -> None:
|
|
61
|
+
self.dictionaryImportFrom[module].add(name)
|
|
62
|
+
|
|
63
|
+
def addImportStr(self, name: str) -> None:
|
|
64
|
+
self.setImport.add(name)
|
|
65
|
+
|
|
66
|
+
def makeListAst(self) -> List[Union[ast.ImportFrom, ast.Import]]:
|
|
67
|
+
listAstImportFrom = [ast.ImportFrom(module=module, names=[ast.alias(name=name, asname=None)], level=0) for module, names in self.dictionaryImportFrom.items() for name in names]
|
|
68
|
+
listAstImport = [ast.Import(names=[ast.alias(name=name, asname=None)]) for name in self.setImport]
|
|
69
|
+
return listAstImportFrom + listAstImport
|
|
70
|
+
|
|
71
|
+
class NodeReplacer(ast.NodeTransformer):
|
|
72
|
+
"""Generic node replacement using configurable predicate and builder."""
|
|
73
|
+
def __init__(self, findMe: Callable[[ast.AST], bool], nodeReplacementBuilder: Callable[[ast.AST], ast.AST]):
|
|
74
|
+
self.findMe = findMe
|
|
75
|
+
self.nodeReplacementBuilder = nodeReplacementBuilder
|
|
76
|
+
|
|
77
|
+
def visit(self, node: ast.AST) -> ast.AST:
|
|
78
|
+
if self.findMe(node):
|
|
79
|
+
return self.nodeReplacementBuilder(node)
|
|
80
|
+
return super().visit(node)
|
|
81
|
+
|
|
82
|
+
class RecursiveInliner(ast.NodeTransformer):
|
|
83
|
+
"""
|
|
84
|
+
Class RecursiveInliner:
|
|
85
|
+
A custom AST NodeTransformer designed to recursively inline function calls from a given dictionary
|
|
86
|
+
of function definitions into the AST. Once a particular function has been inlined, it is marked
|
|
87
|
+
as completed to avoid repeated inlining. This transformation modifies the AST in-place by substituting
|
|
88
|
+
eligible function calls with the body of their corresponding function.
|
|
89
|
+
Attributes:
|
|
90
|
+
dictionaryFunctions (Dict[str, ast.FunctionDef]):
|
|
91
|
+
A mapping of function name to its AST definition, used as a source for inlining.
|
|
92
|
+
callablesCompleted (Set[str]):
|
|
93
|
+
A set to track function names that have already been inlined to prevent multiple expansions.
|
|
94
|
+
Methods:
|
|
95
|
+
inlineFunctionBody(callableTargetName: str) -> Optional[ast.FunctionDef]:
|
|
96
|
+
Retrieves the AST definition for a given function name from dictionaryFunctions
|
|
97
|
+
and recursively inlines any function calls within it. Returns the function definition
|
|
98
|
+
that was inlined or None if the function was already processed.
|
|
99
|
+
visit_Call(callNode: ast.Call) -> ast.AST:
|
|
100
|
+
Inspects calls within the AST. If a function call matches one in dictionaryFunctions,
|
|
101
|
+
it is replaced by the inlined body. If the last statement in the inlined body is a return
|
|
102
|
+
or an expression, that value or expression is substituted; otherwise, a constant is returned.
|
|
103
|
+
visit_Expr(node: ast.Expr) -> Union[ast.AST, List[ast.AST]]:
|
|
104
|
+
Handles expression nodes in the AST. If the expression is a function call from
|
|
105
|
+
dictionaryFunctions, its statements are expanded in place, effectively inlining
|
|
106
|
+
the called function's statements into the surrounding context.
|
|
107
|
+
"""
|
|
108
|
+
def __init__(self, dictionaryFunctions: Dict[str, ast.FunctionDef]):
|
|
109
|
+
self.dictionaryFunctions = dictionaryFunctions
|
|
110
|
+
self.callablesCompleted: Set[str] = set()
|
|
111
|
+
|
|
112
|
+
def inlineFunctionBody(self, callableTargetName: str) -> Optional[ast.FunctionDef]:
|
|
113
|
+
if (callableTargetName in self.callablesCompleted):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
self.callablesCompleted.add(callableTargetName)
|
|
117
|
+
inlineDefinition = self.dictionaryFunctions[callableTargetName]
|
|
118
|
+
for astNode in ast.walk(inlineDefinition):
|
|
119
|
+
self.visit(astNode)
|
|
120
|
+
return inlineDefinition
|
|
121
|
+
|
|
122
|
+
def visit_Call(self, node: ast.Call) -> ast.AST:
|
|
123
|
+
callNodeVisited = self.generic_visit(node)
|
|
124
|
+
if (isinstance(callNodeVisited, ast.Call) and isinstance(callNodeVisited.func, ast.Name) and callNodeVisited.func.id in self.dictionaryFunctions):
|
|
125
|
+
inlineDefinition = self.inlineFunctionBody(callNodeVisited.func.id)
|
|
126
|
+
if (inlineDefinition and inlineDefinition.body):
|
|
127
|
+
statementTerminating = inlineDefinition.body[-1]
|
|
128
|
+
if (isinstance(statementTerminating, ast.Return) and statementTerminating.value is not None):
|
|
129
|
+
return self.visit(statementTerminating.value)
|
|
130
|
+
elif (isinstance(statementTerminating, ast.Expr) and statementTerminating.value is not None):
|
|
131
|
+
return self.visit(statementTerminating.value)
|
|
132
|
+
return ast.Constant(value=None)
|
|
133
|
+
return callNodeVisited
|
|
134
|
+
|
|
135
|
+
def visit_Expr(self, node: ast.Expr) -> Union[ast.AST, List[ast.AST]]:
|
|
136
|
+
if (isinstance(node.value, ast.Call)):
|
|
137
|
+
if (isinstance(node.value.func, ast.Name) and node.value.func.id in self.dictionaryFunctions):
|
|
138
|
+
inlineDefinition = self.inlineFunctionBody(node.value.func.id)
|
|
139
|
+
if (inlineDefinition):
|
|
140
|
+
return [self.visit(stmt) for stmt in inlineDefinition.body]
|
|
141
|
+
return self.generic_visit(node)
|
|
142
|
+
|
|
143
|
+
class UnpackArrays(ast.NodeTransformer):
|
|
144
|
+
"""
|
|
145
|
+
A class that transforms array accesses using enum indices into local variables.
|
|
146
|
+
|
|
147
|
+
This AST transformer identifies array accesses using enum indices and replaces them
|
|
148
|
+
with local variables, adding initialization statements at the start of functions.
|
|
149
|
+
|
|
150
|
+
Parameters:
|
|
151
|
+
enumIndexClass (Type[EnumIndices]): The enum class used for array indexing
|
|
152
|
+
arrayName (str): The name of the array being accessed
|
|
153
|
+
|
|
154
|
+
Attributes:
|
|
155
|
+
enumIndexClass (Type[EnumIndices]): Stored enum class for index lookups
|
|
156
|
+
arrayName (str): Name of the array being transformed
|
|
157
|
+
substitutions (dict): Tracks variable substitutions and their original nodes
|
|
158
|
+
|
|
159
|
+
The transformer handles two main cases:
|
|
160
|
+
1. Scalar array access - array[EnumIndices.MEMBER]
|
|
161
|
+
2. Array slice access - array[EnumIndices.MEMBER, other_indices...]
|
|
162
|
+
For each identified access pattern, it:
|
|
163
|
+
1. Creates a local variable named after the enum member
|
|
164
|
+
2. Adds initialization code at function start
|
|
165
|
+
3. Replaces original array access with the local variable
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(self, enumIndexClass: Type[EnumIndices], arrayName: str):
|
|
169
|
+
self.enumIndexClass = enumIndexClass
|
|
170
|
+
self.arrayName = arrayName
|
|
171
|
+
self.substitutions = {}
|
|
172
|
+
|
|
173
|
+
def extract_member_name(self, node: ast.AST) -> Optional[str]:
|
|
174
|
+
"""Recursively extract enum member name from any node in the AST."""
|
|
175
|
+
if isinstance(node, ast.Attribute) and node.attr == 'value':
|
|
176
|
+
innerAttribute = node.value
|
|
177
|
+
while isinstance(innerAttribute, ast.Attribute):
|
|
178
|
+
if (isinstance(innerAttribute.value, ast.Name) and innerAttribute.value.id == self.enumIndexClass.__name__):
|
|
179
|
+
return innerAttribute.attr
|
|
180
|
+
innerAttribute = innerAttribute.value
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def transform_slice_element(self, node: ast.AST) -> ast.AST:
|
|
184
|
+
"""Transform any enum references within a slice element."""
|
|
185
|
+
if isinstance(node, ast.Subscript):
|
|
186
|
+
if isinstance(node.slice, ast.Attribute):
|
|
187
|
+
member_name = self.extract_member_name(node.slice)
|
|
188
|
+
if member_name:
|
|
189
|
+
return ast.Name(id=member_name, ctx=node.ctx)
|
|
190
|
+
elif isinstance(node, ast.Tuple):
|
|
191
|
+
# Handle tuple slices by transforming each element
|
|
192
|
+
return ast.Tuple(elts=cast(List[ast.expr], [self.transform_slice_element(elt) for elt in node.elts]), ctx=node.ctx)
|
|
193
|
+
elif isinstance(node, ast.Attribute):
|
|
194
|
+
member_name = self.extract_member_name(node)
|
|
195
|
+
if member_name:
|
|
196
|
+
return ast.Name(id=member_name, ctx=ast.Load())
|
|
197
|
+
return node
|
|
198
|
+
|
|
199
|
+
def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
|
|
200
|
+
# Recursively visit any nested subscripts in value or slice
|
|
201
|
+
node.value = self.visit(node.value)
|
|
202
|
+
node.slice = self.visit(node.slice)
|
|
203
|
+
# If node.value is not our arrayName, just return node
|
|
204
|
+
if not (isinstance(node.value, ast.Name) and node.value.id == self.arrayName):
|
|
205
|
+
return node
|
|
206
|
+
|
|
207
|
+
# Handle scalar array access
|
|
208
|
+
if isinstance(node.slice, ast.Attribute):
|
|
209
|
+
memberName = self.extract_member_name(node.slice)
|
|
210
|
+
if memberName:
|
|
211
|
+
self.substitutions[memberName] = ('scalar', node)
|
|
212
|
+
return ast.Name(id=memberName, ctx=ast.Load())
|
|
213
|
+
|
|
214
|
+
# Handle array slice access
|
|
215
|
+
if isinstance(node.slice, ast.Tuple) and node.slice.elts:
|
|
216
|
+
firstElement = node.slice.elts[0]
|
|
217
|
+
memberName = self.extract_member_name(firstElement)
|
|
218
|
+
sliceRemainder = [self.visit(elem) for elem in node.slice.elts[1:]]
|
|
219
|
+
if memberName:
|
|
220
|
+
self.substitutions[memberName] = ('array', node)
|
|
221
|
+
if len(sliceRemainder) == 0:
|
|
222
|
+
return ast.Name(id=memberName, ctx=ast.Load())
|
|
223
|
+
return ast.Subscript(value=ast.Name(id=memberName, ctx=ast.Load()), slice=ast.Tuple(elts=sliceRemainder, ctx=ast.Load()) if len(sliceRemainder) > 1 else sliceRemainder[0], ctx=ast.Load())
|
|
224
|
+
|
|
225
|
+
# If single-element tuple, unwrap
|
|
226
|
+
if isinstance(node.slice, ast.Tuple) and len(node.slice.elts) == 1:
|
|
227
|
+
node.slice = node.slice.elts[0]
|
|
228
|
+
|
|
229
|
+
return node
|
|
230
|
+
|
|
231
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef:
|
|
232
|
+
node = cast(ast.FunctionDef, self.generic_visit(node))
|
|
233
|
+
|
|
234
|
+
initializations = []
|
|
235
|
+
for name, (kind, original_node) in self.substitutions.items():
|
|
236
|
+
if kind == 'scalar':
|
|
237
|
+
initializations.append(ast.Assign(targets=[ast.Name(id=name, ctx=ast.Store())], value=original_node))
|
|
238
|
+
else: # array
|
|
239
|
+
initializations.append(
|
|
240
|
+
ast.Assign(
|
|
241
|
+
targets=[ast.Name(id=name, ctx=ast.Store())],
|
|
242
|
+
value=ast.Subscript(value=ast.Name(id=self.arrayName, ctx=ast.Load()),
|
|
243
|
+
slice=ast.Attribute(value=ast.Attribute(
|
|
244
|
+
value=ast.Name(id=self.enumIndexClass.__name__, ctx=ast.Load()),
|
|
245
|
+
attr=name, ctx=ast.Load()), attr='value', ctx=ast.Load()), ctx=ast.Load())))
|
|
246
|
+
|
|
247
|
+
node.body = initializations + node.body
|
|
248
|
+
return node
|
|
249
|
+
|
|
250
|
+
def Z0Z_UnhandledDecorators(astCallable: ast.FunctionDef) -> ast.FunctionDef:
|
|
251
|
+
# TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
|
|
252
|
+
for decoratorItem in astCallable.decorator_list.copy():
|
|
253
|
+
import warnings
|
|
254
|
+
astCallable.decorator_list.remove(decoratorItem)
|
|
255
|
+
warnings.warn(f"Removed decorator {ast.unparse(decoratorItem)} from {astCallable.name}")
|
|
256
|
+
return astCallable
|
|
257
|
+
def isThisNodeNumbaJitCall(node: ast.AST) -> bool:
|
|
258
|
+
return (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr == Z0Z_getDecoratorCallable())
|
|
259
|
+
def isThisNodeJitCall(node: ast.AST) -> bool:
|
|
260
|
+
return (isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == Z0Z_getDecoratorCallable())
|
|
261
|
+
def isThisNodeNumbaJitDecorator(node: ast.AST) -> bool:
|
|
262
|
+
return isThisNodeNumbaJitCall(node) or isThisNodeJitCall(node)
|
|
263
|
+
def Z0Z_generalizeThis(FunctionDefTarget: ast.FunctionDef, parametersNumba: Optional[ParametersNumba]=None) -> Tuple[ast.FunctionDef, ParametersNumba | None]:
|
|
264
|
+
def recycleParametersNumba(decorator: ast.Call) -> Dict[str, Any]:
|
|
265
|
+
parametersNumbaExtracted: Dict[str, Any] = {}
|
|
266
|
+
for keywordItem in decorator.keywords:
|
|
267
|
+
if isinstance(keywordItem.value, ast.Constant) and keywordItem.arg is not None:
|
|
268
|
+
parametersNumbaExtracted[keywordItem.arg] = keywordItem.value.value
|
|
269
|
+
return parametersNumbaExtracted
|
|
270
|
+
|
|
271
|
+
for decorator in FunctionDefTarget.decorator_list.copy():
|
|
272
|
+
if isThisNodeNumbaJitDecorator(decorator):
|
|
273
|
+
decorator = cast(ast.Call, decorator)
|
|
274
|
+
if parametersNumba is None:
|
|
275
|
+
parametersNumbaSherpa = recycleParametersNumba(decorator)
|
|
276
|
+
if (HunterIsSureThereAreBetterWaysToDoThis := True):
|
|
277
|
+
if parametersNumbaSherpa:
|
|
278
|
+
parametersNumba = cast(ParametersNumba, parametersNumbaSherpa)
|
|
279
|
+
FunctionDefTarget.decorator_list.remove(decorator)
|
|
280
|
+
|
|
281
|
+
return FunctionDefTarget, parametersNumba
|
|
282
|
+
|
|
283
|
+
def decorateCallableWithNumba(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker, parametersNumba: Optional[ParametersNumba]=None) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
284
|
+
datatypeModuleDecorator = Z0Z_getDatatypeModuleScalar()
|
|
285
|
+
def make_arg4parameter(signatureElement: ast.arg):
|
|
286
|
+
if isinstance(signatureElement.annotation, ast.Subscript) and isinstance(signatureElement.annotation.slice, ast.Tuple):
|
|
287
|
+
annotationShape = signatureElement.annotation.slice.elts[0]
|
|
288
|
+
if isinstance(annotationShape, ast.Subscript) and isinstance(annotationShape.slice, ast.Tuple):
|
|
289
|
+
shapeAsListSlices: Sequence[ast.expr] = [ast.Slice() for axis in range(len(annotationShape.slice.elts))]
|
|
290
|
+
shapeAsListSlices[-1] = ast.Slice(step=ast.Constant(value=1))
|
|
291
|
+
shapeAST = ast.Tuple(elts=list(shapeAsListSlices), ctx=ast.Load())
|
|
292
|
+
else:
|
|
293
|
+
shapeAST = ast.Slice(step=ast.Constant(value=1))
|
|
294
|
+
|
|
295
|
+
annotationDtype = signatureElement.annotation.slice.elts[1]
|
|
296
|
+
if (isinstance(annotationDtype, ast.Subscript) and isinstance(annotationDtype.slice, ast.Attribute)):
|
|
297
|
+
datatypeAST = annotationDtype.slice.attr
|
|
298
|
+
else:
|
|
299
|
+
datatypeAST = None
|
|
300
|
+
|
|
301
|
+
ndarrayName = signatureElement.arg
|
|
302
|
+
Z0Z_hacky_dtype = hackSSOTdatatype(ndarrayName)
|
|
303
|
+
datatype_attr = datatypeAST or Z0Z_hacky_dtype
|
|
304
|
+
allImports.addImportFromStr(datatypeModuleDecorator, datatype_attr)
|
|
305
|
+
datatypeNumba = ast.Name(id=datatype_attr, ctx=ast.Load())
|
|
306
|
+
|
|
307
|
+
return ast.Subscript(value=datatypeNumba, slice=shapeAST, ctx=ast.Load())
|
|
308
|
+
|
|
309
|
+
list_argsDecorator: Sequence[ast.expr] = []
|
|
310
|
+
|
|
311
|
+
list_arg4signature_or_function: Sequence[ast.expr] = []
|
|
312
|
+
for parameter in FunctionDefTarget.args.args:
|
|
313
|
+
signatureElement = make_arg4parameter(parameter)
|
|
314
|
+
if signatureElement:
|
|
315
|
+
list_arg4signature_or_function.append(signatureElement)
|
|
316
|
+
|
|
317
|
+
if FunctionDefTarget.returns and isinstance(FunctionDefTarget.returns, ast.Name):
|
|
318
|
+
theReturn: ast.Name = FunctionDefTarget.returns
|
|
319
|
+
list_argsDecorator = [cast(ast.expr, ast.Call(func=ast.Name(id=theReturn.id, ctx=ast.Load())
|
|
320
|
+
, args=list_arg4signature_or_function if list_arg4signature_or_function else [] , keywords=[] ) )]
|
|
321
|
+
elif list_arg4signature_or_function:
|
|
322
|
+
list_argsDecorator = [cast(ast.expr, ast.Tuple(elts=list_arg4signature_or_function, ctx=ast.Load()))]
|
|
323
|
+
|
|
324
|
+
FunctionDefTarget, parametersNumba = Z0Z_generalizeThis(FunctionDefTarget, parametersNumba)
|
|
325
|
+
FunctionDefTarget = Z0Z_UnhandledDecorators(FunctionDefTarget)
|
|
326
|
+
if parametersNumba is None:
|
|
327
|
+
parametersNumba = parametersNumbaDEFAULT
|
|
328
|
+
listDecoratorKeywords = [ast.keyword(arg=parameterName, value=ast.Constant(value=parameterValue)) for parameterName, parameterValue in parametersNumba.items()]
|
|
329
|
+
|
|
330
|
+
decoratorModule = Z0Z_getDatatypeModuleScalar()
|
|
331
|
+
decoratorCallable = Z0Z_getDecoratorCallable()
|
|
332
|
+
allImports.addImportFromStr(decoratorModule, decoratorCallable)
|
|
333
|
+
astDecorator = ast.Call(
|
|
334
|
+
func=ast.Name(id=decoratorCallable, ctx=ast.Load())
|
|
335
|
+
, args=list_argsDecorator if list_argsDecorator else []
|
|
336
|
+
, keywords=listDecoratorKeywords)
|
|
337
|
+
|
|
338
|
+
FunctionDefTarget.decorator_list = [astDecorator]
|
|
339
|
+
return FunctionDefTarget, allImports
|
|
340
|
+
|
|
341
|
+
def makeDecoratorJobNumba(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker, parametersNumba: Optional[ParametersNumba]=None) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
342
|
+
decoratorCallable = Z0Z_getDecoratorCallable()
|
|
343
|
+
def convertToPlainJit(node: ast.Call) -> ast.Call:
|
|
344
|
+
node.func = ast.Name(id=decoratorCallable, ctx=ast.Load())
|
|
345
|
+
return node
|
|
346
|
+
|
|
347
|
+
FunctionDefTarget, parametersNumba = Z0Z_generalizeThis(FunctionDefTarget, parametersNumba)
|
|
348
|
+
|
|
349
|
+
FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumba)
|
|
350
|
+
if isThisNodeNumbaJitCall(FunctionDefTarget.decorator_list[0]):
|
|
351
|
+
FunctionDefTarget.decorator_list[0] = convertToPlainJit(cast(ast.Call, FunctionDefTarget.decorator_list[0]))
|
|
352
|
+
|
|
353
|
+
return FunctionDefTarget, allImports
|
|
354
|
+
|
|
355
|
+
def inlineOneCallable(pythonSource: str, callableTarget: str, parametersNumba: Optional[ParametersNumba]=None, unpackArrays: Optional[bool]=False) -> str:
|
|
356
|
+
astModule: ast.Module = ast.parse(pythonSource, type_comments=True)
|
|
357
|
+
allImports = UniversalImportTracker()
|
|
358
|
+
|
|
359
|
+
for statement in astModule.body:
|
|
360
|
+
if isinstance(statement, (ast.Import, ast.ImportFrom)):
|
|
361
|
+
allImports.addAst(statement)
|
|
362
|
+
|
|
363
|
+
dictionaryFunctionDef = {statement.name: statement for statement in astModule.body if isinstance(statement, ast.FunctionDef)}
|
|
364
|
+
callableInlinerWorkhorse = RecursiveInliner(dictionaryFunctionDef)
|
|
365
|
+
FunctionDefTarget = callableInlinerWorkhorse.inlineFunctionBody(callableTarget)
|
|
366
|
+
|
|
367
|
+
if not FunctionDefTarget:
|
|
368
|
+
raise FREAKOUT
|
|
369
|
+
|
|
370
|
+
ast.fix_missing_locations(FunctionDefTarget)
|
|
371
|
+
|
|
372
|
+
FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumba)
|
|
373
|
+
|
|
374
|
+
if unpackArrays:
|
|
375
|
+
for tupleUnpack in [(indexMy, 'my'), (indexTrack, 'track')]:
|
|
376
|
+
unpacker = UnpackArrays(*tupleUnpack)
|
|
377
|
+
FunctionDefTarget = cast(ast.FunctionDef, unpacker.visit(FunctionDefTarget))
|
|
378
|
+
ast.fix_missing_locations(FunctionDefTarget)
|
|
379
|
+
|
|
380
|
+
moduleAST = ast.Module(body=cast(List[ast.stmt], allImports.makeListAst() + [FunctionDefTarget]), type_ignores=[])
|
|
381
|
+
ast.fix_missing_locations(moduleAST)
|
|
382
|
+
moduleSource = ast.unparse(moduleAST)
|
|
383
|
+
return moduleSource
|
|
384
|
+
|
|
385
|
+
def makeDispatcherNumba(pythonSource: str, callableTarget: str, listStuffYouOughtaKnow: List[youOughtaKnow]) -> str:
|
|
386
|
+
astSource = ast.parse(pythonSource)
|
|
387
|
+
allImports = UniversalImportTracker()
|
|
388
|
+
|
|
389
|
+
for statement in astSource.body:
|
|
390
|
+
if isinstance(statement, (ast.Import, ast.ImportFrom)):
|
|
391
|
+
allImports.addAst(statement)
|
|
392
|
+
|
|
393
|
+
for stuff in listStuffYouOughtaKnow:
|
|
394
|
+
statement = stuff.astForCompetentProgrammers
|
|
395
|
+
if isinstance(statement, (ast.Import, ast.ImportFrom)):
|
|
396
|
+
allImports.addAst(statement)
|
|
397
|
+
|
|
398
|
+
FunctionDefTarget = next((node for node in astSource.body if isinstance(node, ast.FunctionDef) and node.name == callableTarget), None)
|
|
399
|
+
|
|
400
|
+
if not FunctionDefTarget:
|
|
401
|
+
raise ValueError(f"Could not find function {callableTarget} in source code")
|
|
402
|
+
|
|
403
|
+
FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumbaFailEarly)
|
|
404
|
+
|
|
405
|
+
astModule = ast.Module(body=cast(List[ast.stmt], allImports.makeListAst() + [FunctionDefTarget]), type_ignores=[])
|
|
406
|
+
|
|
407
|
+
ast.fix_missing_locations(astModule)
|
|
408
|
+
return ast.unparse(astModule)
|
|
409
|
+
|
|
410
|
+
def makeStrRLEcompacted(arrayTarget: numpy.ndarray, identifierName: Optional[str]=None) -> str:
|
|
411
|
+
"""Converts a NumPy array into a compressed string representation using run-length encoding (RLE).
|
|
412
|
+
|
|
413
|
+
This function takes a NumPy array and converts it into an optimized string representation by:
|
|
414
|
+
1. Compressing consecutive sequences of numbers into range objects
|
|
415
|
+
2. Minimizing repeated zeros using array multiplication syntax
|
|
416
|
+
3. Converting the result into a valid Python array initialization statement
|
|
417
|
+
|
|
418
|
+
Parameters:
|
|
419
|
+
arrayTarget (numpy.ndarray): The input NumPy array to be converted
|
|
420
|
+
identifierName (str): The variable name to use in the output string
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
str: A string containing Python code that recreates the input array in compressed form.
|
|
424
|
+
Format: "{identifierName} = numpy.array({compressed_data}, dtype=numpy.{dtype})"
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
def compressRangesNDArrayNoFlatten(arraySlice):
|
|
428
|
+
if isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim > 1:
|
|
429
|
+
return [compressRangesNDArrayNoFlatten(arraySlice[index]) for index in range(arraySlice.shape[0])]
|
|
430
|
+
elif isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim == 1:
|
|
431
|
+
listWithRanges = []
|
|
432
|
+
for group in more_itertools.consecutive_groups(arraySlice.tolist()):
|
|
433
|
+
ImaSerious = list(group)
|
|
434
|
+
if len(ImaSerious) <= 4:
|
|
435
|
+
listWithRanges += ImaSerious
|
|
436
|
+
else:
|
|
437
|
+
ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
|
|
438
|
+
listWithRanges += ImaRange
|
|
439
|
+
return listWithRanges
|
|
440
|
+
return arraySlice
|
|
441
|
+
|
|
442
|
+
arrayAsNestedLists = compressRangesNDArrayNoFlatten(arrayTarget)
|
|
443
|
+
|
|
444
|
+
stringMinimized = python_minifier.minify(str(arrayAsNestedLists))
|
|
445
|
+
commaZeroMaximum = arrayTarget.shape[-1] - 1
|
|
446
|
+
stringMinimized = stringMinimized.replace('[0' + ',0'*commaZeroMaximum + ']', '[0]*'+str(commaZeroMaximum+1))
|
|
447
|
+
for countZeros in range(commaZeroMaximum, 2, -1):
|
|
448
|
+
stringMinimized = stringMinimized.replace(',0'*countZeros + ']', ']+[0]*'+str(countZeros))
|
|
449
|
+
|
|
450
|
+
stringMinimized = stringMinimized.replace('range', '*range')
|
|
451
|
+
|
|
452
|
+
if identifierName:
|
|
453
|
+
return f"{identifierName} = array({stringMinimized}, dtype={arrayTarget.dtype})"
|
|
454
|
+
return stringMinimized
|
|
455
|
+
|
|
456
|
+
def moveArrayTo_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
457
|
+
arrayType = type(argData)
|
|
458
|
+
moduleConstructor = arrayType.__module__
|
|
459
|
+
constructorName = arrayType.__name__
|
|
460
|
+
# NOTE hack
|
|
461
|
+
constructorName = constructorName.replace('ndarray', 'array')
|
|
462
|
+
argData_dtype: numpy.dtype = argData.dtype
|
|
463
|
+
argData_dtypeName = argData.dtype.name
|
|
464
|
+
|
|
465
|
+
allImports.addImportFromStr(moduleConstructor, constructorName)
|
|
466
|
+
allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
|
|
467
|
+
|
|
468
|
+
onlyDataRLE = makeStrRLEcompacted(argData)
|
|
469
|
+
astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
|
|
470
|
+
dataAst = astStatement.value
|
|
471
|
+
|
|
472
|
+
arrayCall = ast.Call(
|
|
473
|
+
func=ast.Name(id=constructorName, ctx=ast.Load())
|
|
474
|
+
, args=[dataAst]
|
|
475
|
+
, keywords=[ast.keyword(arg='dtype' , value=ast.Name(id=argData_dtypeName , ctx=ast.Load()) ) ] )
|
|
476
|
+
|
|
477
|
+
assignment = ast.Assign( targets=[ast.Name(id=astArg.arg, ctx=ast.Store())], value=arrayCall )
|
|
478
|
+
FunctionDefTarget.body.insert(0, assignment)
|
|
479
|
+
FunctionDefTarget.args.args.remove(astArg)
|
|
480
|
+
|
|
481
|
+
return FunctionDefTarget, allImports
|
|
482
|
+
|
|
483
|
+
def evaluateArrayIn_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
484
|
+
arrayType = type(argData)
|
|
485
|
+
moduleConstructor = arrayType.__module__
|
|
486
|
+
constructorName = arrayType.__name__
|
|
487
|
+
# NOTE hack
|
|
488
|
+
constructorName = constructorName.replace('ndarray', 'array')
|
|
489
|
+
allImports.addImportFromStr(moduleConstructor, constructorName)
|
|
490
|
+
|
|
491
|
+
for stmt in FunctionDefTarget.body.copy():
|
|
492
|
+
if isinstance(stmt, ast.Assign):
|
|
493
|
+
if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
|
|
494
|
+
astAssignee: ast.Name = stmt.targets[0]
|
|
495
|
+
argData_dtypeName = hackSSOTdatatype(astAssignee.id)
|
|
496
|
+
allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
|
|
497
|
+
astSubscript: ast.Subscript = stmt.value
|
|
498
|
+
if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == astArg.arg and isinstance(astSubscript.slice, ast.Attribute):
|
|
499
|
+
indexAs_astAttribute: ast.Attribute = astSubscript.slice
|
|
500
|
+
indexAsStr = ast.unparse(indexAs_astAttribute)
|
|
501
|
+
argDataSlice = argData[eval(indexAsStr)]
|
|
502
|
+
|
|
503
|
+
onlyDataRLE = makeStrRLEcompacted(argDataSlice)
|
|
504
|
+
astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
|
|
505
|
+
dataAst = astStatement.value
|
|
506
|
+
|
|
507
|
+
arrayCall = ast.Call(
|
|
508
|
+
func=ast.Name(id=constructorName, ctx=ast.Load()) , args=[dataAst]
|
|
509
|
+
, keywords=[ast.keyword(arg='dtype', value=ast.Name(id=argData_dtypeName, ctx=ast.Load()) ) ] )
|
|
510
|
+
|
|
511
|
+
assignment = ast.Assign( targets=[astAssignee], value=arrayCall )
|
|
512
|
+
FunctionDefTarget.body.insert(0, assignment)
|
|
513
|
+
FunctionDefTarget.body.remove(stmt)
|
|
514
|
+
|
|
515
|
+
FunctionDefTarget.args.args.remove(astArg)
|
|
516
|
+
return FunctionDefTarget, allImports
|
|
517
|
+
|
|
518
|
+
def evaluate_argIn_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray, Z0Z_listChaff: List[str], allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
519
|
+
moduleConstructor = Z0Z_getDatatypeModuleScalar()
|
|
520
|
+
for stmt in FunctionDefTarget.body.copy():
|
|
521
|
+
if isinstance(stmt, ast.Assign):
|
|
522
|
+
if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
|
|
523
|
+
astAssignee: ast.Name = stmt.targets[0]
|
|
524
|
+
argData_dtypeName = hackSSOTdatatype(astAssignee.id)
|
|
525
|
+
allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
|
|
526
|
+
astSubscript: ast.Subscript = stmt.value
|
|
527
|
+
if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == astArg.arg and isinstance(astSubscript.slice, ast.Attribute):
|
|
528
|
+
indexAs_astAttribute: ast.Attribute = astSubscript.slice
|
|
529
|
+
indexAsStr = ast.unparse(indexAs_astAttribute)
|
|
530
|
+
argDataSlice: int = argData[eval(indexAsStr)].item()
|
|
531
|
+
astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[ast.Constant(value=argDataSlice)], keywords=[])
|
|
532
|
+
assignment = ast.Assign(targets=[astAssignee], value=astCall)
|
|
533
|
+
if astAssignee.id not in Z0Z_listChaff:
|
|
534
|
+
FunctionDefTarget.body.insert(0, assignment)
|
|
535
|
+
FunctionDefTarget.body.remove(stmt)
|
|
536
|
+
FunctionDefTarget.args.args.remove(astArg)
|
|
537
|
+
return FunctionDefTarget, allImports
|
|
538
|
+
|
|
539
|
+
def evaluateAnnAssignIn_body(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
540
|
+
moduleConstructor = Z0Z_getDatatypeModuleScalar()
|
|
541
|
+
for stmt in FunctionDefTarget.body.copy():
|
|
542
|
+
if isinstance(stmt, ast.AnnAssign):
|
|
543
|
+
if isinstance(stmt.target, ast.Name) and isinstance(stmt.value, ast.Constant):
|
|
544
|
+
astAssignee: ast.Name = stmt.target
|
|
545
|
+
argData_dtypeName = hackSSOTdatatype(astAssignee.id)
|
|
546
|
+
allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
|
|
547
|
+
astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[stmt.value], keywords=[])
|
|
548
|
+
assignment = ast.Assign(targets=[astAssignee], value=astCall)
|
|
549
|
+
FunctionDefTarget.body.insert(0, assignment)
|
|
550
|
+
FunctionDefTarget.body.remove(stmt)
|
|
551
|
+
return FunctionDefTarget, allImports
|
|
552
|
+
|
|
553
|
+
def removeIdentifierFrom_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg) -> ast.FunctionDef:
|
|
554
|
+
for stmt in FunctionDefTarget.body.copy():
|
|
555
|
+
if isinstance(stmt, ast.Assign):
|
|
556
|
+
if isinstance(stmt.targets[0], ast.Subscript) and isinstance(stmt.targets[0].value, ast.Name):
|
|
557
|
+
if stmt.targets[0].value.id == astArg.arg:
|
|
558
|
+
FunctionDefTarget.body.remove(stmt)
|
|
559
|
+
FunctionDefTarget.args.args.remove(astArg)
|
|
560
|
+
return FunctionDefTarget
|
|
561
|
+
|
|
562
|
+
def astObjectToAstConstant(FunctionDefTarget: ast.FunctionDef, object: str, value: int) -> ast.FunctionDef:
|
|
563
|
+
"""
|
|
564
|
+
Replaces nodes in astFunction matching the AST of the string `object`
|
|
565
|
+
with a constant node holding the provided value.
|
|
566
|
+
"""
|
|
567
|
+
targetExpression = ast.parse(object, mode='eval').body
|
|
568
|
+
targetDump = ast.dump(targetExpression, annotate_fields=False)
|
|
569
|
+
|
|
570
|
+
def findNode(node: ast.AST) -> bool:
|
|
571
|
+
return ast.dump(node, annotate_fields=False) == targetDump
|
|
572
|
+
|
|
573
|
+
def replaceWithConstant(node: ast.AST) -> ast.AST:
|
|
574
|
+
return ast.copy_location(ast.Constant(value=value), node)
|
|
575
|
+
|
|
576
|
+
transformer = NodeReplacer(findNode, replaceWithConstant)
|
|
577
|
+
newFunction = cast(ast.FunctionDef, transformer.visit(FunctionDefTarget))
|
|
578
|
+
ast.fix_missing_locations(newFunction)
|
|
579
|
+
return newFunction
|
|
580
|
+
|
|
581
|
+
def astNameToAstConstant(FunctionDefTarget: ast.FunctionDef, name: str, value: int) -> ast.FunctionDef:
|
|
582
|
+
def findName(node: ast.AST) -> bool:
|
|
583
|
+
return isinstance(node, ast.Name) and node.id == name
|
|
584
|
+
|
|
585
|
+
def replaceWithConstant(node: ast.AST) -> ast.AST:
|
|
586
|
+
return ast.copy_location(ast.Constant(value=value), node)
|
|
587
|
+
|
|
588
|
+
return cast(ast.FunctionDef, NodeReplacer(findName, replaceWithConstant).visit(FunctionDefTarget))
|
|
589
|
+
|
|
590
|
+
def makeLauncherJobNumba(callableTarget: str, pathFilenameFoldsTotal: pathlib.Path) -> ast.Module:
|
|
591
|
+
linesLaunch = f"""
|
|
592
|
+
if __name__ == '__main__':
|
|
593
|
+
import time
|
|
594
|
+
timeStart = time.perf_counter()
|
|
595
|
+
foldsTotal = {callableTarget}()
|
|
596
|
+
print(foldsTotal, time.perf_counter() - timeStart)
|
|
597
|
+
writeStream = open('{pathFilenameFoldsTotal.as_posix()}', 'w')
|
|
598
|
+
writeStream.write(str(foldsTotal))
|
|
599
|
+
writeStream.close()
|
|
600
|
+
"""
|
|
601
|
+
return ast.parse(linesLaunch)
|
|
602
|
+
|
|
603
|
+
def addReturnJobNumba(FunctionDefTarget: ast.FunctionDef, stateJob: computationState, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
604
|
+
"""Add multiplication and return statement to function, properly constructing AST nodes."""
|
|
605
|
+
# Create AST for multiplication operation
|
|
606
|
+
multiplicand = Z0Z_identifierCountFolds
|
|
607
|
+
datatype = hackSSOTdatatype(multiplicand)
|
|
608
|
+
multiplyOperation = ast.BinOp(
|
|
609
|
+
left=ast.Name(id=multiplicand, ctx=ast.Load()),
|
|
610
|
+
op=ast.Mult(), right=ast.Constant(value=int(stateJob['foldGroups'][-1])))
|
|
611
|
+
|
|
612
|
+
returnStatement = ast.Return(value=multiplyOperation)
|
|
613
|
+
|
|
614
|
+
datatypeModuleScalar = Z0Z_getDatatypeModuleScalar()
|
|
615
|
+
allImports.addImportFromStr(datatypeModuleScalar, datatype)
|
|
616
|
+
FunctionDefTarget.returns = ast.Name(id=datatype, ctx=ast.Load())
|
|
617
|
+
|
|
618
|
+
FunctionDefTarget.body.append(returnStatement)
|
|
619
|
+
|
|
620
|
+
return FunctionDefTarget, allImports
|
|
621
|
+
|
|
622
|
+
def unrollWhileLoop(FunctionDefTarget: ast.FunctionDef, iteratorName: str, iterationsTotal: int, connectionGraph: numpy.ndarray[Tuple[int, int, int], numpy.dtype[numpy.integer[Any]]]) -> ast.FunctionDef:
|
|
623
|
+
"""
|
|
624
|
+
Unroll the countGaps loop: in theDao, it is a while loop, of course.
|
|
625
|
+
However, it could be written as `for indexDimension in range(dimensionsTotal):`.
|
|
626
|
+
It is useful to note that it could also be written as `for indexDimension in range(connectionGraph.shape[0]):`.
|
|
627
|
+
We will unroll the loop into a series of stateJob['my'][indexMy.dimensionsTotal]-many code blocks that are similar but not identical.
|
|
628
|
+
In each code block, we know the value of the identifier `indexDimension`, so we replace the identifier with its value.
|
|
629
|
+
Furthermore, we will split connectionGraph into arrays along the first axis.
|
|
630
|
+
`connectionGraph[indexDimension, leaf1ndex, leafBelow[leafConnectee]]`
|
|
631
|
+
`connectionGraph0[leaf1ndex, leafBelow[leafConnectee]]`
|
|
632
|
+
`connectionGraph1[leaf1ndex, leafBelow[leafConnectee]]`
|
|
633
|
+
`connectionGraphN[leaf1ndex, leafBelow[leafConnectee]]`
|
|
634
|
+
|
|
635
|
+
After unrolling, we can remove three `indexDimension` statements: 1) the first initialization, which is really a memory allocation, 2) the loop initialization, and 3) the loop increment.
|
|
636
|
+
"""
|
|
637
|
+
return FunctionDefTarget
|