Skip to content

Instantly share code, notes, and snippets.

@highfestiva
Last active October 31, 2021 21:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save highfestiva/6cbd812041c17508d54ce2fc846fc76a to your computer and use it in GitHub Desktop.
Save highfestiva/6cbd812041c17508d54ce2fc846fc76a to your computer and use it in GitHub Desktop.
py2nim transpiler
#!/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