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