'''Most important file in Js2Py implementation: PyJs class - father of all PyJs objects''' from copy import copy import re #from translators import translator from .translators.friendly_nodes import REGEXP_CONVERTER from .utils.injector import fix_js_args from types import FunctionType, ModuleType, GeneratorType, BuiltinFunctionType, MethodType, BuiltinMethodType import traceback # python 3 support import six if six.PY3: basestring = str long = int xrange = range unicode = str def str_repr(s): if six.PY2: return repr(s.encode('utf-8')) else: return repr(s) def MakeError(name, message): """Returns PyJsException with PyJsError inside""" return JsToPyException(ERRORS[name](Js(message))) def to_python(val): if not isinstance(val, PyJs): return val if isinstance(val, PyJsUndefined) or isinstance(val, PyJsNull): return None elif isinstance(val, PyJsNumber): # this can be either float or long/int better to assume its int/long when a whole number... v = val.value try: i = int(v) if v==v else v # nan... return v if i!=v else i except: return v elif isinstance(val, (PyJsString, PyJsBoolean)): return val.value elif isinstance(val, PyObjectWrapper): return val.__dict__['obj'] return JsObjectWrapper(val) def to_dict(js_obj, known=None): # fixed recursion error in self referencing objects res = {} if known is None: known = {} if js_obj in known: return known[js_obj] known[js_obj] = res for k in js_obj: name = k.value input = js_obj.get(name) output = to_python(input) if isinstance(output, JsObjectWrapper): if output._obj.Class=='Object': output = to_dict(output._obj, known) known[input] = output elif output._obj.Class=='Array': output = to_list(output._obj) known[input] = output res[name] = output return res def to_list(js_obj, known=None): res = len(js_obj)*[None] if known is None: known = {} if js_obj in known: return known[js_obj] known[js_obj] = res for k in js_obj: try: name = int(k.value) except: continue input = js_obj.get(str(name)) output = to_python(input) if isinstance(output, JsObjectWrapper): if output._obj.Class in ['Array', 'Arguments']: output = to_list(output._obj, known) known[input] = output elif output._obj.Class in ['Object']: output = to_dict(output._obj) known[input] = output res[name] = output return res def HJs(val): if hasattr(val, '__call__'): # @Js def PyWrapper(this, arguments, var=None): args = tuple(to_python(e) for e in arguments.to_list()) try: py_res = val.__call__(*args) except Exception as e: message = 'your Python function failed! ' try: message += e.message except: pass raise MakeError('Error', message) return py_wrap(py_res) try: PyWrapper.func_name = val.__name__ except: pass return PyWrapper if isinstance(val, tuple): val = list(val) return Js(val) def Js(val): '''Converts Py type to PyJs type''' if isinstance(val, PyJs): return val elif val is None: return undefined elif isinstance(val, basestring): return PyJsString(val, StringPrototype) elif isinstance(val, bool): return true if val else false elif isinstance(val, float) or isinstance(val, int) or isinstance(val, long): # This is supposed to speed things up. may not be the case if val in NUM_BANK: return NUM_BANK[val] return PyJsNumber(float(val), NumberPrototype) elif isinstance(val, FunctionType): return PyJsFunction(val, FunctionPrototype) #elif isinstance(val, ModuleType): # mod = {} # for name in dir(val): # value = getattr(val, name) # if isinstance(value, ModuleType): # continue # prevent recursive module conversion # try: # jsval = HJs(value) # except RuntimeError: # print 'Could not convert %s to PyJs object!' % name # continue # mod[name] = jsval # return Js(mod) #elif isintance(val, ClassType): elif isinstance(val, dict): # convert to object temp = PyJsObject({}, ObjectPrototype) for k, v in six.iteritems(val): temp.put(Js(k), Js(v)) return temp elif isinstance(val, (list, tuple)): #Convert to array return PyJsArray(val, ArrayPrototype) elif isinstance(val, JsObjectWrapper): return val.__dict__['_obj'] else: # try to convert to js object return py_wrap(val) #raise RuntimeError('Cant convert python type to js (%s)' % repr(val)) #try: # obj = {} # for name in dir(val): # if name.startswith('_'): #dont wrap attrs that start with _ # continue # value = getattr(val, name) # import types # if not isinstance(value, (FunctionType, BuiltinFunctionType, MethodType, BuiltinMethodType, # dict, int, basestring, bool, float, long, list, tuple)): # continue # obj[name] = HJs(value) # return Js(obj) #except: # raise RuntimeError('Cant convert python type to js (%s)' % repr(val)) def Type(val): try: return val.TYPE except: raise RuntimeError('Invalid type: '+str(val)) def is_data_descriptor(desc): return desc and ('value' in desc or 'writable' in desc) def is_accessor_descriptor(desc): return desc and ('get' in desc or 'set' in desc) def is_generic_descriptor(desc): return desc and not (is_data_descriptor(desc) or is_accessor_descriptor(desc)) ############################################################################## class PyJs(object): PRIMITIVES = {'String', 'Number', 'Boolean', 'Undefined', 'Null'} TYPE = 'Object' Class = None extensible = True prototype = None own = {} GlobalObject = None IS_CHILD_SCOPE = False value = None def __init__(self, value=None, prototype=None, extensible=False): '''Constructor for Number String and Boolean''' # I dont think this is needed anymore # if self.Class=='String' and not isinstance(value, basestring): # raise TypeError # if self.Class=='Number': # if not isinstance(value, float): # if not (isinstance(value, int) or isinstance(value, long)): # raise TypeError # value = float(value) # if self.Class=='Boolean' and not isinstance(value, bool): # raise TypeError self.value = value self.extensible = extensible self.prototype = prototype self.own = {} def is_undefined(self): return self.Class=='Undefined' def is_null(self): return self.Class=='Null' def is_primitive(self): return self.TYPE in self.PRIMITIVES def is_object(self): return not self.is_primitive() def _type(self): return Type(self) def is_callable(self): return hasattr(self, 'call') def get_own_property(self, prop): return self.own.get(prop) def get_property(self, prop): cand = self.get_own_property(prop) if cand: return cand if self.prototype is not None: return self.prototype.get_property(prop) def get(self, prop): #external use! #prop = prop.value if self.Class=='Undefined' or self.Class=='Null': raise MakeError('TypeError', 'Undefiend and null dont have properties!') if not isinstance(prop, basestring): prop = prop.to_string().value if not isinstance(prop, basestring): raise RuntimeError('Bug') cand = self.get_property(prop) if cand is None: return Js(None) if is_data_descriptor(cand): return cand['value'] if cand['get'].is_undefined(): return cand['get'] return cand['get'].call(self) def can_put(self, prop): #to check desc = self.get_own_property(prop) if desc: #if we have this property if is_accessor_descriptor(desc): return desc['set'].is_callable() # Check if setter method is defined else: #data desc return desc['writable'] if self.prototype is not None: return self.extensible inherited = self.get_property(prop) if inherited is None: return self.extensible if is_accessor_descriptor(inherited): return not inherited['set'].is_undefined() elif self.extensible: return inherited['writable'] return False def put(self, prop, val, op=None): #external use! '''Just like in js: self.prop op= val for example when op is '+' it will be self.prop+=val op can be either None for simple assignment or one of: * / % + - << >> & ^ |''' if self.Class=='Undefined' or self.Class=='Null': raise MakeError('TypeError', 'Undefiend and null dont have properties!') if not isinstance(prop, basestring): prop = prop.to_string().value #we need to set the value to the incremented one if op is not None: val = getattr(self.get(prop), OP_METHODS[op])(val) if not self.can_put(prop): return val own_desc = self.get_own_property(prop) if is_data_descriptor(own_desc): if self.Class=='Array': #only array has different define_own_prop self.define_own_property(prop, {'value':val}) else: self.own[prop]['value'] = val return val desc = self.get_property(prop) if is_accessor_descriptor(desc): desc['set'].call(self, (val,)) else: new = {'value' : val, 'writable' : True, 'configurable' : True, 'enumerable' : True} if self.Class=='Array': self.define_own_property(prop, new) else: self.own[prop] = new return val def has_property(self, prop): return self.get_property(prop) is not None def delete(self, prop): if not isinstance(prop, basestring): prop = prop.to_string().value desc = self.get_own_property(prop) if desc is None: return Js(True) if desc['configurable']: del self.own[prop] return Js(True) return Js(False) def default_value(self, hint=None): # made a mistake at the very early stage and made it to prefer string... caused lots! of problems order = ('valueOf', 'toString') if hint=='String' or (hint is None and self.Class=='Date'): order = ('toString', 'valueOf') for meth_name in order: method = self.get(meth_name) if method is not None and method.is_callable(): cand = method.call(self) if cand.is_primitive(): return cand raise MakeError('TypeError', 'Cannot convert object to primitive value') def define_own_property(self, prop, desc): #Internal use only. External through Object # prop must be a Py string. Desc is either a descriptor or accessor. #Messy method - raw translation from Ecma spec to prevent any bugs. # todo check this current = self.get_own_property(prop) extensible = self.extensible if not current: #We are creating a new property if not extensible: return False if is_data_descriptor(desc) or is_generic_descriptor(desc): DEFAULT_DATA_DESC = {'value': undefined, #undefined 'writable': False, 'enumerable': False, 'configurable': False} DEFAULT_DATA_DESC.update(desc) self.own[prop] = DEFAULT_DATA_DESC else: DEFAULT_ACCESSOR_DESC = {'get': undefined, #undefined 'set': undefined, #undefined 'enumerable': False, 'configurable': False} DEFAULT_ACCESSOR_DESC.update(desc) self.own[prop] = DEFAULT_ACCESSOR_DESC return True if not desc or desc==current: #We dont need to change anything. return True configurable = current['configurable'] if not configurable: #Prevent changing configurable or enumerable if desc.get('configurable'): return False if 'enumerable' in desc and desc['enumerable']!=current['enumerable']: return False if is_generic_descriptor(desc): pass elif is_data_descriptor(current)!=is_data_descriptor(desc): if not configurable: return False if is_data_descriptor(current): del current['value'] del current['writable'] current['set'] = undefined #undefined current['get'] = undefined #undefined else: del current['set'] del current['get'] current['value'] = undefined #undefined current['writable'] = False elif is_data_descriptor(current) and is_data_descriptor(desc): if not configurable: if not current['writable'] and desc.get('writable'): return False if not current['writable'] and 'value' in desc and current['value']!=desc['value']: return False elif is_accessor_descriptor(current) and is_accessor_descriptor(desc): if not configurable: if 'set' in desc and desc['set'] is not current['set']: return False if 'get' in desc and desc['get'] is not current['get']: return False current.update(desc) return True #these methods will work only for Number class def is_infinity(self): assert self.Class=='Number' return self.value==float('inf') or self.value==-float('inf') def is_nan(self): assert self.Class=='Number' return self.value!=self.value #nan!=nan evaluates to true def is_finite(self): return not (self.is_nan() or self.is_infinity()) #Type Conversions. to_type. All must return pyjs subclass instance def to_primitive(self, hint=None): if self.is_primitive(): return self if hint is None and (self.Class=='Number' or self.Class=='Boolean'): # favour number for Class== Number or Boolean default = String hint = 'Number' return self.default_value(hint) def to_boolean(self): typ = Type(self) if typ=='Boolean': #no need to convert return self elif typ=='Null' or typ=='Undefined': #they are both always false return false elif typ=='Number' or typ=='String': #false only for 0, '' and NaN return Js(bool(self.value and self.value==self.value)) # test for nan (nan -> flase) else: #object - always true return true def to_number(self): typ = Type(self) if typ=='Null': #null is 0 return Js(0) elif typ=='Undefined': # undefined is NaN return NaN elif typ=='Boolean': # 1 for true 0 for false return Js(int(self.value)) elif typ=='Number':# or self.Class=='Number': # no need to convert return self elif typ=='String': s = self.value.strip() #Strip white space if not s: # '' is simply 0 return Js(0) if 'x' in s or 'X' in s[:3]: #hex (positive only) try: # try to convert num = int(s, 16) except ValueError: # could not convert > NaN return NaN return Js(num) sign = 1 #get sign if s[0] in '+-': if s[0]=='-': sign = -1 s = s[1:] if s=='Infinity': #Check for infinity keyword. 'NaN' will be NaN anyway. return Js(sign*float('inf')) try: #decimal try num = sign*float(s) # Converted except ValueError: return NaN # could not convert to decimal > return NaN return Js(num) else: #object - most likely it will be NaN. return self.to_primitive('Number').to_number() def to_string(self): typ = Type(self) if typ=='Null': return Js('null') elif typ=='Undefined': return Js('undefined') elif typ=='Boolean': return Js('true') if self.value else Js('false') elif typ=='Number': #or self.Class=='Number': if self.is_nan(): return Js('NaN') elif self.is_infinity(): sign = '-' if self.value<0 else '' return Js(sign+'Infinity') elif isinstance(self.value, long) or self.value.is_integer(): # dont print .0 return Js(unicode(int(self.value))) return Js(unicode(self.value)) # accurate enough elif typ=='String': return self else: #object return self.to_primitive('String').to_string() def to_object(self): typ = self.TYPE if typ=='Null' or typ=='Undefined': raise MakeError('TypeError', 'undefined or null can\'t be converted to object') elif typ=='Boolean': # Unsure here... todo repair here return Boolean.create(self) elif typ=='Number': #? return Number.create(self) elif typ=='String': #? return String.create(self) else: #object return self def to_int32(self): num = self.to_number() if num.is_nan() or num.is_infinity(): return 0 int32 = int(num.value) % 2**32 return int(int32 - 2**32 if int32 >= 2**31 else int32) def strict_equality_comparison(self, other): return PyJsStrictEq(self, other) def cok(self): """Check object coercible""" if self.Class in {'Undefined', 'Null'}: raise MakeError('TypeError', 'undefined or null can\'t be converted to object') def to_int(self): num = self.to_number() if num.is_nan(): return 0 elif num.is_infinity(): return 10**20 if num.value>0 else -10**20 return int(num.value) def to_uint32(self): num = self.to_number() if num.is_nan() or num.is_infinity(): return 0 return int(num.value) % 2**32 def to_uint16(self): num = self.to_number() if num.is_nan() or num.is_infinity(): return 0 return int(num.value) % 2**16 def to_int16(self): num = self.to_number() if num.is_nan() or num.is_infinity(): return 0 int16 = int(num.value) % 2**16 return int(int16 - 2**16 if int16 >= 2**15 else int16) def same_as(self, other): typ = Type(self) if typ!=other.Class: return False if typ=='Undefined' or typ=='Null': return True if typ=='Boolean' or typ=='Number' or typ=='String': return self.value==other.value else: #object return self is other #Id compare. #Not to be used by translation (only internal use) def __getitem__(self, item): return self.get(str(item) if not isinstance(item, PyJs) else item.to_string().value) def __setitem__(self, item, value): self.put(str(item) if not isinstance(item, PyJs) else item.to_string().value, Js(value)) def __len__(self): try: return self.get('length').to_uint32() except: raise TypeError('This object (%s) does not have length property'%self.Class) #Oprators------------- #Unary, other will be implemented as functions. Increments and decrements # will be methods of Number class def __neg__(self): #-u return Js(-self.to_number().value) def __pos__(self): #+u return self.to_number() def __invert__(self): #~u return Js(Js(~self.to_int32()).to_int32()) def neg(self): # !u cant do 'not u' :( return Js(not self.to_boolean().value) def __nonzero__(self): return self.to_boolean().value def __bool__(self): return self.to_boolean().value def typeof(self): if self.is_callable(): return Js('function') typ = Type(self).lower() if typ=='null': typ = 'object' return Js(typ) #Bitwise operators # <<, >>, &, ^, | # << def __lshift__(self, other): lnum = self.to_int32() rnum = other.to_uint32() shiftCount = rnum & 0x1F return Js(Js(lnum << shiftCount).to_int32()) # >> def __rshift__(self, other): lnum = self.to_int32() rnum = other.to_uint32() shiftCount = rnum & 0x1F return Js(Js(lnum >> shiftCount).to_int32()) # >>> def pyjs_bshift(self, other): lnum = self.to_uint32() rnum = other.to_uint32() shiftCount = rnum & 0x1F return Js(Js(lnum >> shiftCount).to_uint32()) # & def __and__(self, other): lnum = self.to_int32() rnum = other.to_int32() return Js(Js(lnum & rnum).to_int32()) # ^ def __xor__(self, other): lnum = self.to_int32() rnum = other.to_int32() return Js(Js(lnum ^ rnum).to_int32()) # | def __or__(self, other): lnum = self.to_int32() rnum = other.to_int32() return Js(Js(lnum | rnum).to_int32()) # Additive operators # + and - are implemented here # + def __add__(self, other): a = self.to_primitive() b = other.to_primitive() if a.TYPE=='String' or b.TYPE=='String': return Js(a.to_string().value+b.to_string().value) a = a.to_number() b = b.to_number() return Js(a.value+b.value) # - def __sub__(self, other): return Js(self.to_number().value-other.to_number().value) #Multiplicative operators # *, / and % are implemented here # * def __mul__(self, other): return Js(self.to_number().value*other.to_number().value) # / def __div__(self, other): a = self.to_number().value b = other.to_number().value if b: return Js(a/b) if not a or a!=a: return NaN return Infinity if a>0 else -Infinity # % def __mod__(self, other): a = self.to_number().value b = other.to_number().value if abs(a)==float('inf') or not b: return NaN if abs(b)==float('inf'): return Js(a) pyres = Js(a%b) #different signs in python and javascript #python has the same sign as b and js has the same #sign as a. if a<0 and pyres.value>0: pyres.value -= abs(b) elif a>0 and pyres.value<0: pyres.value += abs(b) return Js(pyres) #Comparisons (I dont implement === and !== here, these # will be implemented as external functions later) # <, <=, !=, ==, >=, > are implemented here. def abstract_relational_comparison(self, other, self_first=True): ''' self= def __ge__(self, other): res = self.abstract_relational_comparison(other, True) if res.is_undefined(): return false return res.neg() #> def __gt__(self, other): res = self.abstract_relational_comparison(other, False) if res.is_undefined(): return false return res def abstract_equality_comparison(self, other): ''' returns the result of JS == compare. result is PyJs type: bool''' tx, ty = self.TYPE, other.TYPE if tx==ty: if tx=='Undefined' or tx=='Null': return true if tx=='Number' or tx=='String' or tx=='Boolean': return Js(self.value==other.value) return Js(self is other) # Object elif (tx=='Undefined' and ty=='Null') or (ty=='Undefined' and tx=='Null'): return true elif tx=='Number' and ty=='String': return self.abstract_equality_comparison(other.to_number()) elif tx=='String' and ty=='Number': return self.to_number().abstract_equality_comparison(other) elif tx=='Boolean': return self.to_number().abstract_equality_comparison(other) elif ty=='Boolean': return self.abstract_equality_comparison(other.to_number()) elif (tx=='String' or tx=='Number') and other.is_object(): return self.abstract_equality_comparison(other.to_primitive()) elif (ty=='String' or ty=='Number') and self.is_object(): return self.to_primitive().abstract_equality_comparison(other) else: return false #== def __eq__(self, other): return self.abstract_equality_comparison(other) #!= def __ne__(self, other): return self.abstract_equality_comparison(other).neg() #Other methods (instanceof) def instanceof(self, other): '''checks if self is instance of other''' if not hasattr(other, 'has_instance'): return false return other.has_instance(self) #iteration def __iter__(self): #Returns a generator of all own enumerable properties # since the size od self.own can change we need to use different method of iteration. # SLOW! New items will NOT show up. returned = {} if not self.IS_CHILD_SCOPE: cands = sorted(name for name in self.own if self.own[name]['enumerable']) else: cands = sorted(name for name in self.own) for cand in cands: check = self.own.get(cand) if check and check['enumerable']: yield Js(cand) def contains(self, other): if not self.is_object(): raise MakeError('TypeError',"You can\'t use 'in' operator to search in non-objects") return Js(self.has_property(other.to_string().value)) #Other Special methods def __call__(self, *args): '''Call a property prop as a function (this will be global object). NOTE: dont pass this and arguments here, these will be added automatically!''' if not self.is_callable(): raise MakeError('TypeError', '%s is not a function'%self.typeof()) return self.call(self.GlobalObject, args) def create(self, *args): '''Generally not a constructor, raise an error''' raise MakeError('TypeError', '%s is not a constructor'%self.Class) def __unicode__(self): return self.to_string().value def __repr__(self): if self.Class=='Object': res = [] for e in self: res.append(str_repr(e.value)+': '+str_repr(self.get(e))) return '{%s}'%', '.join(res) elif self.Class=='String': return str_repr(self.value) elif self.Class=='Array': res = [] for e in self: res.append(repr(self.get(e))) return '[%s]'%', '.join(res) else: val = str_repr(self.to_string().value) return val def _fuck_python3(self): # hack to make object hashable in python 3 (__eq__ causes problems) return object.__hash__(self) def callprop(self, prop, *args): '''Call a property prop as a method (this will be self). NOTE: dont pass this and arguments here, these will be added automatically!''' if not isinstance(prop, basestring): prop = prop.to_string().value cand = self.get(prop) if not cand.is_callable(): raise MakeError('TypeError','%s is not a function'%cand.typeof()) return cand.call(self, args) def to_python(self): """returns equivalent python object. for example if this object is javascript array then this method will return equivalent python array""" return to_python(self) def to_py(self): """returns equivalent python object. for example if this object is javascript array then this method will return equivalent python array""" return self.to_python() if six.PY3: PyJs.__hash__ = PyJs._fuck_python3 PyJs.__truediv__ = PyJs.__div__ #Define some more classes representing operators: def PyJsStrictEq(a, b): '''a===b''' tx, ty = Type(a), Type(b) if tx!=ty: return false if tx=='Undefined' or tx=='Null': return true if a.is_primitive(): #string bool and number case return Js(a.value==b.value) return Js(a is b) # object comparison def PyJsStrictNeq(a, b): ''' a!==b''' return PyJsStrictEq(a, b).neg() def PyJsBshift(a, b): """a>>>b""" return a.pyjs_bshift(b) def PyJsComma(a, b): return b class PyJsException(Exception): def __str__(self): if self.mes.Class=='Error': return self.mes.callprop('toString').value else: return unicode(self.mes) class PyJsSwitchException(Exception): pass PyJs.MakeError = staticmethod(MakeError) def JsToPyException(js): temp = PyJsException() temp.mes = js return temp def PyExceptionToJs(py): return py.mes #Scope class it will hold all the variables accessible to user class Scope(PyJs): Class = 'global' extensible = True IS_CHILD_SCOPE = True # todo speed up # in order to speed up this very important class the top scope should behave differently than # child scopes, child scope should not have this property descriptor thing because they cant be changed anyway # they are all confugurable= False def __init__(self, scope, closure=None): """Doc""" self.prototype = closure if closure is None: # global, top level scope self.own = {} for k, v in six.iteritems(scope): # set all the global items self.define_own_property(k, {'value': v, 'configurable': False, 'writable': False, 'enumerable': False}) else: # not global, less powerful but faster closure. self.own = scope # simple dictionary which maps name directly to js object. def register(self, lval): # registered keeps only global registered variables if self.prototype is None: # define in global scope if lval in self.own: self.own[lval]['configurable'] = False else: self.define_own_property(lval, {'value': undefined, 'configurable': False, 'writable': True, 'enumerable': True}) elif lval not in self.own: # define in local scope since it has not been defined yet self.own[lval] = undefined # default value def registers(self, lvals): """register multiple variables""" for lval in lvals: self.register(lval) def put(self, lval, val, op=None): if self.prototype is None: # global scope put, simple return PyJs.put(self, lval, val, op) else: # trying to put in local scope # we dont know yet in which scope we should place this var if lval in self.own: if op: # increment operation val = getattr(self.own[lval], OP_METHODS[op])(val) self.own[lval] = val return val else: #try to put in the lower scope since we cant put in this one (var wasn't registered) return self.prototype.put(lval, val, op) def force_own_put(self, prop, val, configurable=False): if self.prototype is None: # global scope self.own[prop] = {'value': val, 'writable': True, 'enumerable':True, 'configurable':configurable} else: self.own[prop] = val def get(self, prop, throw=True): #note prop is always a Py String if not isinstance(prop, basestring): prop = prop.to_string().value if self.prototype is not None: # fast local scope cand = self.own.get(prop) if cand is None: return self.prototype.get(prop, throw) return cand # slow, global scope if prop not in self.own: if throw: raise MakeError('ReferenceError', '%s is not defined' % prop) return undefined return PyJs.get(self, prop) def delete(self, lval): if self.prototype is not None: if lval in self.own: return false return self.prototype.delete(lval) # we are in global scope here. Must exist and be configurable to delete if lval not in self.own: # this lval does not exist, why do you want to delete it??? return true if self.own[lval]['configurable']: del self.own[lval] return true # not configurable, cant delete return false def pyimport(self, name, module): self.register(name) self.put(name, py_wrap(module)) def __repr__(self): return u'[Object Global]' def to_python(self): return to_python(self) class This(Scope): IS_CHILD_SCOPE = False def get(self, prop, throw=False): return Scope.get(self, prop, throw) class JsObjectWrapper(object): def __init__(self, obj): self.__dict__['_obj'] = obj def __call__(self, *args): args = tuple(Js(e) for e in args) if '_prop_of' in self.__dict__: parent, meth = self.__dict__['_prop_of'] return to_python(parent._obj.callprop(meth, *args)) return to_python(self._obj(*args)) def __getattr__(self, item): if item == 'new' and self._obj.is_callable(): # return instance initializer def PyJsInstanceInit(*args): args = tuple(Js(e) for e in args) return self._obj.create(*args).to_python() return PyJsInstanceInit cand = to_python(self._obj.get(str(item))) # handling method calling... obj.meth(). Value of this in meth should be self if isinstance(cand, self.__class__): cand.__dict__['_prop_of'] = self, str(item) return cand def __setattr__(self, item, value): self._obj.put(str(item), Js(value)) def __getitem__(self, item): cand = to_python(self._obj.get(str(item))) if isinstance(cand, self.__class__): cand.__dict__['_prop_of'] = self, str(item) return cand def __setitem__(self, item, value): self._obj.put(str(item), Js(value)) def __iter__(self): if self._obj.Class=='Array': return iter(self.to_list()) elif self._obj.Class=='Object': return iter(self.to_dict()) else: raise MakeError('TypeError', '%s is not iterable in Python' % self._obj.Class) def __repr__(self): if self._obj.is_primitive() or self._obj.is_callable(): return repr(self._obj) elif self._obj.Class in {'Array', 'Arguments'}: return repr(self.to_list()) return repr(self.to_dict()) def __len__(self): return len(self._obj) def __nonzero__(self): return bool(self._obj) def __bool__(self): return bool(self._obj) def to_dict(self): return to_dict(self.__dict__['_obj']) def to_list(self): return to_list(self.__dict__['_obj']) class PyObjectWrapper(PyJs): Class = 'PyObjectWrapper' def __init__(self, obj): self.obj = obj def get(self, prop): if not isinstance(prop, basestring): prop = prop.to_string().value try: if prop.isdigit(): return py_wrap(self.obj[int(prop)]) return py_wrap(getattr(self.obj, prop)) except: return undefined def put(self, prop, val, throw=False): if not isinstance(prop, basestring): prop = prop.to_string().value try: setattr(self.obj, prop, to_python(val)) except AttributeError: raise MakeError('TypeError', 'Read only object probably...') return val def __call__(self, *args): py_args = tuple(to_python(e) for e in args) try: py_res = self.obj.__call__(*py_args) except Exception as e: message = 'your Python function failed! ' try: message += e.message except: pass raise MakeError('Error', message) return py_wrap(py_res) def callprop(self, prop, *args): py_args = tuple(to_python(e) for e in args) if not isinstance(prop, basestring): prop = prop.to_string().value return self.get(prop)(*py_args) def delete(self, prop): if not isinstance(prop, basestring): prop = prop.to_string().value try: if prop.isdigit(): del self.obj[int(prop)] else: delattr(self.obj, prop) return true except: return false def __repr__(self): return 'PyObjectWrapper(%s)' % str(self.obj) def to_python(self): return self.obj def to_py(self): return self.obj def py_wrap(py): if isinstance(py, (FunctionType, BuiltinFunctionType, MethodType, BuiltinMethodType, dict, int, str, bool, float, list, tuple, long, basestring)) or py is None : return HJs(py) return PyObjectWrapper(py) ############################################################################## #Define types #Object class PyJsObject(PyJs): Class = 'Object' def __init__(self, prop_descs={}, prototype=None, extensible=True): self.prototype = prototype self.extensible = extensible self.own = {} for prop, desc in six.iteritems(prop_descs): self.define_own_property(prop, desc) def __repr__(self): return repr(self.to_python().to_dict()) ObjectPrototype = PyJsObject() #Function class PyJsFunction(PyJs): Class = 'Function' def __init__(self, func, prototype=None, extensible=True, source=None): cand = fix_js_args(func) has_scope = cand is func func = cand self.argcount = six.get_function_code(func).co_argcount - 2 - has_scope self.code = func self.source = source if source else '{ [python code] }' self.func_name = func.__name__ if not func.__name__.startswith('PyJs_anonymous') else '' self.extensible = extensible self.prototype = prototype self.own = {} #set own property length to the number of arguments self.define_own_property('length', {'value': Js(self.argcount), 'writable': False, 'enumerable': False, 'configurable': False}) if self.func_name: self.define_own_property('name', {'value': Js(self.func_name), 'writable': False, 'enumerable': False, 'configurable': True}) # set own prototype proto = Js({}) # constructor points to this function proto.define_own_property('constructor',{'value': self, 'writable': True, 'enumerable': False, 'configurable': True}) self.define_own_property('prototype', {'value': proto, 'writable': True, 'enumerable': False, 'configurable': False}) def _set_name(self, name): '''name is py type''' if self.own.get('name'): self.func_name = name self.own['name']['value'] = Js(name) def construct(self, *args): proto = self.get('prototype') if not proto.is_object(): # set to standard prototype proto = ObjectPrototype obj = PyJsObject(prototype=proto) cand = self.call(obj, *args) return cand if cand.is_object() else obj def call(self, this, args=()): '''Calls this function and returns a result (converted to PyJs type so func can return python types) this must be a PyJs object and args must be a python tuple of PyJs objects. arguments object is passed automatically and will be equal to Js(args) (tuple converted to arguments object).You dont need to worry about number of arguments you provide if you supply less then missing ones will be set to undefined (but not present in arguments object). And if you supply too much then excess will not be passed (but they will be present in arguments object). ''' if not hasattr(args, '__iter__'): #get rid of it later args = (args,) args = tuple(Js(e) for e in args) # this wont be needed later arguments = PyJsArguments(args, self) # tuple will be converted to arguments object. arglen = self.argcount #function expects this number of args. if len(args)>arglen: args = args[0:arglen] elif len(args)>': '__rshift__', '&': '__and__', '^': '__xor__', '|': '__or__', '>>>': 'pyjs_bshift'} def Empty(): return Js(None) #Number class PyJsNumber(PyJs): #Note i dont implement +0 and -0. Just 0. TYPE = 'Number' Class = 'Number' NumberPrototype = PyJsObject({}, ObjectPrototype) NumberPrototype.Class = 'Number' NumberPrototype.value = 0 Infinity = PyJsNumber(float('inf'), NumberPrototype) NaN = PyJsNumber(float('nan'), NumberPrototype) PyJs.NaN = NaN PyJs.Infinity = Infinity # This dict aims to increase speed of string creation by storing character instances CHAR_BANK = {} NUM_BANK = {} PyJs.CHAR_BANK = CHAR_BANK #String # Different than implementation design in order to improve performance #for example I dont create separate property for each character in string, it would take ages. class PyJsString(PyJs): TYPE = 'String' Class = 'String' extensible = False def __init__(self, value=None, prototype=None): '''Constructor for Number String and Boolean''' if not isinstance(value, basestring): raise TypeError # this will be internal error self.value = value self.prototype = prototype self.own = {} # this should be optimized because its mych slower than python str creation (about 50 times!) # Dont create separate properties for every index. Just self.own['length'] = {'value': Js(len(value)), 'writable': False, 'enumerable': False, 'configurable': False} if len(value)==1: CHAR_BANK[value] = self #, 'writable': False, # 'enumerable': True, 'configurable': False} def get(self, prop): if not isinstance(prop, basestring): prop = prop.to_string().value try: index = int(prop) if index<0: return undefined char = self.value[index] if char not in CHAR_BANK: Js(char) # this will add char to CHAR BANK return CHAR_BANK[char] except Exception: pass return PyJs.get(self, prop) def can_put(self, prop): return False def __iter__(self): for i in xrange(len(self.value)): yield Js(i) # maybe create an int bank? StringPrototype = PyJsObject({}, ObjectPrototype) StringPrototype.Class = 'String' StringPrototype.value = '' CHAR_BANK[''] = Js('') #Boolean class PyJsBoolean(PyJs): TYPE = 'Boolean' Class = 'Boolean' BooleanPrototype = PyJsObject({}, ObjectPrototype) BooleanPrototype.Class = 'Boolean' BooleanPrototype.value = False true = PyJsBoolean(True, BooleanPrototype) false = PyJsBoolean(False, BooleanPrototype) #Undefined class PyJsUndefined(PyJs): TYPE = 'Undefined' Class = 'Undefined' def __init__(self): pass undefined = PyJsUndefined() #Null class PyJsNull(PyJs): TYPE = 'Null' Class = 'Null' def __init__(self): pass null = PyJsNull() PyJs.null = null class PyJsArray(PyJs): Class = 'Array' def __init__(self, arr=[], prototype=None): self.extensible = True self.prototype = prototype self.own = {'length' : {'value': Js(0), 'writable': True, 'enumerable': False, 'configurable': False}} for i, e in enumerate(arr): self.define_own_property(str(i), {'value': Js(e), 'writable': True, 'enumerable': True, 'configurable': True}) def define_own_property(self, prop, desc): old_len_desc = self.get_own_property('length') old_len = old_len_desc['value'].value # value is js type so convert to py. if prop=='length': if 'value' not in desc: return PyJs.define_own_property(self, prop, desc) new_len = desc['value'].to_uint32() if new_len!=desc['value'].to_number().value: raise MakeError('RangeError', 'Invalid range!') new_desc = {k:v for k,v in six.iteritems(desc)} new_desc['value'] = Js(new_len) if new_len>=old_len: return PyJs.define_own_property(self, prop, new_desc) if not old_len_desc['writable']: return False if 'writable' not in new_desc or new_desc['writable']==True: new_writable = True else: new_writable = False new_desc['writable'] = True if not PyJs.define_own_property(self, prop, new_desc): return False if new_len30*len(self.own): for ele in self.own.keys(): if ele.isdigit() and int(ele)>=new_len: if not self.delete(ele): # if failed to delete set len to current len and reject. new_desc['value'] = Js(old_len+1) if not new_writable: new_desc['writable'] = False PyJs.define_own_property(self, prop, new_desc) return False old_len = new_len else: # standard method while new_len=old_len and not old_len_desc['writable']: return False if not PyJs.define_own_property(self, prop, desc): return False if index>=old_len: old_len_desc['value'] = Js(index + 1) return True else: return PyJs.define_own_property(self, prop, desc) def to_list(self): return [self.get(str(e)) for e in xrange(self.get('length').to_uint32())] def __repr__(self): return repr(self.to_python().to_list()) ArrayPrototype = PyJsArray([], ObjectPrototype) class PyJsArguments(PyJs): Class = 'Arguments' def __init__(self, args, callee): self.own = {} self.extensible = True self.prototype = ObjectPrototype self.define_own_property('length', {'value': Js(len(args)), 'writable': True, 'enumerable': False, 'configurable': True}) self.define_own_property('callee', {'value': callee, 'writable': True, 'enumerable': False, 'configurable': True}) for i, e in enumerate(args): self.put(str(i), Js(e)) def to_list(self): return [self.get(str(e)) for e in xrange(self.get('length').to_uint32())] #We can define function proto after number proto because func uses number in its init FunctionPrototype = PyJsFunction(Empty, ObjectPrototype) FunctionPrototype.own['name']['value'] = Js('') # I will not rewrite RegExp engine from scratch. I will use re because its much faster. # I have to only make sure that I am handling all the differences correctly. REGEXP_DB = {} class PyJsRegExp(PyJs): Class = 'RegExp' extensible = True def __init__(self, regexp, prototype=None): self.prototype = prototype self.glob = False self.ignore_case = 0 self.multiline = 0 # self._cache = {'str':'NoStringEmpty23093', # 'iterator': None, # 'lastpos': -1, # 'matches': {}} flags = '' if not regexp[-1]=='/': #contains some flags (allowed are i, g, m spl = regexp.rfind('/') flags = set(regexp[spl+1:]) self.value = regexp[1:spl] if 'g' in flags: self.glob = True if 'i' in flags: self.ignore_case = re.IGNORECASE if 'm' in flags: self.multiline = re.MULTILINE else: self.value = regexp[1:-1] try: if self.value in REGEXP_DB: self.pat = REGEXP_DB[regexp] else: comp = 'None' # we have to check whether pattern is valid. # also this will speed up matching later # todo critical fix patter conversion etc. ..!!!!! # ugly hacks porting js reg exp to py reg exp works in 99% of cases ;) possible_fixes = [ (u'[]', u'[\0]'), (u'[^]', u'[^\0]'), (u'nofix1791', u'nofix1791') ] reg = self.value for fix, rep in possible_fixes: comp = REGEXP_CONVERTER._interpret_regexp(reg, flags) #print 'reg -> comp', reg, '->', comp try: self.pat = re.compile(comp, self.ignore_case | self.multiline) #print reg, '->', comp break except: reg = reg.replace(fix, rep) # print 'Fix', fix, '->', rep, '=', reg else: raise REGEXP_DB[regexp] = self.pat except: #print 'Invalid pattern but fuck it', self.value, comp raise MakeError('SyntaxError', 'Invalid RegExp pattern: %s -> %s'% (repr(self.value), repr(comp))) # now set own properties: self.own = {'source' : {'value': Js(self.value), 'enumerable': False, 'writable': False, 'configurable': False}, 'global' : {'value': Js(self.glob), 'enumerable': False, 'writable': False, 'configurable': False}, 'ignoreCase' : {'value': Js(bool(self.ignore_case)), 'enumerable': False, 'writable': False, 'configurable': False}, 'multiline' : {'value': Js(bool(self.multiline)), 'enumerable': False, 'writable': False, 'configurable': False}, 'lastIndex' : {'value': Js(0), 'enumerable': False, 'writable': True, 'configurable': False}} def match(self, string, pos): '''string is of course py string''' return self.pat.match(string, pos) # way easier :) # assert 0<=pos <= len(string) # if not pos: # return re.match(self.pat, string) # else: # if self._cache['str']==string: # if pos>self._cache['lastpos']: # for m in self._cache['iterator']: # start = m.start() # self._cache['lastpos'] = start # self._cache['matches'][start] = m # if start==pos: # return m # elif start>pos: # return None # self._cache['lastpos'] = len(string) # return None # else: # return self._cache['matches'].get(pos) # else: # self._cache['str'] = string # self._cache['matches'] = {} # self._cache['lastpos'] = -1 # self._cache['iterator'] = re.finditer(self.pat, string) # return self.match(string, pos) def JsRegExp(source): # Takes regexp literal! return PyJsRegExp(source, RegExpPrototype) RegExpPrototype = PyJsRegExp('/(?:)/', ObjectPrototype) ####Exceptions: default_attrs = {'writable':True, 'enumerable':False, 'configurable':True} def fill_in_props(obj, props, default_desc): for prop, value in props.items(): default_desc['value'] = Js(value) obj.define_own_property(prop, default_desc) class PyJsError(PyJs): Class = 'Error' extensible = True def __init__(self, message=None, prototype=None): self.prototype = prototype self.own = {} if message is not None: self.put('message', Js(message).to_string()) self.own['message']['enumerable'] = False ErrorPrototype = PyJsError(Js(''), ObjectPrototype) @Js def Error(message): return PyJsError(None if message.is_undefined() else message, ErrorPrototype) Error.create = Error err = {'name': 'Error', 'constructor': Error} fill_in_props(ErrorPrototype, err, default_attrs) Error.define_own_property('prototype', {'value': ErrorPrototype, 'enumerable': False, 'writable': False, 'configurable': False}) def define_error_type(name): TypeErrorPrototype = PyJsError(None, ErrorPrototype) @Js def TypeError(message): return PyJsError(None if message.is_undefined() else message, TypeErrorPrototype) err = {'name': name, 'constructor': TypeError} fill_in_props(TypeErrorPrototype, err, default_attrs) TypeError.define_own_property('prototype', {'value': TypeErrorPrototype, 'enumerable': False, 'writable': False, 'configurable': False}) ERRORS[name] = TypeError ERRORS = {'Error': Error} ERROR_NAMES = ['Eval', 'Type', 'Range', 'Reference', 'Syntax', 'URI'] for e in ERROR_NAMES: define_error_type(e+'Error') ############################################################################## # Import and fill prototypes here. #this works only for data properties def fill_prototype(prototype, Class, attrs, constructor=False): for i in dir(Class): e = getattr(Class, i) if six.PY2: if hasattr(e, '__func__'): temp = PyJsFunction(e.__func__, FunctionPrototype) attrs = {k:v for k,v in attrs.iteritems()} attrs['value'] = temp prototype.define_own_property(i, attrs) else: if hasattr(e, '__call__') and not i.startswith('__'): temp = PyJsFunction(e, FunctionPrototype) attrs = {k:v for k,v in attrs.items()} attrs['value'] = temp prototype.define_own_property(i, attrs) if constructor: attrs['value'] = constructor prototype.define_own_property('constructor', attrs) PyJs.undefined = undefined PyJs.Js = staticmethod(Js) from .prototypes import jsfunction, jsobject, jsnumber, jsstring, jsboolean, jsarray, jsregexp, jserror #Object proto fill_prototype(ObjectPrototype, jsobject.ObjectPrototype, default_attrs) #Define __proto__ accessor (this cant be done by fill_prototype since) @Js def __proto__(): return this.prototype if this.prototype is not None else null getter = __proto__ @Js def __proto__(val): if val.is_object(): this.prototype = val setter = __proto__ ObjectPrototype.define_own_property('__proto__', {'set': setter, 'get': getter, 'enumerable': False, 'configurable':True}) #Function proto fill_prototype(FunctionPrototype, jsfunction.FunctionPrototype, default_attrs) #Number proto fill_prototype(NumberPrototype, jsnumber.NumberPrototype, default_attrs) #String proto fill_prototype(StringPrototype, jsstring.StringPrototype, default_attrs) #Boolean proto fill_prototype(BooleanPrototype, jsboolean.BooleanPrototype, default_attrs) #Array proto fill_prototype(ArrayPrototype, jsarray.ArrayPrototype, default_attrs) #Error proto fill_prototype(ErrorPrototype, jserror.ErrorPrototype, default_attrs) #RegExp proto fill_prototype(RegExpPrototype, jsregexp.RegExpPrototype, default_attrs) # add exec to regexpfunction (cant add it automatically because of its name :( RegExpPrototype.own['exec'] = RegExpPrototype.own['exec2'] del RegExpPrototype.own['exec2'] ######################################################################### # Constructors # String @Js def String(st): if not len(arguments): return Js('') return arguments[0].to_string() @Js def string_constructor(): temp = PyJsObject(prototype=StringPrototype) temp.Class = 'String' #temp.TYPE = 'String' if not len(arguments): temp.value = '' else: temp.value = arguments[0].to_string().value for i, ch in enumerate(temp.value): # this will make things long... temp.own[str(i)] = {'value': Js(ch), 'writable': False, 'enumerable': True, 'configurable': True} temp.own['length'] = {'value': Js(len(temp.value)), 'writable': False, 'enumerable': False, 'configurable': False} return temp String.create = string_constructor # RegExp REG_EXP_FLAGS = {'g', 'i', 'm'} @Js def RegExp(pattern, flags): if pattern.Class=='RegExp': if not flags.is_undefined(): raise MakeError('TypeError', 'Cannot supply flags when constructing one RegExp from another') # return unchanged return pattern #pattern is not a regexp if pattern.is_undefined(): pattern = '' else: pattern = pattern.to_string().value # try: # pattern = REGEXP_CONVERTER._unescape_string(pattern.to_string().value) # except: # raise MakeError('SyntaxError', 'Invalid regexp') flags = flags.to_string().value if not flags.is_undefined() else '' for flag in flags: if flag not in REG_EXP_FLAGS: raise MakeError('SyntaxError', 'Invalid flags supplied to RegExp constructor "%s"' % flag) if len(set(flags))!=len(flags): raise MakeError('SyntaxError', 'Invalid flags supplied to RegExp constructor "%s"' % flags) pattern = '/%s/'%(pattern if pattern else '(?:)') + flags return JsRegExp(pattern) RegExp.create = RegExp PyJs.RegExp = RegExp # Number @Js def Number(): if len(arguments): return arguments[0].to_number() else: return Js(0) @Js def number_constructor(): temp = PyJsObject(prototype=NumberPrototype) temp.Class = 'Number' #temp.TYPE = 'Number' if len(arguments): temp.value = arguments[0].to_number().value else: temp.value = 0 return temp Number.create = number_constructor # Boolean @Js def Boolean(value): return value.to_boolean() @Js def boolean_constructor(value): temp = PyJsObject(prototype=BooleanPrototype) temp.Class = 'Boolean' #temp.TYPE = 'Boolean' temp.value = value.to_boolean().value return temp Boolean.create = boolean_constructor ############################################################################## def appengine(code): try: return translator.translate_js(code.decode('utf-8')) except: return traceback.format_exc() builtins = ('true','false','null','undefined','Infinity', 'NaN') scope = dict(zip(builtins, [eval(e) for e in builtins])) JS_BUILTINS = {k:v for k,v in scope.items()} # Fill in NUM_BANK for e in xrange(-2**10,2**14): NUM_BANK[e] = Js(e) if __name__=='__main__': print(ObjectPrototype.get('toString').callprop('call')) print(FunctionPrototype.own) a= null-Js(49404) x = a.put('ser', Js('der')) print(Js(0) or Js('p') and Js(4.0000000000050000001)) FunctionPrototype.put('Chuj', Js(409)) for e in FunctionPrototype: print('Obk', e.get('__proto__').get('__proto__').get('__proto__'), e) import code s = Js(4) b = Js(6) s2 = Js(4) o = ObjectPrototype o.put('x', Js(100)) var = Scope(scope) e = code.InteractiveConsole(globals()) #e.raw_input = interactor e.interact()