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