|
|
|
@ -240,7 +240,7 @@ def _js_ternary(cndn, if_true=True, if_false=False):
|
|
|
|
|
def _js_unary_op(op):
|
|
|
|
|
|
|
|
|
|
@wraps_op(op)
|
|
|
|
|
def wrapped(_, a):
|
|
|
|
|
def wrapped(a, _):
|
|
|
|
|
return op(a)
|
|
|
|
|
|
|
|
|
|
return wrapped
|
|
|
|
@ -283,17 +283,6 @@ _OPERATORS = (
|
|
|
|
|
('**', _js_exp),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_COMP_OPERATORS = (
|
|
|
|
|
('===', _js_id_op(operator.is_)),
|
|
|
|
|
('!==', _js_id_op(operator.is_not)),
|
|
|
|
|
('==', _js_eq),
|
|
|
|
|
('!=', _js_neq),
|
|
|
|
|
('<=', _js_comp_op(operator.le)),
|
|
|
|
|
('>=', _js_comp_op(operator.ge)),
|
|
|
|
|
('<', _js_comp_op(operator.lt)),
|
|
|
|
|
('>', _js_comp_op(operator.gt)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_LOG_OPERATORS = (
|
|
|
|
|
('|', _js_bit_op(operator.or_)),
|
|
|
|
|
('^', _js_bit_op(operator.xor)),
|
|
|
|
@ -310,13 +299,27 @@ _SC_OPERATORS = (
|
|
|
|
|
_UNARY_OPERATORS_X = (
|
|
|
|
|
('void', _js_unary_op(lambda _: JS_Undefined)),
|
|
|
|
|
('typeof', _js_unary_op(_js_typeof)),
|
|
|
|
|
# avoid functools.partial here since Py2 update_wrapper(partial) -> no __module__
|
|
|
|
|
('!', _js_unary_op(lambda x: _js_ternary(x, if_true=False, if_false=True))),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_OPERATOR_RE = '|'.join(map(lambda x: re.escape(x[0]), _OPERATORS + _LOG_OPERATORS))
|
|
|
|
|
_COMP_OPERATORS = (
|
|
|
|
|
('===', _js_id_op(operator.is_)),
|
|
|
|
|
('!==', _js_id_op(operator.is_not)),
|
|
|
|
|
('==', _js_eq),
|
|
|
|
|
('!=', _js_neq),
|
|
|
|
|
('<=', _js_comp_op(operator.le)),
|
|
|
|
|
('>=', _js_comp_op(operator.ge)),
|
|
|
|
|
('<', _js_comp_op(operator.lt)),
|
|
|
|
|
('>', _js_comp_op(operator.gt)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_OPERATOR_RE = '|'.join(map(lambda x: re.escape(x[0]), _OPERATORS + _LOG_OPERATORS + _SC_OPERATORS))
|
|
|
|
|
|
|
|
|
|
_NAME_RE = r'[a-zA-Z_$][\w$]*'
|
|
|
|
|
_MATCHING_PARENS = dict(zip(*zip('()', '{}', '[]')))
|
|
|
|
|
_QUOTES = '\'"/'
|
|
|
|
|
_NESTED_BRACKETS = r'[^[\]]+(?:\[[^[\]]+(?:\[[^\]]+\])?\])?'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JS_Break(ExtractorError):
|
|
|
|
@ -353,7 +356,7 @@ class LocalNameSpace(ChainMap):
|
|
|
|
|
raise NotImplementedError('Deleting is not supported')
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return 'LocalNameSpace%s' % (self.maps, )
|
|
|
|
|
return 'LocalNameSpace({0!r})'.format(self.maps)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Debugger(object):
|
|
|
|
@ -374,6 +377,9 @@ class Debugger(object):
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def wrap_interpreter(cls, f):
|
|
|
|
|
if not cls.ENABLED:
|
|
|
|
|
return f
|
|
|
|
|
|
|
|
|
|
@wraps(f)
|
|
|
|
|
def interpret_statement(self, stmt, local_vars, allow_recursion, *args, **kwargs):
|
|
|
|
|
if cls.ENABLED and stmt.strip():
|
|
|
|
@ -414,7 +420,17 @@ class JSInterpreter(object):
|
|
|
|
|
msg = '{0} in: {1!r:.100}'.format(msg.rstrip(), expr)
|
|
|
|
|
super(JSInterpreter.Exception, self).__init__(msg, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
class JS_RegExp(object):
|
|
|
|
|
class JS_Object(object):
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
|
if hasattr(self, key):
|
|
|
|
|
return getattr(self, key)
|
|
|
|
|
raise KeyError(key)
|
|
|
|
|
|
|
|
|
|
def dump(self):
|
|
|
|
|
"""Serialise the instance"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
class JS_RegExp(JS_Object):
|
|
|
|
|
RE_FLAGS = {
|
|
|
|
|
# special knowledge: Python's re flags are bitmask values, current max 128
|
|
|
|
|
# invent new bitmask values well above that for literal parsing
|
|
|
|
@ -435,16 +451,24 @@ class JSInterpreter(object):
|
|
|
|
|
def __init__(self, pattern_txt, flags=0):
|
|
|
|
|
if isinstance(flags, compat_str):
|
|
|
|
|
flags, _ = self.regex_flags(flags)
|
|
|
|
|
# First, avoid https://github.com/python/cpython/issues/74534
|
|
|
|
|
self.__self = None
|
|
|
|
|
pattern_txt = str_or_none(pattern_txt) or '(?:)'
|
|
|
|
|
self.__pattern_txt = pattern_txt.replace('[[', r'[\[')
|
|
|
|
|
# escape unintended embedded flags
|
|
|
|
|
pattern_txt = re.sub(
|
|
|
|
|
r'(\(\?)([aiLmsux]*)(-[imsx]+:|(?<!\?)\))',
|
|
|
|
|
lambda m: ''.join(
|
|
|
|
|
(re.escape(m.group(1)), m.group(2), re.escape(m.group(3)))
|
|
|
|
|
if m.group(3) == ')'
|
|
|
|
|
else ('(?:', m.group(2), m.group(3))),
|
|
|
|
|
pattern_txt)
|
|
|
|
|
# Avoid https://github.com/python/cpython/issues/74534
|
|
|
|
|
self.source = pattern_txt.replace('[[', r'[\[')
|
|
|
|
|
self.__flags = flags
|
|
|
|
|
|
|
|
|
|
def __instantiate(self):
|
|
|
|
|
if self.__self:
|
|
|
|
|
return
|
|
|
|
|
self.__self = re.compile(self.__pattern_txt, self.__flags)
|
|
|
|
|
self.__self = re.compile(self.source, self.__flags)
|
|
|
|
|
# Thx: https://stackoverflow.com/questions/44773522/setattr-on-python2-sre-sre-pattern
|
|
|
|
|
for name in dir(self.__self):
|
|
|
|
|
# Only these? Obviously __class__, __init__.
|
|
|
|
@ -452,16 +476,15 @@ class JSInterpreter(object):
|
|
|
|
|
# that can't be setattr'd but also can't need to be copied.
|
|
|
|
|
if name in ('__class__', '__init__', '__weakref__'):
|
|
|
|
|
continue
|
|
|
|
|
setattr(self, name, getattr(self.__self, name))
|
|
|
|
|
if name == 'flags':
|
|
|
|
|
setattr(self, name, getattr(self.__self, name, self.__flags))
|
|
|
|
|
else:
|
|
|
|
|
setattr(self, name, getattr(self.__self, name))
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
|
self.__instantiate()
|
|
|
|
|
# make Py 2.6 conform to its lying documentation
|
|
|
|
|
if name == 'flags':
|
|
|
|
|
self.flags = self.__flags
|
|
|
|
|
return self.flags
|
|
|
|
|
elif name == 'pattern':
|
|
|
|
|
self.pattern = self.__pattern_txt
|
|
|
|
|
if name == 'pattern':
|
|
|
|
|
self.pattern = self.source
|
|
|
|
|
return self.pattern
|
|
|
|
|
elif hasattr(self.__self, name):
|
|
|
|
|
v = getattr(self.__self, name)
|
|
|
|
@ -469,6 +492,26 @@ class JSInterpreter(object):
|
|
|
|
|
return v
|
|
|
|
|
elif name in ('groupindex', 'groups'):
|
|
|
|
|
return 0 if name == 'groupindex' else {}
|
|
|
|
|
else:
|
|
|
|
|
flag_attrs = ( # order by 2nd elt
|
|
|
|
|
('hasIndices', 'd'),
|
|
|
|
|
('global', 'g'),
|
|
|
|
|
('ignoreCase', 'i'),
|
|
|
|
|
('multiline', 'm'),
|
|
|
|
|
('dotAll', 's'),
|
|
|
|
|
('unicode', 'u'),
|
|
|
|
|
('unicodeSets', 'v'),
|
|
|
|
|
('sticky', 'y'),
|
|
|
|
|
)
|
|
|
|
|
for k, c in flag_attrs:
|
|
|
|
|
if name == k:
|
|
|
|
|
return bool(self.RE_FLAGS[c] & self.__flags)
|
|
|
|
|
else:
|
|
|
|
|
if name == 'flags':
|
|
|
|
|
return ''.join(
|
|
|
|
|
(c if self.RE_FLAGS[c] & self.__flags else '')
|
|
|
|
|
for _, c in flag_attrs)
|
|
|
|
|
|
|
|
|
|
raise AttributeError('{0} has no attribute named {1}'.format(self, name))
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@ -482,7 +525,16 @@ class JSInterpreter(object):
|
|
|
|
|
flags |= cls.RE_FLAGS[ch]
|
|
|
|
|
return flags, expr[idx + 1:]
|
|
|
|
|
|
|
|
|
|
class JS_Date(object):
|
|
|
|
|
def dump(self):
|
|
|
|
|
return '(/{0}/{1})'.format(
|
|
|
|
|
re.sub(r'(?<!\\)/', r'\/', self.source),
|
|
|
|
|
self.flags)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def escape(string_):
|
|
|
|
|
return re.escape(string_)
|
|
|
|
|
|
|
|
|
|
class JS_Date(JS_Object):
|
|
|
|
|
_t = None
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@ -549,6 +601,9 @@ class JSInterpreter(object):
|
|
|
|
|
def valueOf(self):
|
|
|
|
|
return _NaN if self._t is None else self._t
|
|
|
|
|
|
|
|
|
|
def dump(self):
|
|
|
|
|
return '(new Date({0}))'.format(self.toString())
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def __op_chars(cls):
|
|
|
|
|
op_chars = set(';,[')
|
|
|
|
@ -652,6 +707,68 @@ class JSInterpreter(object):
|
|
|
|
|
_SC_OPERATORS, _LOG_OPERATORS, _COMP_OPERATORS, _OPERATORS, _UNARY_OPERATORS_X))
|
|
|
|
|
return _cached
|
|
|
|
|
|
|
|
|
|
def _separate_at_op(self, expr, max_split=None):
|
|
|
|
|
|
|
|
|
|
for op, _ in self._all_operators():
|
|
|
|
|
# hackety: </> have higher priority than <</>>, but don't confuse them
|
|
|
|
|
skip_delim = (op + op) if op in '<>*?' else None
|
|
|
|
|
if op == '?':
|
|
|
|
|
skip_delim = (skip_delim, '?.')
|
|
|
|
|
separated = list(self._separate(expr, op, skip_delims=skip_delim))
|
|
|
|
|
if len(separated) < 2:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
right_expr = separated.pop()
|
|
|
|
|
# handle operators that are both unary and binary, minimal BODMAS
|
|
|
|
|
if op in ('+', '-'):
|
|
|
|
|
# simplify/adjust consecutive instances of these operators
|
|
|
|
|
undone = 0
|
|
|
|
|
separated = [s.strip() for s in separated]
|
|
|
|
|
while len(separated) > 1 and not separated[-1]:
|
|
|
|
|
undone += 1
|
|
|
|
|
separated.pop()
|
|
|
|
|
if op == '-' and undone % 2 != 0:
|
|
|
|
|
right_expr = op + right_expr
|
|
|
|
|
elif op == '+':
|
|
|
|
|
while len(separated) > 1 and set(separated[-1]) <= self.OP_CHARS:
|
|
|
|
|
right_expr = separated.pop() + right_expr
|
|
|
|
|
if separated[-1][-1:] in self.OP_CHARS:
|
|
|
|
|
right_expr = separated.pop() + right_expr
|
|
|
|
|
# hanging op at end of left => unary + (strip) or - (push right)
|
|
|
|
|
separated.append(right_expr)
|
|
|
|
|
dm_ops = ('*', '%', '/', '**')
|
|
|
|
|
dm_chars = set(''.join(dm_ops))
|
|
|
|
|
|
|
|
|
|
def yield_terms(s):
|
|
|
|
|
skip = False
|
|
|
|
|
for i, term in enumerate(s[:-1]):
|
|
|
|
|
if skip:
|
|
|
|
|
skip = False
|
|
|
|
|
continue
|
|
|
|
|
if not (dm_chars & set(term)):
|
|
|
|
|
yield term
|
|
|
|
|
continue
|
|
|
|
|
for dm_op in dm_ops:
|
|
|
|
|
bodmas = list(self._separate(term, dm_op, skip_delims=skip_delim))
|
|
|
|
|
if len(bodmas) > 1 and not bodmas[-1].strip():
|
|
|
|
|
bodmas[-1] = (op if op == '-' else '') + s[i + 1]
|
|
|
|
|
yield dm_op.join(bodmas)
|
|
|
|
|
skip = True
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
if term:
|
|
|
|
|
yield term
|
|
|
|
|
|
|
|
|
|
if not skip and s[-1]:
|
|
|
|
|
yield s[-1]
|
|
|
|
|
|
|
|
|
|
separated = list(yield_terms(separated))
|
|
|
|
|
right_expr = separated.pop() if len(separated) > 1 else None
|
|
|
|
|
expr = op.join(separated)
|
|
|
|
|
if right_expr is None:
|
|
|
|
|
continue
|
|
|
|
|
return op, separated, right_expr
|
|
|
|
|
|
|
|
|
|
def _operator(self, op, left_val, right_expr, expr, local_vars, allow_recursion):
|
|
|
|
|
if op in ('||', '&&'):
|
|
|
|
|
if (op == '&&') ^ _js_ternary(left_val):
|
|
|
|
@ -662,7 +779,7 @@ class JSInterpreter(object):
|
|
|
|
|
elif op == '?':
|
|
|
|
|
right_expr = _js_ternary(left_val, *self._separate(right_expr, ':', 1))
|
|
|
|
|
|
|
|
|
|
right_val = self.interpret_expression(right_expr, local_vars, allow_recursion)
|
|
|
|
|
right_val = self.interpret_expression(right_expr, local_vars, allow_recursion) if right_expr else left_val
|
|
|
|
|
opfunc = op and next((v for k, v in self._all_operators() if k == op), None)
|
|
|
|
|
if not opfunc:
|
|
|
|
|
return right_val
|
|
|
|
@ -707,51 +824,9 @@ class JSInterpreter(object):
|
|
|
|
|
_FINALLY_RE = re.compile(r'finally\s*\{')
|
|
|
|
|
_SWITCH_RE = re.compile(r'switch\s*\(')
|
|
|
|
|
|
|
|
|
|
def handle_operators(self, expr, local_vars, allow_recursion):
|
|
|
|
|
|
|
|
|
|
for op, _ in self._all_operators():
|
|
|
|
|
# hackety: </> have higher priority than <</>>, but don't confuse them
|
|
|
|
|
skip_delim = (op + op) if op in '<>*?' else None
|
|
|
|
|
if op == '?':
|
|
|
|
|
skip_delim = (skip_delim, '?.')
|
|
|
|
|
separated = list(self._separate(expr, op, skip_delims=skip_delim))
|
|
|
|
|
if len(separated) < 2:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
right_expr = separated.pop()
|
|
|
|
|
# handle operators that are both unary and binary, minimal BODMAS
|
|
|
|
|
if op in ('+', '-'):
|
|
|
|
|
# simplify/adjust consecutive instances of these operators
|
|
|
|
|
undone = 0
|
|
|
|
|
separated = [s.strip() for s in separated]
|
|
|
|
|
while len(separated) > 1 and not separated[-1]:
|
|
|
|
|
undone += 1
|
|
|
|
|
separated.pop()
|
|
|
|
|
if op == '-' and undone % 2 != 0:
|
|
|
|
|
right_expr = op + right_expr
|
|
|
|
|
elif op == '+':
|
|
|
|
|
while len(separated) > 1 and set(separated[-1]) <= self.OP_CHARS:
|
|
|
|
|
right_expr = separated.pop() + right_expr
|
|
|
|
|
if separated[-1][-1:] in self.OP_CHARS:
|
|
|
|
|
right_expr = separated.pop() + right_expr
|
|
|
|
|
# hanging op at end of left => unary + (strip) or - (push right)
|
|
|
|
|
left_val = separated[-1] if separated else ''
|
|
|
|
|
for dm_op in ('*', '%', '/', '**'):
|
|
|
|
|
bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim))
|
|
|
|
|
if len(bodmas) > 1 and not bodmas[-1].strip():
|
|
|
|
|
expr = op.join(separated) + op + right_expr
|
|
|
|
|
if len(separated) > 1:
|
|
|
|
|
separated.pop()
|
|
|
|
|
right_expr = op.join((left_val, right_expr))
|
|
|
|
|
else:
|
|
|
|
|
separated = [op.join((left_val, right_expr))]
|
|
|
|
|
right_expr = None
|
|
|
|
|
break
|
|
|
|
|
if right_expr is None:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
left_val = self.interpret_expression(op.join(separated), local_vars, allow_recursion)
|
|
|
|
|
return self._operator(op, left_val, right_expr, expr, local_vars, allow_recursion), True
|
|
|
|
|
def _eval_operator(self, op, left_expr, right_expr, expr, local_vars, allow_recursion):
|
|
|
|
|
left_val = self.interpret_expression(left_expr, local_vars, allow_recursion)
|
|
|
|
|
return self._operator(op, left_val, right_expr, expr, local_vars, allow_recursion)
|
|
|
|
|
|
|
|
|
|
@Debugger.wrap_interpreter
|
|
|
|
|
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
|
|
|
|
@ -807,15 +882,19 @@ class JSInterpreter(object):
|
|
|
|
|
else:
|
|
|
|
|
raise self.Exception('Unsupported object {obj:.100}'.format(**locals()), expr=expr)
|
|
|
|
|
|
|
|
|
|
# apply unary operators (see new above)
|
|
|
|
|
for op, _ in _UNARY_OPERATORS_X:
|
|
|
|
|
if not expr.startswith(op):
|
|
|
|
|
continue
|
|
|
|
|
operand = expr[len(op):]
|
|
|
|
|
if not operand or operand[0] != ' ':
|
|
|
|
|
if not operand or (op.isalpha() and operand[0] != ' '):
|
|
|
|
|
continue
|
|
|
|
|
op_result = self.handle_operators(expr, local_vars, allow_recursion)
|
|
|
|
|
if op_result:
|
|
|
|
|
return op_result[0], should_return
|
|
|
|
|
separated = self._separate_at_op(operand, max_split=1)
|
|
|
|
|
if separated:
|
|
|
|
|
next_op, separated, right_expr = separated
|
|
|
|
|
separated.append(right_expr)
|
|
|
|
|
operand = next_op.join(separated)
|
|
|
|
|
return self._eval_operator(op, operand, '', expr, local_vars, allow_recursion), should_return
|
|
|
|
|
|
|
|
|
|
if expr.startswith('{'):
|
|
|
|
|
inner, outer = self._separate_at_paren(expr)
|
|
|
|
@ -1010,15 +1089,18 @@ class JSInterpreter(object):
|
|
|
|
|
|
|
|
|
|
m = re.match(r'''(?x)
|
|
|
|
|
(?P<assign>
|
|
|
|
|
(?P<out>{_NAME_RE})(?:\[(?P<out_idx>(?:.+?\]\s*\[)*.+?)\])?\s*
|
|
|
|
|
(?P<out>{_NAME_RE})(?P<out_idx>(?:\[{_NESTED_BRACKETS}\])+)?\s*
|
|
|
|
|
(?P<op>{_OPERATOR_RE})?
|
|
|
|
|
=(?!=)(?P<expr>.*)$
|
|
|
|
|
)|(?P<return>
|
|
|
|
|
(?!if|return|true|false|null|undefined|NaN|Infinity)(?P<name>{_NAME_RE})$
|
|
|
|
|
)|(?P<indexing>
|
|
|
|
|
(?P<in>{_NAME_RE})\[(?P<in_idx>(?:.+?\]\s*\[)*.+?)\]$
|
|
|
|
|
)|(?P<attribute>
|
|
|
|
|
(?P<var>{_NAME_RE})(?:(?P<nullish>\?)?\.(?P<member>[^(]+)|\[(?P<member2>[^\]]+)\])\s*
|
|
|
|
|
(?P<var>{_NAME_RE})(?:
|
|
|
|
|
(?P<nullish>\?)?\.(?P<member>[^(]+)|
|
|
|
|
|
\[(?P<member2>{_NESTED_BRACKETS})\]
|
|
|
|
|
)\s*
|
|
|
|
|
)|(?P<indexing>
|
|
|
|
|
(?P<in>{_NAME_RE})(?P<in_idx>\[.+\])$
|
|
|
|
|
)|(?P<function>
|
|
|
|
|
(?P<fname>{_NAME_RE})\((?P<args>.*)\)$
|
|
|
|
|
)'''.format(**globals()), expr)
|
|
|
|
@ -1033,10 +1115,11 @@ class JSInterpreter(object):
|
|
|
|
|
elif left_val in (None, JS_Undefined):
|
|
|
|
|
raise self.Exception('Cannot index undefined variable ' + m.group('out'), expr=expr)
|
|
|
|
|
|
|
|
|
|
indexes = re.split(r'\]\s*\[', m.group('out_idx'))
|
|
|
|
|
for i, idx in enumerate(indexes, 1):
|
|
|
|
|
indexes = md['out_idx']
|
|
|
|
|
while indexes:
|
|
|
|
|
idx, indexes = self._separate_at_paren(indexes)
|
|
|
|
|
idx = self.interpret_expression(idx, local_vars, allow_recursion)
|
|
|
|
|
if i < len(indexes):
|
|
|
|
|
if indexes:
|
|
|
|
|
left_val = self._index(left_val, idx)
|
|
|
|
|
if isinstance(idx, float):
|
|
|
|
|
idx = int(idx)
|
|
|
|
@ -1081,14 +1164,17 @@ class JSInterpreter(object):
|
|
|
|
|
|
|
|
|
|
if md.get('indexing'):
|
|
|
|
|
val = local_vars[m.group('in')]
|
|
|
|
|
for idx in re.split(r'\]\s*\[', m.group('in_idx')):
|
|
|
|
|
indexes = m.group('in_idx')
|
|
|
|
|
while indexes:
|
|
|
|
|
idx, indexes = self._separate_at_paren(indexes)
|
|
|
|
|
idx = self.interpret_expression(idx, local_vars, allow_recursion)
|
|
|
|
|
val = self._index(val, idx)
|
|
|
|
|
return val, should_return
|
|
|
|
|
|
|
|
|
|
op_result = self.handle_operators(expr, local_vars, allow_recursion)
|
|
|
|
|
if op_result:
|
|
|
|
|
return op_result[0], should_return
|
|
|
|
|
separated = self._separate_at_op(expr)
|
|
|
|
|
if separated:
|
|
|
|
|
op, separated, right_expr = separated
|
|
|
|
|
return self._eval_operator(op, op.join(separated), right_expr, expr, local_vars, allow_recursion), should_return
|
|
|
|
|
|
|
|
|
|
if md.get('attribute'):
|
|
|
|
|
variable, member, nullish = m.group('var', 'member', 'nullish')
|
|
|
|
@ -1109,13 +1195,15 @@ class JSInterpreter(object):
|
|
|
|
|
def eval_method(variable, member):
|
|
|
|
|
if (variable, member) == ('console', 'debug'):
|
|
|
|
|
if Debugger.ENABLED:
|
|
|
|
|
Debugger.write(self.interpret_expression('[{}]'.format(arg_str), local_vars, allow_recursion))
|
|
|
|
|
Debugger.write(self.interpret_expression('[{0}]'.format(arg_str), local_vars, allow_recursion))
|
|
|
|
|
return
|
|
|
|
|
types = {
|
|
|
|
|
'String': compat_str,
|
|
|
|
|
'Math': float,
|
|
|
|
|
'Array': list,
|
|
|
|
|
'Date': self.JS_Date,
|
|
|
|
|
'RegExp': self.JS_RegExp,
|
|
|
|
|
# 'Error': self.Exception, # has no std static methods
|
|
|
|
|
}
|
|
|
|
|
obj = local_vars.get(variable)
|
|
|
|
|
if obj in (JS_Undefined, None):
|
|
|
|
@ -1123,7 +1211,7 @@ class JSInterpreter(object):
|
|
|
|
|
if obj is JS_Undefined:
|
|
|
|
|
try:
|
|
|
|
|
if variable not in self._objects:
|
|
|
|
|
self._objects[variable] = self.extract_object(variable)
|
|
|
|
|
self._objects[variable] = self.extract_object(variable, local_vars)
|
|
|
|
|
obj = self._objects[variable]
|
|
|
|
|
except self.Exception:
|
|
|
|
|
if not nullish:
|
|
|
|
@ -1134,7 +1222,7 @@ class JSInterpreter(object):
|
|
|
|
|
|
|
|
|
|
# Member access
|
|
|
|
|
if arg_str is None:
|
|
|
|
|
return self._index(obj, member)
|
|
|
|
|
return self._index(obj, member, nullish)
|
|
|
|
|
|
|
|
|
|
# Function call
|
|
|
|
|
argvals = [
|
|
|
|
@ -1277,7 +1365,8 @@ class JSInterpreter(object):
|
|
|
|
|
assertion(len(argvals) == 2, 'takes exactly two arguments')
|
|
|
|
|
# TODO: argvals[1] callable, other Py vs JS edge cases
|
|
|
|
|
if isinstance(argvals[0], self.JS_RegExp):
|
|
|
|
|
count = 0 if argvals[0].flags & self.JS_RegExp.RE_FLAGS['g'] else 1
|
|
|
|
|
# access JS member with Py reserved name
|
|
|
|
|
count = 0 if self._index(argvals[0], 'global') else 1
|
|
|
|
|
assertion(member != 'replaceAll' or count == 0,
|
|
|
|
|
'replaceAll must be called with a global RegExp')
|
|
|
|
|
return argvals[0].sub(argvals[1], obj, count=count)
|
|
|
|
@ -1318,7 +1407,7 @@ class JSInterpreter(object):
|
|
|
|
|
for v in self._separate(list_txt):
|
|
|
|
|
yield self.interpret_expression(v, local_vars, allow_recursion)
|
|
|
|
|
|
|
|
|
|
def extract_object(self, objname):
|
|
|
|
|
def extract_object(self, objname, *global_stack):
|
|
|
|
|
_FUNC_NAME_RE = r'''(?:{n}|"{n}"|'{n}')'''.format(n=_NAME_RE)
|
|
|
|
|
obj = {}
|
|
|
|
|
fields = next(filter(None, (
|
|
|
|
@ -1339,7 +1428,8 @@ class JSInterpreter(object):
|
|
|
|
|
fields):
|
|
|
|
|
argnames = self.build_arglist(f.group('args'))
|
|
|
|
|
name = remove_quotes(f.group('key'))
|
|
|
|
|
obj[name] = function_with_repr(self.build_function(argnames, f.group('code')), 'F<{0}>'.format(name))
|
|
|
|
|
obj[name] = function_with_repr(
|
|
|
|
|
self.build_function(argnames, f.group('code'), *global_stack), 'F<{0}>'.format(name))
|
|
|
|
|
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|