mapFolding 0.8.0__py3-none-any.whl → 0.8.1__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 +33 -4
- mapFolding/basecamp.py +14 -0
- mapFolding/beDRY.py +16 -1
- mapFolding/filesystem.py +124 -90
- mapFolding/noHomeYet.py +12 -0
- mapFolding/oeis.py +16 -1
- mapFolding/reference/__init__.py +0 -0
- mapFolding/reference/flattened.py +46 -45
- mapFolding/reference/hunterNumba.py +4 -4
- mapFolding/reference/irvineJavaPort.py +1 -1
- mapFolding/reference/lunnanNumpy.py +3 -4
- mapFolding/reference/lunnanWhile.py +5 -7
- mapFolding/reference/rotatedEntryPoint.py +2 -3
- mapFolding/someAssemblyRequired/__init__.py +29 -0
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +32 -14
- mapFolding/someAssemblyRequired/ingredientsNumba.py +22 -1
- mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +196 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaJobVESTIGIAL.py +3 -4
- mapFolding/someAssemblyRequired/transformDataStructures.py +162 -0
- mapFolding/someAssemblyRequired/transformationTools.py +216 -199
- mapFolding/theDao.py +19 -5
- mapFolding/theSSOT.py +19 -1
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/METADATA +50 -44
- mapfolding-0.8.1.dist-info/RECORD +39 -0
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/WHEEL +1 -1
- mapFolding/reference/lunnan.py +0 -153
- mapFolding/someAssemblyRequired/Z0Z_workbench.py +0 -350
- mapFolding/someAssemblyRequired/synthesizeDataConverters.py +0 -117
- mapFolding/syntheticModules/numbaCountHistoricalExample.py +0 -158
- mapFolding/syntheticModules/numba_doTheNeedfulHistoricalExample.py +0 -13
- mapfolding-0.8.0.dist-info/RECORD +0 -41
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info/licenses}/LICENSE +0 -0
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,36 @@
|
|
|
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
1
|
"""
|
|
2
|
+
Tools for transforming Python code through abstract syntax tree (AST) manipulation.
|
|
3
|
+
|
|
4
|
+
This module provides a comprehensive set of utilities for programmatically analyzing,
|
|
5
|
+
transforming, and generating Python code through AST manipulation. It implements
|
|
6
|
+
a highly flexible framework that enables:
|
|
7
|
+
|
|
8
|
+
1. Precise identification of code patterns through composable predicates
|
|
9
|
+
2. Targeted modification of code structures while preserving semantics
|
|
10
|
+
3. Code generation with proper syntax and import management
|
|
11
|
+
4. Analysis of code dependencies and relationships
|
|
12
|
+
5. Clean transformation of one algorithmic implementation to another
|
|
13
|
+
|
|
14
|
+
The utilities are organized into several key components:
|
|
15
|
+
- Predicate factories (ifThis): Create composable functions for matching AST patterns
|
|
16
|
+
- Node transformers: Modify AST structures in targeted ways
|
|
17
|
+
- Code generation helpers (Make): Create well-formed AST nodes programmatically
|
|
18
|
+
- Import tracking: Maintain proper imports during code transformation
|
|
19
|
+
- Analysis tools: Extract and organize code information
|
|
20
|
+
|
|
21
|
+
While these tools were developed to transform the baseline algorithm into optimized formats,
|
|
22
|
+
they are designed as general-purpose utilities applicable to a wide range of code
|
|
23
|
+
transformation scenarios beyond the scope of this package.
|
|
24
|
+
"""
|
|
25
|
+
from autoflake import fix_code as autoflake_fix_code
|
|
12
26
|
from collections import defaultdict
|
|
13
|
-
from collections.abc import Callable, Container,
|
|
14
|
-
from
|
|
27
|
+
from collections.abc import Callable, Container, Sequence
|
|
28
|
+
from copy import deepcopy
|
|
15
29
|
from inspect import getsource as inspect_getsource
|
|
30
|
+
from mapFolding.filesystem import writeStringToHere
|
|
16
31
|
from mapFolding.theSSOT import (
|
|
17
32
|
getSourceAlgorithm,
|
|
33
|
+
raiseIfNoneGitHubIssueNumber3,
|
|
18
34
|
theDataclassIdentifier,
|
|
19
35
|
theDataclassInstance,
|
|
20
36
|
theDataclassInstanceTaskDistribution,
|
|
@@ -32,9 +48,10 @@ from mapFolding.theSSOT import (
|
|
|
32
48
|
theSourceParallelCallable,
|
|
33
49
|
theSourceSequentialCallable,
|
|
34
50
|
)
|
|
35
|
-
from
|
|
51
|
+
from os import PathLike
|
|
52
|
+
from pathlib import Path, PurePath, PurePosixPath
|
|
36
53
|
from types import ModuleType
|
|
37
|
-
from typing import Any,
|
|
54
|
+
from typing import Any, cast, Generic, TypeAlias, TypeGuard, TypeVar
|
|
38
55
|
from Z0Z_tools import updateExtendPolishDictionaryLists
|
|
39
56
|
import ast
|
|
40
57
|
import dataclasses
|
|
@@ -56,24 +73,15 @@ Namespace: uppercase, should only appear in camelCase and means "namespace", low
|
|
|
56
73
|
|
|
57
74
|
# Would `LibCST` be better than `ast` in some cases? https://github.com/hunterhogan/mapFolding/issues/7
|
|
58
75
|
|
|
59
|
-
nodeType = TypeVar('nodeType', bound=ast.AST)
|
|
60
|
-
ast_Identifier: TypeAlias = str
|
|
61
76
|
ast_expr_Slice: TypeAlias = ast.expr
|
|
62
|
-
|
|
63
|
-
strORlist_ast_type_paramORintORNone: TypeAlias = Any
|
|
64
|
-
list_ast_type_paramORintORNone: TypeAlias = Any
|
|
65
|
-
strORintORNone: TypeAlias = Any
|
|
77
|
+
ast_Identifier: TypeAlias = str
|
|
66
78
|
astClassHasAttributeDOTname: TypeAlias = ast.FunctionDef | ast.ClassDef | ast.AsyncFunctionDef
|
|
67
79
|
astMosDef = TypeVar('astMosDef', bound=astClassHasAttributeDOTname)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
yield ImaModule
|
|
75
|
-
finally:
|
|
76
|
-
ImaList = ImaModule.body
|
|
80
|
+
list_ast_type_paramORintORNone: TypeAlias = Any
|
|
81
|
+
nodeType = TypeVar('nodeType', bound=ast.AST)
|
|
82
|
+
strDotStrCuzPyStoopid: TypeAlias = str
|
|
83
|
+
strORintORNone: TypeAlias = Any
|
|
84
|
+
strORlist_ast_type_paramORintORNone: TypeAlias = Any
|
|
77
85
|
|
|
78
86
|
class NodeCollector(Generic[nodeType], ast.NodeVisitor):
|
|
79
87
|
"""A node visitor that collects data via one or more actions when a predicate is met."""
|
|
@@ -87,255 +95,189 @@ class NodeCollector(Generic[nodeType], ast.NodeVisitor):
|
|
|
87
95
|
action(cast(nodeType, node))
|
|
88
96
|
self.generic_visit(node)
|
|
89
97
|
|
|
90
|
-
ImaViolationOfTheLiskovSubstitutionPrinciple: TypeAlias = ast.AST
|
|
91
98
|
class NodeReplacer(Generic[nodeType], ast.NodeTransformer):
|
|
92
99
|
"""A node transformer that replaces or removes AST nodes based on a condition."""
|
|
93
100
|
def __init__(self, findThis: Callable[[ast.AST], TypeGuard[nodeType] | bool], doThat: Callable[[nodeType], ast.AST | Sequence[ast.AST] | None]) -> None:
|
|
94
101
|
self.findThis = findThis
|
|
95
102
|
self.doThat = doThat
|
|
96
103
|
|
|
97
|
-
def visit(self, node:
|
|
104
|
+
def visit(self, node: ast.AST) -> ast.AST | Sequence[ast.AST] | None:
|
|
98
105
|
if self.findThis(node):
|
|
99
106
|
return self.doThat(cast(nodeType, node))
|
|
100
107
|
return super().visit(node)
|
|
101
108
|
|
|
102
109
|
class ifThis:
|
|
103
110
|
@staticmethod
|
|
104
|
-
def ast_IdentifierIsIn(container: Container[ast_Identifier]) -> Callable[[ast_Identifier], TypeGuard[ast_Identifier]]:
|
|
105
|
-
return lambda node: node in container
|
|
106
|
-
|
|
111
|
+
def ast_IdentifierIsIn(container: Container[ast_Identifier]) -> Callable[[ast_Identifier], TypeGuard[ast_Identifier] | bool]:
|
|
112
|
+
return lambda node: node in container
|
|
107
113
|
@staticmethod
|
|
108
|
-
def CallDoesNotCallItself(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call]]:
|
|
114
|
+
def CallDoesNotCallItself(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
|
|
109
115
|
"""If `namespace` is not applicable to your case, then call with `namespace=""`."""
|
|
110
|
-
return lambda node: ifThis.matchesMeButNotAnyDescendant(ifThis.CallReallyIs(namespace, identifier))(node)
|
|
111
|
-
|
|
116
|
+
return lambda node: ifThis.matchesMeButNotAnyDescendant(ifThis.CallReallyIs(namespace, identifier))(node)
|
|
112
117
|
@staticmethod
|
|
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)
|
|
115
|
-
|
|
118
|
+
def CallDoesNotCallItselfAndNameDOTidIsIn(container: Container[ast_Identifier]) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
|
|
119
|
+
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)
|
|
116
120
|
@staticmethod
|
|
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))
|
|
119
|
-
|
|
121
|
+
def CallReallyIs(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
|
|
122
|
+
return ifThis.isAnyOf(ifThis.isCall_Identifier(identifier), ifThis.isCallNamespace_Identifier(namespace, identifier))
|
|
120
123
|
@staticmethod
|
|
121
124
|
def is_keyword(node: ast.AST) -> TypeGuard[ast.keyword]:
|
|
122
125
|
return isinstance(node, ast.keyword)
|
|
123
|
-
|
|
124
126
|
@staticmethod
|
|
125
127
|
def is_keywordAndValueIsConstant(node: ast.AST) -> TypeGuard[ast.keyword]:
|
|
126
128
|
return ifThis.is_keyword(node) and ifThis.isConstant(node.value)
|
|
127
|
-
|
|
128
129
|
@staticmethod
|
|
129
|
-
def is_keyword_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.keyword]]:
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
def is_keyword_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.keyword] | bool]:
|
|
131
|
+
def workhorse(node: ast.AST) -> TypeGuard[ast.keyword] | bool:
|
|
132
|
+
return ifThis.is_keyword(node) and node.arg == identifier
|
|
133
|
+
return workhorse
|
|
132
134
|
@staticmethod
|
|
133
|
-
def is_keyword_IdentifierEqualsConstantValue(identifier: ast_Identifier, ConstantValue: Any) -> Callable[[ast.AST], TypeGuard[ast.keyword]]:
|
|
134
|
-
return lambda node:
|
|
135
|
-
|
|
135
|
+
def is_keyword_IdentifierEqualsConstantValue(identifier: ast_Identifier, ConstantValue: Any) -> Callable[[ast.AST], TypeGuard[ast.keyword] | bool]:
|
|
136
|
+
return lambda node: ifThis.is_keyword_Identifier(identifier)(node) and ifThis.is_keywordAndValueIsConstant(node) and ifThis.isConstantEquals(ConstantValue)(node.value)
|
|
136
137
|
@staticmethod
|
|
137
138
|
def isAllOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
138
139
|
return lambda node: all(predicate(node) for predicate in thesePredicates)
|
|
139
|
-
|
|
140
140
|
@staticmethod
|
|
141
141
|
def isAnnAssign(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
|
|
142
142
|
return isinstance(node, ast.AnnAssign)
|
|
143
|
-
|
|
144
143
|
@staticmethod
|
|
145
144
|
def isAnnAssignAndAnnotationIsName(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
|
|
146
145
|
return ifThis.isAnnAssign(node) and ifThis.isName(node.annotation)
|
|
147
|
-
|
|
148
146
|
@staticmethod
|
|
149
147
|
def isAnnAssignAndTargetIsName(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
|
|
150
148
|
return ifThis.isAnnAssign(node) and ifThis.isName(node.target)
|
|
151
|
-
|
|
152
149
|
@staticmethod
|
|
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)
|
|
155
|
-
|
|
150
|
+
def isAnnAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AnnAssign] | bool]:
|
|
151
|
+
return lambda node: ifThis.isAnnAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target)
|
|
156
152
|
@staticmethod
|
|
157
153
|
def isAnyAssignmentTo(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
|
|
158
154
|
return ifThis.isAnyOf(ifThis.isAssignOnlyTo(identifier), ifThis.isAnnAssignTo(identifier), ifThis.isAugAssignTo(identifier))
|
|
159
|
-
|
|
160
155
|
@staticmethod
|
|
161
156
|
def isAnyCompare(node: ast.AST) -> TypeGuard[ast.Compare] | TypeGuard[ast.BoolOp]:
|
|
162
157
|
return ifThis.isCompare(node) or ifThis.isBoolOp(node)
|
|
163
|
-
|
|
164
158
|
@staticmethod
|
|
165
159
|
def isAnyOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
166
160
|
return lambda node: any(predicate(node) for predicate in thesePredicates)
|
|
167
|
-
|
|
168
161
|
@staticmethod
|
|
169
162
|
def isAssign(node: ast.AST) -> TypeGuard[ast.Assign]:
|
|
170
163
|
return isinstance(node, ast.Assign)
|
|
171
|
-
|
|
172
164
|
@staticmethod
|
|
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)
|
|
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
|
-
|
|
165
|
+
def isAssignAndValueIsCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
|
|
166
|
+
return lambda node: ifThis.isAssign(node) and ifThis.isCall_Identifier(identifier)(node.value)
|
|
180
167
|
@staticmethod
|
|
181
|
-
def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
168
|
+
def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
|
|
182
169
|
return ifThis.isAssignAndValueIs(ifThis.isCallNamespace_Identifier(namespace, identifier))
|
|
183
|
-
|
|
184
170
|
@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])
|
|
187
|
-
|
|
171
|
+
def isAssignOnlyTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
|
|
172
|
+
return lambda node: ifThis.isAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.targets[0])
|
|
188
173
|
@staticmethod
|
|
189
|
-
def isAssignAndTargets0Is(targets0Predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign]]:
|
|
174
|
+
def isAssignAndTargets0Is(targets0Predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
|
|
190
175
|
"""node is Assign and node.targets[0] matches `targets0Predicate`."""
|
|
191
|
-
return lambda node: ifThis.isAssign(node) and targets0Predicate(node.targets[0])
|
|
192
|
-
|
|
176
|
+
return lambda node: ifThis.isAssign(node) and targets0Predicate(node.targets[0])
|
|
193
177
|
@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
|
-
|
|
178
|
+
def isAssignAndValueIs(valuePredicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
|
|
179
|
+
"""node is ast.Assign and node.value matches `valuePredicate`.
|
|
197
180
|
Parameters:
|
|
198
181
|
valuePredicate: Function that evaluates the value of the assignment
|
|
199
|
-
|
|
200
182
|
Returns:
|
|
201
|
-
|
|
183
|
+
predicate: matches assignments with values meeting the criteria
|
|
202
184
|
"""
|
|
203
|
-
return lambda node: ifThis.isAssign(node) and valuePredicate(node.value)
|
|
204
|
-
|
|
185
|
+
return lambda node: ifThis.isAssign(node) and valuePredicate(node.value)
|
|
205
186
|
@staticmethod
|
|
206
187
|
def isAttribute(node: ast.AST) -> TypeGuard[ast.Attribute]:
|
|
207
188
|
return isinstance(node, ast.Attribute)
|
|
208
|
-
|
|
209
189
|
@staticmethod
|
|
210
190
|
def isAugAssign(node: ast.AST) -> TypeGuard[ast.AugAssign]:
|
|
211
191
|
return isinstance(node, ast.AugAssign)
|
|
212
|
-
|
|
213
192
|
@staticmethod
|
|
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)
|
|
216
|
-
|
|
193
|
+
def isAugAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AugAssign] | bool]:
|
|
194
|
+
return lambda node: ifThis.isAugAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target)
|
|
217
195
|
@staticmethod
|
|
218
196
|
def isBoolOp(node: ast.AST) -> TypeGuard[ast.BoolOp]:
|
|
219
197
|
return isinstance(node, ast.BoolOp)
|
|
220
|
-
|
|
221
198
|
@staticmethod
|
|
222
199
|
def isCall(node: ast.AST) -> TypeGuard[ast.Call]:
|
|
223
200
|
return isinstance(node, ast.Call)
|
|
224
|
-
|
|
225
201
|
@staticmethod
|
|
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)
|
|
228
|
-
|
|
202
|
+
def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
|
|
203
|
+
return lambda node: ifThis.isCall(node) and ifThis.isName_Identifier(identifier)(node.func)
|
|
229
204
|
@staticmethod
|
|
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)
|
|
232
|
-
|
|
205
|
+
def isCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
|
|
206
|
+
return lambda node: ifThis.isCall(node) and ifThis.is_nameDOTnameNamespace_Identifier(namespace, identifier)(node.func)
|
|
233
207
|
@staticmethod
|
|
234
208
|
def isCallToName(node: ast.AST) -> TypeGuard[ast.Call]:
|
|
235
209
|
return ifThis.isCall(node) and ifThis.isName(node.func)
|
|
236
|
-
|
|
237
210
|
@staticmethod
|
|
238
211
|
def isClassDef(node: ast.AST) -> TypeGuard[ast.ClassDef]:
|
|
239
212
|
return isinstance(node, ast.ClassDef)
|
|
240
|
-
|
|
241
213
|
@staticmethod
|
|
242
|
-
def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.ClassDef]]:
|
|
243
|
-
return lambda node: ifThis.isClassDef(node) and node.name == identifier
|
|
244
|
-
|
|
214
|
+
def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.ClassDef] | bool]:
|
|
215
|
+
return lambda node: ifThis.isClassDef(node) and node.name == identifier
|
|
245
216
|
@staticmethod
|
|
246
217
|
def isCompare(node: ast.AST) -> TypeGuard[ast.Compare]:
|
|
247
218
|
return isinstance(node, ast.Compare)
|
|
248
|
-
|
|
249
219
|
@staticmethod
|
|
250
220
|
def isConstant(node: ast.AST) -> TypeGuard[ast.Constant]:
|
|
251
221
|
return isinstance(node, ast.Constant)
|
|
252
|
-
|
|
253
222
|
@staticmethod
|
|
254
|
-
def isConstantEquals(value: Any) -> Callable[[ast.AST], TypeGuard[ast.Constant]]:
|
|
255
|
-
return lambda node: ifThis.isConstant(node) and node.value == value
|
|
256
|
-
|
|
223
|
+
def isConstantEquals(value: Any) -> Callable[[ast.AST], TypeGuard[ast.Constant] | bool]:
|
|
224
|
+
return lambda node: ifThis.isConstant(node) and node.value == value
|
|
257
225
|
@staticmethod
|
|
258
226
|
def isExpr(node: ast.AST) -> TypeGuard[ast.Expr]:
|
|
259
227
|
return isinstance(node, ast.Expr)
|
|
260
|
-
|
|
261
228
|
@staticmethod
|
|
262
229
|
def isFunctionDef(node: ast.AST) -> TypeGuard[ast.FunctionDef]:
|
|
263
230
|
return isinstance(node, ast.FunctionDef)
|
|
264
|
-
|
|
265
231
|
@staticmethod
|
|
266
|
-
def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.FunctionDef]]:
|
|
267
|
-
return lambda node: ifThis.isFunctionDef(node) and node.name == identifier
|
|
268
|
-
|
|
232
|
+
def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.FunctionDef] | bool]:
|
|
233
|
+
return lambda node: ifThis.isFunctionDef(node) and node.name == identifier
|
|
269
234
|
@staticmethod
|
|
270
235
|
def isImport(node: ast.AST) -> TypeGuard[ast.Import]:
|
|
271
236
|
return isinstance(node, ast.Import)
|
|
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
|
-
|
|
286
237
|
@staticmethod
|
|
287
238
|
def isName(node: ast.AST) -> TypeGuard[ast.Name]:
|
|
239
|
+
"""TODO
|
|
240
|
+
ast.Name()
|
|
241
|
+
ast.Attribute()
|
|
242
|
+
ast.Subscript()
|
|
243
|
+
ast.Starred()
|
|
244
|
+
"""
|
|
288
245
|
return isinstance(node, ast.Name)
|
|
289
|
-
|
|
290
246
|
@staticmethod
|
|
291
|
-
def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Name]]:
|
|
292
|
-
return lambda node: ifThis.isName(node) and node.id == identifier
|
|
293
|
-
|
|
247
|
+
def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Name] | bool]:
|
|
248
|
+
return lambda node: ifThis.isName(node) and node.id == identifier
|
|
294
249
|
@staticmethod
|
|
295
250
|
def is_nameDOTname(node: ast.AST) -> TypeGuard[ast.Attribute]:
|
|
296
251
|
return ifThis.isAttribute(node) and ifThis.isName(node.value)
|
|
297
|
-
|
|
298
252
|
@staticmethod
|
|
299
|
-
def is_nameDOTnameNamespace(namespace: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute]]:
|
|
300
|
-
return lambda node:
|
|
301
|
-
|
|
253
|
+
def is_nameDOTnameNamespace(namespace: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
|
|
254
|
+
return lambda node: ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value)
|
|
302
255
|
@staticmethod
|
|
303
|
-
def is_nameDOTnameNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute]]:
|
|
304
|
-
return lambda node: (ifThis.
|
|
305
|
-
|
|
306
|
-
@staticmethod
|
|
307
|
-
def isAnyNestedName(node: ast.AST):
|
|
308
|
-
return ifThis.isAnyOf()
|
|
309
|
-
|
|
256
|
+
def is_nameDOTnameNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
|
|
257
|
+
return lambda node: ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value) and node.attr == identifier
|
|
310
258
|
@staticmethod
|
|
311
259
|
def NameReallyIs_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
|
|
312
260
|
# The following logic is incomplete.
|
|
313
261
|
return ifThis.isAnyOf(ifThis.isName_Identifier(identifier), ifThis.isSubscriptIsName_Identifier(identifier))
|
|
314
|
-
|
|
315
262
|
@staticmethod
|
|
316
263
|
def isReturn(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
317
264
|
return isinstance(node, ast.Return)
|
|
318
|
-
|
|
319
265
|
@staticmethod
|
|
320
266
|
def isReturnAnyCompare(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
321
267
|
return ifThis.isReturn(node) and node.value is not None and ifThis.isAnyCompare(node.value)
|
|
322
|
-
|
|
323
268
|
@staticmethod
|
|
324
269
|
def isReturnUnaryOp(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
325
270
|
return ifThis.isReturn(node) and node.value is not None and ifThis.isUnaryOp(node.value)
|
|
326
|
-
|
|
327
271
|
@staticmethod
|
|
328
272
|
def isSubscript(node: ast.AST) -> TypeGuard[ast.Subscript]:
|
|
329
273
|
return isinstance(node, ast.Subscript)
|
|
330
|
-
|
|
331
274
|
@staticmethod
|
|
332
275
|
def isSubscript_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
|
|
333
276
|
"""node is `ast.Subscript` and the top-level `ast.Name` is `identifier`
|
|
334
277
|
Parameters:
|
|
335
278
|
identifier: The identifier to look for in the value chain
|
|
336
|
-
|
|
337
279
|
Returns:
|
|
338
|
-
|
|
280
|
+
predicate: function that checks if a node matches the criteria
|
|
339
281
|
"""
|
|
340
282
|
def predicate(node: ast.AST) -> TypeGuard[ast.Subscript]:
|
|
341
283
|
if not ifThis.isSubscript(node):
|
|
@@ -349,26 +291,21 @@ class ifThis:
|
|
|
349
291
|
return False
|
|
350
292
|
return checkNodeDOTvalue(node.value)
|
|
351
293
|
return predicate
|
|
352
|
-
|
|
353
294
|
@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)
|
|
356
|
-
|
|
295
|
+
def isSubscriptIsName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript] | bool]:
|
|
296
|
+
return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value)
|
|
357
297
|
@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)
|
|
360
|
-
|
|
298
|
+
def isSubscript_Identifier_Identifier(identifier: ast_Identifier, sliceIdentifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript] | bool]:
|
|
299
|
+
return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value) and ifThis.isName_Identifier(sliceIdentifier)(node.slice)
|
|
361
300
|
@staticmethod
|
|
362
301
|
def isUnaryOp(node: ast.AST) -> TypeGuard[ast.UnaryOp]:
|
|
363
302
|
return isinstance(node, ast.UnaryOp)
|
|
364
|
-
|
|
365
|
-
# Does this work?
|
|
303
|
+
# TODO Does this work?
|
|
366
304
|
@staticmethod
|
|
367
305
|
def matchesAtLeast1Descendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
368
306
|
"""Create a predicate that returns True if any descendant of the node matches the given predicate."""
|
|
369
307
|
return lambda node: not ifThis.matchesNoDescendant(predicate)(node)
|
|
370
|
-
|
|
371
|
-
# Does this work?
|
|
308
|
+
# TODO Does this work?
|
|
372
309
|
@staticmethod
|
|
373
310
|
def matchesMeAndMyDescendantsExactlyNTimes(predicate: Callable[[ast.AST], bool], nTimes: int) -> Callable[[ast.AST], bool]:
|
|
374
311
|
"""Create a predicate that returns True if exactly 'count' nodes in the tree match the predicate."""
|
|
@@ -376,12 +313,10 @@ class ifThis:
|
|
|
376
313
|
matches = sum(1 for descendant in ast.walk(node) if predicate(descendant))
|
|
377
314
|
return matches == nTimes
|
|
378
315
|
return countMatchingNodes
|
|
379
|
-
|
|
380
316
|
@staticmethod
|
|
381
317
|
def matchesMeButNotAnyDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
382
318
|
"""Create a predicate that returns True if the node matches but none of its descendants match the predicate."""
|
|
383
319
|
return lambda node: predicate(node) and ifThis.matchesNoDescendant(predicate)(node)
|
|
384
|
-
|
|
385
320
|
@staticmethod
|
|
386
321
|
def matchesNoDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
|
|
387
322
|
"""Create a predicate that returns True if no descendant of the node matches the given predicate."""
|
|
@@ -391,13 +326,11 @@ class ifThis:
|
|
|
391
326
|
return False
|
|
392
327
|
return True
|
|
393
328
|
return checkNoMatchingDescendant
|
|
394
|
-
|
|
395
329
|
@staticmethod
|
|
396
|
-
def
|
|
330
|
+
def onlyReturnAnyCompare(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
|
|
397
331
|
return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnAnyCompare(astFunctionDef.body[0])
|
|
398
|
-
|
|
399
332
|
@staticmethod
|
|
400
|
-
def
|
|
333
|
+
def onlyReturnUnaryOp(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
|
|
401
334
|
return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnUnaryOp(astFunctionDef.body[0])
|
|
402
335
|
|
|
403
336
|
class Make:
|
|
@@ -405,29 +338,23 @@ class Make:
|
|
|
405
338
|
def ast_arg(identifier: ast_Identifier, annotation: ast.expr | None = None, **keywordArguments: strORintORNone) -> ast.arg:
|
|
406
339
|
"""keywordArguments: type_comment:str|None, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
407
340
|
return ast.arg(identifier, annotation, **keywordArguments)
|
|
408
|
-
|
|
409
341
|
@staticmethod
|
|
410
342
|
def ast_keyword(keywordArgument: ast_Identifier, value: ast.expr, **keywordArguments: int) -> ast.keyword:
|
|
411
343
|
return ast.keyword(arg=keywordArgument, value=value, **keywordArguments)
|
|
412
|
-
|
|
413
344
|
@staticmethod
|
|
414
345
|
def astAlias(name: ast_Identifier, asname: ast_Identifier | None = None) -> ast.alias:
|
|
415
346
|
return ast.alias(name, asname)
|
|
416
|
-
|
|
417
347
|
@staticmethod
|
|
418
348
|
def astAnnAssign(target: ast.Name | ast.Attribute | ast.Subscript, annotation: ast.expr, value: ast.expr | None = None, **keywordArguments: int) -> ast.AnnAssign:
|
|
419
349
|
"""`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."""
|
|
420
350
|
return ast.AnnAssign(target, annotation, value, simple=int(isinstance(target, ast.Name)), **keywordArguments)
|
|
421
|
-
|
|
422
351
|
@staticmethod
|
|
423
352
|
def astAssign(listTargets: Any, value: ast.expr, **keywordArguments: strORintORNone) -> ast.Assign:
|
|
424
353
|
"""keywordArguments: type_comment:str|None, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
425
354
|
return ast.Assign(targets=listTargets, value=value, **keywordArguments)
|
|
426
|
-
|
|
427
355
|
@staticmethod
|
|
428
356
|
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
357
|
return ast.arguments(posonlyargs, args, vararg, kwonlyargs, kw_defaults, kwarg, defaults)
|
|
430
|
-
|
|
431
358
|
@staticmethod
|
|
432
359
|
def astAttribute(value: ast.expr, attribute: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Attribute:
|
|
433
360
|
"""
|
|
@@ -436,47 +363,37 @@ class Make:
|
|
|
436
363
|
attribute: the `str` after the dot
|
|
437
364
|
context (ast.Load()): Load/Store/Del"""
|
|
438
365
|
return ast.Attribute(value, attribute, context, **keywordArguments)
|
|
439
|
-
|
|
440
366
|
@staticmethod
|
|
441
367
|
def astCall(caller: ast.Name | ast.Attribute, listArguments: Sequence[ast.expr] | None = None, list_astKeywords: Sequence[ast.keyword] | None = None) -> ast.Call:
|
|
442
368
|
return ast.Call(func=caller, args=list(listArguments) if listArguments else [], keywords=list(list_astKeywords) if list_astKeywords else [])
|
|
443
|
-
|
|
444
369
|
@staticmethod
|
|
445
370
|
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:
|
|
446
371
|
"""keywordArguments: type_params:list[ast.type_param], lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
447
372
|
return ast.ClassDef(name=name, bases=listBases, keywords=list_keyword, body=body, decorator_list=decorator_list, **keywordArguments)
|
|
448
|
-
|
|
449
373
|
@staticmethod
|
|
450
374
|
def astConstant(value: Any, **keywordArguments: strORintORNone) -> ast.Constant:
|
|
451
375
|
"""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
376
|
keywordArguments: kind:str, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
|
|
453
377
|
return ast.Constant(value, **keywordArguments)
|
|
454
|
-
|
|
455
378
|
@staticmethod
|
|
456
379
|
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:
|
|
457
380
|
"""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"""
|
|
458
381
|
return ast.FunctionDef(name=name, args=argumentsSpecification, body=body, decorator_list=decorator_list, returns=returns, **keywordArguments)
|
|
459
|
-
|
|
460
382
|
@staticmethod
|
|
461
383
|
def astImport(moduleName: ast_Identifier, asname: ast_Identifier | None = None, **keywordArguments: int) -> ast.Import:
|
|
462
384
|
return ast.Import(names=[Make.astAlias(moduleName, asname)], **keywordArguments)
|
|
463
|
-
|
|
464
385
|
@staticmethod
|
|
465
386
|
def astImportFrom(moduleName: ast_Identifier, list_astAlias: list[ast.alias], **keywordArguments: int) -> ast.ImportFrom:
|
|
466
387
|
return ast.ImportFrom(module=moduleName, names=list_astAlias, level=0, **keywordArguments)
|
|
467
|
-
|
|
468
388
|
@staticmethod
|
|
469
389
|
def astModule(body: list[ast.stmt], type_ignores: list[ast.TypeIgnore] = []) -> ast.Module:
|
|
470
390
|
return ast.Module(body, type_ignores)
|
|
471
|
-
|
|
472
391
|
@staticmethod
|
|
473
392
|
def astName(identifier: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Name:
|
|
474
393
|
return ast.Name(identifier, context, **keywordArguments)
|
|
475
|
-
|
|
476
394
|
@staticmethod
|
|
477
395
|
def itDOTname(nameChain: ast.Name | ast.Attribute, dotName: str) -> ast.Attribute:
|
|
478
396
|
return ast.Attribute(value=nameChain, attr=dotName, ctx=ast.Load())
|
|
479
|
-
|
|
480
397
|
@staticmethod
|
|
481
398
|
# TODO rewrite with all parameters
|
|
482
399
|
def nameDOTname(identifier: ast_Identifier, *dotName: str) -> ast.Name | ast.Attribute:
|
|
@@ -486,20 +403,15 @@ class Make:
|
|
|
486
403
|
for suffix in dotName:
|
|
487
404
|
nameDOTname = Make.itDOTname(nameDOTname, suffix)
|
|
488
405
|
return nameDOTname
|
|
489
|
-
|
|
490
406
|
@staticmethod
|
|
491
407
|
def astReturn(value: ast.expr | None = None, **keywordArguments: int) -> ast.Return:
|
|
492
408
|
return ast.Return(value, **keywordArguments)
|
|
493
|
-
|
|
494
409
|
@staticmethod
|
|
495
410
|
def astSubscript(value: ast.expr, slice: ast_expr_Slice, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Subscript:
|
|
496
411
|
return ast.Subscript(value, slice, ctx=context, **keywordArguments)
|
|
497
|
-
|
|
498
412
|
@staticmethod
|
|
499
413
|
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:
|
|
501
414
|
"""context: Load/Store/Del"""
|
|
502
|
-
# context = context or ast.Load()
|
|
503
415
|
return ast.Tuple(elts=list(elements), ctx=context, **keywordArguments)
|
|
504
416
|
|
|
505
417
|
class LedgerOfImports:
|
|
@@ -513,8 +425,7 @@ class LedgerOfImports:
|
|
|
513
425
|
self.walkThis(startWith)
|
|
514
426
|
|
|
515
427
|
def addAst(self, astImport_: ast.Import | ast.ImportFrom) -> None:
|
|
516
|
-
|
|
517
|
-
raise ValueError(f"Expected ast.Import or ast.ImportFrom, got {type(astImport_)}")
|
|
428
|
+
assert isinstance(astImport_, (ast.Import, ast.ImportFrom)), f"Expected ast.Import or ast.ImportFrom, got {type(astImport_)}"
|
|
518
429
|
if isinstance(astImport_, ast.Import):
|
|
519
430
|
for alias in astImport_.names:
|
|
520
431
|
self.listImport.append(alias.name)
|
|
@@ -594,8 +505,6 @@ class Then:
|
|
|
594
505
|
def Z0Z_appendAnnAssignOf_nameDOTnameTo(identifier: ast_Identifier, list_nameDOTname: list[ast.AnnAssign]) -> Callable[[ast.AnnAssign], None]:
|
|
595
506
|
return lambda node: list_nameDOTname.append(Make.astAnnAssign(node.target, node.annotation, Make.nameDOTname(identifier, node.target.id))) # type: ignore
|
|
596
507
|
|
|
597
|
-
# TODO When resolving the ledger of imports, remove self-referential imports
|
|
598
|
-
|
|
599
508
|
@dataclasses.dataclass
|
|
600
509
|
class IngredientsFunction:
|
|
601
510
|
"""Everything necessary to integrate a function into a module should be here."""
|
|
@@ -606,11 +515,6 @@ class IngredientsFunction:
|
|
|
606
515
|
class IngredientsModule:
|
|
607
516
|
"""Everything necessary to create one _logical_ `ast.Module` should be here.
|
|
608
517
|
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
518
|
ingredientsFunction: dataclasses.InitVar[Sequence[IngredientsFunction] | IngredientsFunction | None] = None
|
|
615
519
|
|
|
616
520
|
# init var with an existing module? method to deconstruct an existing module?
|
|
@@ -676,7 +580,7 @@ class RecipeSynthesizeFlow:
|
|
|
676
580
|
|
|
677
581
|
# ========================================
|
|
678
582
|
# Filesystem
|
|
679
|
-
pathPackage: PurePosixPath = PurePosixPath(thePathPackage)
|
|
583
|
+
pathPackage: PurePosixPath | None = PurePosixPath(thePathPackage)
|
|
680
584
|
fileExtension: str = theFileExtension
|
|
681
585
|
|
|
682
586
|
# ========================================
|
|
@@ -686,7 +590,7 @@ class RecipeSynthesizeFlow:
|
|
|
686
590
|
formatStrModuleForCallableSynthetic: str = theFormatStrModuleForCallableSynthetic
|
|
687
591
|
|
|
688
592
|
# Package
|
|
689
|
-
packageName: ast_Identifier = thePackageName
|
|
593
|
+
packageName: ast_Identifier | None = thePackageName
|
|
690
594
|
|
|
691
595
|
# Module
|
|
692
596
|
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
@@ -708,6 +612,26 @@ class RecipeSynthesizeFlow:
|
|
|
708
612
|
# Variable
|
|
709
613
|
dataclassInstance: str = sourceDataclassInstance
|
|
710
614
|
|
|
615
|
+
def _makePathFilename(self, filenameStem: str,
|
|
616
|
+
pathRoot: PurePosixPath | None = None,
|
|
617
|
+
logicalPathINFIX: strDotStrCuzPyStoopid | None = None,
|
|
618
|
+
fileExtension: str | None = None,
|
|
619
|
+
) -> PurePosixPath:
|
|
620
|
+
"""filenameStem: (hint: the name of the logical module)"""
|
|
621
|
+
if pathRoot is None:
|
|
622
|
+
pathRoot = self.pathPackage or PurePosixPath(Path.cwd())
|
|
623
|
+
if logicalPathINFIX:
|
|
624
|
+
whyIsThisStillAThing: list[str] = logicalPathINFIX.split('.')
|
|
625
|
+
pathRoot = pathRoot.joinpath(*whyIsThisStillAThing)
|
|
626
|
+
if fileExtension is None:
|
|
627
|
+
fileExtension = self.fileExtension
|
|
628
|
+
filename: str = filenameStem + fileExtension
|
|
629
|
+
return pathRoot.joinpath(filename)
|
|
630
|
+
|
|
631
|
+
@property
|
|
632
|
+
def pathFilenameDispatcher(self) -> PurePosixPath:
|
|
633
|
+
return self._makePathFilename(filenameStem=self.moduleDispatcher, logicalPathINFIX=self.Z0Z_flowLogicalPathRoot)
|
|
634
|
+
|
|
711
635
|
def extractClassDef(identifier: ast_Identifier, module: ast.Module) -> ast.ClassDef | None:
|
|
712
636
|
sherpa: list[ast.ClassDef] = []
|
|
713
637
|
extractor = NodeCollector(ifThis.isClassDef_Identifier(identifier), [Then.appendTo(sherpa)])
|
|
@@ -732,9 +656,9 @@ def makeDictionaryReplacementStatements(module: ast.Module) -> dict[ast_Identifi
|
|
|
732
656
|
dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef] = makeDictionaryFunctionDef(module)
|
|
733
657
|
dictionaryReplacementStatements: dict[ast_Identifier, ast.stmt | list[ast.stmt]] = {}
|
|
734
658
|
for name, astFunctionDef in dictionaryFunctionDef.items():
|
|
735
|
-
if ifThis.
|
|
659
|
+
if ifThis.onlyReturnAnyCompare(astFunctionDef):
|
|
736
660
|
dictionaryReplacementStatements[name] = astFunctionDef.body[0].value # type: ignore
|
|
737
|
-
elif ifThis.
|
|
661
|
+
elif ifThis.onlyReturnUnaryOp(astFunctionDef):
|
|
738
662
|
dictionaryReplacementStatements[name] = astFunctionDef.body[0].value # type: ignore
|
|
739
663
|
else:
|
|
740
664
|
dictionaryReplacementStatements[name] = astFunctionDef.body[0:-1]
|
|
@@ -761,3 +685,96 @@ def Z0Z_executeActionUnlessDescendantMatches(exclusionPredicate: Callable[[ast.A
|
|
|
761
685
|
if not Z0Z_descendantContainsMatchingNode(node, exclusionPredicate):
|
|
762
686
|
actionFunction(node)
|
|
763
687
|
return wrappedAction
|
|
688
|
+
|
|
689
|
+
def inlineThisFunctionWithTheseValues(astFunctionDef: ast.FunctionDef, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> ast.FunctionDef:
|
|
690
|
+
class FunctionInliner(ast.NodeTransformer):
|
|
691
|
+
def __init__(self, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> None:
|
|
692
|
+
self.dictionaryReplacementStatements = dictionaryReplacementStatements
|
|
693
|
+
|
|
694
|
+
def generic_visit(self, node: ast.AST) -> ast.AST:
|
|
695
|
+
"""Visit all nodes and replace them if necessary."""
|
|
696
|
+
return super().generic_visit(node)
|
|
697
|
+
|
|
698
|
+
def visit_Expr(self, node: ast.Expr) -> ast.AST | list[ast.stmt]:
|
|
699
|
+
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
|
|
700
|
+
return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
|
|
701
|
+
return node
|
|
702
|
+
|
|
703
|
+
def visit_Assign(self, node: ast.Assign) -> ast.AST | list[ast.stmt]:
|
|
704
|
+
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
|
|
705
|
+
return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
|
|
706
|
+
return node
|
|
707
|
+
|
|
708
|
+
def visit_Call(self, node: ast.Call) -> ast.AST | list[ast.stmt]:
|
|
709
|
+
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node):
|
|
710
|
+
replacement = self.dictionaryReplacementStatements[node.func.id] # type: ignore[attr-defined]
|
|
711
|
+
if not isinstance(replacement, list):
|
|
712
|
+
return replacement
|
|
713
|
+
return node
|
|
714
|
+
|
|
715
|
+
keepGoing = True
|
|
716
|
+
ImaInlineFunction = deepcopy(astFunctionDef)
|
|
717
|
+
while keepGoing:
|
|
718
|
+
ImaInlineFunction = deepcopy(astFunctionDef)
|
|
719
|
+
FunctionInliner(deepcopy(dictionaryReplacementStatements)).visit(ImaInlineFunction)
|
|
720
|
+
if ast.unparse(ImaInlineFunction) == ast.unparse(astFunctionDef):
|
|
721
|
+
keepGoing = False
|
|
722
|
+
else:
|
|
723
|
+
astFunctionDef = deepcopy(ImaInlineFunction)
|
|
724
|
+
ast.fix_missing_locations(astFunctionDef)
|
|
725
|
+
return ImaInlineFunction
|
|
726
|
+
|
|
727
|
+
def Z0Z_replaceMatchingASTnodes(astTree: ast.AST, mappingFindReplaceNodes: dict[ast.AST, ast.AST]) -> ast.AST:
|
|
728
|
+
class TargetedNodeReplacer(ast.NodeTransformer):
|
|
729
|
+
def __init__(self, mappingFindReplaceNodes: dict[ast.AST, ast.AST]) -> None:
|
|
730
|
+
self.mappingFindReplaceNodes = mappingFindReplaceNodes
|
|
731
|
+
|
|
732
|
+
def visit(self, node: ast.AST) -> ast.AST:
|
|
733
|
+
for nodeFind, nodeReplace in self.mappingFindReplaceNodes.items():
|
|
734
|
+
if self.nodesMatchStructurally(node, nodeFind):
|
|
735
|
+
return nodeReplace
|
|
736
|
+
return self.generic_visit(node)
|
|
737
|
+
|
|
738
|
+
def nodesMatchStructurally(self, nodeSubject: ast.AST | list[Any] | Any, nodePattern: ast.AST | list[Any] | Any) -> bool:
|
|
739
|
+
if nodeSubject is None or nodePattern is None:
|
|
740
|
+
return nodeSubject is None and nodePattern is None
|
|
741
|
+
|
|
742
|
+
if type(nodeSubject) != type(nodePattern):
|
|
743
|
+
return False
|
|
744
|
+
|
|
745
|
+
if isinstance(nodeSubject, ast.AST):
|
|
746
|
+
for field, fieldValueSubject in ast.iter_fields(nodeSubject):
|
|
747
|
+
if field in ('lineno', 'col_offset', 'end_lineno', 'end_col_offset', 'ctx'):
|
|
748
|
+
continue
|
|
749
|
+
attrPattern = getattr(nodePattern, field, None)
|
|
750
|
+
if not self.nodesMatchStructurally(fieldValueSubject, attrPattern):
|
|
751
|
+
return False
|
|
752
|
+
return True
|
|
753
|
+
|
|
754
|
+
if isinstance(nodeSubject, list) and isinstance(nodePattern, list):
|
|
755
|
+
nodeSubjectList: list[Any] = nodeSubject
|
|
756
|
+
nodePatternList: list[Any] = nodePattern
|
|
757
|
+
return len(nodeSubjectList) == len(nodePatternList) and all(
|
|
758
|
+
self.nodesMatchStructurally(elementSubject, elementPattern)
|
|
759
|
+
for elementSubject, elementPattern in zip(nodeSubjectList, nodePatternList)
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
return nodeSubject == nodePattern
|
|
763
|
+
|
|
764
|
+
astTreeCurrent, astTreePrevious = None, astTree
|
|
765
|
+
while astTreeCurrent is None or ast.unparse(astTreeCurrent) != ast.unparse(astTreePrevious):
|
|
766
|
+
astTreePrevious = astTreeCurrent if astTreeCurrent else astTree
|
|
767
|
+
astTreeCurrent = TargetedNodeReplacer(mappingFindReplaceNodes).visit(astTreePrevious)
|
|
768
|
+
|
|
769
|
+
return astTreeCurrent
|
|
770
|
+
|
|
771
|
+
def write_astModule(ingredients: IngredientsModule, pathFilename: str | PathLike[Any] | PurePath, packageName: ast_Identifier | None = None) -> None:
|
|
772
|
+
astModule = ingredients.export()
|
|
773
|
+
ast.fix_missing_locations(astModule)
|
|
774
|
+
pythonSource: str = ast.unparse(astModule)
|
|
775
|
+
if not pythonSource: raise raiseIfNoneGitHubIssueNumber3
|
|
776
|
+
autoflake_additional_imports: list[str] = ingredients.imports.exportListModuleNames()
|
|
777
|
+
if packageName:
|
|
778
|
+
autoflake_additional_imports.append(packageName)
|
|
779
|
+
pythonSource = autoflake_fix_code(pythonSource, autoflake_additional_imports, expand_star_imports=False, remove_all_unused_imports=False, remove_duplicate_keys = False, remove_unused_variables = False)
|
|
780
|
+
writeStringToHere(pythonSource, pathFilename)
|