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