mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-01 00:43:37 +00:00
d97bb7174d
Change improve provider title processing. Change improve handling erroneous JSON responses. Change improve find show with unicode characters. Change improve result for providers Omgwtf, SpeedCD, Transmithenet, Zoogle. Change validate .torrent files that contain optional header data. Fix case where an episode status was not restored on failure. Add raise log error if no wanted qualities are found. Change add un/pw to Config/Media providers/Options for BTN API graceful fallback (can remove Api key for security). Change only download torrent once when using blackhole. Add Cloudflare module 1.6.8 (be0a536) to mitigate CloudFlare (IUAM) access validator. Add Js2Py 0.43 (c1442f1) Cloudflare dependency. Add pyjsparser 2.4.5 (cd5b829) Js2Py dependency.
2781 lines
107 KiB
Python
2781 lines
107 KiB
Python
'''Most important file in Js2Py implementation: PyJs class - father of all PyJs objects'''
|
|
from copy import copy
|
|
import re
|
|
|
|
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
|
|
try:
|
|
import numpy
|
|
NUMPY_AVAILABLE = True
|
|
except:
|
|
NUMPY_AVAILABLE = False
|
|
|
|
|
|
# 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 in ['Array','Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array']:
|
|
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', 'Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array', '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, Clamped=False):
|
|
'''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) or (NUMPY_AVAILABLE and isinstance(val, (numpy.int8,numpy.uint8,
|
|
numpy.int16,numpy.uint16,
|
|
numpy.int32,numpy.uint32,
|
|
numpy.float32,numpy.float64))):
|
|
# 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)
|
|
# convert to typedarray
|
|
elif isinstance(val, JsObjectWrapper):
|
|
return val.__dict__['_obj']
|
|
elif NUMPY_AVAILABLE and isinstance(val, numpy.ndarray):
|
|
if val.dtype == numpy.int8:
|
|
return PyJsInt8Array(val, Int8ArrayPrototype)
|
|
elif val.dtype == numpy.uint8 and not Clamped:
|
|
return PyJsUint8Array(val, Uint8ArrayPrototype)
|
|
elif val.dtype == numpy.uint8 and Clamped:
|
|
return PyJsUint8ClampedArray(val, Uint8ClampedArrayPrototype)
|
|
elif val.dtype == numpy.int16:
|
|
return PyJsInt16Array(val, Int16ArrayPrototype)
|
|
elif val.dtype == numpy.uint16:
|
|
return PyJsUint16Array(val, Uint16ArrayPrototype)
|
|
|
|
elif val.dtype == numpy.int32:
|
|
return PyJsInt32Array(val, Int32ArrayPrototype)
|
|
elif val.dtype == numpy.uint32:
|
|
return PyJsUint16Array(val, Uint32ArrayPrototype)
|
|
|
|
elif val.dtype == numpy.float32:
|
|
return PyJsFloat32Array(val, Float32ArrayPrototype)
|
|
elif val.dtype == numpy.float64:
|
|
return PyJsFloat64Array(val, Float64ArrayPrototype)
|
|
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 = frozenset(['String', 'Number', 'Boolean', 'Undefined', 'Null'])
|
|
TYPE = 'Object'
|
|
Class = None
|
|
extensible = True
|
|
prototype = None
|
|
own = {}
|
|
GlobalObject = None
|
|
IS_CHILD_SCOPE = False
|
|
value = None
|
|
buff = 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 = {}
|
|
self.buff = None
|
|
|
|
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 update_array(self):
|
|
for i in range(self.get('length').to_uint32()):
|
|
self.put(str(i),Js(self.buff[i]))
|
|
|
|
def get(self, prop): #external use!
|
|
#prop = prop.value
|
|
if self.Class=='Undefined' or self.Class=='Null':
|
|
raise MakeError('TypeError', 'Undefined and null dont have properties!')
|
|
if not isinstance(prop, basestring):
|
|
prop = prop.to_string().value
|
|
if not isinstance(prop, basestring): raise RuntimeError('Bug')
|
|
if NUMPY_AVAILABLE and prop.isdigit():
|
|
if isinstance(self.buff,numpy.ndarray):
|
|
self.update_array()
|
|
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', 'Undefined and null dont have properties!')
|
|
if not isinstance(prop, basestring):
|
|
prop = prop.to_string().value
|
|
if NUMPY_AVAILABLE and prop.isdigit():
|
|
if self.Class == 'Int8Array':
|
|
val = Js(numpy.int8(val.to_number().value))
|
|
elif self.Class == 'Uint8Array':
|
|
val = Js(numpy.uint8(val.to_number().value))
|
|
elif self.Class == 'Uint8ClampedArray':
|
|
if val < Js(numpy.uint8(0)):
|
|
val = Js(numpy.uint8(0))
|
|
elif val > Js(numpy.uint8(255)):
|
|
val = Js(numpy.uint8(255))
|
|
else:
|
|
val = Js(numpy.uint8(val.to_number().value))
|
|
elif self.Class == 'Int16Array':
|
|
val = Js(numpy.int16(val.to_number().value))
|
|
elif self.Class == 'Uint16Array':
|
|
val = Js(numpy.uint16(val.to_number().value))
|
|
elif self.Class == 'Int32Array':
|
|
val = Js(numpy.int32(val.to_number().value))
|
|
elif self.Class == 'Uint32Array':
|
|
val = Js(numpy.uint32(val.to_number().value))
|
|
elif self.Class == 'Float32Array':
|
|
val = Js(numpy.float32(val.to_number().value))
|
|
elif self.Class == 'Float64Array':
|
|
val = Js(numpy.float64(val.to_number().value))
|
|
if isinstance(self.buff,numpy.ndarray):
|
|
self.buff[int(prop)] = int(val.to_number().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 in ['Array','Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array']:
|
|
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 in ['Array','Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array']:
|
|
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<other if self_first else other<self.
|
|
Returns the result of the question: is self smaller than other?
|
|
in case self_first is false it returns the answer of:
|
|
is other smaller than self.
|
|
result is PyJs type: bool or undefined'''
|
|
px = self.to_primitive('Number')
|
|
py = other.to_primitive('Number')
|
|
if not self_first: #reverse order
|
|
px, py = py, px
|
|
if not (px.Class=='String' and py.Class=='String'):
|
|
px, py = px.to_number(), py.to_number()
|
|
if px.is_nan() or py.is_nan():
|
|
return undefined
|
|
return Js(px.value<py.value) # same cmp algorithm
|
|
else:
|
|
# I am pretty sure that python has the same
|
|
# string cmp algorithm but I have to confirm it
|
|
return Js(px.value<py.value)
|
|
|
|
#<
|
|
def __lt__(self, other):
|
|
res = self.abstract_relational_comparison(other, True)
|
|
if res.is_undefined():
|
|
return false
|
|
return res
|
|
|
|
#<=
|
|
def __le__(self, other):
|
|
res = self.abstract_relational_comparison(other, False)
|
|
if res.is_undefined():
|
|
return false
|
|
return res.neg()
|
|
|
|
#>=
|
|
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 in ['Array','Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array']:
|
|
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 in ['Array', 'Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array']:
|
|
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', 'Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array', '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)<arglen:
|
|
args += (undefined,)*(arglen-len(args))
|
|
args += this, arguments #append extra params to the arg list
|
|
try:
|
|
return Js(self.code(*args))
|
|
except NotImplementedError:
|
|
raise
|
|
except RuntimeError as e: # maximum recursion
|
|
raise MakeError('RangeError', e.message if not isinstance(e, NotImplementedError) else 'Not implemented!')
|
|
|
|
def has_instance(self, other):
|
|
# I am not sure here so instanceof may not work lol.
|
|
if not other.is_object():
|
|
return false
|
|
proto = self.get('prototype')
|
|
if not proto.is_object():
|
|
raise TypeError('Function has non-object prototype in instanceof check')
|
|
while True:
|
|
other = other.prototype
|
|
if not other: # todo make sure that the condition is not None or null
|
|
return false
|
|
if other is proto:
|
|
return true
|
|
|
|
def create(self, *args):
|
|
proto = self.get('prototype')
|
|
if not proto.is_object():
|
|
proto = ObjectPrototype
|
|
new = PyJsObject(prototype=proto)
|
|
res = self.call(new, args)
|
|
if res.is_object():
|
|
return res
|
|
return new
|
|
|
|
class PyJsBoundFunction(PyJsFunction):
|
|
def __init__(self, target, bound_this, bound_args):
|
|
self.target = target
|
|
self.bound_this = bound_this
|
|
self.bound_args = bound_args
|
|
self.argcount = target.argcount
|
|
self.code = target.code
|
|
self.source = target.source
|
|
self.func_name = target.func_name
|
|
self.extensible = True
|
|
self.prototype = FunctionPrototype
|
|
self.own = {}
|
|
# set own property length to the number of arguments
|
|
self.define_own_property('length', {'value': target.get('length')-Js(len(self.bound_args)), '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 call(self, this, args=()):
|
|
return self.target.call(self.bound_this, self.bound_args+args)
|
|
|
|
def has_instance(self, other):
|
|
return self.target.has_instance(other)
|
|
|
|
PyJs.PyJsBoundFunction = PyJsBoundFunction
|
|
|
|
OP_METHODS = {'*': '__mul__',
|
|
'/': '__div__',
|
|
'%': '__mod__',
|
|
'+': '__add__',
|
|
'-': '__sub__',
|
|
'<<': '__lshift__',
|
|
'>>': '__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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsArrayBuffer(PyJs):
|
|
Class = 'ArrayBuffer'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsInt8Array(PyJs):
|
|
Class = 'Int8Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsUint8Array(PyJs):
|
|
Class = 'Uint8Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsUint8ClampedArray(PyJs):
|
|
Class = 'Uint8ClampedArray'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsInt16Array(PyJs):
|
|
Class = 'Int16Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsUint16Array(PyJs):
|
|
Class = 'Uint16Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsInt32Array(PyJs):
|
|
Class = 'Int32Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsUint32Array(PyJs):
|
|
Class = 'Uint32Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsFloat32Array(PyJs):
|
|
Class = 'Float32Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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())
|
|
|
|
class PyJsFloat64Array(PyJs):
|
|
Class = 'Float64Array'
|
|
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 = dict((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_len<old_len:
|
|
# not very efficient for sparse arrays, so using different method for sparse:
|
|
if old_len>30*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:
|
|
old_len -= 1
|
|
if not self.delete(str(int(old_len))): # 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
|
|
if not new_writable:
|
|
self.own['length']['writable'] = False
|
|
return True
|
|
elif prop.isdigit():
|
|
index = int(int(prop) % 2**32)
|
|
if index>=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)
|
|
|
|
ArrayBufferPrototype = PyJsArrayBuffer([], ObjectPrototype)
|
|
|
|
Int8ArrayPrototype = PyJsInt8Array([], ObjectPrototype)
|
|
|
|
Uint8ArrayPrototype = PyJsUint8Array([], ObjectPrototype)
|
|
|
|
Uint8ClampedArrayPrototype = PyJsUint8ClampedArray([], ObjectPrototype)
|
|
|
|
Int16ArrayPrototype = PyJsInt16Array([], ObjectPrototype)
|
|
|
|
Uint16ArrayPrototype = PyJsUint16Array([], ObjectPrototype)
|
|
|
|
Int32ArrayPrototype = PyJsInt32Array([], ObjectPrototype)
|
|
|
|
Uint32ArrayPrototype = PyJsUint32Array([], ObjectPrototype)
|
|
|
|
Float32ArrayPrototype = PyJsFloat32Array([], ObjectPrototype)
|
|
|
|
Float64ArrayPrototype = PyJsFloat64Array([], 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 = dict((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 = dict((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, jsarraybuffer, jstypedarray
|
|
|
|
|
|
#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)
|
|
# ArrayBuffer proto
|
|
fill_prototype(ArrayBufferPrototype, jsarraybuffer.ArrayBufferPrototype, default_attrs)
|
|
# Int8Array proto
|
|
fill_prototype(Int8ArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Uint8Array proto
|
|
fill_prototype(Uint8ArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Uint8ClampedArray proto
|
|
fill_prototype(Uint8ClampedArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Int16Array proto
|
|
fill_prototype(Int16ArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Uint16Array proto
|
|
fill_prototype(Uint16ArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Int32Array proto
|
|
fill_prototype(Int32ArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Uint32Array proto
|
|
fill_prototype(Uint32ArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Float32Array proto
|
|
fill_prototype(Float32ArrayPrototype, jstypedarray.TypedArrayPrototype, default_attrs)
|
|
# Float64Array proto
|
|
fill_prototype(Float64ArrayPrototype, jstypedarray.TypedArrayPrototype, 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 = dict((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()
|
|
|
|
|