689 lines
18 KiB
Python
Raw Normal View History

2016-12-28 07:10:47 +01:00
from __future__ import unicode_literals
2017-01-29 19:10:55 +01:00
from math import isnan, isinf, log10
from sys import float_info
from types import FunctionType
2016-12-28 07:10:47 +01:00
from ..compat import compat_str
def _to_js(o, name=None):
if isinstance(o, JSProtoBase):
return o
elif o is None:
return undefined
elif isinstance(o, _native_bool):
return JSBoolean.construct(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):
return JSFunctionPrototype(name, o, [])
elif isinstance(o, JSBase) and hasattr(o, 'call'):
return JSFunctionPrototype(o.name, 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-29 19:10:55 +01:00
def to_primitive(o, hint):
return o
def to_string(o):
if o is undefined:
return 'undefined'
elif o is null:
return 'null'
elif isinstance(o, JSBooleanPrototype):
if o is true:
return 'true'
elif o is false:
return 'false'
elif isinstance(o, JSNumberPrototype):
ov = o.value
if isnan(ov):
return 'NaN'
elif ov == 0.0:
return '0'
elif ov < 0:
return '-' + to_string(_to_js(-ov))
elif isinf(ov):
return 'Infinity'
else:
n = log10(ov)
c = 1 if 0 < n else 0
n = int(n)
k = 1
while True:
exp = 10 ** (n - k)
s = int(ov / exp)
if abs(ov - s * exp) < float_info.epsilon:
break
k += 1
if s % 10 == 0:
s //= 10
n += c
m = '%d' % s
if k <= n <= 21:
return m[:k] + '0' * (n - k)
elif 0 < n <= 21:
return m[:n] + '.' + m[n:k]
elif -6 < n <= 0:
return '0.' + '0' * -n + m[:k]
elif k == 1:
return m[0] + 'e%+d' % (n - 1)
else:
return m[0] + '.' + m[:k] + 'e%+d' % (n - 1)
elif isinstance(o, JSObjectPrototype):
prim_value = to_primitive(o, 'String')
return to_string(prim_value)
class JSBase(object):
2016-12-28 07:10:47 +01:00
2017-01-28 00:01:34 +01:00
def __init__(self, name):
self.name = name
2017-01-27 22:51:56 +01:00
self.props = {}
2016-12-28 07:10:47 +01:00
2017-01-28 00:01:34 +01:00
own = {}
class JSProtoBase(JSBase):
def __init__(self):
2017-01-28 00:01:34 +01:00
super(JSProtoBase, self).__init__('')
cls = self.__class__
while cls is not JSProtoBase:
cls = cls.__base__
2017-01-28 00:01:34 +01:00
props = cls.own.copy()
props.update(self.props)
self.props = props
self.value = {}
def __str__(self):
return ''
def get_prop(self, prop):
result = self.value.get(prop) if hasattr(self.value, 'get') else None
if result is None:
result = self.own.get(prop)
if result is None:
result = self.props.get(prop)
return result
def call_prop(self, prop, *args, **kwargs):
func = self.get_prop(prop)
if isinstance(func, _native_function):
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)
class JSObjectPrototype(JSProtoBase):
2016-12-28 07:10:47 +01:00
def __init__(self, value=None):
super(JSObjectPrototype, self).__init__()
self.value = {} if value is None else value
@staticmethod
def _constructor(value=None):
return JSObject.construct(value)
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
def _has_own_property(self, v):
return v in self.own
2016-12-28 07:10:47 +01:00
def _is_prototype_of(self, v):
2016-12-28 07:10:47 +01:00
return 'object has own prop'
def _is_property_enumerable(self, v):
return 'object is property enumerable'
2016-12-28 07:10:47 +01:00
2017-01-28 00:01:34 +01:00
own = {
'constructor': _constructor,
'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
class JSObject(JSBase):
def __init__(self):
2017-01-28 00:01:34 +01:00
super(JSObject, self).__init__(self.name)
@staticmethod
def call(value=None):
if value is null or value is undefined or value is None:
return JSObject.construct(value)
return to_object(_to_js(value))
@staticmethod
def construct(value=None):
value = _to_js(value)
# TODO set [[Prototype]], [[Class]], [[Extensible]], internal methods
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)
def _get_prototype_of(self, o):
return 'object get prototype of'
def _get_own_property_descriptor(self, o, p):
return 'object desc'
@js
def _get_own_property_names(self, o):
return list(o.own.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'
name = 'Object'
2017-01-28 00:01:34 +01:00
own = {
'length': 1,
'prototype': JSObjectPrototype(),
'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, name, body, formal_args):
if name is None and body is None and formal_args is None:
# prototype
super(JSFunctionPrototype, self).__init__()
self.f_name = ''
self.body = ''
else:
if isinstance(body, JSBase):
2017-01-27 22:51:56 +01:00
super(JSFunctionPrototype, self).__init__(body.own)
self.body = '[native code]'
elif isinstance(body, _native_function):
super(JSFunctionPrototype, self).__init__()
self.body = '[native code]'
else:
super(JSFunctionPrototype, self).__init__()
body = _to_js(body)
2017-01-29 19:10:55 +01:00
self.body = to_string(body) if body is not undefined or body is not null else ''
self.f_name = name
self.arguments = list(formal_args)
# FIXME: JSProtoBase sets body to '' instead of None
# 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
@property
def _length(self):
# Yeesh, I dare you to find anything like that in the python specification.
return len([arg for arg, init in self.arguments if init is not None])
@staticmethod
def _constructor(arguments=None):
return JSFunction.construct(arguments)
def _to_string(self):
if self.body is not None:
body = '\n'
body += '\t' + self.body if self.body else self.body
else:
body = ''
return 'function %s(%s) {%s\n}' % (
self.f_name,
', '.join(arg if init is None else arg + '=' + init for arg, init in self.arguments),
body)
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-28 00:01:34 +01:00
own = {
'length': 0,
'constructor': _constructor,
'toString': _to_string,
'apply': _apply,
'call': _call,
'bind': _bind
}
class JSFunction(JSObject):
@staticmethod
def call(formal_args=None):
return JSFunction.construct(formal_args)
@staticmethod
def construct(formal_args=None):
if formal_args is None:
body = ''
formal_args = []
else:
body = formal_args[-1] if formal_args else ''
formal_args = formal_args[:-1]
return JSFunctionPrototype('anonymous', body, formal_args)
name = 'Function'
2017-01-28 00:01:34 +01:00
own = {
'length': 1,
'prototype': JSFunctionPrototype(None, None, None)
}
2016-12-28 07:10:47 +01:00
class JSArrayPrototype(JSObjectPrototype):
2016-12-28 07:10:47 +01:00
def __init__(self, value=None):
super(JSArrayPrototype, self).__init__()
self.value = [] if value is None else list(value)
self.own = {'length': self._length}
def __str__(self):
return 'JSArrayPrototype: %s' % self.value
def __repr__(self):
return 'JSArrayPrototype(%s, %s)' % (self.value, self._length)
@property
def _length(self):
return len(self.value)
@staticmethod
def _constructor(*args):
return JSArray.construct(*args)
def _to_string(self):
2016-12-28 07:10:47 +01:00
return 'array to string'
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:
from_index = len(self.value) - 1
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-28 00:01:34 +01:00
own = {
'length': _length,
'constructor': _constructor,
'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):
@staticmethod
def call(*args):
return JSArray.construct(*args)
@staticmethod
def construct(*args):
if len(args) == 1:
if isinstance(args[0], _native_number):
return JSArrayPrototype([undefined] * args[0])
elif isinstance(args[0], JSNumberPrototype):
return JSArrayPrototype([undefined] * args[0]._value_of())
if args:
return JSArrayPrototype(args)
else:
return JSArrayPrototype()
def _is_array(self, arg):
return 'array is array'
name = 'Array'
2017-01-28 00:01:34 +01:00
own = {
'length': 1,
'prototype': JSArrayPrototype(),
'isArray': _is_array
}
class JSStringPrototype(JSObjectPrototype):
def __init__(self, value=None):
if value is None:
# prototype
value = ''
super(JSStringPrototype, self).__init__(value)
@property
def _length(self):
return len(self.value)
@staticmethod
def _constructor(value=None):
return JSString.construct(value)
def _to_string(self):
return self.value
def _value_of(self):
return self.value
def _char_at(self, pos):
return 'string char at'
def _char_code_at(self, pos):
return 'string char code at'
def _concat(self, *args):
return 'string concat'
def _index_of(self, search, pos):
return 'string index of'
def _last_index_of(self, search, pos):
return 'string last index of'
def _locale_compare(self, that):
return 'string locale compare'
def _match(self, regexp):
return 'string match'
def _replace(self, search, value):
return 'string replace'
def _search(self, regexp):
return 'string search'
def _slice(self, start, end):
return 'string slice'
def _split(self, sep):
return 'string split'
def _substring(self, start, end):
return 'string substring'
def _to_lower_case(self):
return 'string to lower case'
def _to_local_lower_case(self):
return 'string to local lower case'
def _to_upper_case(self):
return 'string to upper case'
def _to_local_upper_case(self):
return 'string to local upper case'
def _trim(self):
return 'string trim'
own = {
'length': _length,
'constructor': _constructor,
'toString': _to_string,
'valueOf': _value_of,
'charAt': _char_at,
'charCodeAt': _char_code_at,
'concat': _concat,
'indexOf': _index_of,
'lastIndexOf': _last_index_of,
'localeCompare': _locale_compare,
'match': _match,
'replace': _replace,
'search': _search,
'slice': _slice,
'split': _split,
'substring': _substring,
'toLowerCase': _to_lower_case,
'toLocalLowerCase': _to_local_lower_case,
'toUpperCase': _to_upper_case,
'toLocalUpperCase': _to_local_upper_case,
'trim': _trim
}
class JSString(JSObject):
@staticmethod
def call(value=None):
return '' if value is None else to_string(value)
@staticmethod
def construct(value=None):
return JSStringPrototype('' if value is None else to_string(value))
def _from_char_code(self, *args):
return 'String from char code'
name = 'String'
own = {
'length': 1,
'prototype': JSStringPrototype(),
'fromCharCode': _from_char_code
}
class JSBooleanPrototype(JSObjectPrototype):
pass
class JSBoolean(JSObject):
@staticmethod
def construct(value=None):
pass
class JSNumberPrototype(JSObjectPrototype):
pass
class JSNumber(JSObject):
pass
undefined = object()
null = object()
true = JSBoolean.construct(True)
false = JSBoolean.construct(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()
global_obj = JSObject.construct({'Object': JSObject(),
'Array': JSArray(),
'Function': JSFunction(),
'String': JSString()
})