ovld 0.4.5__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 call_next, recurse
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,303 @@
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, 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
+ def code_generator(fn):
302
+ fn.specializer = codegen_specializer
303
+ return fn