mapFolding 0.9.0__py3-none-any.whl → 0.9.2__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 +49 -48
- mapFolding/basecamp.py +40 -35
- mapFolding/beDRY.py +75 -69
- mapFolding/oeis.py +74 -85
- mapFolding/reference/__init__.py +2 -2
- mapFolding/someAssemblyRequired/RecipeJob.py +103 -0
- mapFolding/someAssemblyRequired/__init__.py +31 -29
- mapFolding/someAssemblyRequired/_theTypes.py +9 -1
- mapFolding/someAssemblyRequired/_tool_Make.py +1 -2
- mapFolding/someAssemblyRequired/_tool_Then.py +16 -8
- mapFolding/someAssemblyRequired/_toolboxAntecedents.py +111 -23
- mapFolding/someAssemblyRequired/_toolboxContainers.py +27 -28
- mapFolding/someAssemblyRequired/_toolboxPython.py +3 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +83 -51
- mapFolding/someAssemblyRequired/toolboxNumba.py +19 -220
- mapFolding/someAssemblyRequired/transformationTools.py +183 -12
- mapFolding/syntheticModules/{numbaCount_doTheNeedful.py → numbaCount.py} +13 -12
- mapFolding/theDao.py +37 -36
- mapFolding/theSSOT.py +29 -33
- mapFolding/toolboxFilesystem.py +29 -38
- {mapfolding-0.9.0.dist-info → mapfolding-0.9.2.dist-info}/METADATA +2 -1
- mapfolding-0.9.2.dist-info/RECORD +47 -0
- tests/test_computations.py +2 -1
- tests/test_other.py +0 -7
- mapfolding-0.9.0.dist-info/RECORD +0 -46
- /mapFolding/reference/{lunnanNumpy.py → lunnonNumpy.py} +0 -0
- /mapFolding/reference/{lunnanWhile.py → lunnonWhile.py} +0 -0
- {mapfolding-0.9.0.dist-info → mapfolding-0.9.2.dist-info}/WHEEL +0 -0
- {mapfolding-0.9.0.dist-info → mapfolding-0.9.2.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.9.0.dist-info → mapfolding-0.9.2.dist-info}/licenses/LICENSE +0 -0
- {mapfolding-0.9.0.dist-info → mapfolding-0.9.2.dist-info}/top_level.txt +0 -0
|
@@ -2,17 +2,22 @@
|
|
|
2
2
|
AST Node Predicate and Access Utilities for Pattern Matching and Traversal
|
|
3
3
|
|
|
4
4
|
This module provides utilities for accessing and matching AST nodes in a consistent way.
|
|
5
|
-
It contains
|
|
5
|
+
It contains three primary classes:
|
|
6
6
|
|
|
7
7
|
1. DOT: Provides consistent accessor methods for AST node attributes across different
|
|
8
8
|
node types, simplifying the access to node properties.
|
|
9
9
|
|
|
10
|
-
2.
|
|
10
|
+
2. be: Offers type-guard functions that verify AST node types, enabling safe type
|
|
11
|
+
narrowing for static type checking and improving code safety.
|
|
12
|
+
|
|
13
|
+
3. ifThis: Contains predicate functions for matching AST nodes based on various criteria,
|
|
11
14
|
enabling precise targeting of nodes for analysis or transformation.
|
|
12
15
|
|
|
13
16
|
These utilities form the foundation of the pattern-matching component in the AST
|
|
14
17
|
manipulation framework, working in conjunction with the NodeChanger and NodeTourist
|
|
15
|
-
classes to enable precise and targeted code transformations.
|
|
18
|
+
classes to enable precise and targeted code transformations. Together, they implement
|
|
19
|
+
a declarative approach to AST manipulation that separates node identification (ifThis),
|
|
20
|
+
type verification (be), and data access (DOT).
|
|
16
21
|
"""
|
|
17
22
|
|
|
18
23
|
from collections.abc import Callable
|
|
@@ -26,6 +31,7 @@ from mapFolding.someAssemblyRequired import (
|
|
|
26
31
|
astClassHasDOTvalue_expr,
|
|
27
32
|
astClassOptionallyHasDOTnameNotName,
|
|
28
33
|
astClassHasDOTvalue_exprNone,
|
|
34
|
+
ImaCallToName,
|
|
29
35
|
)
|
|
30
36
|
from typing import Any, overload, TypeGuard
|
|
31
37
|
import ast
|
|
@@ -65,9 +71,17 @@ class DOT:
|
|
|
65
71
|
@staticmethod
|
|
66
72
|
def attr(node: ast.Attribute) -> ast_Identifier:
|
|
67
73
|
return node.attr
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
@overload
|
|
77
|
+
def func(node: ImaCallToName) -> ast.Name:...
|
|
78
|
+
@staticmethod
|
|
79
|
+
@overload
|
|
80
|
+
def func(node: ast.Call) -> ast.expr:...
|
|
68
81
|
@staticmethod
|
|
69
|
-
def func(node: ast.Call) -> ast.expr:
|
|
82
|
+
def func(node: ast.Call | ImaCallToName) -> ast.expr | ast.Name:
|
|
70
83
|
return node.func
|
|
84
|
+
|
|
71
85
|
@staticmethod
|
|
72
86
|
def id(node: ast.Name) -> ast_Identifier:
|
|
73
87
|
return node.id
|
|
@@ -111,6 +125,75 @@ class DOT:
|
|
|
111
125
|
def value(node: astClassHasDOTvalue) -> Any | ast.expr | bool | None:
|
|
112
126
|
return node.value
|
|
113
127
|
|
|
128
|
+
class be:
|
|
129
|
+
"""
|
|
130
|
+
Provide type-guard functions for safely verifying AST node types during manipulation.
|
|
131
|
+
|
|
132
|
+
The be class contains static methods that perform runtime type verification of AST nodes,
|
|
133
|
+
returning TypeGuard results that enable static type checkers to narrow node types in
|
|
134
|
+
conditional branches. These type-guards:
|
|
135
|
+
|
|
136
|
+
1. Improve code safety by preventing operations on incompatible node types
|
|
137
|
+
2. Enable IDE tooling to provide better autocompletion and error detection
|
|
138
|
+
3. Document expected node types in a way that's enforced by the type system
|
|
139
|
+
4. Support pattern-matching workflows where node types must be verified before access
|
|
140
|
+
|
|
141
|
+
When used with conditional statements, these type-guards allow for precise,
|
|
142
|
+
type-safe manipulation of AST nodes while maintaining full static type checking
|
|
143
|
+
capabilities, even in complex transformation scenarios.
|
|
144
|
+
"""
|
|
145
|
+
@staticmethod
|
|
146
|
+
def AnnAssign(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
|
|
147
|
+
return isinstance(node, ast.AnnAssign)
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def arg(node: ast.AST) -> TypeGuard[ast.arg]:
|
|
151
|
+
return isinstance(node, ast.arg)
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def Assign(node: ast.AST) -> TypeGuard[ast.Assign]:
|
|
155
|
+
return isinstance(node, ast.Assign)
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def Attribute(node: ast.AST) -> TypeGuard[ast.Attribute]:
|
|
159
|
+
return isinstance(node, ast.Attribute)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def AugAssign(node: ast.AST) -> TypeGuard[ast.AugAssign]:
|
|
163
|
+
return isinstance(node, ast.AugAssign)
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def Call(node: ast.AST) -> TypeGuard[ast.Call]:
|
|
167
|
+
return isinstance(node, ast.Call)
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def ClassDef(node: ast.AST) -> TypeGuard[ast.ClassDef]:
|
|
171
|
+
return isinstance(node, ast.ClassDef)
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def FunctionDef(node: ast.AST) -> TypeGuard[ast.FunctionDef]:
|
|
175
|
+
return isinstance(node, ast.FunctionDef)
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def keyword(node: ast.AST) -> TypeGuard[ast.keyword]:
|
|
179
|
+
return isinstance(node, ast.keyword)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def Name(node: ast.AST) -> TypeGuard[ast.Name]:
|
|
183
|
+
return isinstance(node, ast.Name)
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def Return(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
187
|
+
return isinstance(node, ast.Return)
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def Starred(node: ast.AST) -> TypeGuard[ast.Starred]:
|
|
191
|
+
return isinstance(node, ast.Starred)
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def Subscript(node: ast.AST) -> TypeGuard[ast.Subscript]:
|
|
195
|
+
return isinstance(node, ast.Subscript)
|
|
196
|
+
|
|
114
197
|
class ifThis:
|
|
115
198
|
"""
|
|
116
199
|
Provide predicate functions for matching and filtering AST nodes based on various criteria.
|
|
@@ -135,41 +218,41 @@ class ifThis:
|
|
|
135
218
|
@staticmethod
|
|
136
219
|
def is_arg_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.arg] | bool]:
|
|
137
220
|
"""see also `isArgument_Identifier`"""
|
|
138
|
-
return lambda node:
|
|
221
|
+
return lambda node: be.arg(node) and ifThis._Identifier(identifier)(DOT.arg(node))
|
|
139
222
|
@staticmethod
|
|
140
223
|
def is_keyword_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.keyword] | bool]:
|
|
141
224
|
"""see also `isArgument_Identifier`"""
|
|
142
|
-
return lambda node:
|
|
225
|
+
return lambda node: be.keyword(node) and ifThis._Identifier(identifier)(DOT.arg(node))
|
|
143
226
|
|
|
144
227
|
@staticmethod
|
|
145
228
|
def isAnnAssign_targetIs(targetPredicate: Callable[[ast.expr], TypeGuard[ast.expr] | bool]) -> Callable[[ast.AST], TypeGuard[ast.AnnAssign] | bool]:
|
|
146
229
|
def workhorse(node: ast.AST) -> TypeGuard[ast.AnnAssign] | bool:
|
|
147
|
-
return
|
|
230
|
+
return be.AnnAssign(node) and targetPredicate(DOT.target(node))
|
|
148
231
|
return workhorse
|
|
149
232
|
|
|
150
233
|
@staticmethod
|
|
151
234
|
def isArgument_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.arg | ast.keyword] | bool]:
|
|
152
|
-
return lambda node: (
|
|
235
|
+
return lambda node: (be.arg(node) or be.keyword(node)) and ifThis._Identifier(identifier)(DOT.arg(node))
|
|
153
236
|
|
|
154
237
|
@staticmethod
|
|
155
238
|
def isAssignAndTargets0Is(targets0Predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.AnnAssign] | bool]:
|
|
156
239
|
"""node is Assign and node.targets[0] matches `targets0Predicate`."""
|
|
157
|
-
return lambda node:
|
|
240
|
+
return lambda node: be.Assign(node) and targets0Predicate(node.targets[0])
|
|
158
241
|
@staticmethod
|
|
159
242
|
def isAssignAndValueIs(valuePredicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
|
|
160
243
|
"""node is ast.Assign and node.value matches `valuePredicate`. """
|
|
161
|
-
return lambda node:
|
|
244
|
+
return lambda node: be.Assign(node) and valuePredicate(DOT.value(node))
|
|
162
245
|
|
|
163
246
|
@staticmethod
|
|
164
247
|
def isAttribute_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
|
|
165
248
|
"""node is `ast.Attribute` and the top-level `ast.Name` is `identifier`"""
|
|
166
249
|
def workhorse(node: ast.AST) -> TypeGuard[ast.Attribute]:
|
|
167
|
-
return
|
|
250
|
+
return be.Attribute(node) and ifThis._nested_Identifier(identifier)(DOT.value(node))
|
|
168
251
|
return workhorse
|
|
169
252
|
@staticmethod
|
|
170
253
|
def isAttributeName(node: ast.AST) -> TypeGuard[ast.Attribute]:
|
|
171
254
|
""" Displayed as Name.attribute."""
|
|
172
|
-
return
|
|
255
|
+
return be.Attribute(node) and be.Name(DOT.value(node))
|
|
173
256
|
@staticmethod
|
|
174
257
|
def isAttributeNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
|
|
175
258
|
return lambda node: ifThis.isAttributeName(node) and ifThis.isName_Identifier(namespace)(DOT.value(node)) and ifThis._Identifier(identifier)(DOT.attr(node))
|
|
@@ -177,42 +260,47 @@ class ifThis:
|
|
|
177
260
|
@staticmethod
|
|
178
261
|
def isAugAssign_targetIs(targetPredicate: Callable[[ast.expr], TypeGuard[ast.expr] | bool]) -> Callable[[ast.AST], TypeGuard[ast.AugAssign] | bool]:
|
|
179
262
|
def workhorse(node: ast.AST) -> TypeGuard[ast.AugAssign] | bool:
|
|
180
|
-
return
|
|
263
|
+
return be.AugAssign(node) and targetPredicate(DOT.target(node))
|
|
181
264
|
return workhorse
|
|
182
265
|
|
|
183
266
|
@staticmethod
|
|
184
|
-
def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[
|
|
185
|
-
|
|
267
|
+
def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ImaCallToName] | bool]:
|
|
268
|
+
def workhorse(node: ast.AST) -> TypeGuard[ImaCallToName] | bool:
|
|
269
|
+
return ifThis.isCallToName(node) and ifThis._Identifier(identifier)(DOT.id(DOT.func(node)))
|
|
270
|
+
return workhorse
|
|
271
|
+
|
|
186
272
|
@staticmethod
|
|
187
273
|
def isCallAttributeNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
|
|
188
|
-
|
|
274
|
+
def workhorse(node: ast.AST) -> TypeGuard[ast.Call] | bool:
|
|
275
|
+
return be.Call(node) and ifThis.isAttributeNamespace_Identifier(namespace, identifier)(DOT.func(node))
|
|
276
|
+
return workhorse
|
|
189
277
|
@staticmethod
|
|
190
|
-
def isCallToName(node: ast.AST) -> TypeGuard[
|
|
191
|
-
return
|
|
278
|
+
def isCallToName(node: ast.AST) -> TypeGuard[ImaCallToName]:
|
|
279
|
+
return be.Call(node) and be.Name(DOT.func(node))
|
|
192
280
|
|
|
193
281
|
@staticmethod
|
|
194
282
|
def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.ClassDef] | bool]:
|
|
195
|
-
return lambda node:
|
|
283
|
+
return lambda node: be.ClassDef(node) and ifThis._Identifier(identifier)(DOT.name(node))
|
|
196
284
|
|
|
197
285
|
@staticmethod
|
|
198
286
|
def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.FunctionDef] | bool]:
|
|
199
|
-
return lambda node:
|
|
287
|
+
return lambda node: be.FunctionDef(node) and ifThis._Identifier(identifier)(DOT.name(node))
|
|
200
288
|
|
|
201
289
|
@staticmethod
|
|
202
290
|
def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Name] | bool]:
|
|
203
|
-
return lambda node:
|
|
291
|
+
return lambda node: be.Name(node) and ifThis._Identifier(identifier)(DOT.id(node))
|
|
204
292
|
|
|
205
293
|
@staticmethod
|
|
206
294
|
def isStarred_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Starred] | bool]:
|
|
207
295
|
"""node is `ast.Starred` and the top-level `ast.Name` is `identifier`"""
|
|
208
296
|
def workhorse(node: ast.AST) -> TypeGuard[ast.Starred]:
|
|
209
|
-
return
|
|
297
|
+
return be.Starred(node) and ifThis._nested_Identifier(identifier)(DOT.value(node))
|
|
210
298
|
return workhorse
|
|
211
299
|
@staticmethod
|
|
212
300
|
def isSubscript_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript] | bool]:
|
|
213
301
|
"""node is `ast.Subscript` and the top-level `ast.Name` is `identifier`"""
|
|
214
302
|
def workhorse(node: ast.AST) -> TypeGuard[ast.Subscript]:
|
|
215
|
-
return
|
|
303
|
+
return be.Subscript(node) and ifThis._nested_Identifier(identifier)(DOT.value(node))
|
|
216
304
|
return workhorse
|
|
217
305
|
|
|
218
306
|
@staticmethod
|
|
@@ -29,7 +29,6 @@ from Z0Z_tools import updateExtendPolishDictionaryLists
|
|
|
29
29
|
import ast
|
|
30
30
|
import dataclasses
|
|
31
31
|
|
|
32
|
-
# Consolidate settings classes through inheritance https://github.com/hunterhogan/mapFolding/issues/15
|
|
33
32
|
class LedgerOfImports:
|
|
34
33
|
"""
|
|
35
34
|
Track and manage import statements for programmatically generated code.
|
|
@@ -58,23 +57,22 @@ class LedgerOfImports:
|
|
|
58
57
|
self.walkThis(startWith)
|
|
59
58
|
|
|
60
59
|
def addAst(self, astImport____: ast.Import | ast.ImportFrom) -> None:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
match astImport____:
|
|
61
|
+
case ast.Import():
|
|
62
|
+
for alias in astImport____.names:
|
|
63
|
+
self.listImport.append(alias.name)
|
|
64
|
+
case ast.ImportFrom():
|
|
65
|
+
# TODO fix the mess created by `None` means '.'. I need a `str_nameDOTname` to replace '.'
|
|
66
|
+
if astImport____.module is None:
|
|
67
|
+
astImport____.module = '.'
|
|
68
|
+
for alias in astImport____.names:
|
|
69
|
+
self.dictionaryImportFrom[astImport____.module].append((alias.name, alias.asname))
|
|
70
|
+
case _:
|
|
71
|
+
raise ValueError(f"I received {type(astImport____) = }, but I can only accept {ast.Import} and {ast.ImportFrom}.")
|
|
71
72
|
|
|
72
73
|
def addImport_asStr(self, moduleWithLogicalPath: str_nameDOTname) -> None:
|
|
73
74
|
self.listImport.append(moduleWithLogicalPath)
|
|
74
75
|
|
|
75
|
-
# def addImportFrom_asStr(self, moduleWithLogicalPath: str_nameDOTname, name: ast_Identifier, asname: ast_Identifier | None = None) -> None:
|
|
76
|
-
# self.dictionaryImportFrom[moduleWithLogicalPath].append((name, asname))
|
|
77
|
-
|
|
78
76
|
def addImportFrom_asStr(self, moduleWithLogicalPath: str_nameDOTname, name: ast_Identifier, asname: ast_Identifier | None = None) -> None:
|
|
79
77
|
if moduleWithLogicalPath not in self.dictionaryImportFrom:
|
|
80
78
|
self.dictionaryImportFrom[moduleWithLogicalPath] = []
|
|
@@ -85,15 +83,14 @@ class LedgerOfImports:
|
|
|
85
83
|
self.removeImportFrom(moduleWithLogicalPath, None, None)
|
|
86
84
|
|
|
87
85
|
def removeImportFrom(self, moduleWithLogicalPath: str_nameDOTname, name: ast_Identifier | None, asname: ast_Identifier | None = None) -> None:
|
|
88
|
-
|
|
86
|
+
"""
|
|
87
|
+
name, asname Action
|
|
88
|
+
None, None : remove all matches for the module
|
|
89
|
+
ast_Identifier, ast_Identifier : remove exact matches
|
|
90
|
+
ast_Identifier, None : remove exact matches
|
|
91
|
+
None, ast_Identifier : remove all matches for asname and if entry_asname is None remove name == ast_Identifier
|
|
92
|
+
"""
|
|
89
93
|
if moduleWithLogicalPath in self.dictionaryImportFrom:
|
|
90
|
-
"""
|
|
91
|
-
name, asname Meaning
|
|
92
|
-
ast_Identifier, ast_Identifier : remove exact matches
|
|
93
|
-
ast_Identifier, None : remove exact matches
|
|
94
|
-
None, ast_Identifier : remove all matches for asname and if entry_asname is None remove name == ast_Identifier
|
|
95
|
-
None, None : remove all matches for the module
|
|
96
|
-
"""
|
|
97
94
|
if name is None and asname is None:
|
|
98
95
|
# Remove all entries for the module
|
|
99
96
|
self.dictionaryImportFrom.pop(moduleWithLogicalPath)
|
|
@@ -102,7 +99,6 @@ class LedgerOfImports:
|
|
|
102
99
|
self.dictionaryImportFrom[moduleWithLogicalPath] = [(entry_name, entry_asname) for entry_name, entry_asname in self.dictionaryImportFrom[moduleWithLogicalPath]
|
|
103
100
|
if not (entry_asname == asname) and not (entry_asname is None and entry_name == asname)]
|
|
104
101
|
else:
|
|
105
|
-
# Remove exact matches for the module
|
|
106
102
|
self.dictionaryImportFrom[moduleWithLogicalPath] = [(entry_name, entry_asname) for entry_name, entry_asname in self.dictionaryImportFrom[moduleWithLogicalPath]
|
|
107
103
|
if not (entry_name == name and entry_asname == asname)]
|
|
108
104
|
if not self.dictionaryImportFrom[moduleWithLogicalPath]:
|
|
@@ -255,7 +251,6 @@ class IngredientsModule:
|
|
|
255
251
|
def appendIngredientsFunction(self, *ingredientsFunction: IngredientsFunction) -> None:
|
|
256
252
|
"""Append one or more `IngredientsFunction`."""
|
|
257
253
|
for allegedIngredientsFunction in ingredientsFunction:
|
|
258
|
-
assert isinstance(allegedIngredientsFunction, IngredientsFunction), ValueError(f"I received `{type(allegedIngredientsFunction) = }`, but I can only accept `{IngredientsFunction}`.")
|
|
259
254
|
self.listIngredientsFunctions.append(allegedIngredientsFunction)
|
|
260
255
|
|
|
261
256
|
def removeImportFromModule(self, moduleWithLogicalPath: str_nameDOTname) -> None:
|
|
@@ -308,7 +303,7 @@ class IngredientsModule:
|
|
|
308
303
|
@dataclasses.dataclass
|
|
309
304
|
class RecipeSynthesizeFlow:
|
|
310
305
|
"""
|
|
311
|
-
Configure the generation of
|
|
306
|
+
Configure the generation of new modules, including Numba-accelerated code modules.
|
|
312
307
|
|
|
313
308
|
RecipeSynthesizeFlow defines the complete blueprint for transforming an original
|
|
314
309
|
Python algorithm into an optimized, accelerated implementation. It specifies:
|
|
@@ -358,7 +353,7 @@ class RecipeSynthesizeFlow:
|
|
|
358
353
|
""" `logicalPathFlowRoot` likely corresponds to a physical filesystem directory."""
|
|
359
354
|
|
|
360
355
|
# Module ================================
|
|
361
|
-
moduleDispatcher: ast_Identifier = '
|
|
356
|
+
moduleDispatcher: ast_Identifier = 'numbaCount'
|
|
362
357
|
moduleInitialize: ast_Identifier = moduleDispatcher
|
|
363
358
|
moduleParallel: ast_Identifier = moduleDispatcher
|
|
364
359
|
moduleSequential: ast_Identifier = moduleDispatcher
|
|
@@ -376,6 +371,10 @@ class RecipeSynthesizeFlow:
|
|
|
376
371
|
dataclassInstance: ast_Identifier = sourceDataclassInstance
|
|
377
372
|
dataclassInstanceTaskDistribution: ast_Identifier = sourceDataclassInstanceTaskDistribution
|
|
378
373
|
|
|
374
|
+
removeDataclassDispatcher: bool = False
|
|
375
|
+
removeDataclassInitialize: bool = False
|
|
376
|
+
removeDataclassParallel: bool = True
|
|
377
|
+
removeDataclassSequential: bool = True
|
|
379
378
|
# ========================================
|
|
380
379
|
# Computed
|
|
381
380
|
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
@@ -432,10 +431,10 @@ class ShatteredDataclass:
|
|
|
432
431
|
countingVariableName: ast.Name
|
|
433
432
|
"""AST name node representing the counting variable identifier."""
|
|
434
433
|
|
|
435
|
-
field2AnnAssign: dict[ast_Identifier, ast.AnnAssign] = dataclasses.field(default_factory=dict)
|
|
434
|
+
field2AnnAssign: dict[ast_Identifier, ast.AnnAssign | ast.Assign] = dataclasses.field(default_factory=dict)
|
|
436
435
|
"""Maps field names to their corresponding AST call expressions."""
|
|
437
436
|
|
|
438
|
-
Z0Z_field2AnnAssign: dict[ast_Identifier, tuple[ast.AnnAssign, str]] = dataclasses.field(default_factory=dict)
|
|
437
|
+
Z0Z_field2AnnAssign: dict[ast_Identifier, tuple[ast.AnnAssign | ast.Assign, str]] = dataclasses.field(default_factory=dict)
|
|
439
438
|
|
|
440
439
|
fragments4AssignmentOrParameters: ast.Tuple = dummyTuple
|
|
441
440
|
"""AST tuple used as target for assignment to capture returned fragments."""
|
|
@@ -107,6 +107,7 @@ def importPathFilename2Callable(pathFilename: PathLike[Any] | PurePath, identifi
|
|
|
107
107
|
Load a callable (function, class, etc.) from a Python file.
|
|
108
108
|
This function imports a specified Python file as a module, extracts a callable object
|
|
109
109
|
from it by name, and returns that callable.
|
|
110
|
+
|
|
110
111
|
Parameters
|
|
111
112
|
----------
|
|
112
113
|
pathFilename : Union[PathLike[Any], PurePath]
|
|
@@ -115,10 +116,12 @@ def importPathFilename2Callable(pathFilename: PathLike[Any] | PurePath, identifi
|
|
|
115
116
|
Name of the callable to extract from the imported module.
|
|
116
117
|
moduleIdentifier : Optional[str]
|
|
117
118
|
Name to use for the imported module. If None, the filename stem is used.
|
|
119
|
+
|
|
118
120
|
Returns
|
|
119
121
|
-------
|
|
120
122
|
Callable[..., Any]
|
|
121
123
|
The callable object extracted from the imported module.
|
|
124
|
+
|
|
122
125
|
Raises
|
|
123
126
|
------
|
|
124
127
|
ImportError
|
|
@@ -18,16 +18,26 @@ This creates extremely fast, specialized implementations that can be run directl
|
|
|
18
18
|
as Python scripts or further compiled into standalone executables.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
from mapFolding
|
|
22
|
-
from mapFolding.someAssemblyRequired import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
from mapFolding import getPathFilenameFoldsTotal, raiseIfNoneGitHubIssueNumber3, The
|
|
22
|
+
from mapFolding.someAssemblyRequired import (
|
|
23
|
+
ast_Identifier,
|
|
24
|
+
be,
|
|
25
|
+
ifThis,
|
|
26
|
+
IngredientsFunction,
|
|
27
|
+
IngredientsModule,
|
|
28
|
+
LedgerOfImports,
|
|
29
|
+
Make,
|
|
30
|
+
NodeChanger,
|
|
31
|
+
NodeTourist,
|
|
32
|
+
str_nameDOTname,
|
|
33
|
+
Then,
|
|
34
|
+
)
|
|
35
|
+
from mapFolding.someAssemblyRequired.RecipeJob import RecipeJob
|
|
36
|
+
from mapFolding.someAssemblyRequired.toolboxNumba import parametersNumbaLight, SpicesJobNumba, decorateCallableWithNumba
|
|
37
|
+
from mapFolding.someAssemblyRequired.transformationTools import dictionaryEstimates, extractFunctionDef, write_astModule, makeInitializedComputationState
|
|
30
38
|
from pathlib import PurePosixPath
|
|
39
|
+
from typing import cast, NamedTuple
|
|
40
|
+
from Z0Z_tools import autoDecodingRLE
|
|
31
41
|
import ast
|
|
32
42
|
"""Synthesize one file to compute `foldsTotal` of `mapShape`."""
|
|
33
43
|
|
|
@@ -89,6 +99,10 @@ if __name__ == '__main__':
|
|
|
89
99
|
countWithProgressBar = NodeChanger(findThis, doThat)
|
|
90
100
|
countWithProgressBar.visit(ingredientsFunction.astFunctionDef)
|
|
91
101
|
|
|
102
|
+
removeReturnStatement = NodeChanger(be.Return, Then.removeIt)
|
|
103
|
+
removeReturnStatement.visit(ingredientsFunction.astFunctionDef)
|
|
104
|
+
ingredientsFunction.astFunctionDef.returns = Make.Constant(value=None)
|
|
105
|
+
|
|
92
106
|
ingredientsModule.appendLauncher(ast.parse(linesLaunch))
|
|
93
107
|
|
|
94
108
|
return ingredientsModule, ingredientsFunction
|
|
@@ -119,9 +133,13 @@ def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: Ingre
|
|
|
119
133
|
"""
|
|
120
134
|
ingredientsFunction.imports.update(job.shatteredDataclass.ledger)
|
|
121
135
|
|
|
122
|
-
list_IdentifiersNotUsed = list_IdentifiersNotUsedHARDCODED
|
|
123
|
-
|
|
124
136
|
list_argCuzMyBrainRefusesToThink = ingredientsFunction.astFunctionDef.args.args + ingredientsFunction.astFunctionDef.args.posonlyargs + ingredientsFunction.astFunctionDef.args.kwonlyargs
|
|
137
|
+
list_arg_arg: list[ast_Identifier] = [ast_arg.arg for ast_arg in list_argCuzMyBrainRefusesToThink]
|
|
138
|
+
listName: list[ast.Name] = []
|
|
139
|
+
NodeTourist(be.Name, Then.appendTo(listName)).visit(ingredientsFunction.astFunctionDef)
|
|
140
|
+
list_Identifiers: list[ast_Identifier] = [astName.id for astName in listName]
|
|
141
|
+
list_IdentifiersNotUsed: list[ast_Identifier] = list(set(list_arg_arg) - set(list_Identifiers))
|
|
142
|
+
|
|
125
143
|
for ast_arg in list_argCuzMyBrainRefusesToThink:
|
|
126
144
|
if ast_arg.arg in job.shatteredDataclass.field2AnnAssign:
|
|
127
145
|
if ast_arg.arg in list_IdentifiersNotUsed:
|
|
@@ -132,7 +150,7 @@ def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: Ingre
|
|
|
132
150
|
case 'scalar':
|
|
133
151
|
ImaAnnAssign.value.args[0].value = int(job.state.__dict__[ast_arg.arg]) # type: ignore
|
|
134
152
|
case 'array':
|
|
135
|
-
dataAsStrRLE: str = autoDecodingRLE(job.state.__dict__[ast_arg.arg],
|
|
153
|
+
dataAsStrRLE: str = autoDecodingRLE(job.state.__dict__[ast_arg.arg], True)
|
|
136
154
|
dataAs_astExpr: ast.expr = cast(ast.Expr, ast.parse(dataAsStrRLE).body[0]).value
|
|
137
155
|
ImaAnnAssign.value.args = [dataAs_astExpr] # type: ignore
|
|
138
156
|
case _:
|
|
@@ -181,11 +199,6 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
|
|
|
181
199
|
if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
|
|
182
200
|
ingredientsCount: IngredientsFunction = IngredientsFunction(astFunctionDef, LedgerOfImports())
|
|
183
201
|
|
|
184
|
-
# Change the return so you can dynamically determine which variables are not used
|
|
185
|
-
removeReturnStatement = NodeChanger(lambda node: isinstance(node, ast.Return), Then.removeIt) # type: ignore
|
|
186
|
-
removeReturnStatement.visit(ingredientsCount.astFunctionDef)
|
|
187
|
-
ingredientsCount.astFunctionDef.returns = Make.Constant(value=None)
|
|
188
|
-
|
|
189
202
|
# Remove `foldGroups` and any other unused statements, so you can dynamically determine which variables are not used
|
|
190
203
|
findThis = ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier('foldGroups'))
|
|
191
204
|
doThat = Then.removeIt
|
|
@@ -199,46 +212,63 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
|
|
|
199
212
|
doThat = Then.replaceWith(Make.Constant(int(job.state.__dict__[identifier])))
|
|
200
213
|
NodeChanger(findThis, doThat).visit(ingredientsCount.astFunctionDef)
|
|
201
214
|
|
|
202
|
-
# This launcher eliminates the use of one identifier, so run it now and you can dynamically determine which variables are not used
|
|
203
215
|
ingredientsModule = IngredientsModule()
|
|
216
|
+
# This launcher eliminates the use of one identifier, so run it now and you can dynamically determine which variables are not used
|
|
204
217
|
if spices.useNumbaProgressBar:
|
|
205
218
|
ingredientsModule, ingredientsCount = addLauncherNumbaProgress(ingredientsModule, ingredientsCount, job, spices)
|
|
206
219
|
spices.parametersNumba['nogil'] = True
|
|
220
|
+
else:
|
|
221
|
+
linesLaunch: str = f"""
|
|
222
|
+
if __name__ == '__main__':
|
|
223
|
+
import time
|
|
224
|
+
timeStart = time.perf_counter()
|
|
225
|
+
foldsTotal = {job.countCallable}() * {job.state.leavesTotal}
|
|
226
|
+
print(time.perf_counter() - timeStart)
|
|
227
|
+
print('\\nmap {job.state.mapShape} =', foldsTotal)
|
|
228
|
+
writeStream = open('{job.pathFilenameFoldsTotal.as_posix()}', 'w')
|
|
229
|
+
writeStream.write(str(foldsTotal))
|
|
230
|
+
writeStream.close()
|
|
231
|
+
"""
|
|
232
|
+
# from mapFolding.oeis import getFoldsTotalKnown
|
|
233
|
+
# print(foldsTotal == getFoldsTotalKnown({job.state.mapShape}))
|
|
234
|
+
ingredientsModule.appendLauncher(ast.parse(linesLaunch))
|
|
235
|
+
changeReturnParallelCallable = NodeChanger(be.Return, Then.replaceWith(Make.Return(job.shatteredDataclass.countingVariableName)))
|
|
236
|
+
changeReturnParallelCallable.visit(ingredientsCount.astFunctionDef)
|
|
237
|
+
ingredientsCount.astFunctionDef.returns = job.shatteredDataclass.countingVariableAnnotation
|
|
207
238
|
|
|
208
239
|
ingredientsCount = move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsCount, job)
|
|
209
240
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
241
|
+
class DatatypeConfig(NamedTuple):
|
|
242
|
+
Z0Z_module: str_nameDOTname
|
|
243
|
+
fml: ast_Identifier
|
|
244
|
+
Z0Z_type_name: ast_Identifier
|
|
245
|
+
Z0Z_asname: ast_Identifier | None = None
|
|
246
|
+
|
|
247
|
+
listDatatypeConfigs = [
|
|
248
|
+
DatatypeConfig(fml='DatatypeLeavesTotal', Z0Z_module='numba', Z0Z_type_name='uint16'),
|
|
249
|
+
DatatypeConfig(fml='DatatypeElephino', Z0Z_module='numba', Z0Z_type_name='uint16'),
|
|
250
|
+
DatatypeConfig(fml='DatatypeFoldsTotal', Z0Z_module='numba', Z0Z_type_name='int64'),
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
for datatypeConfig in listDatatypeConfigs:
|
|
254
|
+
ingredientsModule.imports.addImportFrom_asStr(datatypeConfig.Z0Z_module, datatypeConfig.Z0Z_type_name)
|
|
255
|
+
statement = Make.Assign(
|
|
256
|
+
[Make.Name(datatypeConfig.fml, ast.Store())],
|
|
257
|
+
Make.Name(datatypeConfig.Z0Z_type_name)
|
|
258
|
+
)
|
|
259
|
+
ingredientsModule.appendPrologue(statement=statement)
|
|
227
260
|
|
|
228
261
|
ingredientsCount.imports.removeImportFromModule('mapFolding.theSSOT')
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
ingredientsCount.imports.removeImportFrom(Z0Z_module, None, Z0Z_asname)
|
|
240
|
-
Z0Z_type_name = 'uint8'
|
|
241
|
-
ingredientsCount.imports.addImportFrom_asStr(Z0Z_module, Z0Z_type_name, Z0Z_asname)
|
|
262
|
+
|
|
263
|
+
listNumPyTypeConfigs = [
|
|
264
|
+
DatatypeConfig(fml='Array1DLeavesTotal', Z0Z_module='numpy', Z0Z_type_name='uint16', Z0Z_asname='Array1DLeavesTotal'),
|
|
265
|
+
DatatypeConfig(fml='Array1DElephino', Z0Z_module='numpy', Z0Z_type_name='uint16', Z0Z_asname='Array1DElephino'),
|
|
266
|
+
DatatypeConfig(fml='Array3D', Z0Z_module='numpy', Z0Z_type_name='uint16', Z0Z_asname='Array3D'),
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
for typeConfig in listNumPyTypeConfigs:
|
|
270
|
+
ingredientsCount.imports.removeImportFrom(typeConfig.Z0Z_module, None, typeConfig.fml)
|
|
271
|
+
ingredientsCount.imports.addImportFrom_asStr(typeConfig.Z0Z_module, typeConfig.Z0Z_type_name, typeConfig.Z0Z_asname)
|
|
242
272
|
|
|
243
273
|
ingredientsCount.astFunctionDef.decorator_list = [] # TODO low-priority, handle this more elegantly
|
|
244
274
|
# TODO when I add the function signature in numba style back to the decorator, the logic needs to handle `ProgressBarType:`
|
|
@@ -269,11 +299,13 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
|
|
|
269
299
|
"""
|
|
270
300
|
|
|
271
301
|
if __name__ == '__main__':
|
|
272
|
-
mapShape = (2,
|
|
302
|
+
mapShape = (2,2,2,2,2,2,2,2)
|
|
273
303
|
state = makeInitializedComputationState(mapShape)
|
|
274
|
-
foldsTotalEstimated = getFoldsTotalKnown(state.mapShape) // state.leavesTotal
|
|
304
|
+
# foldsTotalEstimated = getFoldsTotalKnown(state.mapShape) // state.leavesTotal
|
|
305
|
+
foldsTotalEstimated = dictionaryEstimates[state.mapShape] // state.leavesTotal
|
|
275
306
|
pathModule = PurePosixPath(The.pathPackage, 'jobs')
|
|
276
307
|
pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(state.mapShape, pathModule))
|
|
277
308
|
aJob = RecipeJob(state, foldsTotalEstimated, pathModule=pathModule, pathFilenameFoldsTotal=pathFilenameFoldsTotal)
|
|
278
|
-
spices = SpicesJobNumba()
|
|
309
|
+
spices = SpicesJobNumba(useNumbaProgressBar=False, parametersNumba=parametersNumbaLight)
|
|
310
|
+
# spices = SpicesJobNumba()
|
|
279
311
|
makeJobNumba(aJob, spices)
|