Slang/compilers/pyssembly/genstdlib.py

182 lines
6.8 KiB
Python
Executable File

#!/usr/bin/python3
from utils.nolog import *
builtin_types = {i for i in itertools.chain(builtins.__dict__.values(), types.__dict__.values()) if isinstance(i, type)}
@dispatch
def gen(x, name: NoneType, *, scope: dict):
return gen(x, '', scope=scope).rstrip(' ')
@dispatch
def gen(x: lambda x: x is NoneType, name: str, *, scope: dict):
return f"void {name}"
@dispatch
def gen(x: lambda x: isinstance(x, type) and (getattr(x, '__module__', None) == '__main__' or x in builtin_types), name: str, *, scope: dict):
return f"{x.__name__} {name}"
@dispatch
def gen(x: lambda x: typing_inspect.get_origin(x) is not None, name: str, *, scope: dict):
o = typing_inspect.get_origin(x)
if (o is typing.Literal):
return gen(type(x.__args__[0]), name, scope=scope)
elif (o is typing.Union):
if (x.__args__[1] is NoneType): return f"{gen(x.__args__[0], name, scope=scope).rstrip()}?"
else: return f"{'|'.join(gen(i, None, scope=scope) for i in x.__args__)} {name}"
elif (o in (tuple, list, dict, set, frozenset)):
return f"{o.__name__}{', '.join(gen(i, None, scope=scope) for i in x.__args__ if i is not ...).join('[]') if (x.__args__ and x.__args__ != (typing.Any,) and x.__args__ != (typing.Any, ...)) else ''} {name}" # TODO args
# TODO FIXME:
elif (o is collections.abc.Iterable):
return f"iterable {name}"
elif (o is collections.abc.Iterator):
return f"iterator {name}"
elif (o is collections.abc.Sequence):
return f"sequence {name}"
elif (o is collections.abc.Mapping):
return f"mapping[{gen(x.__args__[0], None, scope=scope)}, {gen(x.__args__[1], None, scope=scope)}] {name}"
elif (o is collections.abc.ItemsView):
return f"items {name}"
elif (o is collections.abc.KeysView):
return f"keys {name}"
elif (o is collections.abc.ValuesView):
return f"values {name}"
elif (o is collections.abc.Set):
return f"set {name}"
elif (o is collections.abc.Callable):
return f"callable {name}"
elif (o is type):
return f"{x.__args__[0].__name__} {name}"
else: raise NotImplementedError(x, o)
@dispatch
def gen(x: lambda x: x is typing.Any, name: str, *, scope: dict): # TODO FIXME
return f"object {name}"
@dispatch
def gen(x: typing.TypeVar, name: str, *, scope: dict):
return f"{x.__name__} {name}"
@dispatch
def gen(x: lambda x: isinstance(x, type) and issubclass(x, typing.Protocol), name: str, *, scope: dict):
if (x is typing.SupportsInt):
t = int
elif (x is typing.SupportsFloat):
t = float
else: raise NotImplementedError(x)
return gen(t, name, scope=scope)
@dispatch
def gen(x: function, name: str, *, scope: dict):
fsig = inspect.signature(x)
return f"{gen(typing.get_type_hints(x, scope)['return'], None, scope=scope)} {name}({', '.join(gen(v, x, k, scope=scope) for ii, (k, v) in enumerate(fsig.parameters.items()) if not (ii == 0 and k == 'self'))})"
@dispatch
def gen(x: inspect.Parameter, f, name: str, *, scope: dict):
t = gen(typing.get_type_hints(f, scope)[x.name], name, scope=scope)
if (x.default is not inspect._empty):
if (x.default is ...):
if (t[-1:] != '?'): t += '?'
else: raise NotImplementedError(x.default)
return t
@dispatch
def gen(x: property, name: str, *, scope: dict):
return gen(typing.get_type_hints(x.fget, scope)['return'], name, scope=scope)
@dispatch
def gen(x: lambda x: isinstance(x, type) and x.__module__ == '__main__', name: str, *, scope: dict):
return f"{x.__name__} {name}"
#@dispatch
#def gen(x: type, name: str, *, scope: dict):
# return f"{type.__name__} {name}"
@dispatch
def gen(x: lambda x: isinstance(x, type) and x.__module__ == '__main__', *, scope: dict):
r = []
for k, v in typing.get_type_hints(x, scope).items():
r.append(f"{gen(v, k, scope=scope)};".lstrip(';'))
for k, v in x.__dict__.items():
if (k in ('__module__', '__doc__')): continue
if (not isinstance(v, property) and (not isinstance(v, function) or getattr(v, '__module__', None) != '__main__')): continue
r.append(f"{gen(v, k, scope=scope)};".lstrip(';'))
if (not r): return ''
return f"class {x.__name__} {{\n\t"+'\n\t'.join(r)+'\n}'
@dispatch
def gen(x: CodeType, *, package):
r = {'__name__': '__main__', '__package__': package}
exec(x, r)
return '\n\n'.join(Slist(gen(i, scope=r) for i in r.values() if isinstance(i, type) and i.__module__ == '__main__').strip(''))
class AnnotationsFileLoader(importlib.machinery.SourceFileLoader):
header = "from __future__ import annotations"
def get_data(self, path):
dlog(path)
data = super().get_data(path)
if (not path.endswith('.pyi')): return data
return (self.header+'\n\n').encode() + data
def path_hook_factory(f):
def path_hook(path):
finder = f(path)
finder._loaders.insert(0, ('.pyi', AnnotationsFileLoader))
return path_hook
@apmain
@aparg('typeshed', metavar='<typeshed repo path>')
@aparg('output', metavar='<stdlib .sld output dir>')
def main(cargs):
if (sys.version_info < (3,8)): raise NotImplementedError("Currently only Python 3.8+ is supported.")
dirs = ('stdlib', 'stubs')
import __future__
sys.dont_write_bytecode = True
sys.path = [__future__.__file__] + [os.path.join(cargs.typeshed, i) for i in dirs] + [os.path.join(cargs.typeshed, 'stubs', i, j) for i in os.listdir(os.path.join(cargs.typeshed, 'stubs')) for j in os.listdir(os.path.join(cargs.typeshed, 'stubs', i))]
#sys.path_hooks[-1] = path_hook_factory(sys.path_hooks[-1])
#sys.path_hooks[-1] = importlib.machinery.FileFinder.path_hook((AnnotationsFileLoader, ['.pyi']+[i for i in importlib.machinery.all_suffixes() if i != '.pyc']))
sys.path_hooks = [importlib.machinery.FileFinder.path_hook((AnnotationsFileLoader, ['.pyi']))]
sys.meta_path = [sys.meta_path[2]]
skipped = int()
for d in dirs:
for v in sorted(os.listdir(os.path.join(cargs.typeshed, d))):
if (v in ('@python2', '_typeshed')): continue
for p, _, n in os.walk(os.path.join(cargs.typeshed, d, v)):
if ('@python2' in p): continue
o = os.path.join(cargs.output, p.partition(os.path.join(cargs.typeshed, d, v, ''))[2])
os.makedirs(o, exist_ok=True)
for i in sorted(n, key=lambda x: 'builtins.pyi' not in x):
if (not i.endswith('.pyi')): continue
filename = os.path.join(p, i)
log(filename)
code = compile(b"from __future__ import annotations\n\n"+open(filename, 'rb').read(), filename, 'exec')
try: r = gen(code, package=os.path.splitext(filename)[0].replace('/', '.'))
except Exception as ex:
if ('builtins.pyi' in i): raise
logexception(ex)
skipped += 1
raise # XXX
else:
if (not r): continue
if ('builtins.pyi' in i): r = 'import sld:std:*;\n\n'+r
else: r = 'import py:builtins:*;\n\n'+r
open(os.path.join(o, os.path.splitext(i)[0]+'.sld'), 'w').write(r+'\n')
if (skipped): print(f"\033[93m{skipped} errors caught ({skipped} files skipped).\033[0m")
print("\033[1mSuccess!\033[0m")
if (__name__ == '__main__'): exit(main(nolog=True))
else: logimported()
# by Sdore, 2021