mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-23 19:23:37 +00:00
733 lines
24 KiB
Python
733 lines
24 KiB
Python
from __future__ import unicode_literals
|
|
from pyjsparser.pyjsparserdata import *
|
|
from .friendly_nodes import *
|
|
import random
|
|
import six
|
|
|
|
if six.PY3:
|
|
from functools import reduce
|
|
xrange = range
|
|
unicode = str
|
|
# number of characters above which expression will be split to multiple lines in order to avoid python parser stack overflow
|
|
# still experimental so I suggest to set it to 400 in order to avoid common errors
|
|
# set it to smaller value only if you have problems with parser stack overflow
|
|
LINE_LEN_LIMIT = 400 # 200 # or any other value - the larger the smaller probability of errors :)
|
|
|
|
|
|
class LoopController:
|
|
def __init__(self):
|
|
self.update = [""]
|
|
self.label_to_update_idx = {}
|
|
|
|
def enter(self, update=""):
|
|
self.update.append(update)
|
|
|
|
def leave(self):
|
|
self.update.pop()
|
|
|
|
def get_update(self, label=None):
|
|
if label is None:
|
|
return self.update[-1]
|
|
if label not in self.label_to_update_idx:
|
|
raise SyntaxError("Undefined label %s" % label)
|
|
if self.label_to_update_idx[label] >= len(self.update):
|
|
raise SyntaxError("%s is not a iteration statement label?" % label)
|
|
return self.update[self.label_to_update_idx[label]]
|
|
|
|
def register_label(self, label):
|
|
if label in self.label_to_update_idx:
|
|
raise SyntaxError("label %s already used")
|
|
self.label_to_update_idx[label] = len(self.update)
|
|
|
|
def deregister_label(self, label):
|
|
del self.label_to_update_idx[label]
|
|
|
|
|
|
|
|
|
|
|
|
class InlineStack:
|
|
NAME = 'PyJs_%s_%d_'
|
|
|
|
def __init__(self):
|
|
self.reps = {}
|
|
self.names = []
|
|
|
|
def inject_inlines(self, source):
|
|
for lval in self.names: # first in first out! Its important by the way
|
|
source = inject_before_lval(source, lval, self.reps[lval])
|
|
return source
|
|
|
|
def require(self, typ):
|
|
name = self.NAME % (typ, len(self.names))
|
|
self.names.append(name)
|
|
return name
|
|
|
|
def define(self, name, val):
|
|
self.reps[name] = val
|
|
|
|
def reset(self):
|
|
self.rel = {}
|
|
self.names = []
|
|
|
|
|
|
class ContextStack:
|
|
def __init__(self):
|
|
self.to_register = set([])
|
|
self.to_define = {}
|
|
|
|
def reset(self):
|
|
self.to_register = set([])
|
|
self.to_define = {}
|
|
|
|
def register(self, var):
|
|
self.to_register.add(var)
|
|
|
|
def define(self, name, code):
|
|
self.to_define[name] = code
|
|
self.register(name)
|
|
|
|
def get_code(self):
|
|
code = 'var.registers([%s])\n' % ', '.join(
|
|
repr(e) for e in self.to_register)
|
|
for name, func_code in six.iteritems(self.to_define):
|
|
code += func_code
|
|
return code
|
|
|
|
|
|
def clean_stacks():
|
|
global Context, inline_stack, loop_controller
|
|
Context = ContextStack()
|
|
inline_stack = InlineStack()
|
|
loop_controller = LoopController()
|
|
|
|
|
|
def to_key(literal_or_identifier):
|
|
''' returns string representation of this object'''
|
|
if literal_or_identifier['type'] == 'Identifier':
|
|
return literal_or_identifier['name']
|
|
elif literal_or_identifier['type'] == 'Literal':
|
|
k = literal_or_identifier['value']
|
|
if isinstance(k, float):
|
|
return unicode(float_repr(k))
|
|
elif 'regex' in literal_or_identifier:
|
|
return compose_regex(k)
|
|
elif isinstance(k, bool):
|
|
return 'true' if k else 'false'
|
|
elif k is None:
|
|
return 'null'
|
|
else:
|
|
return unicode(k)
|
|
|
|
def is_iteration_statement(cand):
|
|
if not isinstance(cand, dict):
|
|
# Multiple statements.
|
|
return False
|
|
return cand.get("type", "?") in {"ForStatement", "ForInStatement", "WhileStatement", "DoWhileStatement"}
|
|
|
|
|
|
|
|
def trans(ele, standard=False):
|
|
"""Translates esprima syntax tree to python by delegating to appropriate translating node"""
|
|
try:
|
|
node = globals().get(ele['type'])
|
|
if not node:
|
|
raise NotImplementedError('%s is not supported!' % ele['type'])
|
|
if standard:
|
|
node = node.__dict__[
|
|
'standard'] if 'standard' in node.__dict__ else node
|
|
return node(**ele)
|
|
except:
|
|
#print ele
|
|
raise
|
|
|
|
|
|
def limited(func):
|
|
'''Decorator limiting resulting line length in order to avoid python parser stack overflow -
|
|
If expression longer than LINE_LEN_LIMIT characters then it will be moved to upper line
|
|
USE ONLY ON EXPRESSIONS!!! '''
|
|
|
|
def f(standard=False, **args):
|
|
insert_pos = len(
|
|
inline_stack.names
|
|
) # in case line is longer than limit we will have to insert the lval at current position
|
|
# this is because calling func will change inline_stack.
|
|
# we cant use inline_stack.require here because we dont know whether line overflows yet
|
|
res = func(**args)
|
|
if len(res) > LINE_LEN_LIMIT:
|
|
name = inline_stack.require('LONG')
|
|
inline_stack.names.pop()
|
|
inline_stack.names.insert(insert_pos, name)
|
|
res = 'def %s(var=var):\n return %s\n' % (name, res)
|
|
inline_stack.define(name, res)
|
|
return name + '()'
|
|
else:
|
|
return res
|
|
|
|
f.__dict__['standard'] = func
|
|
return f
|
|
|
|
|
|
# ==== IDENTIFIERS AND LITERALS =======
|
|
|
|
inf = float('inf')
|
|
|
|
|
|
def Literal(type, value, raw, regex=None):
|
|
if regex: # regex
|
|
return 'JsRegExp(%s)' % repr(compose_regex(value))
|
|
elif value is None: # null
|
|
return 'var.get(u"null")'
|
|
# Todo template
|
|
# String, Bool, Float
|
|
return 'Js(%s)' % repr(value) if value != inf else 'Js(float("inf"))'
|
|
|
|
|
|
def Identifier(type, name):
|
|
return 'var.get(%s)' % repr(name)
|
|
|
|
|
|
@limited
|
|
def MemberExpression(type, computed, object, property):
|
|
far_left = trans(object)
|
|
if computed: # obj[prop] type accessor
|
|
# may be literal which is the same in every case so we can save some time on conversion
|
|
if property['type'] == 'Literal':
|
|
prop = repr(to_key(property))
|
|
else: # worst case
|
|
prop = trans(property)
|
|
else: # always the same since not computed (obj.prop accessor)
|
|
prop = repr(to_key(property))
|
|
return far_left + '.get(%s)' % prop
|
|
|
|
|
|
def ThisExpression(type):
|
|
return 'var.get(u"this")'
|
|
|
|
|
|
@limited
|
|
def CallExpression(type, callee, arguments):
|
|
arguments = [trans(e) for e in arguments]
|
|
if callee['type'] == 'MemberExpression':
|
|
far_left = trans(callee['object'])
|
|
if callee['computed']: # obj[prop] type accessor
|
|
# may be literal which is the same in every case so we can save some time on conversion
|
|
if callee['property']['type'] == 'Literal':
|
|
prop = repr(to_key(callee['property']))
|
|
else: # worst case
|
|
prop = trans(
|
|
callee['property']) # its not a string literal! so no repr
|
|
else: # always the same since not computed (obj.prop accessor)
|
|
prop = repr(to_key(callee['property']))
|
|
arguments.insert(0, prop)
|
|
return far_left + '.callprop(%s)' % ', '.join(arguments)
|
|
else: # standard call
|
|
return trans(callee) + '(%s)' % ', '.join(arguments)
|
|
|
|
|
|
# ========== ARRAYS ============
|
|
|
|
|
|
def ArrayExpression(type, elements): # todo fix null inside problem
|
|
return 'Js([%s])' % ', '.join(trans(e) if e else 'None' for e in elements)
|
|
|
|
|
|
# ========== OBJECTS =============
|
|
|
|
|
|
def ObjectExpression(type, properties):
|
|
name = None
|
|
elems = []
|
|
after = ''
|
|
for p in properties:
|
|
if p['kind'] == 'init':
|
|
elems.append('%s:%s' % Property(**p))
|
|
else:
|
|
if name is None:
|
|
name = inline_stack.require('Object')
|
|
if p['kind'] == 'set':
|
|
k, setter = Property(
|
|
**p
|
|
) # setter is just a lval referring to that function, it will be defined in InlineStack automatically
|
|
after += '%s.define_own_property(%s, {"set":%s, "configurable":True, "enumerable":True})\n' % (
|
|
name, k, setter)
|
|
elif p['kind'] == 'get':
|
|
k, getter = Property(**p)
|
|
after += '%s.define_own_property(%s, {"get":%s, "configurable":True, "enumerable":True})\n' % (
|
|
name, k, getter)
|
|
else:
|
|
raise RuntimeError('Unexpected object propery kind')
|
|
definition = 'Js({%s})' % ','.join(elems)
|
|
if name is None:
|
|
return definition
|
|
body = '%s = %s\n' % (name, definition)
|
|
body += after
|
|
body += 'return %s\n' % name
|
|
code = 'def %s():\n%s' % (name, indent(body))
|
|
inline_stack.define(name, code)
|
|
return name + '()'
|
|
|
|
|
|
def Property(type, kind, key, computed, value, method, shorthand):
|
|
if shorthand or computed:
|
|
raise NotImplementedError(
|
|
'Shorthand and Computed properties not implemented!')
|
|
k = to_key(key)
|
|
if k is None:
|
|
raise SyntaxError('Invalid key in dictionary! Or bug in Js2Py')
|
|
v = trans(value)
|
|
return repr(k), v
|
|
|
|
|
|
# ========== EXPRESSIONS ============
|
|
|
|
|
|
@limited
|
|
def UnaryExpression(type, operator, argument, prefix):
|
|
a = trans(
|
|
argument, standard=True
|
|
) # unary involve some complex operations so we cant use line shorteners here
|
|
if operator == 'delete':
|
|
if argument['type'] in ('Identifier', 'MemberExpression'):
|
|
# means that operation is valid
|
|
return js_delete(a)
|
|
return 'PyJsComma(%s, Js(True))' % a # otherwise not valid, just perform expression and return true.
|
|
elif operator == 'typeof':
|
|
return js_typeof(a)
|
|
return UNARY[operator](a)
|
|
|
|
|
|
@limited
|
|
def BinaryExpression(type, operator, left, right):
|
|
a = trans(left)
|
|
b = trans(right)
|
|
# delegate to our friends
|
|
return BINARY[operator](a, b)
|
|
|
|
|
|
@limited
|
|
def UpdateExpression(type, operator, argument, prefix):
|
|
a = trans(
|
|
argument, standard=True
|
|
) # also complex operation involving parsing of the result so no line length reducing here
|
|
return js_postfix(a, operator == '++', not prefix)
|
|
|
|
|
|
@limited
|
|
def AssignmentExpression(type, operator, left, right):
|
|
operator = operator[:-1]
|
|
if left['type'] == 'Identifier':
|
|
if operator:
|
|
return 'var.put(%s, %s, %s)' % (repr(to_key(left)), trans(right),
|
|
repr(operator))
|
|
else:
|
|
return 'var.put(%s, %s)' % (repr(to_key(left)), trans(right))
|
|
elif left['type'] == 'MemberExpression':
|
|
far_left = trans(left['object'])
|
|
if left['computed']: # obj[prop] type accessor
|
|
# may be literal which is the same in every case so we can save some time on conversion
|
|
if left['property']['type'] == 'Literal':
|
|
prop = repr(to_key(left['property']))
|
|
else: # worst case
|
|
prop = trans(
|
|
left['property']) # its not a string literal! so no repr
|
|
else: # always the same since not computed (obj.prop accessor)
|
|
prop = repr(to_key(left['property']))
|
|
if operator:
|
|
return far_left + '.put(%s, %s, %s)' % (prop, trans(right),
|
|
repr(operator))
|
|
else:
|
|
return far_left + '.put(%s, %s)' % (prop, trans(right))
|
|
else:
|
|
raise SyntaxError('Invalid left hand side in assignment!')
|
|
|
|
|
|
six
|
|
|
|
|
|
@limited
|
|
def SequenceExpression(type, expressions):
|
|
return reduce(js_comma, (trans(e) for e in expressions))
|
|
|
|
|
|
@limited
|
|
def NewExpression(type, callee, arguments):
|
|
return trans(callee) + '.create(%s)' % ', '.join(
|
|
trans(e) for e in arguments)
|
|
|
|
|
|
@limited
|
|
def ConditionalExpression(
|
|
type, test, consequent,
|
|
alternate): # caused plenty of problems in my home-made translator :)
|
|
return '(%s if %s else %s)' % (trans(consequent), trans(test),
|
|
trans(alternate))
|
|
|
|
|
|
# =========== STATEMENTS =============
|
|
|
|
|
|
def BlockStatement(type, body):
|
|
return StatementList(
|
|
body) # never returns empty string! In the worst case returns pass\n
|
|
|
|
|
|
def ExpressionStatement(type, expression):
|
|
return trans(expression) + '\n' # end expression space with new line
|
|
|
|
|
|
def BreakStatement(type, label):
|
|
if label:
|
|
return 'raise %s("Breaked")\n' % (get_break_label(label['name']))
|
|
else:
|
|
return 'break\n'
|
|
|
|
|
|
def ContinueStatement(type, label):
|
|
if label:
|
|
maybe_update_expr = loop_controller.get_update(label=label['name'])
|
|
continue_stmt = 'raise %s("Continued")\n' % (get_continue_label(label['name']))
|
|
else:
|
|
maybe_update_expr = loop_controller.get_update()
|
|
continue_stmt = "continue\n"
|
|
if maybe_update_expr:
|
|
return "# continue update\n%s\n%s" % (maybe_update_expr, continue_stmt)
|
|
return continue_stmt
|
|
|
|
|
|
def ReturnStatement(type, argument):
|
|
return 'return %s\n' % (trans(argument)
|
|
if argument else "var.get('undefined')")
|
|
|
|
|
|
def EmptyStatement(type):
|
|
return 'pass\n'
|
|
|
|
|
|
def DebuggerStatement(type):
|
|
return 'pass\n'
|
|
|
|
|
|
def DoWhileStatement(type, body, test):
|
|
loop_controller.enter()
|
|
body_code = trans(body)
|
|
loop_controller.leave()
|
|
inside = body_code + 'if not %s:\n' % trans(test) + indent('break\n')
|
|
result = 'while 1:\n' + indent(inside)
|
|
return result
|
|
|
|
|
|
def ForStatement(type, init, test, update, body):
|
|
update = trans(update) if update else ''
|
|
init = trans(init) if init else ''
|
|
if not init.endswith('\n'):
|
|
init += '\n'
|
|
test = trans(test) if test else '1'
|
|
loop_controller.enter(update)
|
|
if not update:
|
|
result = '#for JS loop\n%swhile %s:\n%s%s\n' % (
|
|
init, test, indent(trans(body)), update)
|
|
else:
|
|
result = '#for JS loop\n%swhile %s:\n' % (init, test)
|
|
result += indent("%s# update\n%s\n" % (trans(body), update))
|
|
loop_controller.leave()
|
|
return result
|
|
|
|
|
|
def ForInStatement(type, left, right, body, each):
|
|
res = 'for PyJsTemp in %s:\n' % trans(right)
|
|
if left['type'] == "VariableDeclaration":
|
|
addon = trans(left) # make sure variable is registered
|
|
if addon != 'pass\n':
|
|
res = addon + res # we have to execute this expression :(
|
|
# now extract the name
|
|
try:
|
|
name = left['declarations'][0]['id']['name']
|
|
except:
|
|
raise RuntimeError('Unusual ForIn loop')
|
|
elif left['type'] == 'Identifier':
|
|
name = left['name']
|
|
else:
|
|
raise RuntimeError('Unusual ForIn loop')
|
|
loop_controller.enter()
|
|
res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body))
|
|
loop_controller.leave()
|
|
return res
|
|
|
|
|
|
def IfStatement(type, test, consequent, alternate):
|
|
# NOTE we cannot do elif because function definition inside elif statement would not be possible!
|
|
IF = 'if %s:\n' % trans(test)
|
|
IF += indent(trans(consequent))
|
|
if not alternate:
|
|
return IF
|
|
ELSE = 'else:\n' + indent(trans(alternate))
|
|
return IF + ELSE
|
|
|
|
|
|
def LabeledStatement(type, label, body):
|
|
# todo consider using smarter approach!
|
|
label_name = label['name']
|
|
loop_controller.register_label(label_name)
|
|
inside = trans(body)
|
|
loop_controller.deregister_label(label_name)
|
|
defs = ''
|
|
if is_iteration_statement(body) and (inside.startswith('while ') or inside.startswith(
|
|
'for ') or inside.startswith('#for')):
|
|
# we have to add contine label as well...
|
|
# 3 or 1 since #for loop type has more lines before real for.
|
|
sep = 1 if not inside.startswith('#for') else 3
|
|
cont_label = get_continue_label(label_name)
|
|
temp = inside.split('\n')
|
|
injected = 'try:\n' + '\n'.join(temp[sep:])
|
|
injected += 'except %s:\n pass\n' % cont_label
|
|
inside = '\n'.join(temp[:sep]) + '\n' + indent(injected)
|
|
defs += 'class %s(Exception): pass\n' % cont_label
|
|
break_label = get_break_label(label_name)
|
|
inside = 'try:\n%sexcept %s:\n pass\n' % (indent(inside), break_label)
|
|
defs += 'class %s(Exception): pass\n' % break_label
|
|
return defs + inside
|
|
|
|
|
|
def StatementList(lis):
|
|
if lis: # ensure we don't return empty string because it may ruin indentation!
|
|
code = ''.join(trans(e) for e in lis)
|
|
return code if code else 'pass\n'
|
|
else:
|
|
return 'pass\n'
|
|
|
|
|
|
def PyimportStatement(type, imp):
|
|
lib = imp['name']
|
|
jlib = 'PyImport_%s' % lib
|
|
code = 'import %s as %s\n' % (lib, jlib)
|
|
#check whether valid lib name...
|
|
try:
|
|
compile(code, '', 'exec')
|
|
except:
|
|
raise SyntaxError(
|
|
'Invalid Python module name (%s) in pyimport statement' % lib)
|
|
# var.pyimport will handle module conversion to PyJs object
|
|
code += 'var.pyimport(%s, %s)\n' % (repr(lib), jlib)
|
|
return code
|
|
|
|
|
|
def SwitchStatement(type, discriminant, cases):
|
|
#TODO there will be a problem with continue in a switch statement.... FIX IT
|
|
code = 'while 1:\n' + indent('SWITCHED = False\nCONDITION = (%s)\n')
|
|
code = code % trans(discriminant)
|
|
for case in cases:
|
|
case_code = None
|
|
if case['test']: # case (x):
|
|
case_code = 'if SWITCHED or PyJsStrictEq(CONDITION, %s):\n' % (
|
|
trans(case['test']))
|
|
else: # default:
|
|
case_code = 'if True:\n'
|
|
case_code += indent('SWITCHED = True\n')
|
|
case_code += indent(StatementList(case['consequent']))
|
|
# one more indent for whole
|
|
code += indent(case_code)
|
|
# prevent infinite loop and sort out nested switch...
|
|
code += indent('SWITCHED = True\nbreak\n')
|
|
return code
|
|
|
|
|
|
def ThrowStatement(type, argument):
|
|
return 'PyJsTempException = JsToPyException(%s)\nraise PyJsTempException\n' % trans(
|
|
argument)
|
|
|
|
|
|
def TryStatement(type, block, handler, handlers, guardedHandlers, finalizer):
|
|
result = 'try:\n%s' % indent(trans(block))
|
|
# complicated catch statement...
|
|
if handler:
|
|
identifier = handler['param']['name']
|
|
holder = 'PyJsHolder_%s_%d' % (to_hex(identifier),
|
|
random.randrange(1e8))
|
|
identifier = repr(identifier)
|
|
result += 'except PyJsException as PyJsTempException:\n'
|
|
# fill in except ( catch ) block and remember to recover holder variable to its previous state
|
|
result += indent(
|
|
TRY_CATCH.replace('HOLDER',
|
|
holder).replace('NAME', identifier).replace(
|
|
'BLOCK', indent(trans(handler['body']))))
|
|
# translate finally statement if present
|
|
if finalizer:
|
|
result += 'finally:\n%s' % indent(trans(finalizer))
|
|
return result
|
|
|
|
|
|
def LexicalDeclaration(type, declarations, kind):
|
|
raise NotImplementedError(
|
|
'let and const not implemented yet but they will be soon! Check github for updates.'
|
|
)
|
|
|
|
|
|
def VariableDeclarator(type, id, init):
|
|
name = id['name']
|
|
# register the name if not already registered
|
|
Context.register(name)
|
|
if init:
|
|
return 'var.put(%s, %s)\n' % (repr(name), trans(init))
|
|
return ''
|
|
|
|
|
|
def VariableDeclaration(type, declarations, kind):
|
|
code = ''.join(trans(d) for d in declarations)
|
|
return code if code else 'pass\n'
|
|
|
|
|
|
def WhileStatement(type, test, body):
|
|
test_code = trans(test)
|
|
loop_controller.enter()
|
|
body_code = trans(body)
|
|
loop_controller.leave()
|
|
result = 'while %s:\n' % test_code + indent(body_code)
|
|
return result
|
|
|
|
|
|
def WithStatement(type, object, body):
|
|
raise NotImplementedError('With statement not implemented!')
|
|
|
|
|
|
def Program(type, body):
|
|
inline_stack.reset()
|
|
code = ''.join(trans(e) for e in body)
|
|
# here add hoisted elements (register variables and define functions)
|
|
code = Context.get_code() + code
|
|
# replace all inline variables
|
|
code = inline_stack.inject_inlines(code)
|
|
return code
|
|
|
|
|
|
# ======== FUNCTIONS ============
|
|
|
|
|
|
def FunctionDeclaration(type, id, params, defaults, body, generator,
|
|
expression):
|
|
if generator:
|
|
raise NotImplementedError('Generators not supported')
|
|
if defaults:
|
|
raise NotImplementedError('Defaults not supported')
|
|
if not id:
|
|
return FunctionExpression(type, id, params, defaults, body, generator,
|
|
expression) + '\n'
|
|
JsName = id['name']
|
|
PyName = 'PyJsHoisted_%s_' % JsName
|
|
PyName = PyName if is_valid_py_name(PyName) else 'PyJsHoistedNonPyName'
|
|
# this is quite complicated
|
|
global Context
|
|
previous_context = Context
|
|
# change context to the context of this function
|
|
Context = ContextStack()
|
|
# translate body within current context
|
|
code = trans(body)
|
|
# get arg names
|
|
vars = [v['name'] for v in params]
|
|
# args are automaticaly registered variables
|
|
Context.to_register.update(vars)
|
|
# add all hoisted elements inside function
|
|
code = Context.get_code() + code
|
|
# check whether args are valid python names:
|
|
used_vars = []
|
|
for v in vars:
|
|
if is_valid_py_name(v):
|
|
used_vars.append(v)
|
|
else: # invalid arg in python, for example $, replace with alternatice arg
|
|
used_vars.append('PyJsArg_%s_' % to_hex(v))
|
|
header = '@Js\n'
|
|
header += 'def %s(%sthis, arguments, var=var):\n' % (
|
|
PyName, ', '.join(used_vars) + (', ' if vars else ''))
|
|
# transfer names from Py scope to Js scope
|
|
arg_map = dict(zip(vars, used_vars))
|
|
arg_map.update({'this': 'this', 'arguments': 'arguments'})
|
|
arg_conv = 'var = Scope({%s}, var)\n' % ', '.join(
|
|
repr(k) + ':' + v for k, v in six.iteritems(arg_map))
|
|
# and finally set the name of the function to its real name:
|
|
footer = '%s.func_name = %s\n' % (PyName, repr(JsName))
|
|
footer += 'var.put(%s, %s)\n' % (repr(JsName), PyName)
|
|
whole_code = header + indent(arg_conv + code) + footer
|
|
# restore context
|
|
Context = previous_context
|
|
# define in upper context
|
|
Context.define(JsName, whole_code)
|
|
return 'pass\n'
|
|
|
|
|
|
def FunctionExpression(type, id, params, defaults, body, generator,
|
|
expression):
|
|
if generator:
|
|
raise NotImplementedError('Generators not supported')
|
|
if defaults:
|
|
raise NotImplementedError('Defaults not supported')
|
|
JsName = id['name'] if id else 'anonymous'
|
|
if not is_valid_py_name(JsName):
|
|
ScriptName = 'InlineNonPyName'
|
|
else:
|
|
ScriptName = JsName
|
|
PyName = inline_stack.require(ScriptName) # this is unique
|
|
|
|
# again quite complicated
|
|
global Context
|
|
previous_context = Context
|
|
# change context to the context of this function
|
|
Context = ContextStack()
|
|
# translate body within current context
|
|
code = trans(body)
|
|
# get arg names
|
|
vars = [v['name'] for v in params]
|
|
# args are automaticaly registered variables
|
|
Context.to_register.update(vars)
|
|
# add all hoisted elements inside function
|
|
code = Context.get_code() + code
|
|
# check whether args are valid python names:
|
|
used_vars = []
|
|
for v in vars:
|
|
if is_valid_py_name(v):
|
|
used_vars.append(v)
|
|
else: # invalid arg in python, for example $, replace with alternatice arg
|
|
used_vars.append('PyJsArg_%s_' % to_hex(v))
|
|
header = '@Js\n'
|
|
header += 'def %s(%sthis, arguments, var=var):\n' % (
|
|
PyName, ', '.join(used_vars) + (', ' if vars else ''))
|
|
# transfer names from Py scope to Js scope
|
|
arg_map = dict(zip(vars, used_vars))
|
|
arg_map.update({'this': 'this', 'arguments': 'arguments'})
|
|
if id: # make self available from inside...
|
|
if id['name'] not in arg_map:
|
|
arg_map[id['name']] = PyName
|
|
arg_conv = 'var = Scope({%s}, var)\n' % ', '.join(
|
|
repr(k) + ':' + v for k, v in six.iteritems(arg_map))
|
|
# and finally set the name of the function to its real name:
|
|
footer = '%s._set_name(%s)\n' % (PyName, repr(JsName))
|
|
whole_code = header + indent(arg_conv + code) + footer
|
|
# restore context
|
|
Context = previous_context
|
|
# define in upper context
|
|
inline_stack.define(PyName, whole_code)
|
|
return PyName
|
|
|
|
|
|
LogicalExpression = BinaryExpression
|
|
PostfixExpression = UpdateExpression
|
|
|
|
clean_stacks()
|
|
|
|
if __name__ == '__main__':
|
|
import codecs
|
|
import time
|
|
import pyjsparser
|
|
|
|
c = None #'''`ijfdij`'''
|
|
if not c:
|
|
with codecs.open("esp.js", "r", "utf-8") as f:
|
|
c = f.read()
|
|
|
|
print('Started')
|
|
t = time.time()
|
|
res = trans(pyjsparser.PyJsParser().parse(c))
|
|
dt = time.time() - t + 0.000000001
|
|
print('Translated everyting in', round(dt, 5), 'seconds.')
|
|
print('Thats %d characters per second' % int(len(c) / dt))
|
|
with open('res.py', 'w') as f:
|
|
f.write(res)
|