From 93c0bb53a672576301dafc9f45807f5052882b67 Mon Sep 17 00:00:00 2001 From: sulyi Date: Mon, 11 Jun 2018 07:47:53 +0200 Subject: [PATCH] [jsinterp] Fixing types and operators - Adds `jsbuilt_ins.nan` and `jsbuilt_ins.infinity` - Adds arithmetic operator overload to `jsbuilt_ins.jsnumber.JSNumberPrototype` - Adds equality operator overload to `jsinterp.Reference` - Adds better strict equality and typeof operator in `tstream` --- youtube_dl/jsinterp2/jsbuilt_ins/__init__.py | 1 + youtube_dl/jsinterp2/jsbuilt_ins/jsnumber.py | 38 +++++++++++++-- youtube_dl/jsinterp2/jsinterp.py | 20 ++++++-- youtube_dl/jsinterp2/tstream.py | 49 ++++++++++++++++---- 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py b/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py index 31e439cd5..c1e22bc45 100644 --- a/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py +++ b/youtube_dl/jsinterp2/jsbuilt_ins/__init__.py @@ -10,6 +10,7 @@ from . import jsnumber from .base import null, undefined from .jsboolean import false, true +from .jsnumber import infinity, nan def _eval(code): diff --git a/youtube_dl/jsinterp2/jsbuilt_ins/jsnumber.py b/youtube_dl/jsinterp2/jsbuilt_ins/jsnumber.py index e2f195c92..7e7670166 100644 --- a/youtube_dl/jsinterp2/jsbuilt_ins/jsnumber.py +++ b/youtube_dl/jsinterp2/jsbuilt_ins/jsnumber.py @@ -16,6 +16,34 @@ class JSNumberPrototype(JSObjectPrototype): self.value = value self.own = {} + def __add__(self, other): + if isinstance(other, JSNumberPrototype): + other = other.value + return JSNumberPrototype(self.value + other) + + def __sub__(self, other): + if isinstance(other, JSNumberPrototype): + other = other.value + return JSNumberPrototype(self.value - other) + + def __mul__(self, other): + if isinstance(other, JSNumberPrototype): + other = other.value + return JSNumberPrototype(self.value * other) + + def __div__(self, other): + if isinstance(other, JSNumberPrototype): + other = other.value + return JSNumberPrototype(self.value / other) + + def __neg__(self): + return JSNumberPrototype(-self.value) + + def __pos__(self): + return JSNumberPrototype(+self.value) + + # __invert__? + @staticmethod def _constructor(value=None): return JSNumber.construct(value) @@ -53,6 +81,10 @@ class JSNumberPrototype(JSObjectPrototype): } +nan = JSNumberPrototype(float('nan')) +infinity = JSNumberPrototype(float('inf')) + + class JSNumber(JSObject): @staticmethod def call(value=None): @@ -68,7 +100,7 @@ class JSNumber(JSObject): 'prototype': JSNumberPrototype(), 'MAX_VALUE': 1.7976931348623157 * 10 ** 308, 'MIN_VALUE': 5 * 10 ** (-324), - 'NAN': float('nan'), - 'NEGATIVE_INFINITY': float('-inf'), - 'POSITIVE_INFINITY': float('inf'), + 'NAN': nan, + 'NEGATIVE_INFINITY': infinity * -1, + 'POSITIVE_INFINITY': infinity, } diff --git a/youtube_dl/jsinterp2/jsinterp.py b/youtube_dl/jsinterp2/jsinterp.py index 0b5c3f63b..2f31b5e46 100644 --- a/youtube_dl/jsinterp2/jsinterp.py +++ b/youtube_dl/jsinterp2/jsinterp.py @@ -25,10 +25,13 @@ class Context(object): class Reference(object): - def __init__(self, value, parent=None): + def __init__(self, value, parent_key=None): super(Reference, self).__init__() self._value = value - self._parent = parent + if parent_key is not None: + self._parent, self._name = parent_key + else: + self._parent = self._name = None def getvalue(self, deep=False): value = self._value @@ -46,10 +49,9 @@ class Reference(object): def putvalue(self, value): if self._parent is None: raise ExtractorError('Trying to set a read-only reference') - parent, key = self._parent - if not hasattr(parent, '__setitem__'): + if not hasattr(self._parent, '__setitem__'): raise ExtractorError('Unknown reference') - parent.__setitem__(key, Reference(value, (parent, key))) + self._parent.__setitem__(self._name, Reference(value, (self._parent, self._name))) self._value = value return value @@ -60,6 +62,14 @@ class Reference(object): str(self._value), parent.__class__.__name__, id(parent), key) return '' % (self._value, None) + def __eq__(self, other): + if isinstance(other, Reference): + return self._parent is other._parent and self._name == other._name + return False + + def __ne__(self, other): + return not self.__eq__(other) + class JSInterpreter(object): # TODO support json diff --git a/youtube_dl/jsinterp2/tstream.py b/youtube_dl/jsinterp2/tstream.py index 8572cca9f..c92527a43 100644 --- a/youtube_dl/jsinterp2/tstream.py +++ b/youtube_dl/jsinterp2/tstream.py @@ -16,6 +16,35 @@ from .jsgrammar import ( UNARY_OPERATORS_RE, TokenTypes ) +from .jsbuilt_ins import false, true, nan +from .jsbuilt_ins.internals import jstype, undefined_type, null_type, number_type, boolean_type, string_type + + +def convert_to_unary(token_value): + return {TokenTypes.ADD: _UNARY_OPERATORS['+'], TokenTypes.SUB: _UNARY_OPERATORS['-']}[token_value[0]] + + +def strict_equal(x, y): + from .jsinterp import Reference + + if jstype(x) != jstype(y): + return False + if jstype(x) in (undefined_type, null_type): + return True + if jstype(x) is number_type: + if x is nan or y is nan: + return False + if x.value == y.value: + return True + return False + if jstype(x): + return x.value == y.value + if jstype(x) is boolean_type: + return (x is true and y is true) or (x is false and y is false) + if isinstance(x, Reference): + return isinstance(y, Reference) and x == y + return False + _PUNCTUATIONS = { '{': TokenTypes.COPEN, @@ -35,8 +64,8 @@ _LOGICAL_OPERATORS = { '||': (TokenTypes.OR, lambda cur, right: cur or right) } _UNARY_OPERATORS = { - '+': (TokenTypes.PLUS, lambda cur: cur), - '-': (TokenTypes.NEG, lambda cur: cur * -1), + '+': (TokenTypes.PLUS, operator.pos), + '-': (TokenTypes.NEG, operator.neg), '++': (TokenTypes.INC, lambda cur: cur + 1), '--': (TokenTypes.DEC, lambda cur: cur - 1), '!': (TokenTypes.NOT, operator.not_), @@ -44,7 +73,7 @@ _UNARY_OPERATORS = { # XXX define these operators 'delete': (TokenTypes.DEL, None), 'void': (TokenTypes.VOID, None), - 'typeof': (TokenTypes.TYPE, lambda cur: type(cur)) + 'typeof': (TokenTypes.TYPE, lambda cur: _type_strings[jstype(cur)]) } _RELATIONS = { '<': (TokenTypes.LT, operator.lt), @@ -54,8 +83,8 @@ _RELATIONS = { # XXX check python and JavaScript equality difference '==': (TokenTypes.EQ, operator.eq), '!=': (TokenTypes.NE, operator.ne), - '===': (TokenTypes.SEQ, lambda cur, right: cur == right and type(cur) == type(right)), - '!==': (TokenTypes.SNE, lambda cur, right: not cur == right or not type(cur) == type(right)), + '===': (TokenTypes.SEQ, strict_equal), + '!==': (TokenTypes.SNE, lambda cur, right: not strict_equal(cur, right)), 'in': (TokenTypes.IN, operator.contains), 'instanceof': (TokenTypes.INSTANCEOF, lambda cur, right: isinstance(cur, right)) } @@ -100,9 +129,13 @@ _input_element = re.compile(r'\s*(?:%(comment)s|%(token)s|%(lop)s|%(uop)s|%(aop) _line_terminator = re.compile(LINETERMINATORSEQ_RE) - -def convert_to_unary(token_value): - return {TokenTypes.ADD: _UNARY_OPERATORS['+'], TokenTypes.SUB: _UNARY_OPERATORS['-']}[token_value[0]] +_type_strings = { + undefined_type: 'undefinied', + null_type: 'null', + boolean_type: 'boolean', + number_type: 'number', + string_type: 'string' +} class Token(object):