From 4d386f027314ff7cd49a51237d5552061e86245d Mon Sep 17 00:00:00 2001 From: sulyi Date: Tue, 21 Feb 2017 22:02:56 +0100 Subject: [PATCH] [jsbuilt-ins] major refactor --- youtube_dl/jsinterp/jsbuilt_ins.py | 911 ------------------ youtube_dl/jsinterp/jsbuilt_ins/__init__.py | 59 ++ youtube_dl/jsinterp/jsbuilt_ins/base.py | 97 ++ youtube_dl/jsinterp/jsbuilt_ins/internals.py | 197 ++++ youtube_dl/jsinterp/jsbuilt_ins/jsarray.py | 149 +++ youtube_dl/jsinterp/jsbuilt_ins/jsboolean.py | 56 ++ youtube_dl/jsinterp/jsbuilt_ins/jsfunction.py | 97 ++ youtube_dl/jsinterp/jsbuilt_ins/jsnumber.py | 65 ++ youtube_dl/jsinterp/jsbuilt_ins/jsobject.py | 130 +++ youtube_dl/jsinterp/jsbuilt_ins/jsstring.py | 124 +++ youtube_dl/jsinterp/jsinterp.py | 6 +- 11 files changed, 977 insertions(+), 914 deletions(-) delete mode 100644 youtube_dl/jsinterp/jsbuilt_ins.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/__init__.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/base.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/internals.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/jsarray.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/jsboolean.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/jsfunction.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/jsnumber.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/jsobject.py create mode 100644 youtube_dl/jsinterp/jsbuilt_ins/jsstring.py diff --git a/youtube_dl/jsinterp/jsbuilt_ins.py b/youtube_dl/jsinterp/jsbuilt_ins.py deleted file mode 100644 index ce85eda83..000000000 --- a/youtube_dl/jsinterp/jsbuilt_ins.py +++ /dev/null @@ -1,911 +0,0 @@ -from __future__ import unicode_literals - -import re - -from math import isnan, isinf, log10 -from sys import float_info -from types import FunctionType - -from ..compat import compat_str -from .jsgrammar import __HEXADECIMAL_RE - - -def to_js(o, name=None): - 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): - 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 jstype(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 o is true or o is false: - return _boolean_type - elif isinstance(o, _native_string): - return _string_type - elif isinstance(o, _native_number): - return _number_type - elif isinstance(o, _native_object): - return _object_type - return None - - -def to_primitive(o, hint=None): - # TODO to_primitive - return o - - -def to_boolean(o): - if o is undefined or o is null: - return false - elif isinstance(o, JSBooleanPrototype): - return o.value - elif isinstance(o, JSNumberPrototype): - return true if o.value and not isnan(o.value) else false - elif isinstance(o, JSStringPrototype): - return true if o.value else false - elif isinstance(o, JSObjectPrototype): - return true - else: - raise Exception('Failed to convert type %s to Boolean (not specified)' % type(o)) - - -def to_number(o): - if o is undefined: - return float('nan') - elif o is null or isinstance(o, JSBooleanPrototype) and o.value is false: - return 0 - elif isinstance(o, JSBooleanPrototype) and o.value is true: - return 1 - elif isinstance(o, JSStringPrototype): - _STR_FLOAT_RE = r'(?:(?:[0-9]+(?:\.[0-9]*)?)|(?:\.[0-9]+))(?:[eE][+-]?[0-9]+)?' - m = re.match(r'^[\s\n]*(?P(?:[+-]*(?:Infinity|%(float)s))|%(hex)s)?[\s\n]*$' % {'float': _STR_FLOAT_RE, - 'hex': __HEXADECIMAL_RE}, - o.value) - if m: - v = m.group('value') - if v: - s = 1 if v.startswith('+') or v.startswith('-') else 0 - if v[s:] == 'Infinity': - return float(v[:s] + 'inf') # 10 ** 10000 according to spec - elif v[s:].isdigit(): - return int(v) - elif v.startswith('0x') or v.startswith('0X'): - return int(v, 16) - else: - return float(v) - else: - return 0 - else: - return float('nan') - - elif isinstance(o, JSObjectPrototype): - prim_value = to_primitive(o, 'Number') - return to_number(prim_value) - else: - raise Exception('Failed to convert type %s to Number (not specified)' % type(o)) - - -def to_integer(o): - number = to_number(o) - if isnan(number): - return 0 - elif isinf(number) or number == 0: - return number - return int(number) # equivalent to: int(copysign(floor(abs(number)), number)) - - -def to_int32(o): - number = to_number(o) - if isnan(number) or isinf(number) or number == 0: - return 0 - pos_int = int(number) - int32 = pos_int % 2 ** 32 - return int32 if int32 < 2 ** 31 else int32 - 2 ** 32 - - -def to_uint32(o): - number = to_number(o) - if isnan(number) or isinf(number) or number == 0: - return 0 - pos_int = int(number) - int32 = pos_int % 2 ** 32 - return int32 - - -def to_uint16(o): - number = to_number(o) - if isnan(number) or isinf(number) or number == 0: - return 0 - pos_int = int(number) - int16 = pos_int % 2 ** 16 - return int16 - - -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: - # numerically unstable example: 3333330000000000000.3 or 3.3333300000000000003e+20 - n = log10(ov) + 1 - n = int(n) - k = 1 - - while True: - exp = 10 ** (k - n) - s = int(ov * exp) - if abs(ov * exp - s) < float_info.epsilon: - break - k += 1 - - if s % 10 == 0: - s //= 10 - 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) - else: - raise Exception('Failed to convert type %s to String (not specified)' % type(o)) - - -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 - - -class JSBase(object): - - def __init__(self, name): - self.name = name - self.props = {} - - own = {} - - -class JSProtoBase(JSBase): - - def __init__(self): - super(JSProtoBase, self).__init__('') - cls = self.__class__ - while cls.__base__ is not JSProtoBase: - cls = cls.__base__ - props = cls.own.copy() - props.update(self.props) - self.props = props - self.value = {} - - 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) - - jsclass = '' - - -class JSObjectPrototype(JSProtoBase): - - 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' - - def _has_own_property(self, v): - return v in self.own - - def _is_prototype_of(self, v): - return 'object has own prop' - - def _is_property_enumerable(self, v): - return 'object is property enumerable' - - jsclass = 'Object' - 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 - } - - -class JSObject(JSBase): - - def __init__(self): - 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 = JSObjectPrototype.jsclass - 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): - 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) - 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") - - @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' - - jsclass = 'Function' - 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 = JSFunctionPrototype.jsclass - own = { - 'length': 1, - 'prototype': JSFunctionPrototype(None, None, None) - } - - -class JSArrayPrototype(JSObjectPrototype): - - 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): - 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' - - jsclass = 'Array' - 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 = JSArrayPrototype.jsclass - 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' - - jsclass = 'String' - 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 = JSStringPrototype.jsclass - own = { - 'length': 1, - 'prototype': JSStringPrototype(), - 'fromCharCode': _from_char_code - } - - -class JSBooleanPrototype(JSObjectPrototype): - - def __init__(self, value=None): - if value is None: - # prototype - value = False - super(JSBooleanPrototype, self).__init__(value) - - @staticmethod - def _constructor(value=None): - return JSBoolean.construct(value) - - def _to_string(self): - # TODO find way to test it in other interpreters - if jstype(self) is _boolean_type: - b = self - elif jstype(self) is _object_type and self.jsclass == 'Boolean': - b = self.value - else: - raise Exception('TypeError') - return 'true' if b is true else 'false' - - def _value_of(self): - return 'boolean value of' - - jsclass = 'Boolean' - own = { - 'constructor': _constructor, - 'toString': _to_string, - 'valueOf': _value_of - } - - -class JSBoolean(JSObject): - - @staticmethod - def call(value=None): - return to_boolean(to_js(value)) - - @staticmethod - def construct(value=None): - return JSBooleanPrototype(to_boolean(to_js(value))) - - name = JSBooleanPrototype.jsclass - own = { - 'length': 1, - 'prototype': JSBooleanPrototype() - } - - -class JSNumberPrototype(JSObjectPrototype): - - @staticmethod - def _constructor(value=None): - return JSNumber.construct(value) - - def _to_string(self, radix=None): - pass - - def _to_locale_string(self): - pass - - def _value_of(self): - if jstype(self.value) is not _number_type or isinstance(self.value, JSNumberPrototype): - # TODO find way to test it in other interpreters - raise Exception('TypeError') - return self.value - - def _to_fixed(self, frac_digits): - return 'Number toFixed' - - def _to_exponential(self, frac_digits): - return 'Number toExponential' - - def _to_precision(self, prec): - return 'Number toPrecision' - - jsclass = 'Number' - own = { - 'constructor': _constructor, - 'toString': _to_string, - 'toLocaleString': _to_locale_string, - 'valueOf': _value_of, - 'toFixed': _to_fixed, - 'toExponential': _to_exponential, - 'toPrecision': _to_precision - } - - -class JSNumber(JSObject): - @staticmethod - def call(value=None): - return to_number(to_js(value)) if value is not None else 0 - - @staticmethod - def construct(value=None): - return JSNumberPrototype(to_number(to_js(value)) if value is not None else 0) - - name = JSNumberPrototype.jsclass - own = { - 'length': 1, - 'prototype': JSNumberPrototype(), - 'MAX_VALUE': 1.7976931348623157 * 10 ** 308, - 'MIN_VALUE': 5 * 10 ** (-324), - 'NAN': float('nan'), - 'NEGATIVE_INFINITY': float('-inf'), - 'POSITIVE_INFINITY': float('inf'), - } - - -def _eval(code): - pass - - -def _parse_int(string, radix): - pass - - -def _parse_float(string): - pass - - -def _is_nan(number): - pass - - -def _is_infinite(number): - pass - - -def _decode_uri(encoded_uri): - pass - - -def _decode_uri_component (encoded_uri_component): - pass - - -def _encode_uri(uri): - pass - - -def _encode_uri_component(uri_component): - pass - - -undefined = JSBase('undefined') -null = JSBase('null') -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() - -global_obj = JSObject.construct({'Object': JSObject(), - 'Array': JSArray(), - 'Function': JSFunction(), - 'String': JSString() - }) diff --git a/youtube_dl/jsinterp/jsbuilt_ins/__init__.py b/youtube_dl/jsinterp/jsbuilt_ins/__init__.py new file mode 100644 index 000000000..a6a07f11e --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/__init__.py @@ -0,0 +1,59 @@ +from __future__ import unicode_literals + +from . import base +from . import jsobject +from . import jsfunction +from . import jsarray +from . import jsboolean +from . import jsstring +from . import jsnumber + +undefined = base.JSBase('undefined') +null = base.JSBase('null') +true = jsboolean.JSBooleanPrototype(True) +false = jsboolean.JSBooleanPrototype(False) + + +def _eval(code): + pass + + +def _parse_int(string, radix): + pass + + +def _parse_float(string): + pass + + +def _is_nan(number): + pass + + +def _is_infinite(number): + pass + + +def _decode_uri(encoded_uri): + pass + + +def _decode_uri_component(encoded_uri_component): + pass + + +def _encode_uri(uri): + pass + + +def _encode_uri_component(uri_component): + pass + + +global_obj = jsobject.JSObject.construct( + {'Object': jsobject.JSObject(), + 'Array': jsarray.JSArray(), + 'Function': jsfunction.JSFunction(), + 'String': jsstring.JSString(), + 'Number': jsnumber.JSNumber() + }) diff --git a/youtube_dl/jsinterp/jsbuilt_ins/base.py b/youtube_dl/jsinterp/jsbuilt_ins/base.py new file mode 100644 index 000000000..9d8099f66 --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/base.py @@ -0,0 +1,97 @@ +from __future__ import unicode_literals + +from types import FunctionType + +from ...compat import compat_str +from . import undefined +from .jsobject import JSObjectPrototype +from .jsfunction import JSFunctionPrototype +from .jsarray import JSArrayPrototype +from .jsboolean import JSBooleanPrototype +from .jsstring import JSStringPrototype +from .jsnumber import JSNumberPrototype + + +class JSBase(object): + + def __init__(self, name): + self.name = name + self.props = {} + + own = {} + + +class JSProtoBase(JSBase): + + def __init__(self): + super(JSProtoBase, self).__init__('') + cls = self.__class__ + while cls.__base__ is not JSProtoBase: + cls = cls.__base__ + props = cls.own.copy() + props.update(self.props) + self.props = props + self.value = {} + + 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) + + jsclass = '' + + +def to_js(o, name=None): + 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): + 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 + + +native_bool = bool +native_string = compat_str +native_number = (int, float) +native_object = dict +native_array = (list, tuple) +native_function = FunctionType diff --git a/youtube_dl/jsinterp/jsbuilt_ins/internals.py b/youtube_dl/jsinterp/jsbuilt_ins/internals.py new file mode 100644 index 000000000..e2a56b1b8 --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/internals.py @@ -0,0 +1,197 @@ +from __future__ import unicode_literals + +import re +from math import isnan, isinf, log10 +from sys import float_info + +from . import undefined, null, true, false +from .base import to_js, native_bool, native_string, native_number, native_object +from .jsobject import JSObjectPrototype +from .jsboolean import JSBooleanPrototype +from .jsstring import JSStringPrototype +from .jsnumber import JSNumberPrototype +from ..jsgrammar import __HEXADECIMAL_RE + + +def jstype(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 o is true or o is false: + return boolean_type + elif isinstance(o, native_string): + return string_type + elif isinstance(o, native_number): + return number_type + elif isinstance(o, native_object): + return object_type + return None + + +def to_primitive(o, hint=None): + # TODO to_primitive + return o + + +def to_boolean(o): + if o is undefined or o is null: + return false + elif isinstance(o, JSBooleanPrototype): + return o.value + elif isinstance(o, JSNumberPrototype): + return true if o.value and not isnan(o.value) else false + elif isinstance(o, JSStringPrototype): + return true if o.value else false + elif isinstance(o, JSObjectPrototype): + return true + else: + raise Exception('Failed to convert type %s to Boolean (not specified)' % type(o)) + + +def to_number(o): + if o is undefined: + return float('nan') + elif o is null or isinstance(o, JSBooleanPrototype) and o.value is false: + return 0 + elif isinstance(o, JSBooleanPrototype) and o.value is true: + return 1 + elif isinstance(o, JSStringPrototype): + _STR_FLOAT_RE = r'(?:(?:[0-9]+(?:\.[0-9]*)?)|(?:\.[0-9]+))(?:[eE][+-]?[0-9]+)?' + m = re.match(r'^[\s\n]*(?P(?:[+-]*(?:Infinity|%(float)s))|%(hex)s)?[\s\n]*$' % {'float': _STR_FLOAT_RE, + 'hex': __HEXADECIMAL_RE}, + o.value) + if m: + v = m.group('value') + if v: + s = 1 if v.startswith('+') or v.startswith('-') else 0 + if v[s:] == 'Infinity': + return float(v[:s] + 'inf') # 10 ** 10000 according to spec + elif v[s:].isdigit(): + return int(v) + elif v.startswith('0x') or v.startswith('0X'): + return int(v, 16) + else: + return float(v) + else: + return 0 + else: + return float('nan') + + elif isinstance(o, JSObjectPrototype): + prim_value = to_primitive(o, 'Number') + return to_number(prim_value) + else: + raise Exception('Failed to convert type %s to Number (not specified)' % type(o)) + + +def to_integer(o): + number = to_number(o) + if isnan(number): + return 0 + elif isinf(number) or number == 0: + return number + return int(number) # equivalent to: int(copysign(floor(abs(number)), number)) + + +def to_int32(o): + number = to_number(o) + if isnan(number) or isinf(number) or number == 0: + return 0 + pos_int = int(number) + int32 = pos_int % 2 ** 32 + return int32 if int32 < 2 ** 31 else int32 - 2 ** 32 + + +def to_uint32(o): + number = to_number(o) + if isnan(number) or isinf(number) or number == 0: + return 0 + pos_int = int(number) + int32 = pos_int % 2 ** 32 + return int32 + + +def to_uint16(o): + number = to_number(o) + if isnan(number) or isinf(number) or number == 0: + return 0 + pos_int = int(number) + int16 = pos_int % 2 ** 16 + return int16 + + +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: + # numerically unstable example: 3333330000000000000.3 or 3.3333300000000000003e+20 + n = log10(ov) + 1 + n = int(n) + k = 1 + + while True: + exp = 10 ** (k - n) + s = int(ov * exp) + if abs(ov * exp - s) < float_info.epsilon: + break + k += 1 + + if s % 10 == 0: + s //= 10 + 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) + else: + raise Exception('Failed to convert type %s to String (not specified)' % type(o)) + + +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 + + +undefined_type = object() +null_type = object() +boolean_type = object() +string_type = object() +number_type = object() +object_type = object() diff --git a/youtube_dl/jsinterp/jsbuilt_ins/jsarray.py b/youtube_dl/jsinterp/jsbuilt_ins/jsarray.py new file mode 100644 index 000000000..8e7a4da71 --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/jsarray.py @@ -0,0 +1,149 @@ +from __future__ import unicode_literals + +from . import undefined +from .base import native_number +from .jsobject import JSObject, JSObjectPrototype +from .jsnumber import JSNumberPrototype + + +class JSArrayPrototype(JSObjectPrototype): + + 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): + 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' + + jsclass = 'Array' + 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 = JSArrayPrototype.jsclass + own = { + 'length': 1, + 'prototype': JSArrayPrototype(), + 'isArray': _is_array + } diff --git a/youtube_dl/jsinterp/jsbuilt_ins/jsboolean.py b/youtube_dl/jsinterp/jsbuilt_ins/jsboolean.py new file mode 100644 index 000000000..4df25578a --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/jsboolean.py @@ -0,0 +1,56 @@ +from __future__ import unicode_literals + +from . import true +from .internals import jstype, boolean_type, object_type, to_boolean +from .base import to_js +from .jsobject import JSObject, JSObjectPrototype + + +class JSBooleanPrototype(JSObjectPrototype): + + def __init__(self, value=None): + if value is None: + # prototype + value = False + super(JSBooleanPrototype, self).__init__(value) + + @staticmethod + def _constructor(value=None): + return JSBoolean.construct(value) + + def _to_string(self): + # TODO find way to test it in other interpreters + if jstype(self) is boolean_type: + b = self + elif jstype(self) is object_type and self.jsclass == 'Boolean': + b = self.value + else: + raise Exception('TypeError') + return 'true' if b is true else 'false' + + def _value_of(self): + return 'boolean value of' + + jsclass = 'Boolean' + own = { + 'constructor': _constructor, + 'toString': _to_string, + 'valueOf': _value_of + } + + +class JSBoolean(JSObject): + + @staticmethod + def call(value=None): + return to_boolean(to_js(value)) + + @staticmethod + def construct(value=None): + return JSBooleanPrototype(to_boolean(to_js(value))) + + name = JSBooleanPrototype.jsclass + own = { + 'length': 1, + 'prototype': JSBooleanPrototype() + } diff --git a/youtube_dl/jsinterp/jsbuilt_ins/jsfunction.py b/youtube_dl/jsinterp/jsbuilt_ins/jsfunction.py new file mode 100644 index 000000000..e83cdc308 --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/jsfunction.py @@ -0,0 +1,97 @@ +from __future__ import unicode_literals + +from . import undefined, null +from .internals import to_string +from .base import to_js, native_function, JSBase +from .jsobject import JSObject, JSObjectPrototype + + +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): + 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) + 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") + + @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' + + jsclass = 'Function' + 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 = JSFunctionPrototype.jsclass + own = { + 'length': 1, + 'prototype': JSFunctionPrototype(None, None, None) + } diff --git a/youtube_dl/jsinterp/jsbuilt_ins/jsnumber.py b/youtube_dl/jsinterp/jsbuilt_ins/jsnumber.py new file mode 100644 index 000000000..2ff893ec0 --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/jsnumber.py @@ -0,0 +1,65 @@ +from __future__ import unicode_literals + +from .internals import jstype, number_type, to_number +from .base import to_js +from .jsobject import JSObject, JSObjectPrototype + + +class JSNumberPrototype(JSObjectPrototype): + + @staticmethod + def _constructor(value=None): + return JSNumber.construct(value) + + def _to_string(self, radix=None): + pass + + def _to_locale_string(self): + pass + + def _value_of(self): + if jstype(self.value) is not number_type or isinstance(self.value, JSNumberPrototype): + # TODO find way to test it in other interpreters + raise Exception('TypeError') + return self.value + + def _to_fixed(self, frac_digits): + return 'Number toFixed' + + def _to_exponential(self, frac_digits): + return 'Number toExponential' + + def _to_precision(self, prec): + return 'Number toPrecision' + + jsclass = 'Number' + own = { + 'constructor': _constructor, + 'toString': _to_string, + 'toLocaleString': _to_locale_string, + 'valueOf': _value_of, + 'toFixed': _to_fixed, + 'toExponential': _to_exponential, + 'toPrecision': _to_precision + } + + +class JSNumber(JSObject): + @staticmethod + def call(value=None): + return to_number(to_js(value)) if value is not None else 0 + + @staticmethod + def construct(value=None): + return JSNumberPrototype(to_number(to_js(value)) if value is not None else 0) + + name = JSNumberPrototype.jsclass + own = { + 'length': 1, + 'prototype': JSNumberPrototype(), + 'MAX_VALUE': 1.7976931348623157 * 10 ** 308, + 'MIN_VALUE': 5 * 10 ** (-324), + 'NAN': float('nan'), + 'NEGATIVE_INFINITY': float('-inf'), + 'POSITIVE_INFINITY': float('inf'), + } diff --git a/youtube_dl/jsinterp/jsbuilt_ins/jsobject.py b/youtube_dl/jsinterp/jsbuilt_ins/jsobject.py new file mode 100644 index 000000000..147611eca --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/jsobject.py @@ -0,0 +1,130 @@ +from __future__ import unicode_literals + +from . import null, undefined +from .base import JSProtoBase, to_js, js, JSBase +from .internals import to_object +from .jsboolean import JSBooleanPrototype +from .jsnumber import JSNumberPrototype +from .jsstring import JSStringPrototype + + +class JSObjectPrototype(JSProtoBase): + + 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' + + def _has_own_property(self, v): + return v in self.own + + def _is_prototype_of(self, v): + return 'object has own prop' + + def _is_property_enumerable(self, v): + return 'object is property enumerable' + + jsclass = 'Object' + 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 + } + + +class JSObject(JSBase): + + def __init__(self): + 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 = JSObjectPrototype.jsclass + 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 + } diff --git a/youtube_dl/jsinterp/jsbuilt_ins/jsstring.py b/youtube_dl/jsinterp/jsbuilt_ins/jsstring.py new file mode 100644 index 000000000..9243fb98f --- /dev/null +++ b/youtube_dl/jsinterp/jsbuilt_ins/jsstring.py @@ -0,0 +1,124 @@ +from __future__ import unicode_literals + +from .internals import to_string +from .jsobject import JSObject, JSObjectPrototype + + +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' + + jsclass = 'String' + 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 = JSStringPrototype.jsclass + own = { + 'length': 1, + 'prototype': JSStringPrototype(), + 'fromCharCode': _from_char_code + } diff --git a/youtube_dl/jsinterp/jsinterp.py b/youtube_dl/jsinterp/jsinterp.py index 826c78b17..fdcb7bf65 100644 --- a/youtube_dl/jsinterp/jsinterp.py +++ b/youtube_dl/jsinterp/jsinterp.py @@ -2,11 +2,11 @@ from __future__ import unicode_literals import re +from . import jsbuilt_ins +from .jsgrammar import Token, token_keys +from .tstream import TokenStream, convert_to_unary from ..compat import compat_str from ..utils import ExtractorError -from . import jsbuilt_ins -from .tstream import TokenStream, convert_to_unary -from .jsgrammar import Token, token_keys class Context(object):