diff --git a/test/js2tests/call.py b/test/js2tests/call.py index f08f96a80..57f31b798 100644 --- a/test/js2tests/call.py +++ b/test/js2tests/call.py @@ -39,8 +39,6 @@ tests = [ ]) ] }, { - # FIXME built-in functions are not yet implemented - 'exclude': ('jsinterp2',), 'code': 'function x(a) { return a.split(""); }', 'asserts': [{'value': ["a", "b", "c"], 'call': ('x', "abc")}], 'ast': [ diff --git a/test/js2tests/stringprototype.py b/test/js2tests/stringprototype.py index e7d75b35b..6c67bb23e 100644 --- a/test/js2tests/stringprototype.py +++ b/test/js2tests/stringprototype.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals skip = { 'jsinterp': 'String literals are not supported', - 'interpret': 'Builtins are not yet implemented', 'parse': 'Test is not yet implemented: missing ast' } diff --git a/test/test_jsinterp.py b/test/test_jsinterp.py index 7f631c452..3110d7960 100644 --- a/test/test_jsinterp.py +++ b/test/test_jsinterp.py @@ -22,7 +22,7 @@ __doc__ = """see: `js2tests`""" defs = gettestcases() # set level to logging.DEBUG to see messages about missing assertions # set level to logging.DEBUG to see messages about code tests are running -logging.basicConfig(stream=sys.stderr, level=logging.INFO) +logging.basicConfig(stream=sys.stderr, level=logging.WARNING) log = logging.getLogger('TestJSInterpreter') diff --git a/test/test_jsinterp2.py b/test/test_jsinterp2.py index 916f8b01e..060d458e8 100644 --- a/test/test_jsinterp2.py +++ b/test/test_jsinterp2.py @@ -22,7 +22,7 @@ __doc__ = """see: `js2tests`""" defs = gettestcases() # set level to logging.DEBUG to see messages about missing assertions # set level to logging.DEBUG to see messages about code tests are running -logging.basicConfig(stream=sys.stderr, level=logging.INFO) +logging.basicConfig(stream=sys.stderr, level=logging.WARNING) log = logging.getLogger('TestJSInterpreter2') diff --git a/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py b/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py index b87d1a03d..31e439cd5 100644 --- a/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py +++ b/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py @@ -53,5 +53,9 @@ global_obj = jsobject.JSObject.construct( 'Array': jsarray.JSArray(), 'Function': jsfunction.JSFunction(), 'String': jsstring.JSString(), - 'Number': jsnumber.JSNumber() + 'Number': jsnumber.JSNumber(), + 'false': jsboolean.false, + 'true': jsboolean.true, + 'null': base.null, + 'undefined': base.undefined }) diff --git a/youtube_dl/jsinterp2/jsbuilt_ins/base.py b/youtube_dl/jsinterp2/jsbuilt_ins/base.py index 2e63a958d..d50ec0797 100644 --- a/youtube_dl/jsinterp2/jsbuilt_ins/base.py +++ b/youtube_dl/jsinterp2/jsbuilt_ins/base.py @@ -24,10 +24,10 @@ class JSProtoBase(JSBase): 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 + cls = cls.__base__ self.value = {} def get_prop(self, prop): @@ -63,3 +63,8 @@ native_number = (int, float) native_object = dict native_array = (list, tuple) native_function = FunctionType + + +def isprimitive(value): + return (isinstance(value, (native_bool, native_string, native_number, native_object, native_array, native_function)) + or value is None) diff --git a/youtube_dl/jsinterp2/jsbuilt_ins/jsstring.py b/youtube_dl/jsinterp2/jsbuilt_ins/jsstring.py index bcabe74bd..a64b6306b 100644 --- a/youtube_dl/jsinterp2/jsbuilt_ins/jsstring.py +++ b/youtube_dl/jsinterp2/jsbuilt_ins/jsstring.py @@ -60,7 +60,9 @@ class JSStringPrototype(JSObjectPrototype): return 'string slice' def _split(self, sep): - return 'string split' + if sep == '': + return list(self.value) + return self.value.split(sep) def _substring(self, start, end): return 'string substring' diff --git a/youtube_dl/jsinterp2/jsinterp.py b/youtube_dl/jsinterp2/jsinterp.py index 5e9fe39fc..3c7654655 100644 --- a/youtube_dl/jsinterp2/jsinterp.py +++ b/youtube_dl/jsinterp2/jsinterp.py @@ -6,6 +6,10 @@ from ..compat import compat_str from ..utils import ExtractorError from .jsparser import Parser from .jsgrammar import Token, token_keys +from .jsbuilt_ins import global_obj +from .jsbuilt_ins.base import isprimitive +from .jsbuilt_ins.internals import to_string +from .jsbuilt_ins.utils import to_js class Context(object): @@ -31,7 +35,7 @@ class Reference(object): if deep: if isinstance(self._value, (list, tuple)): # TODO test nested arrays - value = [elem.getvalue() for elem in self._value] + value = [elem if isprimitive(elem) else elem.getvalue() for elem in self._value] elif isinstance(self._value, dict): value = {} for key, prop in self._value.items(): @@ -60,8 +64,6 @@ class Reference(object): class JSInterpreter(object): # TODO support json - undefined = object() - def __init__(self, code, variables=None): super(JSInterpreter, self).__init__() self.code = code @@ -116,7 +118,8 @@ class JSInterpreter(object): ref = s.getvalue() elif name is Token.VAR: for name, value in stmt[1]: - value = self.interpret_expression(value).getvalue() if value is not None else self.undefined + value = (self.interpret_expression(value).getvalue() if value is not None else + global_obj.get_prop('undefined')) self.this[name] = Reference(value, (self.this, name)) elif name is Token.EXPR: for expr in stmt[1]: @@ -158,7 +161,7 @@ class JSInterpreter(object): if lid[0] is Token.ID and args is None and tail is None: key = lid[1] if key is not None: - u = Reference(self.undefined, (self.this, key)) + u = Reference(global_obj.get_prop('undefined'), (self.this, key)) leftref = self.this[key] = u else: raise ExtractorError('Invalid left-hand side in assignment') @@ -209,16 +212,31 @@ class JSInterpreter(object): if args is not None: # TODO interpret NewExpression pass + source = None while tail is not None: tail_name, tail_value, tail = tail if tail_name is Token.FIELD: - target = target.getvalue()[tail_value] + source = to_js(target.getvalue()) + target = source.get_prop(tail_value) elif tail_name is Token.ELEM: - index = self.interpret_expression(tail_value).getvalue() - target = target.getvalue()[index] + prop = self.interpret_expression(tail_value).getvalue() + target = to_js(target.getvalue()).get_prop(to_string(to_js(prop))) elif tail_name is Token.CALL: args = (self.interpret_expression(arg).getvalue() for arg in tail_value) - target = Reference(target.getvalue()(*args)) + if isprimitive(target): + if source is None: + target = target(*args) + else: + target = target(source, *args) + else: + if source is None: + target = target.getvalue()(*args) + else: + target = target.getvalue()(source, *args) + if isprimitive(target): + target = Reference(target) + else: + target = Reference(target.getvalue()) ref = target elif name is Token.ID: diff --git a/youtube_dl/jsinterp2/jsparser.py b/youtube_dl/jsinterp2/jsparser.py index 6da7ba61b..beaddcb09 100644 --- a/youtube_dl/jsinterp2/jsparser.py +++ b/youtube_dl/jsinterp2/jsparser.py @@ -440,7 +440,7 @@ class Parser(object): # Rhino has check for args length # Rhino has experimental syntax allowing an object literal to follow a new expression else: - target = self._primary_expression(stack_top) + target = self._primary_expression(stack_top - 1) args = None return (Token.MEMBER, target, args, self._member_tail(stack_top - 1))