1from __future__ import unicode_literals
2from pyjsparser.pyjsparserdata import *
3from .friendly_nodes import *
4import random
5import six
6
7if six.PY3:
8    from functools import reduce
9    xrange = range
10    unicode = str
11# number of characters above which expression will be split to multiple lines in order to avoid python parser stack overflow
12# still experimental so I suggest to set it to 400 in order to avoid common errors
13# set it to smaller value only if you have problems with parser stack overflow
14LINE_LEN_LIMIT = 400  #  200  # or any other value - the larger the smaller probability of errors :)
15
16
17class ForController:
18    def __init__(self):
19        self.inside = [False]
20        self.update = ''
21
22    def enter_for(self, update):
23        self.inside.append(True)
24        self.update = update
25
26    def leave_for(self):
27        self.inside.pop()
28
29    def enter_other(self):
30        self.inside.append(False)
31
32    def leave_other(self):
33        self.inside.pop()
34
35    def is_inside(self):
36        return self.inside[-1]
37
38
39class InlineStack:
40    NAME = 'PyJs_%s_%d_'
41
42    def __init__(self):
43        self.reps = {}
44        self.names = []
45
46    def inject_inlines(self, source):
47        for lval in self.names:  # first in first out! Its important by the way
48            source = inject_before_lval(source, lval, self.reps[lval])
49        return source
50
51    def require(self, typ):
52        name = self.NAME % (typ, len(self.names))
53        self.names.append(name)
54        return name
55
56    def define(self, name, val):
57        self.reps[name] = val
58
59    def reset(self):
60        self.rel = {}
61        self.names = []
62
63
64class ContextStack:
65    def __init__(self):
66        self.to_register = set([])
67        self.to_define = {}
68
69    def reset(self):
70        self.to_register = set([])
71        self.to_define = {}
72
73    def register(self, var):
74        self.to_register.add(var)
75
76    def define(self, name, code):
77        self.to_define[name] = code
78        self.register(name)
79
80    def get_code(self):
81        code = 'var.registers([%s])\n' % ', '.join(
82            repr(e) for e in self.to_register)
83        for name, func_code in six.iteritems(self.to_define):
84            code += func_code
85        return code
86
87
88def clean_stacks():
89    global Context, inline_stack
90    Context = ContextStack()
91    inline_stack = InlineStack()
92
93
94def to_key(literal_or_identifier):
95    ''' returns string representation of this object'''
96    if literal_or_identifier['type'] == 'Identifier':
97        return literal_or_identifier['name']
98    elif literal_or_identifier['type'] == 'Literal':
99        k = literal_or_identifier['value']
100        if isinstance(k, float):
101            return unicode(float_repr(k))
102        elif 'regex' in literal_or_identifier:
103            return compose_regex(k)
104        elif isinstance(k, bool):
105            return 'true' if k else 'false'
106        elif k is None:
107            return 'null'
108        else:
109            return unicode(k)
110
111def is_iteration_statement(cand):
112    if not isinstance(cand, dict):
113        # Multiple statements.
114        return False
115    return cand.get("type", "?") in {"ForStatement", "ForInStatement", "WhileStatement", "DoWhileStatement"}
116
117
118
119def trans(ele, standard=False):
120    """Translates esprima syntax tree to python by delegating to appropriate translating node"""
121    try:
122        node = globals().get(ele['type'])
123        if not node:
124            raise NotImplementedError('%s is not supported!' % ele['type'])
125        if standard:
126            node = node.__dict__[
127                'standard'] if 'standard' in node.__dict__ else node
128        return node(**ele)
129    except:
130        #print ele
131        raise
132
133
134def limited(func):
135    '''Decorator limiting resulting line length in order to avoid python parser stack overflow -
136      If expression longer than LINE_LEN_LIMIT characters then it will be moved to upper line
137     USE ONLY ON EXPRESSIONS!!! '''
138
139    def f(standard=False, **args):
140        insert_pos = len(
141            inline_stack.names
142        )  # in case line is longer than limit we will have to insert the lval at current position
143        # this is because calling func will change inline_stack.
144        # we cant use inline_stack.require here because we dont know whether line overflows yet
145        res = func(**args)
146        if len(res) > LINE_LEN_LIMIT:
147            name = inline_stack.require('LONG')
148            inline_stack.names.pop()
149            inline_stack.names.insert(insert_pos, name)
150            res = 'def %s(var=var):\n    return %s\n' % (name, res)
151            inline_stack.define(name, res)
152            return name + '()'
153        else:
154            return res
155
156    f.__dict__['standard'] = func
157    return f
158
159
160# ==== IDENTIFIERS AND LITERALS  =======
161
162inf = float('inf')
163
164
165def Literal(type, value, raw, regex=None):
166    if regex:  # regex
167        return 'JsRegExp(%s)' % repr(compose_regex(value))
168    elif value is None:  # null
169        return 'var.get(u"null")'
170    # Todo template
171    # String, Bool, Float
172    return 'Js(%s)' % repr(value) if value != inf else 'Js(float("inf"))'
173
174
175def Identifier(type, name):
176    return 'var.get(%s)' % repr(name)
177
178
179@limited
180def MemberExpression(type, computed, object, property):
181    far_left = trans(object)
182    if computed:  # obj[prop] type accessor
183        # may be literal which is the same in every case so we can save some time on conversion
184        if property['type'] == 'Literal':
185            prop = repr(to_key(property))
186        else:  # worst case
187            prop = trans(property)
188    else:  # always the same since not computed (obj.prop accessor)
189        prop = repr(to_key(property))
190    return far_left + '.get(%s)' % prop
191
192
193def ThisExpression(type):
194    return 'var.get(u"this")'
195
196
197@limited
198def CallExpression(type, callee, arguments):
199    arguments = [trans(e) for e in arguments]
200    if callee['type'] == 'MemberExpression':
201        far_left = trans(callee['object'])
202        if callee['computed']:  # obj[prop] type accessor
203            # may be literal which is the same in every case so we can save some time on conversion
204            if callee['property']['type'] == 'Literal':
205                prop = repr(to_key(callee['property']))
206            else:  # worst case
207                prop = trans(
208                    callee['property'])  # its not a string literal! so no repr
209        else:  # always the same since not computed (obj.prop accessor)
210            prop = repr(to_key(callee['property']))
211        arguments.insert(0, prop)
212        return far_left + '.callprop(%s)' % ', '.join(arguments)
213    else:  # standard call
214        return trans(callee) + '(%s)' % ', '.join(arguments)
215
216
217# ========== ARRAYS ============
218
219
220def ArrayExpression(type, elements):  # todo fix null inside problem
221    return 'Js([%s])' % ', '.join(trans(e) if e else 'None' for e in elements)
222
223
224# ========== OBJECTS =============
225
226
227def ObjectExpression(type, properties):
228    name = None
229    elems = []
230    after = ''
231    for p in properties:
232        if p['kind'] == 'init':
233            elems.append('%s:%s' % Property(**p))
234        else:
235            if name is None:
236                name = inline_stack.require('Object')
237            if p['kind'] == 'set':
238                k, setter = Property(
239                    **p
240                )  # setter is just a lval referring to that function, it will be defined in InlineStack automatically
241                after += '%s.define_own_property(%s, {"set":%s, "configurable":True, "enumerable":True})\n' % (
242                    name, k, setter)
243            elif p['kind'] == 'get':
244                k, getter = Property(**p)
245                after += '%s.define_own_property(%s, {"get":%s, "configurable":True, "enumerable":True})\n' % (
246                    name, k, getter)
247            else:
248                raise RuntimeError('Unexpected object propery kind')
249    definition = 'Js({%s})' % ','.join(elems)
250    if name is None:
251        return definition
252    body = '%s = %s\n' % (name, definition)
253    body += after
254    body += 'return %s\n' % name
255    code = 'def %s():\n%s' % (name, indent(body))
256    inline_stack.define(name, code)
257    return name + '()'
258
259
260def Property(type, kind, key, computed, value, method, shorthand):
261    if shorthand or computed:
262        raise NotImplementedError(
263            'Shorthand and Computed properties not implemented!')
264    k = to_key(key)
265    if k is None:
266        raise SyntaxError('Invalid key in dictionary! Or bug in Js2Py')
267    v = trans(value)
268    return repr(k), v
269
270
271# ========== EXPRESSIONS ============
272
273
274@limited
275def UnaryExpression(type, operator, argument, prefix):
276    a = trans(
277        argument, standard=True
278    )  # unary involve some complex operations so we cant use line shorteners here
279    if operator == 'delete':
280        if argument['type'] in ('Identifier', 'MemberExpression'):
281            # means that operation is valid
282            return js_delete(a)
283        return 'PyJsComma(%s, Js(True))' % a  # otherwise not valid, just perform expression and return true.
284    elif operator == 'typeof':
285        return js_typeof(a)
286    return UNARY[operator](a)
287
288
289@limited
290def BinaryExpression(type, operator, left, right):
291    a = trans(left)
292    b = trans(right)
293    # delegate to our friends
294    return BINARY[operator](a, b)
295
296
297@limited
298def UpdateExpression(type, operator, argument, prefix):
299    a = trans(
300        argument, standard=True
301    )  # also complex operation involving parsing of the result so no line length reducing here
302    return js_postfix(a, operator == '++', not prefix)
303
304
305@limited
306def AssignmentExpression(type, operator, left, right):
307    operator = operator[:-1]
308    if left['type'] == 'Identifier':
309        if operator:
310            return 'var.put(%s, %s, %s)' % (repr(to_key(left)), trans(right),
311                                            repr(operator))
312        else:
313            return 'var.put(%s, %s)' % (repr(to_key(left)), trans(right))
314    elif left['type'] == 'MemberExpression':
315        far_left = trans(left['object'])
316        if left['computed']:  # obj[prop] type accessor
317            # may be literal which is the same in every case so we can save some time on conversion
318            if left['property']['type'] == 'Literal':
319                prop = repr(to_key(left['property']))
320            else:  # worst case
321                prop = trans(
322                    left['property'])  # its not a string literal! so no repr
323        else:  # always the same since not computed (obj.prop accessor)
324            prop = repr(to_key(left['property']))
325        if operator:
326            return far_left + '.put(%s, %s, %s)' % (prop, trans(right),
327                                                    repr(operator))
328        else:
329            return far_left + '.put(%s, %s)' % (prop, trans(right))
330    else:
331        raise SyntaxError('Invalid left hand side in assignment!')
332
333
334six
335
336
337@limited
338def SequenceExpression(type, expressions):
339    return reduce(js_comma, (trans(e) for e in expressions))
340
341
342@limited
343def NewExpression(type, callee, arguments):
344    return trans(callee) + '.create(%s)' % ', '.join(
345        trans(e) for e in arguments)
346
347
348@limited
349def ConditionalExpression(
350        type, test, consequent,
351        alternate):  # caused plenty of problems in my home-made translator :)
352    return '(%s if %s else %s)' % (trans(consequent), trans(test),
353                                   trans(alternate))
354
355
356# ===========  STATEMENTS =============
357
358
359def BlockStatement(type, body):
360    return StatementList(
361        body)  # never returns empty string! In the worst case returns pass\n
362
363
364def ExpressionStatement(type, expression):
365    return trans(expression) + '\n'  # end expression space with new line
366
367
368def BreakStatement(type, label):
369    if label:
370        return 'raise %s("Breaked")\n' % (get_break_label(label['name']))
371    else:
372        return 'break\n'
373
374
375def ContinueStatement(type, label):
376    if label:
377        return 'raise %s("Continued")\n' % (get_continue_label(label['name']))
378    else:
379        return 'continue\n'
380
381
382def ReturnStatement(type, argument):
383    return 'return %s\n' % (trans(argument)
384                            if argument else "var.get('undefined')")
385
386
387def EmptyStatement(type):
388    return 'pass\n'
389
390
391def DebuggerStatement(type):
392    return 'pass\n'
393
394
395def DoWhileStatement(type, body, test):
396    inside = trans(body) + 'if not %s:\n' % trans(test) + indent('break\n')
397    result = 'while 1:\n' + indent(inside)
398    return result
399
400
401def ForStatement(type, init, test, update, body):
402    update = indent(trans(update)) if update else ''
403    init = trans(init) if init else ''
404    if not init.endswith('\n'):
405        init += '\n'
406    test = trans(test) if test else '1'
407    if not update:
408        result = '#for JS loop\n%swhile %s:\n%s%s\n' % (
409            init, test, indent(trans(body)), update)
410    else:
411        result = '#for JS loop\n%swhile %s:\n' % (init, test)
412        body = 'try:\n%sfinally:\n    %s\n' % (indent(trans(body)), update)
413        result += indent(body)
414    return result
415
416
417def ForInStatement(type, left, right, body, each):
418    res = 'for PyJsTemp in %s:\n' % trans(right)
419    if left['type'] == "VariableDeclaration":
420        addon = trans(left)  # make sure variable is registered
421        if addon != 'pass\n':
422            res = addon + res  # we have to execute this expression :(
423        # now extract the name
424        try:
425            name = left['declarations'][0]['id']['name']
426        except:
427            raise RuntimeError('Unusual ForIn loop')
428    elif left['type'] == 'Identifier':
429        name = left['name']
430    else:
431        raise RuntimeError('Unusual ForIn loop')
432    res += indent('var.put(%s, PyJsTemp)\n' % repr(name) + trans(body))
433    return res
434
435
436def IfStatement(type, test, consequent, alternate):
437    # NOTE we cannot do elif because function definition inside elif statement would not be possible!
438    IF = 'if %s:\n' % trans(test)
439    IF += indent(trans(consequent))
440    if not alternate:
441        return IF
442    ELSE = 'else:\n' + indent(trans(alternate))
443    return IF + ELSE
444
445
446def LabeledStatement(type, label, body):
447    # todo consider using smarter approach!
448    inside = trans(body)
449    defs = ''
450    if is_iteration_statement(body) and (inside.startswith('while ') or inside.startswith(
451            'for ') or inside.startswith('#for')):
452        # we have to add contine label as well...
453        # 3 or 1 since #for loop type has more lines before real for.
454        sep = 1 if not inside.startswith('#for') else 3
455        cont_label = get_continue_label(label['name'])
456        temp = inside.split('\n')
457        injected = 'try:\n' + '\n'.join(temp[sep:])
458        injected += 'except %s:\n    pass\n' % cont_label
459        inside = '\n'.join(temp[:sep]) + '\n' + indent(injected)
460        defs += 'class %s(Exception): pass\n' % cont_label
461    break_label = get_break_label(label['name'])
462    inside = 'try:\n%sexcept %s:\n    pass\n' % (indent(inside), break_label)
463    defs += 'class %s(Exception): pass\n' % break_label
464    return defs + inside
465
466
467def StatementList(lis):
468    if lis:  # ensure we don't return empty string because it may ruin indentation!
469        code = ''.join(trans(e) for e in lis)
470        return code if code else 'pass\n'
471    else:
472        return 'pass\n'
473
474
475def PyimportStatement(type, imp):
476    lib = imp['name']
477    jlib = 'PyImport_%s' % lib
478    code = 'import %s as %s\n' % (lib, jlib)
479    #check whether valid lib name...
480    try:
481        compile(code, '', 'exec')
482    except:
483        raise SyntaxError(
484            'Invalid Python module name (%s) in pyimport statement' % lib)
485    # var.pyimport will handle module conversion to PyJs object
486    code += 'var.pyimport(%s, %s)\n' % (repr(lib), jlib)
487    return code
488
489
490def SwitchStatement(type, discriminant, cases):
491    #TODO there will be a problem with continue in a switch statement.... FIX IT
492    code = 'while 1:\n' + indent('SWITCHED = False\nCONDITION = (%s)\n')
493    code = code % trans(discriminant)
494    for case in cases:
495        case_code = None
496        if case['test']:  # case (x):
497            case_code = 'if SWITCHED or PyJsStrictEq(CONDITION, %s):\n' % (
498                trans(case['test']))
499        else:  # default:
500            case_code = 'if True:\n'
501        case_code += indent('SWITCHED = True\n')
502        case_code += indent(StatementList(case['consequent']))
503        # one more indent for whole
504        code += indent(case_code)
505    # prevent infinite loop and sort out nested switch...
506    code += indent('SWITCHED = True\nbreak\n')
507    return code
508
509
510def ThrowStatement(type, argument):
511    return 'PyJsTempException = JsToPyException(%s)\nraise PyJsTempException\n' % trans(
512        argument)
513
514
515def TryStatement(type, block, handler, handlers, guardedHandlers, finalizer):
516    result = 'try:\n%s' % indent(trans(block))
517    # complicated catch statement...
518    if handler:
519        identifier = handler['param']['name']
520        holder = 'PyJsHolder_%s_%d' % (to_hex(identifier),
521                                       random.randrange(1e8))
522        identifier = repr(identifier)
523        result += 'except PyJsException as PyJsTempException:\n'
524        # fill in except ( catch ) block and remember to recover holder variable to its previous state
525        result += indent(
526            TRY_CATCH.replace('HOLDER',
527                              holder).replace('NAME', identifier).replace(
528                                  'BLOCK', indent(trans(handler['body']))))
529    # translate finally statement if present
530    if finalizer:
531        result += 'finally:\n%s' % indent(trans(finalizer))
532    return result
533
534
535def LexicalDeclaration(type, declarations, kind):
536    raise NotImplementedError(
537        'let and const not implemented yet but they will be soon! Check github for updates.'
538    )
539
540
541def VariableDeclarator(type, id, init):
542    name = id['name']
543    # register the name if not already registered
544    Context.register(name)
545    if init:
546        return 'var.put(%s, %s)\n' % (repr(name), trans(init))
547    return ''
548
549
550def VariableDeclaration(type, declarations, kind):
551    code = ''.join(trans(d) for d in declarations)
552    return code if code else 'pass\n'
553
554
555def WhileStatement(type, test, body):
556    result = 'while %s:\n' % trans(test) + indent(trans(body))
557    return result
558
559
560def WithStatement(type, object, body):
561    raise NotImplementedError('With statement not implemented!')
562
563
564def Program(type, body):
565    inline_stack.reset()
566    code = ''.join(trans(e) for e in body)
567    # here add hoisted elements (register variables and define functions)
568    code = Context.get_code() + code
569    # replace all inline variables
570    code = inline_stack.inject_inlines(code)
571    return code
572
573
574# ======== FUNCTIONS ============
575
576
577def FunctionDeclaration(type, id, params, defaults, body, generator,
578                        expression):
579    if generator:
580        raise NotImplementedError('Generators not supported')
581    if defaults:
582        raise NotImplementedError('Defaults not supported')
583    if not id:
584        return FunctionExpression(type, id, params, defaults, body, generator,
585                                  expression) + '\n'
586    JsName = id['name']
587    PyName = 'PyJsHoisted_%s_' % JsName
588    PyName = PyName if is_valid_py_name(PyName) else 'PyJsHoistedNonPyName'
589    # this is quite complicated
590    global Context
591    previous_context = Context
592    # change context to the context of this function
593    Context = ContextStack()
594    # translate body within current context
595    code = trans(body)
596    # get arg names
597    vars = [v['name'] for v in params]
598    # args are automaticaly registered variables
599    Context.to_register.update(vars)
600    # add all hoisted elements inside function
601    code = Context.get_code() + code
602    # check whether args are valid python names:
603    used_vars = []
604    for v in vars:
605        if is_valid_py_name(v):
606            used_vars.append(v)
607        else:  # invalid arg in python, for example $, replace with alternatice arg
608            used_vars.append('PyJsArg_%s_' % to_hex(v))
609    header = '@Js\n'
610    header += 'def %s(%sthis, arguments, var=var):\n' % (
611        PyName, ', '.join(used_vars) + (', ' if vars else ''))
612    # transfer names from Py scope to Js scope
613    arg_map = dict(zip(vars, used_vars))
614    arg_map.update({'this': 'this', 'arguments': 'arguments'})
615    arg_conv = 'var = Scope({%s}, var)\n' % ', '.join(
616        repr(k) + ':' + v for k, v in six.iteritems(arg_map))
617    # and finally set the name of the function to its real name:
618    footer = '%s.func_name = %s\n' % (PyName, repr(JsName))
619    footer += 'var.put(%s, %s)\n' % (repr(JsName), PyName)
620    whole_code = header + indent(arg_conv + code) + footer
621    # restore context
622    Context = previous_context
623    # define in upper context
624    Context.define(JsName, whole_code)
625    return 'pass\n'
626
627
628def FunctionExpression(type, id, params, defaults, body, generator,
629                       expression):
630    if generator:
631        raise NotImplementedError('Generators not supported')
632    if defaults:
633        raise NotImplementedError('Defaults not supported')
634    JsName = id['name'] if id else 'anonymous'
635    if not is_valid_py_name(JsName):
636        ScriptName = 'InlineNonPyName'
637    else:
638        ScriptName = JsName
639    PyName = inline_stack.require(ScriptName)  # this is unique
640
641    # again quite complicated
642    global Context
643    previous_context = Context
644    # change context to the context of this function
645    Context = ContextStack()
646    # translate body within current context
647    code = trans(body)
648    # get arg names
649    vars = [v['name'] for v in params]
650    # args are automaticaly registered variables
651    Context.to_register.update(vars)
652    # add all hoisted elements inside function
653    code = Context.get_code() + code
654    # check whether args are valid python names:
655    used_vars = []
656    for v in vars:
657        if is_valid_py_name(v):
658            used_vars.append(v)
659        else:  # invalid arg in python, for example $, replace with alternatice arg
660            used_vars.append('PyJsArg_%s_' % to_hex(v))
661    header = '@Js\n'
662    header += 'def %s(%sthis, arguments, var=var):\n' % (
663        PyName, ', '.join(used_vars) + (', ' if vars else ''))
664    # transfer names from Py scope to Js scope
665    arg_map = dict(zip(vars, used_vars))
666    arg_map.update({'this': 'this', 'arguments': 'arguments'})
667    if id:  # make self available from inside...
668        if id['name'] not in arg_map:
669            arg_map[id['name']] = PyName
670    arg_conv = 'var = Scope({%s}, var)\n' % ', '.join(
671        repr(k) + ':' + v for k, v in six.iteritems(arg_map))
672    # and finally set the name of the function to its real name:
673    footer = '%s._set_name(%s)\n' % (PyName, repr(JsName))
674    whole_code = header + indent(arg_conv + code) + footer
675    # restore context
676    Context = previous_context
677    # define in upper context
678    inline_stack.define(PyName, whole_code)
679    return PyName
680
681
682LogicalExpression = BinaryExpression
683PostfixExpression = UpdateExpression
684
685clean_stacks()
686
687if __name__ == '__main__':
688    import codecs
689    import time
690    import pyjsparser
691
692    c = None  #'''`ijfdij`'''
693    if not c:
694        with codecs.open("esp.js", "r", "utf-8") as f:
695            c = f.read()
696
697    print('Started')
698    t = time.time()
699    res = trans(pyjsparser.PyJsParser().parse(c))
700    dt = time.time() - t + 0.000000001
701    print('Translated everyting in', round(dt, 5), 'seconds.')
702    print('Thats %d characters per second' % int(len(c) / dt))
703    with open('res.py', 'w') as f:
704        f.write(res)
705