ovld 0.4.6__py3-none-any.whl → 0.5.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.
- ovld/__init__.py +23 -1
- ovld/codegen.py +306 -0
- ovld/core.py +76 -361
- ovld/dependent.py +22 -71
- ovld/medley.py +416 -0
- ovld/mro.py +1 -3
- ovld/recode.py +96 -165
- ovld/signatures.py +275 -0
- ovld/typemap.py +40 -38
- ovld/types.py +36 -37
- ovld/utils.py +48 -18
- ovld/version.py +1 -1
- {ovld-0.4.6.dist-info → ovld-0.5.1.dist-info}/METADATA +62 -16
- ovld-0.5.1.dist-info/RECORD +18 -0
- {ovld-0.4.6.dist-info → ovld-0.5.1.dist-info}/WHEEL +1 -1
- ovld-0.4.6.dist-info/RECORD +0 -15
- {ovld-0.4.6.dist-info → ovld-0.5.1.dist-info}/licenses/LICENSE +0 -0
ovld/__init__.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import TYPE_CHECKING
|
2
2
|
|
3
3
|
from . import abc # noqa: F401
|
4
|
+
from .codegen import Code, Def, Lambda, code_generator
|
4
5
|
from .core import (
|
5
6
|
Ovld,
|
6
7
|
OvldBase,
|
@@ -15,12 +16,21 @@ from .dependent import (
|
|
15
16
|
ParametrizedDependentType,
|
16
17
|
dependent_check,
|
17
18
|
)
|
19
|
+
from .medley import (
|
20
|
+
CodegenParameter,
|
21
|
+
Medley,
|
22
|
+
)
|
18
23
|
from .mro import (
|
19
24
|
TypeRelationship,
|
20
25
|
subclasscheck,
|
21
26
|
typeorder,
|
22
27
|
)
|
23
|
-
from .recode import
|
28
|
+
from .recode import (
|
29
|
+
call_next,
|
30
|
+
current_code,
|
31
|
+
recurse,
|
32
|
+
resolve,
|
33
|
+
)
|
24
34
|
from .typemap import (
|
25
35
|
MultiTypeMap,
|
26
36
|
TypeMap,
|
@@ -37,7 +47,9 @@ from .types import (
|
|
37
47
|
from .utils import (
|
38
48
|
BOOTSTRAP,
|
39
49
|
MISSING,
|
50
|
+
CodegenInProgress,
|
40
51
|
Named,
|
52
|
+
NameDatabase,
|
41
53
|
keyword_decorator,
|
42
54
|
)
|
43
55
|
from .version import version as __version__
|
@@ -57,14 +69,22 @@ __all__ = [
|
|
57
69
|
"extend_super",
|
58
70
|
"is_ovld",
|
59
71
|
"ovld",
|
72
|
+
"Code",
|
73
|
+
"Def",
|
74
|
+
"Lambda",
|
75
|
+
"code_generator",
|
60
76
|
"Dependent",
|
61
77
|
"ParametrizedDependentType",
|
62
78
|
"DependentType",
|
63
79
|
"dependent_check",
|
80
|
+
"Medley",
|
81
|
+
"CodegenParameter",
|
64
82
|
"BOOTSTRAP",
|
83
|
+
"CodegenInProgress",
|
65
84
|
"MISSING",
|
66
85
|
"Dataclass",
|
67
86
|
"Named",
|
87
|
+
"NameDatabase",
|
68
88
|
"Deferred",
|
69
89
|
"Exactly",
|
70
90
|
"Intersection",
|
@@ -77,5 +97,7 @@ __all__ = [
|
|
77
97
|
"keyword_decorator",
|
78
98
|
"call_next",
|
79
99
|
"recurse",
|
100
|
+
"resolve",
|
101
|
+
"current_code",
|
80
102
|
"__version__",
|
81
103
|
]
|
ovld/codegen.py
ADDED
@@ -0,0 +1,306 @@
|
|
1
|
+
import inspect
|
2
|
+
import linecache
|
3
|
+
import re
|
4
|
+
import textwrap
|
5
|
+
from ast import _splitlines_no_ff as splitlines
|
6
|
+
from itertools import count
|
7
|
+
from types import FunctionType
|
8
|
+
|
9
|
+
from .utils import MISSING, NameDatabase, keyword_decorator, sigstring
|
10
|
+
|
11
|
+
_current = count()
|
12
|
+
|
13
|
+
|
14
|
+
def rename_code(co, newname): # pragma: no cover
|
15
|
+
if hasattr(co, "replace"):
|
16
|
+
if hasattr(co, "co_qualname"):
|
17
|
+
return co.replace(co_name=newname, co_qualname=newname)
|
18
|
+
else:
|
19
|
+
return co.replace(co_name=newname)
|
20
|
+
else:
|
21
|
+
return type(co)(
|
22
|
+
co.co_argcount,
|
23
|
+
co.co_kwonlyargcount,
|
24
|
+
co.co_nlocals,
|
25
|
+
co.co_stacksize,
|
26
|
+
co.co_flags,
|
27
|
+
co.co_code,
|
28
|
+
co.co_consts,
|
29
|
+
co.co_names,
|
30
|
+
co.co_varnames,
|
31
|
+
co.co_filename,
|
32
|
+
newname,
|
33
|
+
co.co_firstlineno,
|
34
|
+
co.co_lnotab,
|
35
|
+
co.co_freevars,
|
36
|
+
co.co_cellvars,
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
def transfer_function(
|
41
|
+
func,
|
42
|
+
argdefs=MISSING,
|
43
|
+
closure=MISSING,
|
44
|
+
code=MISSING,
|
45
|
+
globals=MISSING,
|
46
|
+
name=MISSING,
|
47
|
+
):
|
48
|
+
new_fn = FunctionType(
|
49
|
+
argdefs=func.__defaults__ if argdefs is MISSING else argdefs,
|
50
|
+
closure=func.__closure__ if closure is MISSING else closure,
|
51
|
+
code=func.__code__ if code is MISSING else code,
|
52
|
+
globals=func.__globals__ if globals is MISSING else globals,
|
53
|
+
name=func.__name__ if name is MISSING else name,
|
54
|
+
)
|
55
|
+
new_fn.__kwdefaults__ = func.__kwdefaults__
|
56
|
+
new_fn.__annotations__ = func.__annotations__
|
57
|
+
new_fn.__dict__.update(func.__dict__)
|
58
|
+
return new_fn
|
59
|
+
|
60
|
+
|
61
|
+
def rename_function(fn, newname):
|
62
|
+
"""Create a copy of the function with a different name."""
|
63
|
+
return transfer_function(
|
64
|
+
func=fn,
|
65
|
+
code=rename_code(fn.__code__, newname),
|
66
|
+
name=newname,
|
67
|
+
)
|
68
|
+
|
69
|
+
|
70
|
+
def instantiate_code(symbol, code, inject={}):
|
71
|
+
virtual_file = f"<ovld:{abs(hash(code)):x}>"
|
72
|
+
linecache.cache[virtual_file] = (None, None, splitlines(code), virtual_file)
|
73
|
+
code = compile(source=code, filename=virtual_file, mode="exec")
|
74
|
+
glb = {**inject}
|
75
|
+
exec(code, glb, glb)
|
76
|
+
return glb[symbol]
|
77
|
+
|
78
|
+
|
79
|
+
# # Previous version: generate a temporary file
|
80
|
+
# def instantiate_code(symbol, code, inject={}):
|
81
|
+
# tf = tempfile.NamedTemporaryFile("w")
|
82
|
+
# _tempfiles.append(tf)
|
83
|
+
# tf.write(code)
|
84
|
+
# tf.flush()
|
85
|
+
# glb = runpy.run_path(tf.name)
|
86
|
+
# rval = glb[symbol]
|
87
|
+
# rval.__globals__.update(inject)
|
88
|
+
# return rval
|
89
|
+
|
90
|
+
|
91
|
+
subr = re.compile(r"\$(|=|:)(|\[[^\[\]]+\])([a-zA-Z0-9_]+)")
|
92
|
+
symr = re.compile(r"[a-zA-Z0-9_]")
|
93
|
+
|
94
|
+
|
95
|
+
def sub(template, subs):
|
96
|
+
idx = next(_current)
|
97
|
+
|
98
|
+
def repl_fn(m):
|
99
|
+
prefix, sep, name = m.groups()
|
100
|
+
value = subs(name, bool(sep))
|
101
|
+
if value is None:
|
102
|
+
return m.group()
|
103
|
+
if sep:
|
104
|
+
value = sep[1:-1].join(value)
|
105
|
+
if prefix == "=" and not symr.fullmatch(value):
|
106
|
+
return f"{name}__{idx}"
|
107
|
+
if prefix == ":" and not symr.fullmatch(value):
|
108
|
+
return f"({name}__{idx} := {value})"
|
109
|
+
return value
|
110
|
+
|
111
|
+
return subr.sub(string=template, repl=repl_fn)
|
112
|
+
|
113
|
+
|
114
|
+
def format_code(code, indent=0, nl=False):
|
115
|
+
if isinstance(code, str):
|
116
|
+
return f"{code}\n" if nl and not code.endswith("\n") else code
|
117
|
+
elif isinstance(code, (list, tuple)):
|
118
|
+
lines = [format_code(line, indent + 4, True) for line in code]
|
119
|
+
block = "".join(lines)
|
120
|
+
return textwrap.indent(block, " " * indent)
|
121
|
+
else: # pragma: no cover
|
122
|
+
raise TypeError(f"Cannot format code from type {type(code)}")
|
123
|
+
|
124
|
+
|
125
|
+
def _gensym():
|
126
|
+
return f"__G{next(_current)}"
|
127
|
+
|
128
|
+
|
129
|
+
class Code:
|
130
|
+
def __init__(self, template, substitutions={}, **substitutions_kw):
|
131
|
+
self.template = template
|
132
|
+
self.substitutions = {**substitutions, **substitutions_kw}
|
133
|
+
|
134
|
+
def sub(self, **subs):
|
135
|
+
return Code(self.template, self.substitutions, **subs)
|
136
|
+
|
137
|
+
def defaults(self, subs):
|
138
|
+
return Code(self.template, subs, **self.substitutions)
|
139
|
+
|
140
|
+
def _mapsub(self, template, code_recurse, getsub):
|
141
|
+
if isinstance(template, (list, tuple)):
|
142
|
+
return [self._mapsub(t, code_recurse, getsub) for t in template]
|
143
|
+
|
144
|
+
if isinstance(template, Code):
|
145
|
+
return code_recurse(template)
|
146
|
+
|
147
|
+
return sub(template, getsub)
|
148
|
+
|
149
|
+
def rename(self, subs):
|
150
|
+
def getsub(name, sep=False):
|
151
|
+
return f"${renaming[name]}" if name in renaming else None
|
152
|
+
|
153
|
+
subs = {k: v for k, v in subs.items() if k not in self.substitutions}
|
154
|
+
|
155
|
+
renaming = {k: _gensym() for k in subs}
|
156
|
+
new_subs = {renaming[k]: v for k, v in subs.items()}
|
157
|
+
|
158
|
+
def _rename_step(v):
|
159
|
+
if isinstance(v, Code):
|
160
|
+
return v.rename(subs)
|
161
|
+
elif isinstance(v, (list, tuple)):
|
162
|
+
if any(isinstance(x, Code) for x in v):
|
163
|
+
return [_rename_step(x) for x in v]
|
164
|
+
else:
|
165
|
+
return v
|
166
|
+
|
167
|
+
new_subs.update({k: _rename_step(v) for k, v in self.substitutions.items()})
|
168
|
+
new_template = self._mapsub(self.template, lambda c: c.rename(subs), getsub)
|
169
|
+
return Code(new_template, new_subs)
|
170
|
+
|
171
|
+
def fill(self, ndb=None):
|
172
|
+
if ndb is None:
|
173
|
+
ndb = NameDatabase()
|
174
|
+
|
175
|
+
def make(name, v, sep):
|
176
|
+
if isinstance(v, Code):
|
177
|
+
return v.defaults(self.substitutions).fill(ndb)
|
178
|
+
elif sep:
|
179
|
+
return [make(name, x, sep) for x in v]
|
180
|
+
else:
|
181
|
+
return ndb.get(v, suggested_name=name)
|
182
|
+
|
183
|
+
def getsub(name, sep=False):
|
184
|
+
if name in self.substitutions:
|
185
|
+
return make(name, self.substitutions[name], sep)
|
186
|
+
else: # pragma: no cover
|
187
|
+
return None
|
188
|
+
|
189
|
+
result = self._mapsub(
|
190
|
+
self.template, lambda c: c.defaults(self.substitutions).fill(ndb), getsub
|
191
|
+
)
|
192
|
+
return format_code(result)
|
193
|
+
|
194
|
+
|
195
|
+
class Function:
|
196
|
+
def __init__(self, args, code=None, **subs):
|
197
|
+
if code is None:
|
198
|
+
code = args
|
199
|
+
args = ...
|
200
|
+
self.args = args
|
201
|
+
if subs:
|
202
|
+
self.code = code.sub(**subs) if isinstance(code, Code) else Code(code, subs)
|
203
|
+
else:
|
204
|
+
self.code = code if isinstance(code, Code) else Code(code)
|
205
|
+
|
206
|
+
def rename(self, *args):
|
207
|
+
return self.code.rename(dict(zip(self.args, args)))
|
208
|
+
|
209
|
+
def __call__(self, *args):
|
210
|
+
self.args = args if self.args is ... else self.args
|
211
|
+
args = [Code(name) if isinstance(name, str) else name for name in args]
|
212
|
+
return self.rename(*args)
|
213
|
+
|
214
|
+
|
215
|
+
class Def(Function):
|
216
|
+
def create_body(self, argnames):
|
217
|
+
return self(*argnames)
|
218
|
+
|
219
|
+
def create_expression(self, argnames):
|
220
|
+
raise ValueError("Cannot convert Def to expression")
|
221
|
+
|
222
|
+
|
223
|
+
class Lambda(Function):
|
224
|
+
def create_body(self, argnames):
|
225
|
+
return Code("return $body", body=self(*argnames))
|
226
|
+
|
227
|
+
def create_expression(self, argnames):
|
228
|
+
return self(*argnames)
|
229
|
+
|
230
|
+
|
231
|
+
def regen_signature(fn, ndb): # pragma: no cover
|
232
|
+
sig = inspect.signature(fn)
|
233
|
+
args = []
|
234
|
+
ko_flag = False
|
235
|
+
po_flag = False
|
236
|
+
for argname, arg in sig.parameters.items():
|
237
|
+
if argname == "cls":
|
238
|
+
argname = "self"
|
239
|
+
ndb.register(argname)
|
240
|
+
argstring = argname
|
241
|
+
if arg.default is not inspect._empty:
|
242
|
+
argstring += f" = {ndb[arg.default]}"
|
243
|
+
|
244
|
+
if arg.kind is inspect._KEYWORD_ONLY:
|
245
|
+
if not ko_flag:
|
246
|
+
ko_flag = True
|
247
|
+
args.append("*")
|
248
|
+
elif arg.kind is inspect._VAR_POSITIONAL:
|
249
|
+
ko_flag = True
|
250
|
+
elif arg.kind is inspect._VAR_KEYWORD:
|
251
|
+
raise TypeError("**kwargs are not accepted")
|
252
|
+
elif arg.kind is inspect._POSITIONAL_OR_KEYWORD:
|
253
|
+
if po_flag:
|
254
|
+
args.append("/")
|
255
|
+
po_flag = False
|
256
|
+
elif arg.kind is inspect._POSITIONAL_ONLY:
|
257
|
+
po_flag = True
|
258
|
+
args.append(argstring)
|
259
|
+
|
260
|
+
if po_flag:
|
261
|
+
args.append("/")
|
262
|
+
|
263
|
+
return ", ".join(args)
|
264
|
+
|
265
|
+
|
266
|
+
fgen_template = """
|
267
|
+
def {fn}({args}):
|
268
|
+
{body}
|
269
|
+
"""
|
270
|
+
|
271
|
+
|
272
|
+
def codegen_specializer(typemap, fn, tup):
|
273
|
+
is_method = typemap.ovld.argument_analysis.is_method
|
274
|
+
ndb = NameDatabase(default_name="INJECT")
|
275
|
+
args = regen_signature(fn, ndb)
|
276
|
+
body = fn(typemap.ovld.specialization_self, *tup) if is_method else fn(*tup)
|
277
|
+
cg = None
|
278
|
+
if isinstance(body, Function):
|
279
|
+
cg = body
|
280
|
+
argnames = [
|
281
|
+
"self" if arg == "cls" else arg for arg in inspect.signature(fn).parameters
|
282
|
+
]
|
283
|
+
body = body.create_body(argnames)
|
284
|
+
if isinstance(body, Code):
|
285
|
+
body = body.fill(ndb)
|
286
|
+
elif isinstance(body, str):
|
287
|
+
pass
|
288
|
+
elif isinstance(body, FunctionType):
|
289
|
+
return body
|
290
|
+
elif body is None:
|
291
|
+
return None
|
292
|
+
body = textwrap.indent(body, " ")
|
293
|
+
code = fgen_template.format(fn="__GENERATED__", args=args, body=body)
|
294
|
+
func = instantiate_code("__GENERATED__", code, inject=ndb.variables)
|
295
|
+
adjusted_name = f"{fn.__name__.split('[')[0]}[{sigstring(tup)}]"
|
296
|
+
func = rename_function(func, adjusted_name)
|
297
|
+
func.__codegen__ = cg
|
298
|
+
return func
|
299
|
+
|
300
|
+
|
301
|
+
@keyword_decorator
|
302
|
+
def code_generator(fn, priority=0):
|
303
|
+
fn.specializer = codegen_specializer
|
304
|
+
if priority:
|
305
|
+
fn.priority = priority
|
306
|
+
return fn
|