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 n1: #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='+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')