2016-12-28 07:10:47 +01:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
from types import FunctionType
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-23 22:40:41 +01:00
|
|
|
from ..compat import compat_str
|
|
|
|
|
|
|
|
|
|
|
|
def _to_js(o):
|
|
|
|
if isinstance(o, JSProtoBase):
|
|
|
|
return o
|
|
|
|
elif o is None:
|
|
|
|
return undefined
|
|
|
|
elif isinstance(o, _native_bool):
|
|
|
|
return JSBooleanPrototype(o)
|
|
|
|
elif isinstance(o, _native_string):
|
|
|
|
return JSStringPrototype(o)
|
|
|
|
elif isinstance(o, _native_number):
|
|
|
|
return JSNumberPrototype(o)
|
|
|
|
elif isinstance(o, _native_object):
|
|
|
|
return JSObjectPrototype(o)
|
|
|
|
elif isinstance(o, _native_function) or (isinstance(o, JSBase) and hasattr(o, 'call')):
|
|
|
|
return JSFunctionPrototype(o)
|
|
|
|
elif isinstance(o, _native_array):
|
|
|
|
return JSArrayPrototype(o)
|
|
|
|
else:
|
|
|
|
raise Exception('Not allowed conversion %s to js' % type(o))
|
|
|
|
|
|
|
|
|
|
|
|
def js(func):
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
return _to_js(func(*args, **kwargs))
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
def _type(o):
|
|
|
|
if o is undefined:
|
|
|
|
return _undefined_type
|
|
|
|
elif o is None or o is null:
|
|
|
|
return _null_type
|
|
|
|
elif isinstance(o, _native_bool) or isinstance(o, JSBooleanPrototype):
|
|
|
|
return _boolean_type
|
|
|
|
elif isinstance(o, _native_string) or isinstance(o, JSStringPrototype):
|
|
|
|
return _string_type
|
|
|
|
elif isinstance(o, _native_number) or isinstance(o, JSNumberPrototype):
|
|
|
|
return _number_type
|
|
|
|
elif isinstance(o, _native_object) or isinstance(o, JSObjectPrototype):
|
|
|
|
return _object_type
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def to_object(o):
|
|
|
|
if o is undefined or o is null:
|
|
|
|
raise Exception('TypeError: Cannot convert undefined or null to object')
|
|
|
|
elif isinstance(o, JSBooleanPrototype):
|
|
|
|
return JSBooleanPrototype(o)
|
|
|
|
elif isinstance(o, JSNumberPrototype):
|
|
|
|
return JSNumberPrototype(o)
|
|
|
|
elif isinstance(o, JSStringPrototype):
|
|
|
|
return JSStringPrototype(o)
|
|
|
|
elif isinstance(o, JSObjectPrototype):
|
|
|
|
return o
|
|
|
|
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
class JSBase(object):
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
def __init__(self, name, value):
|
|
|
|
self.props = self.__class__.props.copy()
|
|
|
|
self.name = name
|
|
|
|
self.value = value
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def __str__(self):
|
|
|
|
return '[native code]'
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
props = {}
|
|
|
|
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
class JSProtoBase(JSBase):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
cls = self.__class__
|
|
|
|
while cls is not JSProtoBase:
|
|
|
|
cls = cls.__base__
|
2017-01-23 01:37:50 +01:00
|
|
|
props = cls.props.copy()
|
|
|
|
props.update(self.props)
|
|
|
|
self.props = props
|
2017-01-23 22:40:41 +01:00
|
|
|
super(JSProtoBase, self).__init__('', {})
|
2017-01-22 00:23:27 +01:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return ''
|
|
|
|
|
2017-01-23 22:40:41 +01:00
|
|
|
def __get_prop(self, prop):
|
2017-01-23 01:37:50 +01:00
|
|
|
result = self.value.get(prop)
|
|
|
|
if result is None:
|
|
|
|
result = self.props.get(prop)
|
|
|
|
return result
|
|
|
|
|
|
|
|
@js
|
2017-01-22 00:23:27 +01:00
|
|
|
def get_prop(self, prop):
|
2017-01-23 22:40:41 +01:00
|
|
|
return self.__get_prop(prop)
|
2017-01-22 00:23:27 +01:00
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
@js
|
2017-01-23 22:40:41 +01:00
|
|
|
def call_prop(self, prop, *args, **kwargs):
|
|
|
|
func = self.__get_prop(prop)
|
|
|
|
if isinstance(func, FunctionType):
|
|
|
|
return func(self, *args, **kwargs)
|
|
|
|
elif isinstance(func, staticmethod):
|
|
|
|
return func.__func__(*args, **kwargs)
|
|
|
|
elif isinstance(func, classmethod):
|
|
|
|
return func.__func__(self.__class__, *args, **kwargs)
|
|
|
|
elif isinstance(func, JSBase) and hasattr(func, 'call'):
|
|
|
|
return func.call(*args, **kwargs)
|
|
|
|
else:
|
|
|
|
# FIXME instead of prop should return the whole expression
|
|
|
|
# needs to use internal exception
|
|
|
|
# interpreter should raise JSTypeError
|
|
|
|
raise Exception('TypeError: %s is not a function' % prop)
|
2017-01-22 00:23:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
class JSObjectPrototype(JSProtoBase):
|
2016-12-28 07:10:47 +01:00
|
|
|
|
|
|
|
def __init__(self, value=None):
|
2017-01-22 00:23:27 +01:00
|
|
|
super(JSObjectPrototype, self).__init__()
|
|
|
|
if value is not None:
|
2017-01-23 01:37:50 +01:00
|
|
|
self.value = value
|
2017-01-22 00:23:27 +01:00
|
|
|
|
2017-01-23 22:40:41 +01:00
|
|
|
@staticmethod
|
|
|
|
def _constructor(value=None):
|
|
|
|
value = _to_js(value)
|
|
|
|
if value is undefined or value is null:
|
|
|
|
return JSObjectPrototype()
|
|
|
|
elif isinstance(value, JSObjectPrototype):
|
|
|
|
return value
|
|
|
|
elif isinstance(value, (JSStringPrototype, JSNumberPrototype, JSBooleanPrototype)):
|
|
|
|
return to_object(value)
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def _to_string(self):
|
|
|
|
return 'object to string'
|
|
|
|
|
|
|
|
def _to_locale_string(self):
|
|
|
|
return 'object to locale string'
|
|
|
|
|
|
|
|
def _value_of(self):
|
|
|
|
return 'object value of'
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def _has_own_property(self, v):
|
2017-01-23 01:37:50 +01:00
|
|
|
return v in self.value
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def _is_prototype_of(self, v):
|
2016-12-28 07:10:47 +01:00
|
|
|
return 'object has own prop'
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def _is_property_enumerable(self, v):
|
|
|
|
return 'object is property enumerable'
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
props = {
|
2017-01-23 22:40:41 +01:00
|
|
|
'constructor': _constructor,
|
2017-01-22 00:23:27 +01:00
|
|
|
'toString': _to_string,
|
|
|
|
'toLocaleString': _to_locale_string,
|
|
|
|
'valueOf': _value_of,
|
|
|
|
'hasOwnProperty': _has_own_property,
|
|
|
|
'isPrototypeOf': _is_prototype_of,
|
|
|
|
'propertyIsEnumerable': _is_property_enumerable
|
|
|
|
}
|
2016-12-28 07:10:47 +01:00
|
|
|
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
class JSObject(JSBase):
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
def __init__(self):
|
|
|
|
super(JSObject, self).__init__(self.name, self.props)
|
2017-01-22 00:23:27 +01:00
|
|
|
|
2017-01-23 22:40:41 +01:00
|
|
|
@staticmethod
|
|
|
|
def construct(value=None):
|
|
|
|
return JSObjectPrototype._constructor(value)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def call(value=None):
|
|
|
|
return JSObject.construct(value)
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def _get_prototype_of(self, o):
|
|
|
|
return 'object get prototype of'
|
|
|
|
|
|
|
|
def _get_own_property_descriptor(self, o, p):
|
|
|
|
return 'object desc'
|
|
|
|
|
|
|
|
def _get_own_property_names(self, o):
|
|
|
|
return list(o.value.keys())
|
|
|
|
|
|
|
|
def _create(self, o, props=None):
|
|
|
|
return 'object create'
|
|
|
|
|
|
|
|
def _define_property(self, o, p, attr):
|
|
|
|
return 'object define prop'
|
|
|
|
|
|
|
|
def _define_properties(self, o, props):
|
|
|
|
return 'object define properties'
|
|
|
|
|
|
|
|
def _seal(self, o):
|
|
|
|
return 'object seal'
|
|
|
|
|
|
|
|
def _freeze(self, o):
|
|
|
|
return 'object freeze'
|
|
|
|
|
|
|
|
def _prevent_extensions(self, o):
|
|
|
|
return 'object prevent extension'
|
|
|
|
|
|
|
|
def _is_sealed(self, o):
|
|
|
|
return 'object is sealed'
|
|
|
|
|
|
|
|
def _is_frozen(self, o):
|
|
|
|
return 'object is frozen'
|
|
|
|
|
|
|
|
def _is_extensible(self, o):
|
|
|
|
return 'object is extensible'
|
|
|
|
|
|
|
|
def _keys(self, o):
|
|
|
|
return 'object keys'
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
name = 'Object'
|
|
|
|
props = {
|
2017-01-22 00:23:27 +01:00
|
|
|
'length': 1,
|
2017-01-23 22:40:41 +01:00
|
|
|
'prototype': JSObjectPrototype(),
|
2017-01-22 00:23:27 +01:00
|
|
|
'getPrototypeOf': _get_prototype_of,
|
|
|
|
'getOwnPropertyDescriptor': _get_own_property_descriptor,
|
|
|
|
'getOwnPropertyNames': _get_own_property_names,
|
|
|
|
'create': _create,
|
|
|
|
'defineProperty': _define_property,
|
|
|
|
'defineProperties': _define_properties,
|
|
|
|
'seal': _seal,
|
|
|
|
'freeze': _freeze,
|
|
|
|
'preventExtensions': _prevent_extensions,
|
|
|
|
'isSealed': _is_sealed,
|
|
|
|
'isFrozen': _is_frozen,
|
|
|
|
'isExtensible': _is_extensible,
|
|
|
|
'keys': _keys
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class JSFunctionPrototype(JSObjectPrototype):
|
|
|
|
|
|
|
|
def __init__(self, *args):
|
|
|
|
body = args[-1] if args else ''
|
|
|
|
if isinstance(body, JSBase):
|
2017-01-23 01:37:50 +01:00
|
|
|
super(JSFunctionPrototype, self).__init__(body.props)
|
|
|
|
self.fname = body.name
|
2017-01-22 00:23:27 +01:00
|
|
|
else:
|
|
|
|
super(JSFunctionPrototype, self).__init__()
|
2017-01-23 01:37:50 +01:00
|
|
|
self.fname = 'anonymous'
|
2017-01-22 00:23:27 +01:00
|
|
|
|
|
|
|
# FIXME: JSProtoBase sets body to '' instead of None
|
2017-01-23 01:37:50 +01:00
|
|
|
self.body = str(body)
|
|
|
|
self.args = [sarg.strip() for arg in args[:-1] for sarg in str(arg).split(',')]
|
2017-01-22 00:23:27 +01:00
|
|
|
# TODO check if self._args can be parsed as formal parameter list
|
|
|
|
# TODO check if self._body can be parsed as function body
|
|
|
|
# TODO set strict
|
|
|
|
# TODO throw strict mode exceptions
|
|
|
|
# (double argument, "eval" or "arguments" in arguments, function identifier is "eval" or "arguments")
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
@property
|
|
|
|
def _length(self):
|
|
|
|
# FIXME: returns maximum instead of "typical" number of arguments
|
|
|
|
# Yeesh, I dare you to find anything like that in the python specification.
|
2017-01-23 01:37:50 +01:00
|
|
|
return len(self.args)
|
2017-01-22 00:23:27 +01:00
|
|
|
|
|
|
|
def _to_string(self):
|
2017-01-23 01:37:50 +01:00
|
|
|
if self.body is not None:
|
2017-01-22 00:23:27 +01:00
|
|
|
body = '\n'
|
2017-01-23 01:37:50 +01:00
|
|
|
body += '\t' + self.body if self.body else self.body
|
2017-01-22 00:23:27 +01:00
|
|
|
else:
|
|
|
|
body = ''
|
2017-01-23 01:37:50 +01:00
|
|
|
return 'function %s(%s) {%s\n}' % (self.fname, ', '.join(self.args), body)
|
2017-01-22 00:23:27 +01:00
|
|
|
|
|
|
|
def _apply(self, this_arg, arg_array):
|
|
|
|
return 'function apply'
|
|
|
|
|
|
|
|
def _call(self, this_arg, *args):
|
|
|
|
return 'function call'
|
|
|
|
|
|
|
|
def _bind(self, this_arg, *args):
|
|
|
|
return 'function bind'
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
props = {
|
2017-01-22 00:23:27 +01:00
|
|
|
'length': 0,
|
|
|
|
'constructor': __init__,
|
|
|
|
'toString': _to_string,
|
|
|
|
'apply': _apply,
|
|
|
|
'call': _call,
|
|
|
|
'bind': _bind
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class JSFuction(JSObject):
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
name = 'Function'
|
|
|
|
props = {
|
2017-01-22 00:23:27 +01:00
|
|
|
'length': 1,
|
2017-01-23 01:37:50 +01:00
|
|
|
'prototype': JSFunctionPrototype()
|
2017-01-22 00:23:27 +01:00
|
|
|
}
|
2016-12-28 07:10:47 +01:00
|
|
|
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
class JSArrayPrototype(JSObjectPrototype):
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def __init__(self, value=None, length=0):
|
|
|
|
super(JSArrayPrototype, self).__init__()
|
2017-01-23 01:37:50 +01:00
|
|
|
self.list = [] if value is None else value
|
|
|
|
self.value['length'] = self._length
|
2016-12-28 07:10:47 +01:00
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
@property
|
|
|
|
def _length(self):
|
|
|
|
return len(self.list)
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
def __str__(self):
|
|
|
|
return 'JSArrayPrototype: %s' % self.list
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return 'JSArrayPrototype(%s, %s)' % (self.list, self._length)
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def _to_string(self):
|
2016-12-28 07:10:47 +01:00
|
|
|
return 'array to string'
|
|
|
|
|
2017-01-22 00:23:27 +01:00
|
|
|
def _to_locale_string(self):
|
|
|
|
return 'array to locale string'
|
|
|
|
|
|
|
|
def _concat(self, *items):
|
|
|
|
return 'array concat'
|
|
|
|
|
|
|
|
def _join(self, sep):
|
|
|
|
return 'array join'
|
|
|
|
|
|
|
|
def _pop(self):
|
|
|
|
return 'array pop'
|
|
|
|
|
|
|
|
def _push(self, *items):
|
|
|
|
return 'array push'
|
|
|
|
|
|
|
|
def _reverse(self):
|
|
|
|
return 'array reverse'
|
|
|
|
|
|
|
|
def _shift(self):
|
|
|
|
return 'array shift'
|
|
|
|
|
|
|
|
def _slice(self, start, end):
|
|
|
|
return 'array slice'
|
|
|
|
|
|
|
|
def _sort(self, cmp):
|
|
|
|
return 'array sort'
|
|
|
|
|
|
|
|
def _splice(self, start, delete_count, *items):
|
|
|
|
return 'array splice'
|
|
|
|
|
|
|
|
def _unshift(self, *items):
|
|
|
|
return 'array unshift'
|
|
|
|
|
|
|
|
def _index_of(self, elem, from_index=0):
|
|
|
|
return 'array index of'
|
|
|
|
|
|
|
|
def _last_index_of(self, elem, from_index=None):
|
|
|
|
if from_index is None:
|
2017-01-23 01:37:50 +01:00
|
|
|
from_index = len(self.value) - 1
|
2017-01-22 00:23:27 +01:00
|
|
|
return 'array index of'
|
|
|
|
|
|
|
|
def _every(self, callback, this_arg=None):
|
|
|
|
return 'array every'
|
|
|
|
|
|
|
|
def _some(self, callback, this_arg=None):
|
|
|
|
return 'array some'
|
|
|
|
|
|
|
|
def _for_each(self, callback, this_arg=None):
|
|
|
|
return 'array for_each'
|
|
|
|
|
|
|
|
def _map(self, callback, this_arg=None):
|
|
|
|
return 'array map'
|
|
|
|
|
|
|
|
def _filter(self, callback, this_arg=None):
|
|
|
|
return 'array filter'
|
|
|
|
|
|
|
|
def _reduce(self, callback, init=None):
|
|
|
|
return 'array reduce'
|
|
|
|
|
|
|
|
def _reduce_right(self, callback, init=None):
|
|
|
|
return 'array reduce right'
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
props = {
|
2017-01-22 00:23:27 +01:00
|
|
|
'length': 0,
|
|
|
|
'constructor': __init__,
|
|
|
|
'toString': _to_string,
|
|
|
|
'toLocaleString': _to_locale_string,
|
|
|
|
'concat': _concat,
|
|
|
|
'join': _join,
|
|
|
|
'pop': _pop,
|
|
|
|
'push': _push,
|
|
|
|
'reverse': _reverse,
|
|
|
|
'shift': _shift,
|
|
|
|
'slice': _slice,
|
|
|
|
'sort': _sort,
|
|
|
|
'splice': _splice,
|
|
|
|
'unshift': _unshift,
|
|
|
|
'indexOf': _index_of,
|
|
|
|
'lastIndexOf': _last_index_of,
|
|
|
|
'every': _every,
|
|
|
|
'some': _some,
|
|
|
|
'forEach': _for_each,
|
|
|
|
'map': _map,
|
|
|
|
'filter': _filter,
|
|
|
|
'reduce': _reduce,
|
|
|
|
'reduceRight': _reduce_right
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class JSArray(JSObject):
|
|
|
|
|
|
|
|
def _is_array(self, arg):
|
|
|
|
return 'array is array'
|
|
|
|
|
2017-01-23 22:40:41 +01:00
|
|
|
name = 'Array'
|
2017-01-23 01:37:50 +01:00
|
|
|
props = {
|
2017-01-22 00:23:27 +01:00
|
|
|
'length': 1,
|
2017-01-23 01:37:50 +01:00
|
|
|
'prototype': JSArrayPrototype.props,
|
2017-01-22 00:23:27 +01:00
|
|
|
'isArray': _is_array
|
|
|
|
}
|
|
|
|
|
2017-01-23 22:40:41 +01:00
|
|
|
|
|
|
|
class JSStringPrototype(JSObjectPrototype):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class JSString(JSObject):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class JSBooleanPrototype(JSObjectPrototype):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class JSBoolean(JSObject):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class JSNumberPrototype(JSObjectPrototype):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class JSNumber(JSObject):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
undefined = object()
|
|
|
|
null = object()
|
|
|
|
true = JSBooleanPrototype(True)
|
|
|
|
false = JSBooleanPrototype(False)
|
|
|
|
|
|
|
|
_native_bool = bool
|
|
|
|
_native_string = compat_str
|
|
|
|
_native_number = (int, float)
|
|
|
|
_native_object = dict
|
|
|
|
_native_array = (list, tuple)
|
|
|
|
_native_function = FunctionType
|
|
|
|
|
|
|
|
_undefined_type = object()
|
|
|
|
_null_type = object()
|
|
|
|
_boolean_type = object()
|
|
|
|
_string_type = object()
|
|
|
|
_number_type = object()
|
|
|
|
_object_type = object()
|
|
|
|
|
2017-01-23 01:37:50 +01:00
|
|
|
global_obj = JSObjectPrototype({'Object': JSObject(),
|
|
|
|
'Array': JSArray(),
|
|
|
|
'Function': JSFuction()})
|