mapFolding 0.7.1__py3-none-any.whl → 0.8.0__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/beDRY.py +77 -81
- mapFolding/noHomeYet.py +2 -2
- mapFolding/oeis.py +2 -2
- mapFolding/someAssemblyRequired/Z0Z_workbench.py +347 -30
- mapFolding/someAssemblyRequired/__init__.py +4 -3
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +0 -1
- mapFolding/someAssemblyRequired/ingredientsNumba.py +87 -2
- mapFolding/someAssemblyRequired/synthesizeDataConverters.py +34 -52
- mapFolding/someAssemblyRequired/{synthesizeNumbaJob.py → synthesizeNumbaJobVESTIGIAL.py} +18 -21
- mapFolding/someAssemblyRequired/transformationTools.py +546 -208
- mapFolding/syntheticModules/numbaCount_doTheNeedful.py +197 -12
- mapFolding/theDao.py +23 -16
- mapFolding/theSSOT.py +28 -43
- {mapfolding-0.7.1.dist-info → mapfolding-0.8.0.dist-info}/METADATA +6 -7
- mapfolding-0.8.0.dist-info/RECORD +41 -0
- {mapfolding-0.7.1.dist-info → mapfolding-0.8.0.dist-info}/WHEEL +1 -1
- tests/conftest.py +2 -3
- tests/test_filesystem.py +0 -2
- tests/test_other.py +2 -3
- tests/test_tasks.py +0 -4
- mapFolding/someAssemblyRequired/synthesizeCountingFunctions.py +0 -7
- mapFolding/someAssemblyRequired/synthesizeNumba.py +0 -91
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +0 -91
- mapFolding/someAssemblyRequired/whatWillBe.py +0 -357
- mapFolding/syntheticModules/__init__.py +0 -0
- mapFolding/syntheticModules/dataNamespaceFlattened.py +0 -30
- mapFolding/syntheticModules/multiprocessingCount_doTheNeedful.py +0 -216
- mapFolding/syntheticModules/numbaCount.py +0 -90
- mapFolding/syntheticModules/numbaCountSequential.py +0 -111
- mapFolding/syntheticModules/numba_doTheNeedful.py +0 -12
- mapfolding-0.7.1.dist-info/RECORD +0 -51
- /mapFolding/syntheticModules/{numbaCountExample.py → numbaCountHistoricalExample.py} +0 -0
- /mapFolding/syntheticModules/{numba_doTheNeedfulExample.py → numba_doTheNeedfulHistoricalExample.py} +0 -0
- {mapfolding-0.7.1.dist-info → mapfolding-0.8.0.dist-info}/LICENSE +0 -0
- {mapfolding-0.7.1.dist-info → mapfolding-0.8.0.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.7.1.dist-info → mapfolding-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -1,15 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
|
|
1
|
+
"""As of 2025-03-15
|
|
2
|
+
Tools for transforming Python code from one format to another.
|
|
3
|
+
|
|
4
|
+
Scope:
|
|
5
|
+
- What is necessary to transform the baseline algorithm into optimized formats used by the official package.
|
|
6
|
+
|
|
7
|
+
Aspirations:
|
|
8
|
+
- Each tool is abstracted or generic enough to be used beyond the scope of the official package.
|
|
9
|
+
- Each tool is designed to be used in a modular fashion, allowing for the creation of new tools by combining existing tools.
|
|
10
|
+
- If a tool has a default setting, the setting shall be the setting used by the official package.
|
|
11
|
+
"""
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from collections.abc import Callable, Container, Generator, Sequence
|
|
14
|
+
from contextlib import contextmanager
|
|
15
|
+
from inspect import getsource as inspect_getsource
|
|
16
|
+
from mapFolding.theSSOT import (
|
|
17
|
+
getSourceAlgorithm,
|
|
18
|
+
theDataclassIdentifier,
|
|
19
|
+
theDataclassInstance,
|
|
20
|
+
theDataclassInstanceTaskDistribution,
|
|
21
|
+
theDispatcherCallable,
|
|
22
|
+
theFileExtension,
|
|
23
|
+
theFormatStrModuleForCallableSynthetic,
|
|
24
|
+
theFormatStrModuleSynthetic,
|
|
25
|
+
theLogicalPathModuleDataclass,
|
|
26
|
+
theLogicalPathModuleDispatcherSynthetic,
|
|
27
|
+
theModuleDispatcherSynthetic,
|
|
28
|
+
theModuleOfSyntheticModules,
|
|
29
|
+
thePackageName,
|
|
30
|
+
thePathPackage,
|
|
31
|
+
theSourceInitializeCallable,
|
|
32
|
+
theSourceParallelCallable,
|
|
33
|
+
theSourceSequentialCallable,
|
|
34
|
+
)
|
|
35
|
+
from pathlib import PurePosixPath
|
|
36
|
+
from types import ModuleType
|
|
37
|
+
from typing import Any, Generic, cast, TypeAlias, TypeGuard, TypeVar
|
|
38
|
+
from Z0Z_tools import updateExtendPolishDictionaryLists
|
|
4
39
|
import ast
|
|
5
|
-
|
|
6
|
-
|
|
40
|
+
import dataclasses
|
|
41
|
+
|
|
7
42
|
"""
|
|
8
43
|
Semiotic notes:
|
|
9
44
|
In the `ast` package, some things that look and feel like a "name" are not `ast.Name` type. The following semiotics are a balance between technical precision and practical usage.
|
|
10
45
|
|
|
11
46
|
astName: always means `ast.Name`.
|
|
12
47
|
Name: uppercase, _should_ be interchangeable with astName, even in camelCase.
|
|
48
|
+
Hunter: ^^ did you do that ^^ ? Are you sure? You just fixed some that should have been "_name" because it confused you.
|
|
13
49
|
name: lowercase, never means `ast.Name`. In camelCase, I _should_ avoid using it in such a way that it could be confused with "Name", uppercase.
|
|
14
50
|
_Identifier: very strongly correlates with the private `ast._Identifier`, which is a TypeAlias for `str`.
|
|
15
51
|
identifier: lowercase, a general term that includes the above and other Python identifiers.
|
|
@@ -17,126 +53,89 @@ Identifier: uppercase, without the leading underscore should only appear in came
|
|
|
17
53
|
namespace: lowercase, in dotted-names, such as `pathlib.Path` or `collections.abc`, "namespace" is the part before the dot.
|
|
18
54
|
Namespace: uppercase, should only appear in camelCase and means "namespace", lowercase.
|
|
19
55
|
"""
|
|
20
|
-
# TODO consider semiotic usefulness of "namespace" or variations such as "namespaceName", "namespacePath", and "namespace_Identifier"
|
|
21
56
|
|
|
22
|
-
#
|
|
57
|
+
# Would `LibCST` be better than `ast` in some cases? https://github.com/hunterhogan/mapFolding/issues/7
|
|
23
58
|
|
|
24
|
-
|
|
59
|
+
nodeType = TypeVar('nodeType', bound=ast.AST)
|
|
25
60
|
ast_Identifier: TypeAlias = str
|
|
61
|
+
ast_expr_Slice: TypeAlias = ast.expr
|
|
26
62
|
strDotStrCuzPyStoopid: TypeAlias = str
|
|
27
63
|
strORlist_ast_type_paramORintORNone: TypeAlias = Any
|
|
28
64
|
list_ast_type_paramORintORNone: TypeAlias = Any
|
|
29
65
|
strORintORNone: TypeAlias = Any
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
66
|
+
astClassHasAttributeDOTname: TypeAlias = ast.FunctionDef | ast.ClassDef | ast.AsyncFunctionDef
|
|
67
|
+
astMosDef = TypeVar('astMosDef', bound=astClassHasAttributeDOTname)
|
|
68
|
+
|
|
69
|
+
@contextmanager
|
|
70
|
+
def listAsNode(ImaList: list[ast.stmt]) -> Generator[ast.Module, None, None]:
|
|
71
|
+
"""Convert a list of AST nodes into a module for use in node walking tools."""
|
|
72
|
+
ImaModule = ast.Module(ImaList)
|
|
73
|
+
try:
|
|
74
|
+
yield ImaModule
|
|
75
|
+
finally:
|
|
76
|
+
ImaList = ImaModule.body
|
|
77
|
+
|
|
78
|
+
class NodeCollector(Generic[nodeType], ast.NodeVisitor):
|
|
79
|
+
"""A node visitor that collects data via one or more actions when a predicate is met."""
|
|
80
|
+
def __init__(self, findThis: Callable[[ast.AST], TypeGuard[nodeType] | bool], doThat: list[Callable[[nodeType], Any]]) -> None:
|
|
81
|
+
self.findThis = findThis
|
|
82
|
+
self.doThat = doThat
|
|
45
83
|
|
|
46
84
|
def visit(self, node: ast.AST) -> None:
|
|
47
|
-
if self.
|
|
48
|
-
for action in self.
|
|
49
|
-
action(node)
|
|
85
|
+
if self.findThis(node):
|
|
86
|
+
for action in self.doThat:
|
|
87
|
+
action(cast(nodeType, node))
|
|
50
88
|
self.generic_visit(node)
|
|
51
89
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
A node transformer that replaces or removes AST nodes based on a condition.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Attributes:
|
|
60
|
-
findMe: A function that finds all locations that match a one or more conditions.
|
|
61
|
-
doThis: A function that does work at each location, such as make a new node, collect information or delete the node.
|
|
62
|
-
|
|
63
|
-
Methods:
|
|
64
|
-
visit(node: ast.AST) -> Optional[ast.AST]:
|
|
65
|
-
Visits each node in the AST, replacing or removing it based on the predicate.
|
|
66
|
-
"""
|
|
67
|
-
def __init__(self
|
|
68
|
-
, findMe: Callable[[ast.AST], bool]
|
|
69
|
-
, doThis: Callable[[ast.AST], ast.AST | Sequence[ast.AST] | None]
|
|
70
|
-
) -> None:
|
|
71
|
-
self.findMe = findMe
|
|
72
|
-
self.doThis = doThis
|
|
73
|
-
|
|
74
|
-
def visit(self, node: ast.AST) -> ast.AST | Sequence[ast.AST] | None:
|
|
75
|
-
if self.findMe(node):
|
|
76
|
-
return self.doThis(node)
|
|
77
|
-
return super().visit(node)
|
|
78
|
-
|
|
79
|
-
def descendantContainsMatchingNode(node: ast.AST, predicateFunction: Callable[[ast.AST], bool]) -> bool:
|
|
80
|
-
""" Return True if any descendant of the node (or the node itself) matches the predicateFunction. """
|
|
81
|
-
matchFound = False
|
|
82
|
-
|
|
83
|
-
class DescendantFinder(ast.NodeVisitor):
|
|
84
|
-
def generic_visit(self, node: ast.AST) -> None:
|
|
85
|
-
nonlocal matchFound
|
|
86
|
-
if predicateFunction(node):
|
|
87
|
-
matchFound = True
|
|
88
|
-
else:
|
|
89
|
-
super().generic_visit(node)
|
|
90
|
-
|
|
91
|
-
DescendantFinder().visit(node)
|
|
92
|
-
return matchFound
|
|
90
|
+
ImaViolationOfTheLiskovSubstitutionPrinciple: TypeAlias = ast.AST
|
|
91
|
+
class NodeReplacer(Generic[nodeType], ast.NodeTransformer):
|
|
92
|
+
"""A node transformer that replaces or removes AST nodes based on a condition."""
|
|
93
|
+
def __init__(self, findThis: Callable[[ast.AST], TypeGuard[nodeType] | bool], doThat: Callable[[nodeType], ast.AST | Sequence[ast.AST] | None]) -> None:
|
|
94
|
+
self.findThis = findThis
|
|
95
|
+
self.doThat = doThat
|
|
93
96
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"""
|
|
99
|
-
def wrappedAction(node: ast.AST) -> None:
|
|
100
|
-
if not descendantContainsMatchingNode(node, exclusionPredicate):
|
|
101
|
-
actionFunction(node)
|
|
102
|
-
return wrappedAction
|
|
97
|
+
def visit(self, node: ImaViolationOfTheLiskovSubstitutionPrinciple) -> ast.AST | Sequence[ast.AST] | None:
|
|
98
|
+
if self.findThis(node):
|
|
99
|
+
return self.doThat(cast(nodeType, node))
|
|
100
|
+
return super().visit(node)
|
|
103
101
|
|
|
104
102
|
class ifThis:
|
|
105
103
|
@staticmethod
|
|
106
|
-
def
|
|
107
|
-
return lambda
|
|
108
|
-
|
|
109
|
-
@staticmethod
|
|
110
|
-
def ast_IdentifierIsIn(container: Container[ast_Identifier]) -> Callable[[ast_Identifier], bool]:
|
|
111
|
-
return lambda node: node in container
|
|
104
|
+
def ast_IdentifierIsIn(container: Container[ast_Identifier]) -> Callable[[ast_Identifier], TypeGuard[ast_Identifier]]:
|
|
105
|
+
return lambda node: node in container # type: ignore
|
|
112
106
|
|
|
113
|
-
# TODO is this only useable if namespace is not `None`? Yes, but use "" for namespace if necessary.
|
|
114
107
|
@staticmethod
|
|
115
|
-
def CallDoesNotCallItself(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
116
|
-
|
|
108
|
+
def CallDoesNotCallItself(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
|
|
109
|
+
"""If `namespace` is not applicable to your case, then call with `namespace=""`."""
|
|
110
|
+
return lambda node: ifThis.matchesMeButNotAnyDescendant(ifThis.CallReallyIs(namespace, identifier))(node) # type: ignore
|
|
117
111
|
|
|
118
112
|
@staticmethod
|
|
119
|
-
def CallDoesNotCallItselfAndNameDOTidIsIn(container: Container[ast_Identifier]) -> Callable[[ast.AST],
|
|
120
|
-
return lambda
|
|
113
|
+
def CallDoesNotCallItselfAndNameDOTidIsIn(container: Container[ast_Identifier]) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
|
|
114
|
+
return lambda node: ifThis.isCall(node) and ifThis.isName(node.func) and ifThis.ast_IdentifierIsIn(container)(node.func.id) and ifThis.CallDoesNotCallItself("", node.func.id)(node) # type: ignore
|
|
121
115
|
|
|
122
116
|
@staticmethod
|
|
123
|
-
def CallReallyIs(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
124
|
-
return ifThis.
|
|
117
|
+
def CallReallyIs(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
|
|
118
|
+
return ifThis.isAnyOf(ifThis.isCall_Identifier(identifier), ifThis.isCallNamespace_Identifier(namespace, identifier)) # type: ignore
|
|
125
119
|
|
|
126
120
|
@staticmethod
|
|
127
121
|
def is_keyword(node: ast.AST) -> TypeGuard[ast.keyword]:
|
|
128
122
|
return isinstance(node, ast.keyword)
|
|
129
123
|
|
|
130
124
|
@staticmethod
|
|
131
|
-
def is_keywordAndValueIsConstant(
|
|
132
|
-
return ifThis.is_keyword(
|
|
125
|
+
def is_keywordAndValueIsConstant(node: ast.AST) -> TypeGuard[ast.keyword]:
|
|
126
|
+
return ifThis.is_keyword(node) and ifThis.isConstant(node.value)
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def is_keyword_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.keyword]]:
|
|
130
|
+
return lambda node: ifThis.is_keyword(node) and node.arg == identifier # type: ignore
|
|
133
131
|
|
|
134
132
|
@staticmethod
|
|
135
|
-
def
|
|
136
|
-
return lambda
|
|
133
|
+
def is_keyword_IdentifierEqualsConstantValue(identifier: ast_Identifier, ConstantValue: Any) -> Callable[[ast.AST], TypeGuard[ast.keyword]]:
|
|
134
|
+
return lambda node: (ifThis.is_keyword_Identifier(identifier)(node) and ifThis.is_keywordAndValueIsConstant(node) and ifThis.isConstantEquals(ConstantValue)(node.value)) # type: ignore
|
|
135
|
+
|
|
137
136
|
@staticmethod
|
|
138
|
-
def
|
|
139
|
-
return lambda
|
|
137
|
+
def isAllOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
138
|
+
return lambda node: all(predicate(node) for predicate in thesePredicates)
|
|
140
139
|
|
|
141
140
|
@staticmethod
|
|
142
141
|
def isAnnAssign(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
|
|
@@ -147,24 +146,61 @@ class ifThis:
|
|
|
147
146
|
return ifThis.isAnnAssign(node) and ifThis.isName(node.annotation)
|
|
148
147
|
|
|
149
148
|
@staticmethod
|
|
150
|
-
def isAnnAssignAndTargetIsName(
|
|
151
|
-
return ifThis.isAnnAssign(
|
|
149
|
+
def isAnnAssignAndTargetIsName(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
|
|
150
|
+
return ifThis.isAnnAssign(node) and ifThis.isName(node.target)
|
|
152
151
|
|
|
153
152
|
@staticmethod
|
|
154
|
-
def isAnnAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
155
|
-
return lambda
|
|
153
|
+
def isAnnAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AnnAssign]]:
|
|
154
|
+
return lambda node: ifThis.isAnnAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target) # type: ignore
|
|
156
155
|
|
|
157
156
|
@staticmethod
|
|
158
157
|
def isAnyAssignmentTo(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
|
|
159
|
-
return ifThis.
|
|
158
|
+
return ifThis.isAnyOf(ifThis.isAssignOnlyTo(identifier), ifThis.isAnnAssignTo(identifier), ifThis.isAugAssignTo(identifier))
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def isAnyCompare(node: ast.AST) -> TypeGuard[ast.Compare] | TypeGuard[ast.BoolOp]:
|
|
162
|
+
return ifThis.isCompare(node) or ifThis.isBoolOp(node)
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def isAnyOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
166
|
+
return lambda node: any(predicate(node) for predicate in thesePredicates)
|
|
160
167
|
|
|
161
168
|
@staticmethod
|
|
162
169
|
def isAssign(node: ast.AST) -> TypeGuard[ast.Assign]:
|
|
163
170
|
return isinstance(node, ast.Assign)
|
|
164
171
|
|
|
165
172
|
@staticmethod
|
|
166
|
-
def
|
|
167
|
-
return lambda
|
|
173
|
+
def isAssignAndValueIsCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
174
|
+
return lambda node: ifThis.isAssign(node) and ifThis.isCall_Identifier(identifier)(node.value) # type: ignore
|
|
175
|
+
|
|
176
|
+
# @staticmethod
|
|
177
|
+
# def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
178
|
+
# return lambda node: ifThis.isAssign(node) and ifThis.isCallNamespace_Identifier(namespace, identifier)(node.value)
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
182
|
+
return ifThis.isAssignAndValueIs(ifThis.isCallNamespace_Identifier(namespace, identifier))
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def isAssignOnlyTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
186
|
+
return lambda node: ifThis.isAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.targets[0]) # type: ignore
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def isAssignAndTargets0Is(targets0Predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
190
|
+
"""node is Assign and node.targets[0] matches `targets0Predicate`."""
|
|
191
|
+
return lambda node: ifThis.isAssign(node) and targets0Predicate(node.targets[0]) # type: ignore
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def isAssignAndValueIs(valuePredicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
195
|
+
"""node is Assign and node.value matches `valuePredicate`.
|
|
196
|
+
|
|
197
|
+
Parameters:
|
|
198
|
+
valuePredicate: Function that evaluates the value of the assignment
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
A predicate that matches assignments with values meeting the criteria
|
|
202
|
+
"""
|
|
203
|
+
return lambda node: ifThis.isAssign(node) and valuePredicate(node.value) # type: ignore
|
|
168
204
|
|
|
169
205
|
@staticmethod
|
|
170
206
|
def isAttribute(node: ast.AST) -> TypeGuard[ast.Attribute]:
|
|
@@ -175,21 +211,24 @@ class ifThis:
|
|
|
175
211
|
return isinstance(node, ast.AugAssign)
|
|
176
212
|
|
|
177
213
|
@staticmethod
|
|
178
|
-
def isAugAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
179
|
-
return lambda
|
|
214
|
+
def isAugAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AugAssign]]:
|
|
215
|
+
return lambda node: ifThis.isAugAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target) # type: ignore
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def isBoolOp(node: ast.AST) -> TypeGuard[ast.BoolOp]:
|
|
219
|
+
return isinstance(node, ast.BoolOp)
|
|
180
220
|
|
|
181
221
|
@staticmethod
|
|
182
222
|
def isCall(node: ast.AST) -> TypeGuard[ast.Call]:
|
|
183
223
|
return isinstance(node, ast.Call)
|
|
184
224
|
|
|
185
225
|
@staticmethod
|
|
186
|
-
def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
187
|
-
return lambda
|
|
226
|
+
def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
|
|
227
|
+
return lambda node: ifThis.isCall(node) and ifThis.isName_Identifier(identifier)(node.func) # type: ignore
|
|
188
228
|
|
|
189
|
-
# TODO what happens if `None` is passed as the namespace?
|
|
190
229
|
@staticmethod
|
|
191
|
-
def isCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
192
|
-
return lambda node: ifThis.isCall(node) and ifThis.
|
|
230
|
+
def isCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
|
|
231
|
+
return lambda node: ifThis.isCall(node) and ifThis.is_nameDOTnameNamespace_Identifier(namespace, identifier)(node.func) # type: ignore
|
|
193
232
|
|
|
194
233
|
@staticmethod
|
|
195
234
|
def isCallToName(node: ast.AST) -> TypeGuard[ast.Call]:
|
|
@@ -200,78 +239,184 @@ class ifThis:
|
|
|
200
239
|
return isinstance(node, ast.ClassDef)
|
|
201
240
|
|
|
202
241
|
@staticmethod
|
|
203
|
-
def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
204
|
-
return lambda node: ifThis.isClassDef(node) and node.name == identifier
|
|
242
|
+
def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.ClassDef]]:
|
|
243
|
+
return lambda node: ifThis.isClassDef(node) and node.name == identifier # type: ignore
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
def isCompare(node: ast.AST) -> TypeGuard[ast.Compare]:
|
|
247
|
+
return isinstance(node, ast.Compare)
|
|
205
248
|
|
|
206
249
|
@staticmethod
|
|
207
250
|
def isConstant(node: ast.AST) -> TypeGuard[ast.Constant]:
|
|
208
251
|
return isinstance(node, ast.Constant)
|
|
209
252
|
|
|
210
253
|
@staticmethod
|
|
211
|
-
def isConstantEquals(value: Any) -> Callable[[ast.AST],
|
|
212
|
-
return lambda node: ifThis.isConstant(node) and node.value == value
|
|
254
|
+
def isConstantEquals(value: Any) -> Callable[[ast.AST], TypeGuard[ast.Constant]]:
|
|
255
|
+
return lambda node: ifThis.isConstant(node) and node.value == value # type: ignore
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def isExpr(node: ast.AST) -> TypeGuard[ast.Expr]:
|
|
259
|
+
return isinstance(node, ast.Expr)
|
|
213
260
|
|
|
214
261
|
@staticmethod
|
|
215
262
|
def isFunctionDef(node: ast.AST) -> TypeGuard[ast.FunctionDef]:
|
|
216
263
|
return isinstance(node, ast.FunctionDef)
|
|
217
264
|
|
|
218
265
|
@staticmethod
|
|
219
|
-
def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
220
|
-
return lambda node: ifThis.isFunctionDef(node) and node.name == identifier
|
|
266
|
+
def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.FunctionDef]]:
|
|
267
|
+
return lambda node: ifThis.isFunctionDef(node) and node.name == identifier # type: ignore
|
|
221
268
|
|
|
222
269
|
@staticmethod
|
|
223
270
|
def isImport(node: ast.AST) -> TypeGuard[ast.Import]:
|
|
224
271
|
return isinstance(node, ast.Import)
|
|
225
272
|
|
|
273
|
+
# A "name" is roughly ast._Identifier
|
|
274
|
+
|
|
275
|
+
# A Name can be a "name"
|
|
276
|
+
# ast.Name()
|
|
277
|
+
|
|
278
|
+
# These classes can be a "name": I will call the group 'ClassQuizas'
|
|
279
|
+
# The technical mechanism is `type[ast.ClassQuizas.value] == ast.Name`
|
|
280
|
+
# or, and this is the annoying part,
|
|
281
|
+
# `type[ast.ClassQuizas.value] == ast.ClassQuizas and type[ast.ClassQuizas.value.value] == ast.ClassQuizas and ... == ast.Name`
|
|
282
|
+
# ast.Attribute()
|
|
283
|
+
# ast.Subscript()
|
|
284
|
+
# ast.Starred()
|
|
285
|
+
|
|
226
286
|
@staticmethod
|
|
227
287
|
def isName(node: ast.AST) -> TypeGuard[ast.Name]:
|
|
228
288
|
return isinstance(node, ast.Name)
|
|
229
289
|
|
|
230
290
|
@staticmethod
|
|
231
|
-
def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
232
|
-
return lambda node: ifThis.isName(node) and node.id == identifier
|
|
291
|
+
def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Name]]:
|
|
292
|
+
return lambda node: ifThis.isName(node) and node.id == identifier # type: ignore
|
|
233
293
|
|
|
234
294
|
@staticmethod
|
|
235
|
-
def
|
|
295
|
+
def is_nameDOTname(node: ast.AST) -> TypeGuard[ast.Attribute]:
|
|
236
296
|
return ifThis.isAttribute(node) and ifThis.isName(node.value)
|
|
237
297
|
|
|
238
298
|
@staticmethod
|
|
239
|
-
def
|
|
240
|
-
return lambda node: ifThis.
|
|
299
|
+
def is_nameDOTnameNamespace(namespace: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute]]:
|
|
300
|
+
return lambda node: (ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value)) # type: ignore
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def is_nameDOTnameNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute]]:
|
|
304
|
+
return lambda node: (ifThis.is_nameDOTnameNamespace(namespace)(node) and node.attr == identifier) # type: ignore
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def isAnyNestedName(node: ast.AST):
|
|
308
|
+
return ifThis.isAnyOf()
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def NameReallyIs_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
|
|
312
|
+
# The following logic is incomplete.
|
|
313
|
+
return ifThis.isAnyOf(ifThis.isName_Identifier(identifier), ifThis.isSubscriptIsName_Identifier(identifier))
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
def isReturn(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
317
|
+
return isinstance(node, ast.Return)
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def isReturnAnyCompare(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
321
|
+
return ifThis.isReturn(node) and node.value is not None and ifThis.isAnyCompare(node.value)
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def isReturnUnaryOp(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
325
|
+
return ifThis.isReturn(node) and node.value is not None and ifThis.isUnaryOp(node.value)
|
|
241
326
|
|
|
242
327
|
@staticmethod
|
|
243
328
|
def isSubscript(node: ast.AST) -> TypeGuard[ast.Subscript]:
|
|
244
329
|
return isinstance(node, ast.Subscript)
|
|
245
330
|
|
|
246
331
|
@staticmethod
|
|
247
|
-
def isSubscript_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST],
|
|
248
|
-
|
|
332
|
+
def isSubscript_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
|
|
333
|
+
"""node is `ast.Subscript` and the top-level `ast.Name` is `identifier`
|
|
334
|
+
Parameters:
|
|
335
|
+
identifier: The identifier to look for in the value chain
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
A predicate function that checks if a node matches the criteria
|
|
339
|
+
"""
|
|
340
|
+
def predicate(node: ast.AST) -> TypeGuard[ast.Subscript]:
|
|
341
|
+
if not ifThis.isSubscript(node):
|
|
342
|
+
return False
|
|
343
|
+
def checkNodeDOTvalue(nodeDOTvalue: ast.AST) -> bool:
|
|
344
|
+
if ifThis.isName(nodeDOTvalue):
|
|
345
|
+
if nodeDOTvalue.id == identifier:
|
|
346
|
+
return True
|
|
347
|
+
elif hasattr(nodeDOTvalue, "value"):
|
|
348
|
+
return checkNodeDOTvalue(nodeDOTvalue.value) # type: ignore
|
|
349
|
+
return False
|
|
350
|
+
return checkNodeDOTvalue(node.value)
|
|
351
|
+
return predicate
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def isSubscriptIsName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
|
|
355
|
+
return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value) # type: ignore
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
def isSubscript_Identifier_Identifier(identifier: ast_Identifier, sliceIdentifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
|
|
359
|
+
return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value) and ifThis.isName_Identifier(sliceIdentifier)(node.slice) # type: ignore
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def isUnaryOp(node: ast.AST) -> TypeGuard[ast.UnaryOp]:
|
|
363
|
+
return isinstance(node, ast.UnaryOp)
|
|
364
|
+
|
|
365
|
+
# Does this work?
|
|
366
|
+
@staticmethod
|
|
367
|
+
def matchesAtLeast1Descendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
368
|
+
"""Create a predicate that returns True if any descendant of the node matches the given predicate."""
|
|
369
|
+
return lambda node: not ifThis.matchesNoDescendant(predicate)(node)
|
|
370
|
+
|
|
371
|
+
# Does this work?
|
|
372
|
+
@staticmethod
|
|
373
|
+
def matchesMeAndMyDescendantsExactlyNTimes(predicate: Callable[[ast.AST], bool], nTimes: int) -> Callable[[ast.AST], bool]:
|
|
374
|
+
"""Create a predicate that returns True if exactly 'count' nodes in the tree match the predicate."""
|
|
375
|
+
def countMatchingNodes(node: ast.AST) -> bool:
|
|
376
|
+
matches = sum(1 for descendant in ast.walk(node) if predicate(descendant))
|
|
377
|
+
return matches == nTimes
|
|
378
|
+
return countMatchingNodes
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def matchesMeButNotAnyDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
382
|
+
"""Create a predicate that returns True if the node matches but none of its descendants match the predicate."""
|
|
383
|
+
return lambda node: predicate(node) and ifThis.matchesNoDescendant(predicate)(node)
|
|
249
384
|
|
|
250
385
|
@staticmethod
|
|
251
|
-
def
|
|
252
|
-
|
|
386
|
+
def matchesNoDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
387
|
+
"""Create a predicate that returns True if no descendant of the node matches the given predicate."""
|
|
388
|
+
def checkNoMatchingDescendant(node: ast.AST) -> bool:
|
|
389
|
+
for descendant in ast.walk(node):
|
|
390
|
+
if descendant is not node and predicate(descendant):
|
|
391
|
+
return False
|
|
392
|
+
return True
|
|
393
|
+
return checkNoMatchingDescendant
|
|
253
394
|
|
|
254
395
|
@staticmethod
|
|
255
|
-
def
|
|
256
|
-
return ifThis.
|
|
396
|
+
def OnlyReturnAnyCompare(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
|
|
397
|
+
return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnAnyCompare(astFunctionDef.body[0])
|
|
398
|
+
|
|
399
|
+
@staticmethod
|
|
400
|
+
def OnlyReturnUnaryOp(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
|
|
401
|
+
return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnUnaryOp(astFunctionDef.body[0])
|
|
257
402
|
|
|
258
403
|
class Make:
|
|
259
404
|
@staticmethod
|
|
260
|
-
def
|
|
261
|
-
"""
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return
|
|
405
|
+
def ast_arg(identifier: ast_Identifier, annotation: ast.expr | None = None, **keywordArguments: strORintORNone) -> ast.arg:
|
|
406
|
+
"""keywordArguments: type_comment:str|None, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
407
|
+
return ast.arg(identifier, annotation, **keywordArguments)
|
|
408
|
+
|
|
409
|
+
@staticmethod
|
|
410
|
+
def ast_keyword(keywordArgument: ast_Identifier, value: ast.expr, **keywordArguments: int) -> ast.keyword:
|
|
411
|
+
return ast.keyword(arg=keywordArgument, value=value, **keywordArguments)
|
|
267
412
|
|
|
268
413
|
@staticmethod
|
|
269
414
|
def astAlias(name: ast_Identifier, asname: ast_Identifier | None = None) -> ast.alias:
|
|
270
|
-
return ast.alias(name
|
|
415
|
+
return ast.alias(name, asname)
|
|
271
416
|
|
|
272
417
|
@staticmethod
|
|
273
418
|
def astAnnAssign(target: ast.Name | ast.Attribute | ast.Subscript, annotation: ast.expr, value: ast.expr | None = None, **keywordArguments: int) -> ast.AnnAssign:
|
|
274
|
-
"""
|
|
419
|
+
"""`simple: int`: uses a clever int-from-boolean to assign the correct value to the `simple` attribute. So, don't add it as a parameter."""
|
|
275
420
|
return ast.AnnAssign(target, annotation, value, simple=int(isinstance(target, ast.Name)), **keywordArguments)
|
|
276
421
|
|
|
277
422
|
@staticmethod
|
|
@@ -280,23 +425,33 @@ class Make:
|
|
|
280
425
|
return ast.Assign(targets=listTargets, value=value, **keywordArguments)
|
|
281
426
|
|
|
282
427
|
@staticmethod
|
|
283
|
-
def
|
|
284
|
-
|
|
285
|
-
return ast.arg(identifier, annotation, **keywordArguments)
|
|
428
|
+
def astArgumentsSpecification(posonlyargs: list[ast.arg]=[], args: list[ast.arg]=[], vararg: ast.arg|None=None, kwonlyargs: list[ast.arg]=[], kw_defaults: list[ast.expr|None]=[None], kwarg: ast.arg|None=None, defaults: list[ast.expr]=[]) -> ast.arguments:
|
|
429
|
+
return ast.arguments(posonlyargs, args, vararg, kwonlyargs, kw_defaults, kwarg, defaults)
|
|
286
430
|
|
|
287
431
|
@staticmethod
|
|
288
|
-
def
|
|
289
|
-
|
|
432
|
+
def astAttribute(value: ast.expr, attribute: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Attribute:
|
|
433
|
+
"""
|
|
434
|
+
Parameters:
|
|
435
|
+
value: the part before the dot (hint `ast.Name` for nameDOTname)
|
|
436
|
+
attribute: the `str` after the dot
|
|
437
|
+
context (ast.Load()): Load/Store/Del"""
|
|
438
|
+
return ast.Attribute(value, attribute, context, **keywordArguments)
|
|
290
439
|
|
|
291
440
|
@staticmethod
|
|
292
|
-
def astCall(caller: ast.Name | ast.Attribute,
|
|
293
|
-
return ast.Call(func=caller, args=list(
|
|
441
|
+
def astCall(caller: ast.Name | ast.Attribute, listArguments: Sequence[ast.expr] | None = None, list_astKeywords: Sequence[ast.keyword] | None = None) -> ast.Call:
|
|
442
|
+
return ast.Call(func=caller, args=list(listArguments) if listArguments else [], keywords=list(list_astKeywords) if list_astKeywords else [])
|
|
294
443
|
|
|
295
444
|
@staticmethod
|
|
296
445
|
def astClassDef(name: ast_Identifier, listBases: list[ast.expr]=[], list_keyword: list[ast.keyword]=[], body: list[ast.stmt]=[], decorator_list: list[ast.expr]=[], **keywordArguments: list_ast_type_paramORintORNone) -> ast.ClassDef:
|
|
297
446
|
"""keywordArguments: type_params:list[ast.type_param], lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
298
447
|
return ast.ClassDef(name=name, bases=listBases, keywords=list_keyword, body=body, decorator_list=decorator_list, **keywordArguments)
|
|
299
448
|
|
|
449
|
+
@staticmethod
|
|
450
|
+
def astConstant(value: Any, **keywordArguments: strORintORNone) -> ast.Constant:
|
|
451
|
+
"""value: str|int|float|bool|None|bytes|bytearray|memoryview|complex|list|tuple|dict|set, or any other type that can be represented as a constant in Python.
|
|
452
|
+
keywordArguments: kind:str, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
453
|
+
return ast.Constant(value, **keywordArguments)
|
|
454
|
+
|
|
300
455
|
@staticmethod
|
|
301
456
|
def astFunctionDef(name: ast_Identifier, argumentsSpecification: ast.arguments=ast.arguments(), body: list[ast.stmt]=[], decorator_list: list[ast.expr]=[], returns: ast.expr|None=None, **keywordArguments: strORlist_ast_type_paramORintORNone) -> ast.FunctionDef:
|
|
302
457
|
"""keywordArguments: type_comment:str|None, type_params:list[ast.type_param], lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
@@ -310,23 +465,20 @@ class Make:
|
|
|
310
465
|
def astImportFrom(moduleName: ast_Identifier, list_astAlias: list[ast.alias], **keywordArguments: int) -> ast.ImportFrom:
|
|
311
466
|
return ast.ImportFrom(module=moduleName, names=list_astAlias, level=0, **keywordArguments)
|
|
312
467
|
|
|
313
|
-
@staticmethod
|
|
314
|
-
def astKeyword(keywordArgument: ast_Identifier, value: ast.expr, **keywordArguments: int) -> ast.keyword:
|
|
315
|
-
return ast.keyword(arg=keywordArgument, value=value, **keywordArguments)
|
|
316
|
-
|
|
317
468
|
@staticmethod
|
|
318
469
|
def astModule(body: list[ast.stmt], type_ignores: list[ast.TypeIgnore] = []) -> ast.Module:
|
|
319
470
|
return ast.Module(body, type_ignores)
|
|
320
471
|
|
|
321
472
|
@staticmethod
|
|
322
|
-
def astName(identifier: ast_Identifier) -> ast.Name:
|
|
323
|
-
return ast.Name(
|
|
473
|
+
def astName(identifier: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Name:
|
|
474
|
+
return ast.Name(identifier, context, **keywordArguments)
|
|
324
475
|
|
|
325
476
|
@staticmethod
|
|
326
477
|
def itDOTname(nameChain: ast.Name | ast.Attribute, dotName: str) -> ast.Attribute:
|
|
327
478
|
return ast.Attribute(value=nameChain, attr=dotName, ctx=ast.Load())
|
|
328
479
|
|
|
329
480
|
@staticmethod
|
|
481
|
+
# TODO rewrite with all parameters
|
|
330
482
|
def nameDOTname(identifier: ast_Identifier, *dotName: str) -> ast.Name | ast.Attribute:
|
|
331
483
|
nameDOTname: ast.Name | ast.Attribute = Make.astName(identifier)
|
|
332
484
|
if not dotName:
|
|
@@ -337,79 +489,225 @@ class Make:
|
|
|
337
489
|
|
|
338
490
|
@staticmethod
|
|
339
491
|
def astReturn(value: ast.expr | None = None, **keywordArguments: int) -> ast.Return:
|
|
340
|
-
return ast.Return(value
|
|
492
|
+
return ast.Return(value, **keywordArguments)
|
|
341
493
|
|
|
342
494
|
@staticmethod
|
|
343
|
-
def
|
|
495
|
+
def astSubscript(value: ast.expr, slice: ast_expr_Slice, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Subscript:
|
|
496
|
+
return ast.Subscript(value, slice, ctx=context, **keywordArguments)
|
|
497
|
+
|
|
498
|
+
@staticmethod
|
|
499
|
+
def astTuple(elements: Sequence[ast.expr], context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Tuple:
|
|
500
|
+
# def astTuple(elements: Sequence[ast.expr], context: ast.expr_context | None = None, **keywordArguments: int) -> ast.Tuple:
|
|
344
501
|
"""context: Load/Store/Del"""
|
|
345
|
-
context = context or ast.Load()
|
|
502
|
+
# context = context or ast.Load()
|
|
346
503
|
return ast.Tuple(elts=list(elements), ctx=context, **keywordArguments)
|
|
347
504
|
|
|
505
|
+
class LedgerOfImports:
|
|
506
|
+
# TODO When resolving the ledger of imports, remove self-referential imports
|
|
507
|
+
|
|
508
|
+
def __init__(self, startWith: ast.AST | None = None) -> None:
|
|
509
|
+
self.dictionaryImportFrom: dict[str, list[tuple[str, str | None]]] = defaultdict(list)
|
|
510
|
+
self.listImport: list[str] = []
|
|
511
|
+
|
|
512
|
+
if startWith:
|
|
513
|
+
self.walkThis(startWith)
|
|
514
|
+
|
|
515
|
+
def addAst(self, astImport_: ast.Import | ast.ImportFrom) -> None:
|
|
516
|
+
if not isinstance(astImport_, (ast.Import, ast.ImportFrom)):
|
|
517
|
+
raise ValueError(f"Expected ast.Import or ast.ImportFrom, got {type(astImport_)}")
|
|
518
|
+
if isinstance(astImport_, ast.Import):
|
|
519
|
+
for alias in astImport_.names:
|
|
520
|
+
self.listImport.append(alias.name)
|
|
521
|
+
else:
|
|
522
|
+
if astImport_.module is not None:
|
|
523
|
+
for alias in astImport_.names:
|
|
524
|
+
self.dictionaryImportFrom[astImport_.module].append((alias.name, alias.asname))
|
|
525
|
+
|
|
526
|
+
def addImportStr(self, module: str) -> None:
|
|
527
|
+
self.listImport.append(module)
|
|
528
|
+
|
|
529
|
+
def addImportFromStr(self, module: str, name: str, asname: str | None = None) -> None:
|
|
530
|
+
self.dictionaryImportFrom[module].append((name, asname))
|
|
531
|
+
|
|
532
|
+
def exportListModuleNames(self) -> list[str]:
|
|
533
|
+
listModuleNames: list[str] = list(self.dictionaryImportFrom.keys())
|
|
534
|
+
listModuleNames.extend(self.listImport)
|
|
535
|
+
return sorted(set(listModuleNames))
|
|
536
|
+
|
|
537
|
+
def makeListAst(self) -> list[ast.ImportFrom | ast.Import]:
|
|
538
|
+
listAstImportFrom: list[ast.ImportFrom] = []
|
|
539
|
+
|
|
540
|
+
for module, listOfNameTuples in sorted(self.dictionaryImportFrom.items()):
|
|
541
|
+
listOfNameTuples = sorted(list(set(listOfNameTuples)), key=lambda nameTuple: nameTuple[0])
|
|
542
|
+
listAlias: list[ast.alias] = []
|
|
543
|
+
for name, asname in listOfNameTuples:
|
|
544
|
+
listAlias.append(Make.astAlias(name, asname))
|
|
545
|
+
listAstImportFrom.append(Make.astImportFrom(module, listAlias))
|
|
546
|
+
|
|
547
|
+
listAstImport: list[ast.Import] = [Make.astImport(name) for name in sorted(set(self.listImport))]
|
|
548
|
+
return listAstImportFrom + listAstImport
|
|
549
|
+
|
|
550
|
+
def update(self, *fromLedger: 'LedgerOfImports') -> None:
|
|
551
|
+
"""Update this ledger with imports from one or more other ledgers.
|
|
552
|
+
Parameters:
|
|
553
|
+
*fromLedger: One or more other `LedgerOfImports` objects from which to merge.
|
|
554
|
+
"""
|
|
555
|
+
self.dictionaryImportFrom = updateExtendPolishDictionaryLists(self.dictionaryImportFrom, *(ledger.dictionaryImportFrom for ledger in fromLedger), destroyDuplicates=True, reorderLists=True)
|
|
556
|
+
|
|
557
|
+
for ledger in fromLedger:
|
|
558
|
+
self.listImport.extend(ledger.listImport)
|
|
559
|
+
|
|
560
|
+
def walkThis(self, walkThis: ast.AST) -> None:
|
|
561
|
+
for smurf in ast.walk(walkThis):
|
|
562
|
+
if isinstance(smurf, (ast.Import, ast.ImportFrom)):
|
|
563
|
+
self.addAst(smurf)
|
|
564
|
+
|
|
348
565
|
class Then:
|
|
349
566
|
@staticmethod
|
|
350
|
-
def
|
|
351
|
-
return lambda
|
|
567
|
+
def append_targetTo(listName: list[ast.AST]) -> Callable[[ast.AnnAssign], None]:
|
|
568
|
+
return lambda node: listName.append(node.target)
|
|
569
|
+
@staticmethod
|
|
570
|
+
def appendTo(listOfAny: list[Any]) -> Callable[[ast.AST], None]:
|
|
571
|
+
return lambda node: listOfAny.append(node)
|
|
352
572
|
@staticmethod
|
|
353
|
-
def
|
|
354
|
-
return lambda
|
|
573
|
+
def insertThisAbove(list_astAST: Sequence[ast.AST]) -> Callable[[ast.AST], Sequence[ast.AST]]:
|
|
574
|
+
return lambda aboveMe: [*list_astAST, aboveMe]
|
|
355
575
|
@staticmethod
|
|
356
|
-
def
|
|
357
|
-
return lambda
|
|
576
|
+
def insertThisBelow(list_astAST: Sequence[ast.AST]) -> Callable[[ast.AST], Sequence[ast.AST]]:
|
|
577
|
+
return lambda belowMe: [belowMe, *list_astAST]
|
|
358
578
|
@staticmethod
|
|
359
|
-
def removeThis(
|
|
579
|
+
def removeThis(_node: ast.AST) -> None:
|
|
360
580
|
return None
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
class FunctionInliner(ast.NodeTransformer):
|
|
379
|
-
def __init__(self, dictionaryFunctions: dict[str, ast.FunctionDef]) -> None:
|
|
380
|
-
self.dictionaryFunctions: dict[str, ast.FunctionDef] = dictionaryFunctions
|
|
381
|
-
|
|
382
|
-
def inlineFunctionBody(self, callableTargetName: str) -> ast.FunctionDef:
|
|
383
|
-
inlineDefinition: ast.FunctionDef = self.dictionaryFunctions[callableTargetName]
|
|
384
|
-
# Process nested calls within the inlined function
|
|
385
|
-
for astNode in ast.walk(inlineDefinition):
|
|
386
|
-
self.visit(astNode)
|
|
387
|
-
return inlineDefinition
|
|
388
|
-
|
|
389
|
-
def visit_Call(self, node: ast.Call):
|
|
390
|
-
astCall = self.generic_visit(node)
|
|
391
|
-
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryFunctions)(astCall):
|
|
392
|
-
inlineDefinition: ast.FunctionDef = self.inlineFunctionBody(cast(ast.Name, cast(ast.Call, astCall).func).id)
|
|
393
|
-
|
|
394
|
-
if (inlineDefinition and inlineDefinition.body):
|
|
395
|
-
statementTerminating: ast.stmt = inlineDefinition.body[-1]
|
|
396
|
-
|
|
397
|
-
if (isinstance(statementTerminating, ast.Return)
|
|
398
|
-
and statementTerminating.value is not None):
|
|
399
|
-
return self.visit(statementTerminating.value)
|
|
400
|
-
elif isinstance(statementTerminating, ast.Expr):
|
|
401
|
-
return self.visit(statementTerminating.value)
|
|
402
|
-
else:
|
|
403
|
-
return ast.Constant(value=None)
|
|
404
|
-
return astCall
|
|
405
|
-
|
|
406
|
-
def visit_Expr(self, node: ast.Expr):
|
|
407
|
-
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryFunctions)(node.value):
|
|
408
|
-
inlineDefinition: ast.FunctionDef = self.inlineFunctionBody(cast(ast.Name, cast(ast.Call, node.value).func).id)
|
|
409
|
-
return [self.visit(stmt) for stmt in inlineDefinition.body]
|
|
410
|
-
return self.generic_visit(node)
|
|
581
|
+
@staticmethod
|
|
582
|
+
def replaceWith(astAST: ast.AST) -> Callable[[ast.AST], ast.AST]:
|
|
583
|
+
return lambda _replaceMe: astAST
|
|
584
|
+
@staticmethod
|
|
585
|
+
def updateThis(dictionaryOf_astMosDef: dict[ast_Identifier, astMosDef]) -> Callable[[astMosDef], astMosDef]:
|
|
586
|
+
return lambda node: dictionaryOf_astMosDef.setdefault(node.name, node)
|
|
587
|
+
@staticmethod
|
|
588
|
+
def Z0Z_ledger(logicalPath: strDotStrCuzPyStoopid, ledger: LedgerOfImports) -> Callable[[ast.AnnAssign], None]:
|
|
589
|
+
return lambda node: ledger.addImportFromStr(logicalPath, node.annotation.id) # type: ignore
|
|
590
|
+
@staticmethod
|
|
591
|
+
def Z0Z_appendKeywordMirroredTo(list_keyword: list[ast.keyword]) -> Callable[[ast.AnnAssign], None]:
|
|
592
|
+
return lambda node: list_keyword.append(Make.ast_keyword(node.target.id, node.target)) # type: ignore
|
|
593
|
+
@staticmethod
|
|
594
|
+
def Z0Z_appendAnnAssignOf_nameDOTnameTo(identifier: ast_Identifier, list_nameDOTname: list[ast.AnnAssign]) -> Callable[[ast.AnnAssign], None]:
|
|
595
|
+
return lambda node: list_nameDOTname.append(Make.astAnnAssign(node.target, node.annotation, Make.nameDOTname(identifier, node.target.id))) # type: ignore
|
|
596
|
+
|
|
411
597
|
# TODO When resolving the ledger of imports, remove self-referential imports
|
|
412
598
|
|
|
599
|
+
@dataclasses.dataclass
|
|
600
|
+
class IngredientsFunction:
|
|
601
|
+
"""Everything necessary to integrate a function into a module should be here."""
|
|
602
|
+
astFunctionDef: ast.FunctionDef # hint `Make.astFunctionDef`
|
|
603
|
+
imports: LedgerOfImports = dataclasses.field(default_factory=LedgerOfImports)
|
|
604
|
+
|
|
605
|
+
@dataclasses.dataclass
|
|
606
|
+
class IngredientsModule:
|
|
607
|
+
"""Everything necessary to create one _logical_ `ast.Module` should be here.
|
|
608
|
+
Extrinsic qualities should _probably_ be handled externally."""
|
|
609
|
+
# If an `ast.Module` had a logical name that would be reasonable, but Python is firmly opposed
|
|
610
|
+
# to a reasonable namespace, therefore, Hunter, you were silly to add a `name` field to this
|
|
611
|
+
# dataclass for building an `ast.Module`.
|
|
612
|
+
# name: ast_Identifier
|
|
613
|
+
# Hey, genius, note that this is dataclasses.InitVar
|
|
614
|
+
ingredientsFunction: dataclasses.InitVar[Sequence[IngredientsFunction] | IngredientsFunction | None] = None
|
|
615
|
+
|
|
616
|
+
# init var with an existing module? method to deconstruct an existing module?
|
|
617
|
+
|
|
618
|
+
# `body` attribute of `ast.Module`
|
|
619
|
+
imports: LedgerOfImports = dataclasses.field(default_factory=LedgerOfImports)
|
|
620
|
+
prologue: list[ast.stmt] = dataclasses.field(default_factory=list)
|
|
621
|
+
functions: list[ast.FunctionDef | ast.stmt] = dataclasses.field(default_factory=list)
|
|
622
|
+
epilogue: list[ast.stmt] = dataclasses.field(default_factory=list)
|
|
623
|
+
launcher: list[ast.stmt] = dataclasses.field(default_factory=list)
|
|
624
|
+
|
|
625
|
+
# parameter for `ast.Module` constructor
|
|
626
|
+
type_ignores: list[ast.TypeIgnore] = dataclasses.field(default_factory=list)
|
|
627
|
+
|
|
628
|
+
def __post_init__(self, ingredientsFunction: Sequence[IngredientsFunction] | IngredientsFunction | None = None) -> None:
|
|
629
|
+
if ingredientsFunction is not None:
|
|
630
|
+
if isinstance(ingredientsFunction, IngredientsFunction):
|
|
631
|
+
self.addIngredientsFunction(ingredientsFunction)
|
|
632
|
+
else:
|
|
633
|
+
self.addIngredientsFunction(*ingredientsFunction)
|
|
634
|
+
|
|
635
|
+
def addIngredientsFunction(self, *ingredientsFunction: IngredientsFunction) -> None:
|
|
636
|
+
"""Add one or more `IngredientsFunction`."""
|
|
637
|
+
listLedgers: list[LedgerOfImports] = []
|
|
638
|
+
for definition in ingredientsFunction:
|
|
639
|
+
self.functions.append(definition.astFunctionDef)
|
|
640
|
+
listLedgers.append(definition.imports)
|
|
641
|
+
self.imports.update(*listLedgers)
|
|
642
|
+
|
|
643
|
+
def _makeModuleBody(self) -> list[ast.stmt]:
|
|
644
|
+
body: list[ast.stmt] = []
|
|
645
|
+
body.extend(self.imports.makeListAst())
|
|
646
|
+
body.extend(self.prologue)
|
|
647
|
+
body.extend(self.functions)
|
|
648
|
+
body.extend(self.epilogue)
|
|
649
|
+
body.extend(self.launcher)
|
|
650
|
+
# TODO `launcher`, if it exists, must start with `if __name__ == '__main__':` and be indented
|
|
651
|
+
return body
|
|
652
|
+
|
|
653
|
+
def export(self) -> ast.Module:
|
|
654
|
+
"""Create a new `ast.Module` from the ingredients."""
|
|
655
|
+
return Make.astModule(self._makeModuleBody(), self.type_ignores)
|
|
656
|
+
|
|
657
|
+
@dataclasses.dataclass
|
|
658
|
+
class RecipeSynthesizeFlow:
|
|
659
|
+
"""Settings for synthesizing flow."""
|
|
660
|
+
# ========================================
|
|
661
|
+
# Source
|
|
662
|
+
sourceAlgorithm: ModuleType = getSourceAlgorithm()
|
|
663
|
+
sourcePython: str = inspect_getsource(sourceAlgorithm)
|
|
664
|
+
source_astModule: ast.Module = ast.parse(sourcePython)
|
|
665
|
+
|
|
666
|
+
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
667
|
+
sourceDispatcherCallable: str = theDispatcherCallable
|
|
668
|
+
sourceInitializeCallable: str = theSourceInitializeCallable
|
|
669
|
+
sourceParallelCallable: str = theSourceParallelCallable
|
|
670
|
+
sourceSequentialCallable: str = theSourceSequentialCallable
|
|
671
|
+
|
|
672
|
+
sourceDataclassIdentifier: str = theDataclassIdentifier
|
|
673
|
+
sourceDataclassInstance: str = theDataclassInstance
|
|
674
|
+
sourceDataclassInstanceTaskDistribution: str = theDataclassInstanceTaskDistribution
|
|
675
|
+
sourcePathModuleDataclass: str = theLogicalPathModuleDataclass
|
|
676
|
+
|
|
677
|
+
# ========================================
|
|
678
|
+
# Filesystem
|
|
679
|
+
pathPackage: PurePosixPath = PurePosixPath(thePathPackage)
|
|
680
|
+
fileExtension: str = theFileExtension
|
|
681
|
+
|
|
682
|
+
# ========================================
|
|
683
|
+
# Logical identifiers
|
|
684
|
+
# meta
|
|
685
|
+
formatStrModuleSynthetic: str = theFormatStrModuleSynthetic
|
|
686
|
+
formatStrModuleForCallableSynthetic: str = theFormatStrModuleForCallableSynthetic
|
|
687
|
+
|
|
688
|
+
# Package
|
|
689
|
+
packageName: ast_Identifier = thePackageName
|
|
690
|
+
|
|
691
|
+
# Module
|
|
692
|
+
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
693
|
+
Z0Z_flowLogicalPathRoot: str = theModuleOfSyntheticModules
|
|
694
|
+
moduleDispatcher: str = theModuleDispatcherSynthetic
|
|
695
|
+
logicalPathModuleDataclass: str = sourcePathModuleDataclass
|
|
696
|
+
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
697
|
+
# `theLogicalPathModuleDispatcherSynthetic` is a problem. It is defined in theSSOT, but it can also be calculated.
|
|
698
|
+
logicalPathModuleDispatcher: str = theLogicalPathModuleDispatcherSynthetic
|
|
699
|
+
|
|
700
|
+
# Function
|
|
701
|
+
dispatcherCallable: str = sourceDispatcherCallable
|
|
702
|
+
initializeCallable: str = sourceInitializeCallable
|
|
703
|
+
parallelCallable: str = sourceParallelCallable
|
|
704
|
+
sequentialCallable: str = sourceSequentialCallable
|
|
705
|
+
|
|
706
|
+
dataclassIdentifier: str = sourceDataclassIdentifier
|
|
707
|
+
|
|
708
|
+
# Variable
|
|
709
|
+
dataclassInstance: str = sourceDataclassInstance
|
|
710
|
+
|
|
413
711
|
def extractClassDef(identifier: ast_Identifier, module: ast.Module) -> ast.ClassDef | None:
|
|
414
712
|
sherpa: list[ast.ClassDef] = []
|
|
415
713
|
extractor = NodeCollector(ifThis.isClassDef_Identifier(identifier), [Then.appendTo(sherpa)])
|
|
@@ -423,3 +721,43 @@ def extractFunctionDef(identifier: ast_Identifier, module: ast.Module) -> ast.Fu
|
|
|
423
721
|
extractor.visit(module)
|
|
424
722
|
astClassDef = sherpa[0] if sherpa else None
|
|
425
723
|
return astClassDef
|
|
724
|
+
|
|
725
|
+
def makeDictionaryFunctionDef(module: ast.Module) -> dict[ast_Identifier, ast.FunctionDef]:
|
|
726
|
+
dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef] = {}
|
|
727
|
+
NodeCollector(ifThis.isFunctionDef, [Then.updateThis(dictionaryFunctionDef)]).visit(module)
|
|
728
|
+
return dictionaryFunctionDef
|
|
729
|
+
|
|
730
|
+
def makeDictionaryReplacementStatements(module: ast.Module) -> dict[ast_Identifier, ast.stmt | list[ast.stmt]]:
|
|
731
|
+
"""Return a dictionary of function names and their replacement statements."""
|
|
732
|
+
dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef] = makeDictionaryFunctionDef(module)
|
|
733
|
+
dictionaryReplacementStatements: dict[ast_Identifier, ast.stmt | list[ast.stmt]] = {}
|
|
734
|
+
for name, astFunctionDef in dictionaryFunctionDef.items():
|
|
735
|
+
if ifThis.OnlyReturnAnyCompare(astFunctionDef):
|
|
736
|
+
dictionaryReplacementStatements[name] = astFunctionDef.body[0].value # type: ignore
|
|
737
|
+
elif ifThis.OnlyReturnUnaryOp(astFunctionDef):
|
|
738
|
+
dictionaryReplacementStatements[name] = astFunctionDef.body[0].value # type: ignore
|
|
739
|
+
else:
|
|
740
|
+
dictionaryReplacementStatements[name] = astFunctionDef.body[0:-1]
|
|
741
|
+
return dictionaryReplacementStatements
|
|
742
|
+
|
|
743
|
+
def Z0Z_descendantContainsMatchingNode(node: ast.AST, predicateFunction: Callable[[ast.AST], bool]) -> bool:
|
|
744
|
+
"""Return True if any descendant of the node (or the node itself) matches the predicateFunction."""
|
|
745
|
+
matchFound = False
|
|
746
|
+
|
|
747
|
+
class DescendantFinder(ast.NodeVisitor):
|
|
748
|
+
def generic_visit(self, node: ast.AST) -> None:
|
|
749
|
+
nonlocal matchFound
|
|
750
|
+
if predicateFunction(node):
|
|
751
|
+
matchFound = True
|
|
752
|
+
else:
|
|
753
|
+
super().generic_visit(node)
|
|
754
|
+
|
|
755
|
+
DescendantFinder().visit(node)
|
|
756
|
+
return matchFound
|
|
757
|
+
|
|
758
|
+
def Z0Z_executeActionUnlessDescendantMatches(exclusionPredicate: Callable[[ast.AST], bool], actionFunction: Callable[[ast.AST], None]) -> Callable[[ast.AST], None]:
|
|
759
|
+
"""Return a new action that will execute actionFunction only if no descendant (or the node itself) matches exclusionPredicate."""
|
|
760
|
+
def wrappedAction(node: ast.AST) -> None:
|
|
761
|
+
if not Z0Z_descendantContainsMatchingNode(node, exclusionPredicate):
|
|
762
|
+
actionFunction(node)
|
|
763
|
+
return wrappedAction
|