Last active
October 31, 2021 21:59
-
-
Save highfestiva/6cbd812041c17508d54ce2fc846fc76a to your computer and use it in GitHub Desktop.
py2nim transpiler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
'''Start of a trivial Python-to-Nim transpiler. So far, only a few | |
basic syntax matters are settled. Curious to see if it's hard to | |
get anything useful done with AST as input while hard-coding | |
output formatting.''' | |
from ast import * | |
from functools import reduce | |
ast_parse = parse | |
indent = ' ' | |
all_scopes = [] | |
class Token: | |
def __init__(self, clazz, s, scope_level): | |
self.clazz = clazz | |
self.s = s | |
self.scope_level = scope_level | |
self.end = ' ' | |
assert type(s) == str | |
def __str__(self): | |
return self.s + self.end | |
class ScopeHolder: | |
def __init__(self, parent=None, clazz=None, op=None): | |
self.headers = [] | |
self.imports = [] | |
self.constants = [] | |
self.stack = [] | |
self.types = [] | |
self.functions = [] | |
self.children = [] | |
self.variables = set() | |
self.parent = parent | |
self.clazz = clazz | |
self.op_precedence = calc_op_precedence(op) | |
all_scopes.append(self) | |
def findvar(self, v): | |
if v in self.variables: | |
return True | |
if self.parent and self.parent.findvar(v): | |
return True | |
return False | |
def to_string(self, endl='\n'): | |
l = self.strif(self.headers, prefix=[]) | |
l += self.strif(self.imports, prefix=['']) | |
l += self.strif(self.constants) | |
l += self.strif(self.stack) | |
l += self.strif(self.types) | |
l += self.strif(self.functions) | |
return endl.join(l) + '\n'.join(c.to_string() for c in self.children) | |
def strif(self, lines, prefix=['','']): | |
if lines: | |
identify = lambda line: indent*line[0].scope_level if line else '' | |
return prefix + [identify(line) + ''.join(str(t) for t in line).rstrip() for line in lines] | |
return [] | |
class NimModule: | |
def __init__(self, headers=['# Generated by py2nim converter.']): | |
self.scope = ScopeHolder() | |
self.scope.headers = [[Token(None, header, 0) for header in headers]] | |
self.scope_level = 0 | |
def token(self, clazz, *args): | |
self.scope.stack.append([Token(clazz, arg, self.scope_level) for arg in args]) | |
return self | |
def parse(self, e): | |
return handler[e.__class__](self, e) | |
def parse_all(self, l, exclude=[]): | |
for e in l: | |
if type(e) not in exclude: | |
self.parse(e) | |
return self | |
def put(self, field): | |
field.extend(self.scope.stack) | |
self.scope.stack.clear() | |
return self | |
def func_check(self, fobj): | |
fname = self.parse(fobj) | |
if fname in ('read', 'write', 'flush', 'print'): | |
self.scope.functions[-1][0] = 'proc' | |
return fname | |
def push(self, clazz=None, op=None): | |
self.scope = ScopeHolder(self.scope, clazz, op) | |
self.paren_in() | |
return self | |
def pop(self): | |
self.paren_out() | |
s = self.scope | |
p = s.parent | |
p.headers.extend(s.headers) | |
p.imports.extend(s.imports) | |
p.constants.extend(s.constants) | |
p.stack.extend(s.stack) | |
p.types.extend(s.types) | |
p.functions.extend(s.functions) | |
p.children.extend(s.children) | |
self.scope = p | |
return self | |
def pop_child(self): | |
s = self.scope | |
p = s.parent | |
p.children.append(s) | |
self.scope = p | |
return self | |
def paren_in(self): | |
if self.scope.op_precedence and self.scope.op_precedence < self.scope.parent.op_precedence: | |
self.token(None, '(') | |
self.scope.stack[-1][-1].end = '' | |
def paren_out(self): | |
if self.scope.op_precedence and self.scope.op_precedence < self.scope.parent.op_precedence: | |
self.token(None, ')') | |
self.scope.stack[-2][-1].end = '' | |
def jointoken(self): | |
self.joinline() | |
self.scope.stack[0][0].end = '' | |
return self | |
def joinline(self): | |
self.scope.stack = [[t for line in self.scope.stack for t in line]] | |
return self | |
def scope_add(self, n): | |
self.scope_level += n | |
return self | |
def join_args(self, e): | |
cls = self.scope.parent.clazz.name if self.scope.parent and self.scope.parent.clazz else None | |
args = [a.arg for a in e.args.args] | |
if cls: | |
args[0] = args[0] + ': '+cls | |
return ', '.join(a for a in args if a) | |
def methods_to_funcs(self): | |
pass | |
# print(self.scope) | |
# self.scope.children = [child for types in self.scope.types for typ in types for child in typ.children] + self.scope.children | |
# for types in self.scope.types: | |
# for typ | |
# typ.children.clear() | |
def to_string(self): | |
return self.scope.to_string() | |
def calc_op_precedence(op): | |
if op is None: | |
return 0 | |
precedence = {Add:1, Sub:1, USub:1, Mult:2, Div:2, FloorDiv:2, Mod:2, Pow:3} | |
return precedence[op.__class__] | |
def funcname(func): | |
if hasattr(func, 'id'): | |
return func.id | |
v = func.value | |
if hasattr(v, 'id'): | |
return v.id | |
if hasattr(v, 'func'): | |
return funcname(v.func) | |
if type(v.value) == Call: | |
return funcname(v.value.func) | |
return v.value | |
handler = { \ | |
Module: lambda nc,e: nc.parse_all(e.body), | |
Import: lambda nc,e: [nc.token(e, 'import', a.name) for a in e.names] and nc.put(nc.scope.imports), | |
ImportFrom: lambda nc,e: [nc.token(e, 'import', e.module+'.'+a.name) for a in e.names] and nc.put(nc.scope.imports), | |
Dict: lambda nc,e: nc.token(e, 'dict'), | |
Assign: lambda nc,e: nc.push().parse_all(e.targets).token(e, '=').parse(e.value).joinline().pop(), | |
ClassDef: lambda nc,e: nc.push().token(e, 'type', e.name, '= object').put(nc.scope.types).push(e).parse_all(e.body, exclude=[Pass]).pop().pop(), | |
FunctionDef: lambda nc,e: nc.push().token(e, 'func', e.name+'('+nc.join_args(e)+') =').scope_add(+1).parse_all(e.body).scope_add(-1).put(nc.scope.functions).pop_child(), | |
Lambda: lambda nc,e: nc.push().token(e, 'func', 'lambdaName('+nc.join_args(e)+') =').parse(e.body).joinline().scope_add(0).put(nc.scope.functions).pop(), | |
alias: lambda nc,e: nc.token(e, 'alias'), | |
Name: lambda nc,e: nc.token(e, e.id), | |
Call: lambda nc,e: nc.token(e, funcname(e.func) + '()'), | |
Constant: lambda nc,e: nc.token(e, repr(e.value)), | |
Pass: lambda nc,e: nc.token(e, 'discard'), | |
arguments: lambda nc,e: nc.token(e, 'arguments'), | |
Expr: lambda nc,e: nc.token(e, 'expr'), | |
If: lambda nc,e: nc.token(e, 'if'), | |
Return: lambda nc,e: nc.push().token(e, 'return').parse(e.value).joinline().pop(), | |
Assert: lambda nc,e: nc.token(e, 'assert'), | |
AugAssign: lambda nc,e: nc.token(e, 'augAssign'), | |
Try: lambda nc,e: nc.token(e, 'try'), | |
For: lambda nc,e: nc.token(e, 'for'), | |
Store: lambda nc,e: nc.token(e, 'store'), | |
Attribute: lambda nc,e: nc.token(e, 'attr'), | |
Load: lambda nc,e: nc.token(e, 'load'), | |
arg: lambda nc,e: nc.token(e, 'arg'), | |
BinOp: lambda nc,e: nc.push(op=e.op).parse(e.left).parse(e.op).parse(e.right).joinline().pop(), | |
BitAnd: lambda nc,e: nc.token(e, '&'), | |
BitOr: lambda nc,e: nc.token(e, '|'), | |
IfExp: lambda nc,e: nc.token(e, 'IfExpr'), | |
Subscript: lambda nc,e: nc.push().parse(e.value).token(e, '[').parse(e.slice).token(e, ']').joinline().pop(), | |
Index: lambda nc,e: nc.token(e, 'index'), | |
Compare: lambda nc,e: nc.token(e, 'comp'), | |
BoolOp: lambda nc,e: nc.token(e, 'boolOp'), | |
Tuple: lambda nc,e: nc.token(e, 'tuple'), | |
ListComp: lambda nc,e: nc.token(e, 'listcomp'), | |
UnaryOp: lambda nc,e: nc.push().parse(e.op).parse(e.operand).jointoken().pop(), | |
Add: lambda nc,e: nc.token(e, '+'), | |
Sub: lambda nc,e: nc.token(e, '-'), | |
USub: lambda nc,e: nc.token(e, '-'), | |
Mult: lambda nc,e: nc.token(e, '*'), | |
Div: lambda nc,e: nc.token(e, '/'), | |
FloorDiv: lambda nc,e: nc.token(e, '/'), | |
Mod: lambda nc,e: nc.token(e, '%'), | |
Pow: lambda nc,e: nc.token(e, '^'), | |
Invert: lambda nc,e: nc.token(e, '!'), | |
ExceptHandler: lambda nc,e: nc.token(e, 'catch'), | |
keyword: lambda nc,e: nc.token(e, 'keyword'), | |
In: lambda nc,e: nc.token(e, 'in'), | |
NotIn: lambda nc,e: nc.token(e, '!in'), | |
Is: lambda nc,e: nc.token(e, 'is'), | |
Eq: lambda nc,e: nc.token(e, '=='), | |
NotEq: lambda nc,e: nc.token(e, '!='), | |
Lt: lambda nc,e: nc.token(e, '<'), | |
LtE: lambda nc,e: nc.token(e, '<='), | |
Gt: lambda nc,e: nc.token(e, '>'), | |
GtE: lambda nc,e: nc.token(e, '>='), | |
And: lambda nc,e: nc.token(e, '&&'), | |
Or: lambda nc,e: nc.token(e, '||'), | |
Not: lambda nc,e: nc.token(e, '!'), | |
comprehension: lambda nc,e: nc.token(e, 'comprehension'), | |
GeneratorExp: lambda nc,e: nc.token(e, 'gen'), | |
Slice: lambda nc,e: nc.token(e, '::'), | |
Del: lambda nc,e: nc.token(e, 'del'), | |
Delete: lambda nc,e: nc.token(e, 'del'), | |
Continue: lambda nc,e: nc.token(e, 'continue'), | |
Starred: lambda nc,e: nc.token(e, '???'), | |
List: lambda nc,e: nc.token(e, '[]'), | |
} | |
def add_keywords(scope): | |
lines = scope.stack + scope.functions | |
for i,tokens in enumerate(lines): | |
for j,token in enumerate(tokens): | |
if type(token.clazz) == Assign: | |
for k in range(j): | |
if not scope.findvar(tokens[k].s): | |
scope.variables.add(tokens[k].s) | |
tokens.insert(0, Token(None, 'var', tokens[0].scope_level)) | |
for c in scope.children: | |
add_keywords(c) | |
def find_child_scopes(parent): | |
for scope in all_scopes: | |
if scope.parent == parent: | |
yield scope | |
tree = ast_parse(open('test.py').read()) | |
code = NimModule() | |
code.parse(tree) | |
code.methods_to_funcs() | |
add_keywords(code.scope) | |
print(code.to_string()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment