Skip to content

Instantly share code, notes, and snippets.

@NickBeeuwsaert
Last active January 18, 2016 03:19
Show Gist options
  • Select an option

  • Save NickBeeuwsaert/23b4e6c3d7790742e57f to your computer and use it in GitHub Desktop.

Select an option

Save NickBeeuwsaert/23b4e6c3d7790742e57f to your computer and use it in GitHub Desktop.
Attempt at converting Python's AST into ES2015
#!/usr/bin/env python3
""" NOTE: This is incomplete!
There are a lot of things that don't work,
like classes, generator functions and async functions!
"""
import ast
import itertools
import json
from io import StringIO
import argparse
BINOPS = {
ast.Add: '({} + {})',
ast.Mult: '({} * {})',
ast.Div: '({} / {})',
ast.Sub: '({} - {})',
ast.Pow: '({} ** {})',
# NOTE: I think pythons notion of the mod operator
# Is different than JS, double check this later...
ast.Mod: '({} % {})',
ast.LShift: '({} << {})',
ast.RShift: '({} >> {})',
ast.BitOr: '({}|{})',
ast.BitAnd: '({}&{})',
ast.BitXor: '({}^{})',
ast.FloorDiv: 'Math.floor({} / {})',
}
BOOLOPS = {
ast.And: ' && ',
ast.Or: ' || '
}
UNARYOPS = {
ast.Not: '!',
ast.UAdd: '+',
ast.USub: '-',
ast.Invert: '~'
}
CMPOPS = {
ast.Eq: '({} == {})',
ast.NotEq: '({} !== {})',
ast.Lt: '({} < {})',
ast.LtE: '({} <= {})',
ast.GtE: '({} >= {})',
ast.Gt: '({} > {})',
ast.Is: '({} === {})',
ast.IsNot: '({} !== {})',
# TODO:
# Python is awesome and lets you compare if objects are equal using `==`
# and if they are the same object using `is`
# But in JS, [] != [], so I need to find a clean way to replace
# pythons in in JS (same for the comparision operators above)
ast.In: 'Array.prototype.indexOf.call({1}, {0}) !== -1',
ast.NotIn: 'Array.prototype.indexOf.call({1}, {0}) === -1'
}
class Visitor(ast.NodeVisitor):
def indent(self, s, amount=4):
return '\n'.join(' '*amount + line for line in s.split('\n'))
def visit_Module(self, node):
return '\n'.join(map(self.visit, node.body))
def visit_Import(self, node):
return '\n'.join(
'var {} = require({!r});'.format(
name.asname or name.name,
name.name
) for name in node.names
)
result = []
for name in node.names:
result.append('var {} = require({!r});'.format(
name.asname or name.name,
name.name
))
return '\n'.join(result)
def visit_ClassDef(self, node):
# TODO: replace this with a better alternative to python classes
# ES2015 classes even close to Python classes
# Need to figure out a way to do multiple inheritance
# and methods need to get the self argument
return '\n'.join([
'class ' + node.name + (' extends '+self.visit(node.bases[-1]) if node.bases else '') + ' {'
] + [
self.indent(self.visit(stmt)) for stmt in node.body
] + [
'}'
]) + '\n'
def visit_ImportFrom(self, node):
aliases = []
for name in node.names:
if name.asname:
aliases.append('{}: {}'.format(name.name, name.asname))
else:
aliases.append(name.name)
return 'var {{{}}} = require({!r});'.format(
', '.join(aliases),
'../'*node.level + node.module
)
def visit_Nonlocal(self, node):
# TODO: Mark these variables as nonlocal
# which would basically mean omitting declaring them with var
return 'var ' + ', '.join(map(self.visit, node.names)) + ';'
def visit_Global(self, node):
# TODO: declare these as global
# which, for a browner might mean
# declaring it as var name = window.name;
# or something...
return 'var ' + ', '.join(map(self.visit, node.names)) + ''
def generic_visit(self, node):
print('Unused: ', type(node).__name__)
super().generic_visit(node)
return ''
def visit_FunctionDef(self, node):
# TODO: Track variable declarations in the function,
# So they can be declared at the top
# Also, if yield is called in the function
# so the function can be declared as a generator (function* func_name)
result = [
'function {}({}){{'.format(node.name, self.visit(node.args)),
self.indent('"use strict";'),
self.indent('\n'.join(self.visit(stmt) for stmt in node.body)),
'}'
]
# Loop through the decorators and apply them
for decorator in node.decorator_list:
result.append('{0} = {1}({0});'.format(node.name, self.visit(decorator)))
return '\n'.join(result) + '\n'
# TODO: then pop the 'stack'
def visit_Lambda(self, node):
# Those phat arrows tho
return '({}) => {}'.format(
self.visit(node.args),
self.visit(node.body)
)
def visit_arguments(self, node):
# TODO: Work on how we can pass javascript arguments in a pythonic way
# Currently the function is being built up like this:
# function(a, b, c, {d=1, e=2}, args=[], kwargs={})
# which works for very simple functions,
# But won't work when you do things like
# pass positional arguments as keywords
num_pos_args = len(node.args) - len(node.defaults)
args = []
positional_args, keyword_args = node.args[:num_pos_args], node.args[num_pos_args:]
args += map(self.visit, positional_args)
kwargs = [
'{}={}'.format(self.visit(k), self.visit(v))
for k, v in zip(keyword_args, node.defaults)
]
kwargs += list(map(self.visit, node.kwonlyargs))
if kwargs:
args.append('{' + ', '.join(kwargs) + '}')
if node.vararg:
args.append(self.visit(node.vararg) + '=[]')
if node.kwarg:
args.append(self.visit(node.kwarg) + '={}')
return ', '.join(args)
def visit_Pass(self, node):
# No-op
return ''
def visit_Break(self, node):
return 'break;'
def visit_Continue(self, node):
return 'continue;'
def visit_For(self, node):
return '\n'.join([
'for(var {} of {})'.format(
self.visit(node.target),
self.visit(node.iter)
) + '{'
] + [self.indent(self.visit(stmt)) for stmt in node.body] + ['}'])
# TODO: Handle the else condition
def visit_While(self, node):
return '\n'.join([
'while({})'.format(
self.visit(node.test)
) + '{'
] + [self.indent(self.visit(stmt)) for stmt in node.body] + ['}'])
# TODO: Handle the else condition
def visit_Assign(self, node):
# TODO: Check current scope and omit 'var' if the variable
# has already been declared
return 'var {} = {};'.format(
', '.join(map(self.visit, node.targets)),
self.visit(node.value)
)
def visit_AugAssign(self, node):
# we could use Javascripts shorthand
# assignments here but I'm lazy
return 'var {} = {};'.format(
self.visit(node.target),
BINOPS[type(node.op)].format(
self.visit(node.target),
self.visit(node.value)
)
)
def convert_array(self, node):
return '[' + ', '.join(map(self.visit, node.elts)) + ']'
visit_Tuple = convert_array
visit_List = convert_array
def visit_Return(self, node):
if not node.value:
return 'return;'
return 'return {};'.format(self.visit(node.value))
def visit_If(self, node):
result = ['if({}) {{\n{}\n}}'.format(
self.visit(node.test),
self.indent('\n'.join(map(self.visit, node.body)))
)]
result += [
' else '+self.visit(n)
if isinstance(n, ast.If)
else ' else {{\n{}\n}}'.format(self.indent(self.visit(n)))
for n in node.orelse
]
return ''.join(result)
def visit_Num(self, node):
return str(node.n)
def visit_BoolOp(self, node):
return '({})'.format(
BOOLOPS[type(node.op)].join(self.visit(v) for v in node.values)
)
def visit_NameConstant(self, node):
return json.dumps(node.value)
def visit_BinOp(self, node):
return BINOPS[type(node.op)].format(
self.visit(node.left),
self.visit(node.right)
)
def visit_Compare(self, node):
output = []
for l, o, p in zip([node.left] + node.comparators, node.ops, node.comparators):
op_type = type(o)
output.append(
CMPOPS[op_type].format(
self.visit(l),
self.visit(p)
)
)
return ' && '.join(output)
def visit_arg(self, node):
return node.arg
def visit_Name(self, node):
return node.id
def visit_Attribute(self, node):
return '{}.{}'.format(
self.visit(node.value),
node.attr
)
def visit_UnaryOp(self, node):
return UNARYOPS[type(node.op)] + self.visit(node.operand)
def visit_Call(self, node):
return '{}({})'.format(
self.visit(node.func),
', '.join(map(self.visit, node.args))
)
def visit_Str(self, node):
return json.dumps(node.s)
def visit_Expr(self, node):
return self.visit(node.value) + ';'
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('input', metavar='INPUT', help='Python file to convert to JS')
args = parser.parse_args()
v = Visitor()
with open(args.input, 'r') as tree_fp:
tree = ast.parse(tree_fp.read())
print(v.visit(tree))
import functools as ft
from argparse import ArgumentParser as AP
class TestClass(object):
def __init__(self, x, y, z):
self._x, self._y, self._z = x, y, z
@property
def x(self):
return self._x
def test_func(a, b, c, d = 1, e = 2, f = 3, *args, **kwargs):
print("Hello, world")
return 5
def test_func_kwargs(**kwargs):
print("{} {}".format(":D", "D:"))
return
def kwonly_args(b = 1, *args, x = 1):
from ..x import x
if 1 < 2 < 3:
print("Guess what! 1 is less than 2 (which is also less than 3)")
elif 1 > 2 > 3:
print("Something is wrong with the universe")
else:
print("Save yourself")
while True:
x -= 1
if not x: break
else:
print("This wont get converted yet")
for i in range(1, 10):
print("#" + i)
else:
print("this either")
x = lambda a, b, c: a+b+c
if __name__ == "__main__":
x = 1, 2, 3
y = 5 ** 6
y **= 2
y //= 3
print("Hello, world!")
for x in ft.chain([x, y], [1, 2, 3], [4, 5, 6]):
print(x**2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment