from __future__ import print_function from jsparser import * from utils import * import re from utils import * #Note all white space sent to this module must be ' ' so no '\n' REPL = {} #PROBLEMS # <<=, >>=, >>>= # they are unusual so I will not fix that now. a++ +b works fine and a+++++b (a++ + ++b) does not work even in V8 ASSIGNMENT_MATCH = '(?<!=|!|<|>)=(?!=)' def unary_validitator(keyword, before, after): if keyword[-1] in IDENTIFIER_PART: if not after or after[0] in IDENTIFIER_PART: return False if before and before[-1] in IDENTIFIER_PART: # I am not sure here... return False return True def comb_validitator(keyword, before, after): if keyword == 'instanceof' or keyword == 'in': if before and before[-1] in IDENTIFIER_PART: return False elif after and after[0] in IDENTIFIER_PART: return False return True def bracket_replace(code): new = '' for e in bracket_split(code, ['()', '[]'], False): if e[0] == '[': name = '#PYJSREPL' + str(len(REPL)) + '{' new += name REPL[name] = e elif e[0] == '(': # can be a function call name = '@PYJSREPL' + str(len(REPL)) + '}' new += name REPL[name] = e else: new += e return new class NodeVisitor: def __init__(self, code): self.code = code def rl(self, lis, op): """performs this operation on a list from *right to left* op must take 2 args a,b,c => op(a, op(b, c))""" it = reversed(lis) res = trans(it.next()) for e in it: e = trans(e) res = op(e, res) return res def lr(self, lis, op): """performs this operation on a list from *left to right* op must take 2 args a,b,c => op(op(a, b), c)""" it = iter(lis) res = trans(it.next()) for e in it: e = trans(e) res = op(res, e) return res def translate(self): """Translates outer operation and calls translate on inner operation. Returns fully translated code.""" if not self.code: return '' new = bracket_replace(self.code) #Check comma operator: cand = new.split(',') #every comma in new must be an operator if len(cand) > 1: #LR return self.lr(cand, js_comma) #Check = operator: # dont split at != or !== or == or === or <= or >= #note <<=, >>= or this >>> will NOT be supported # maybe I will change my mind later # Find this crappy ?: if '?' in new: cond_ind = new.find('?') tenary_start = 0 for ass in re.finditer(ASSIGNMENT_MATCH, new): cand = ass.span()[1] if cand < cond_ind: tenary_start = cand else: break actual_tenary = new[tenary_start:] spl = ''.join(split_at_any(new, [':', '?'], translate=trans)) tenary_translation = transform_crap(spl) assignment = new[:tenary_start] + ' PyJsConstantTENARY' return trans(assignment).replace('PyJsConstantTENARY', tenary_translation) cand = list(split_at_single(new, '=', ['!', '=', '<', '>'], ['='])) if len(cand) > 1: # RL it = reversed(cand) res = trans(it.next()) for e in it: e = e.strip() if not e: raise SyntaxError('Missing left-hand in assignment!') op = '' if e[-2:] in OP_METHODS: op = ',' + e[-2:].__repr__() e = e[:-2] elif e[-1:] in OP_METHODS: op = ',' + e[-1].__repr__() e = e[:-1] e = trans(e) #Now replace last get method with put and change args c = list(bracket_split(e, ['()'])) beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip( ) #strips just to make sure... I will remove it later if beg[-4:] != '.get': raise SyntaxError('Invalid left-hand side in assignment') beg = beg[0:-3] + 'put' arglist = arglist[0:-1] + ', ' + res + op + ')' res = beg + arglist return res #Now check remaining 2 arg operators that are not handled by python #They all have Left to Right (LR) associativity order = [OR, AND, BOR, BXOR, BAND, EQS, COMPS, BSHIFTS, ADDS, MULTS] # actually we dont need OR and AND because they can be handled easier. But just for fun dangerous = ['<', '>'] for typ in order: #we have to use special method for ADDS since they can be also unary operation +/++ or -/-- FUCK if '+' in typ: cand = list(split_add_ops(new)) else: #dont translate. cant start or end on dangerous op. cand = list( split_at_any( new, typ.keys(), False, dangerous, dangerous, validitate=comb_validitator)) if not len(cand) > 1: continue n = 1 res = trans(cand[0]) if not res: raise SyntaxError("Missing operand!") while n < len(cand): e = cand[n] if not e: raise SyntaxError("Missing operand!") if n % 2: op = typ[e] else: res = op(res, trans(e)) n += 1 return res #Now replace unary operators - only they are left cand = list( split_at_any( new, UNARY.keys(), False, validitate=unary_validitator)) if len(cand) > 1: #contains unary operators if '++' in cand or '--' in cand: #it cant contain both ++ and -- if '--' in cand: op = '--' meths = js_post_dec, js_pre_dec else: op = '++' meths = js_post_inc, js_pre_inc pos = cand.index(op) if cand[pos - 1].strip(): # post increment a = cand[pos - 1] meth = meths[0] elif cand[pos + 1].strip(): #pre increment a = cand[pos + 1] meth = meths[1] else: raise SyntaxError('Invalid use of ++ operator') if cand[pos + 2:]: raise SyntaxError('Too many operands') operand = meth(trans(a)) cand = cand[:pos - 1] # now last cand should be operand and every other odd element should be empty else: operand = trans(cand[-1]) del cand[-1] for i, e in enumerate(reversed(cand)): if i % 2: if e.strip(): raise SyntaxError('Too many operands') else: operand = UNARY[e](operand) return operand #Replace brackets if new[0] == '@' or new[0] == '#': if len( list(bracket_split(new, ('#{', '@}'))) ) == 1: # we have only one bracket, otherwise pseudobracket like @@.... assert new in REPL if new[0] == '#': raise SyntaxError( '[] cant be used as brackets! Use () instead.') return '(' + trans(REPL[new][1:-1]) + ')' #Replace function calls and prop getters # 'now' must be a reference like: a or b.c.d but it can have also calls or getters ( for example a["b"](3)) #From here @@ means a function call and ## means get operation (note they dont have to present) it = bracket_split(new, ('#{', '@}')) res = [] for e in it: if e[0] != '#' and e[0] != '@': res += [x.strip() for x in e.split('.')] else: res += [e.strip()] # res[0] can be inside @@ (name)... res = filter(lambda x: x, res) if is_internal(res[0]): out = res[0] elif res[0][0] in {'#', '@'}: out = '(' + trans(REPL[res[0]][1:-1]) + ')' elif is_valid_lval( res[0]) or res[0] in {'this', 'false', 'true', 'null'}: out = 'var.get(' + res[0].__repr__() + ')' else: if is_reserved(res[0]): raise SyntaxError('Unexpected reserved word: "%s"' % res[0]) raise SyntaxError('Invalid identifier: "%s"' % res[0]) if len(res) == 1: return out n = 1 while n < len(res): #now every func call is a prop call e = res[n] if e[0] == '@': # direct call out += trans_args(REPL[e]) n += 1 continue args = False #assume not prop call if n + 1 < len(res) and res[n + 1][0] == '@': #prop call args = trans_args(REPL[res[n + 1]])[1:] if args != ')': args = ',' + args if e[0] == '#': prop = trans(REPL[e][1:-1]) else: if not is_lval(e): raise SyntaxError('Invalid identifier: "%s"' % e) prop = e.__repr__() if args: # prop call n += 1 out += '.callprop(' + prop + args else: #prop get out += '.get(' + prop + ')' n += 1 return out def js_comma(a, b): return 'PyJsComma(' + a + ',' + b + ')' def js_or(a, b): return '(' + a + ' or ' + b + ')' def js_bor(a, b): return '(' + a + '|' + b + ')' def js_bxor(a, b): return '(' + a + '^' + b + ')' def js_band(a, b): return '(' + a + '&' + b + ')' def js_and(a, b): return '(' + a + ' and ' + b + ')' def js_strict_eq(a, b): return 'PyJsStrictEq(' + a + ',' + b + ')' def js_strict_neq(a, b): return 'PyJsStrictNeq(' + a + ',' + b + ')' #Not handled by python in the same way like JS. For example 2==2==True returns false. # In JS above would return true so we need brackets. def js_abstract_eq(a, b): return '(' + a + '==' + b + ')' #just like == def js_abstract_neq(a, b): return '(' + a + '!=' + b + ')' def js_lt(a, b): return '(' + a + '<' + b + ')' def js_le(a, b): return '(' + a + '<=' + b + ')' def js_ge(a, b): return '(' + a + '>=' + b + ')' def js_gt(a, b): return '(' + a + '>' + b + ')' def js_in(a, b): return b + '.contains(' + a + ')' def js_instanceof(a, b): return a + '.instanceof(' + b + ')' def js_lshift(a, b): return '(' + a + '<<' + b + ')' def js_rshift(a, b): return '(' + a + '>>' + b + ')' def js_shit(a, b): return 'PyJsBshift(' + a + ',' + b + ')' def js_add( a, b): # To simplify later process of converting unary operators + and ++ return '(%s+%s)' % (a, b) def js_sub(a, b): # To simplify return '(%s-%s)' % (a, b) def js_mul(a, b): return '(' + a + '*' + b + ')' def js_div(a, b): return '(' + a + '/' + b + ')' def js_mod(a, b): return '(' + a + '%' + b + ')' def js_typeof(a): cand = list(bracket_split(a, ('()', ))) if len(cand) == 2 and cand[0] == 'var.get': return cand[0] + cand[1][:-1] + ',throw=False).typeof()' return a + '.typeof()' def js_void(a): return '(' + a + ')' def js_new(a): cands = list(bracket_split(a, ('()', ))) lim = len(cands) if lim < 2: return a + '.create()' n = 0 while n < lim: c = cands[n] if c[0] == '(': if cands[n - 1].endswith( '.get') and n + 1 >= lim: # last get operation. return a + '.create()' elif cands[n - 1][0] == '(': return ''.join(cands[:n]) + '.create' + c + ''.join( cands[n + 1:]) elif cands[n - 1] == '.callprop': beg = ''.join(cands[:n - 1]) args = argsplit(c[1:-1], ',') prop = args[0] new_args = ','.join(args[1:]) create = '.get(%s).create(%s)' % (prop, new_args) return beg + create + ''.join(cands[n + 1:]) n += 1 return a + '.create()' def js_delete(a): #replace last get with delete. c = list(bracket_split(a, ['()'])) beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip( ) #strips just to make sure... I will remove it later if beg[-4:] != '.get': raise SyntaxError('Invalid delete operation') return beg[:-3] + 'delete' + arglist def js_neg(a): return '(-' + a + ')' def js_pos(a): return '(+' + a + ')' def js_inv(a): return '(~' + a + ')' def js_not(a): return a + '.neg()' def postfix(a, inc, post): bra = list(bracket_split(a, ('()', ))) meth = bra[-2] if not meth.endswith('get'): raise SyntaxError('Invalid ++ or -- operation.') bra[-2] = bra[-2][:-3] + 'put' bra[-1] = '(%s,%s%sJs(1))' % (bra[-1][1:-1], a, '+' if inc else '-') res = ''.join(bra) return res if not post else '(%s%sJs(1))' % (res, '-' if inc else '+') def js_pre_inc(a): return postfix(a, True, False) def js_post_inc(a): return postfix(a, True, True) def js_pre_dec(a): return postfix(a, False, False) def js_post_dec(a): return postfix(a, False, True) OR = {'||': js_or} AND = {'&&': js_and} BOR = {'|': js_bor} BXOR = {'^': js_bxor} BAND = {'&': js_band} EQS = { '===': js_strict_eq, '!==': js_strict_neq, '==': js_abstract_eq, # we need == and != too. Read a note above method '!=': js_abstract_neq } #Since JS does not have chained comparisons we need to implement all cmp methods. COMPS = { '<': js_lt, '<=': js_le, '>=': js_ge, '>': js_gt, 'instanceof': js_instanceof, #todo change to validitate 'in': js_in } BSHIFTS = {'<<': js_lshift, '>>': js_rshift, '>>>': js_shit} ADDS = {'+': js_add, '-': js_sub} MULTS = {'*': js_mul, '/': js_div, '%': js_mod} #Note they dont contain ++ and -- methods because they both have 2 different methods # correct method will be found automatically in translate function UNARY = { 'typeof': js_typeof, 'void': js_void, 'new': js_new, 'delete': js_delete, '!': js_not, '-': js_neg, '+': js_pos, '~': js_inv, '++': None, '--': None } def transform_crap(code): #needs some more tests """Transforms this ?: crap into if else python syntax""" ind = code.rfind('?') if ind == -1: return code sep = code.find(':', ind) if sep == -1: raise SyntaxError('Invalid ?: syntax (probably missing ":" )') beg = max(code.rfind(':', 0, ind), code.find('?', 0, ind)) + 1 end = code.find(':', sep + 1) end = len(code) if end == -1 else end formula = '(' + code[ind + 1:sep] + ' if ' + code[ beg:ind] + ' else ' + code[sep + 1:end] + ')' return transform_crap(code[:beg] + formula + code[end:]) from code import InteractiveConsole #e = InteractiveConsole(globals()).interact() import traceback def trans(code): return NodeVisitor(code.strip()).translate().strip() #todo finish this trans args def trans_args(code): new = bracket_replace(code.strip()[1:-1]) args = ','.join(trans(e) for e in new.split(',')) return '(%s)' % args EXP = 0 def exp_translator(code): global REPL, EXP EXP += 1 REPL = {} #print EXP, code code = code.replace('\n', ' ') assert '@' not in code assert ';' not in code assert '#' not in code #if not code.strip(): #? # return 'var.get("undefined")' try: return trans(code) except: #print '\n\ntrans failed on \n\n' + code #raw_input('\n\npress enter') raise if __name__ == '__main__': #print 'Here', trans('(eee ) . ii [ PyJsMarker ] [ jkj ] ( j , j ) . # jiji (h , ji , i)(non )( )()()()') for e in xrange(3): print(exp_translator('jk = kk.ik++')) #First line translated with PyJs: PyJsStrictEq(PyJsAdd((Js(100)*Js(50)),Js(30)), Js("5030")), yay! print(exp_translator('delete a.f'))