From c0ef911bc66e995b4195edc492ce93d32a60c954 Mon Sep 17 00:00:00 2001 From: sulyi Date: Mon, 11 Jun 2018 12:27:02 +0200 Subject: [PATCH] [jsinterp] Adding delete and void operators - Refractors `Context` and `Reference` classes into their own module named `environment` (saves local import in `tstream`) --- youtube_dl/jsinterp2/environment.py | 63 +++++++++++++++++++++++++++++ youtube_dl/jsinterp2/jsinterp.py | 60 +-------------------------- youtube_dl/jsinterp2/tstream.py | 26 +++++++++--- 3 files changed, 85 insertions(+), 64 deletions(-) create mode 100644 youtube_dl/jsinterp2/environment.py diff --git a/youtube_dl/jsinterp2/environment.py b/youtube_dl/jsinterp2/environment.py new file mode 100644 index 000000000..ccc041fec --- /dev/null +++ b/youtube_dl/jsinterp2/environment.py @@ -0,0 +1,63 @@ +from __future__ import unicode_literals + +from ..utils import ExtractorError +from .jsbuilt_ins.base import isprimitive + + +class Context(object): + def __init__(self, variables=None, ended=False): + super(Context, self).__init__() + self.ended = ended + self.no_in = True + self.local_vars = {} + if variables is not None: + for k, v in dict(variables).items(): + # XXX validate identifiers + self.local_vars[k] = Reference(v, (self.local_vars, k)) + + +class Reference(object): + def __init__(self, value, parent_key=None): + super(Reference, self).__init__() + self.value = value + 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 + if deep: + if isinstance(self.value, (list, tuple)): + # TODO test nested arrays + 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(): + value[key] = prop.getvalue() + + return value + + def putvalue(self, value): + if self.parent is None: + raise ExtractorError('Trying to set a read-only reference') + if not hasattr(self.parent, '__setitem__'): + raise ExtractorError('Unknown reference') + self.parent.__setitem__(self.name, Reference(value, (self.parent, self.name))) + self.value = value + return value + + def __repr__(self): + if self.parent is not None: + parent, key = self.parent + return '' % ( + 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) \ No newline at end of file diff --git a/youtube_dl/jsinterp2/jsinterp.py b/youtube_dl/jsinterp2/jsinterp.py index 2f31b5e46..99238e7b4 100644 --- a/youtube_dl/jsinterp2/jsinterp.py +++ b/youtube_dl/jsinterp2/jsinterp.py @@ -4,6 +4,7 @@ import re from ..compat import compat_str from ..utils import ExtractorError +from .environment import Context, Reference from .jsparser import Parser from .jsgrammar import TokenTypes, token_keys from .jsbuilt_ins import global_obj @@ -11,64 +12,7 @@ from .jsbuilt_ins.base import isprimitive from .jsbuilt_ins.internals import to_string from .jsbuilt_ins.utils import to_js - -class Context(object): - def __init__(self, variables=None, ended=False): - super(Context, self).__init__() - self.ended = ended - self.no_in = True - self.local_vars = {} - if variables is not None: - for k, v in dict(variables).items(): - # XXX validate identifiers - self.local_vars[k] = Reference(v, (self.local_vars, k)) - - -class Reference(object): - def __init__(self, value, parent_key=None): - super(Reference, self).__init__() - self._value = value - 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 - if deep: - if isinstance(self._value, (list, tuple)): - # TODO test nested arrays - 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(): - value[key] = prop.getvalue() - - return value - - def putvalue(self, value): - if self._parent is None: - raise ExtractorError('Trying to set a read-only reference') - if not hasattr(self._parent, '__setitem__'): - raise ExtractorError('Unknown reference') - self._parent.__setitem__(self._name, Reference(value, (self._parent, self._name))) - self._value = value - return value - - def __repr__(self): - if self._parent is not None: - parent, key = self._parent - return '' % ( - 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) +# TODO use JSObject for Contex.local_vars and JSInterpreter.global_vars class JSInterpreter(object): diff --git a/youtube_dl/jsinterp2/tstream.py b/youtube_dl/jsinterp2/tstream.py index c92527a43..872f8dd4a 100644 --- a/youtube_dl/jsinterp2/tstream.py +++ b/youtube_dl/jsinterp2/tstream.py @@ -16,7 +16,8 @@ from .jsgrammar import ( UNARY_OPERATORS_RE, TokenTypes ) -from .jsbuilt_ins import false, true, nan +from .environment import Reference +from .jsbuilt_ins import false, true, nan, undefined from .jsbuilt_ins.internals import jstype, undefined_type, null_type, number_type, boolean_type, string_type @@ -25,8 +26,6 @@ def convert_to_unary(token_value): def strict_equal(x, y): - from .jsinterp import Reference - if jstype(x) != jstype(y): return False if jstype(x) in (undefined_type, null_type): @@ -46,6 +45,22 @@ def strict_equal(x, y): return False +def delete(ref): + if not isinstance(ref, Reference): + return True + if ref.value not in (None, undefined): + # XXX raise SyntaxError if ref is strict reference + return True + # XXX handle if ref is property reference + else: + # XXX raise SyntaxError if ref is strict reference (again) + if ref.name not in ref.parent: + return False + # FIXME `JSInterperter.global_vars` will be changed from `dict` to `JSObjectPrototype` + ref.parent.remove(ref.name) + return True + + _PUNCTUATIONS = { '{': TokenTypes.COPEN, '}': TokenTypes.CCLOSE, @@ -70,9 +85,8 @@ _UNARY_OPERATORS = { '--': (TokenTypes.DEC, lambda cur: cur - 1), '!': (TokenTypes.NOT, operator.not_), '~': (TokenTypes.BNOT, operator.inv), - # XXX define these operators - 'delete': (TokenTypes.DEL, None), - 'void': (TokenTypes.VOID, None), + 'delete': (TokenTypes.DEL, delete), + 'void': (TokenTypes.VOID, lambda cur: undefined), 'typeof': (TokenTypes.TYPE, lambda cur: _type_strings[jstype(cur)]) } _RELATIONS = {