mirror of
https://github.com/ytdl-org/youtube-dl.git
synced 2025-08-14 02:27:42 -07:00
[jsinterp] Fix and improve arithmetic operations
* addition becomes concat with a string operand * improve handling of edgier cases * arithmetic in float like JS (more places need cast to int?) * increase test coverage
This commit is contained in:
parent
7d3045ce12
commit
9993e8c5a9
2 changed files with 54 additions and 19 deletions
|
@ -11,6 +11,7 @@ from functools import update_wrapper, wraps
|
|||
from .utils import (
|
||||
error_to_compat_str,
|
||||
ExtractorError,
|
||||
float_or_none,
|
||||
js_to_json,
|
||||
remove_quotes,
|
||||
unified_timestamp,
|
||||
|
@ -81,35 +82,47 @@ def _js_bit_op(op):
|
|||
return wrapped
|
||||
|
||||
|
||||
def _js_arith_op(op):
|
||||
def _js_arith_op(op, div=False):
|
||||
|
||||
@wraps_op(op)
|
||||
def wrapped(a, b):
|
||||
if JS_Undefined in (a, b):
|
||||
return _NaN
|
||||
return op(a or 0, b or 0)
|
||||
# null, "" --> 0
|
||||
a, b = (float_or_none(
|
||||
(x.strip() if isinstance(x, compat_basestring) else x) or 0,
|
||||
default=_NaN) for x in (a, b))
|
||||
if _NaN in (a, b):
|
||||
return _NaN
|
||||
try:
|
||||
return op(a, b)
|
||||
except ZeroDivisionError:
|
||||
return _NaN if not (div and (a or b)) else _Infinity
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def _js_div(a, b):
|
||||
if JS_Undefined in (a, b) or not (a or b):
|
||||
return _NaN
|
||||
return operator.truediv(a or 0, b) if b else _Infinity
|
||||
_js_arith_add = _js_arith_op(operator.add)
|
||||
|
||||
|
||||
def _js_mod(a, b):
|
||||
if JS_Undefined in (a, b) or not b:
|
||||
return _NaN
|
||||
return (a or 0) % b
|
||||
def _js_add(a, b):
|
||||
if not (isinstance(a, compat_basestring) or isinstance(b, compat_basestring)):
|
||||
return _js_arith_add(a, b)
|
||||
if not isinstance(a, compat_basestring):
|
||||
a = _js_toString(a)
|
||||
elif not isinstance(b, compat_basestring):
|
||||
b = _js_toString(b)
|
||||
return operator.concat(a, b)
|
||||
|
||||
|
||||
_js_mod = _js_arith_op(operator.mod)
|
||||
__js_exp = _js_arith_op(operator.pow)
|
||||
|
||||
|
||||
def _js_exp(a, b):
|
||||
if not b:
|
||||
return 1 # even 0 ** 0 !!
|
||||
elif JS_Undefined in (a, b):
|
||||
return _NaN
|
||||
return (a or 0) ** b
|
||||
return __js_exp(a, b)
|
||||
|
||||
|
||||
def _js_to_primitive(v):
|
||||
|
@ -117,7 +130,7 @@ def _js_to_primitive(v):
|
|||
','.join(map(_js_toString, v)) if isinstance(v, list)
|
||||
else '[object Object]' if isinstance(v, dict)
|
||||
else compat_str(v) if not isinstance(v, (
|
||||
compat_numeric_types, compat_basestring, bool))
|
||||
compat_numeric_types, compat_basestring))
|
||||
else v
|
||||
)
|
||||
|
||||
|
@ -128,7 +141,9 @@ def _js_toString(v):
|
|||
else 'Infinity' if v == _Infinity
|
||||
else 'NaN' if v is _NaN
|
||||
else 'null' if v is None
|
||||
else compat_str(v) if isinstance(v, compat_numeric_types)
|
||||
# bool <= int: do this first
|
||||
else ('false', 'true')[v] if isinstance(v, bool)
|
||||
else '{0:.7f}'.format(v).rstrip('.0') if isinstance(v, compat_numeric_types)
|
||||
else _js_to_primitive(v))
|
||||
|
||||
|
||||
|
@ -240,11 +255,11 @@ def _js_typeof(expr):
|
|||
_OPERATORS = (
|
||||
('>>', _js_bit_op(operator.rshift)),
|
||||
('<<', _js_bit_op(operator.lshift)),
|
||||
('+', _js_arith_op(operator.add)),
|
||||
('+', _js_add),
|
||||
('-', _js_arith_op(operator.sub)),
|
||||
('*', _js_arith_op(operator.mul)),
|
||||
('%', _js_mod),
|
||||
('/', _js_div),
|
||||
('/', _js_arith_op(operator.truediv, div=True)),
|
||||
('**', _js_exp),
|
||||
)
|
||||
|
||||
|
@ -873,7 +888,7 @@ class JSInterpreter(object):
|
|||
start, end = m.span()
|
||||
sign = m.group('pre_sign') or m.group('post_sign')
|
||||
ret = local_vars[var]
|
||||
local_vars[var] += 1 if sign[0] == '+' else -1
|
||||
local_vars[var] = _js_add(ret, 1 if sign[0] == '+' else -1)
|
||||
if m.group('pre_sign'):
|
||||
ret = local_vars[var]
|
||||
expr = expr[:start] + self._dump(ret, local_vars) + expr[end:]
|
||||
|
@ -1023,7 +1038,7 @@ class JSInterpreter(object):
|
|||
if obj is compat_str:
|
||||
if member == 'fromCharCode':
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
return ''.join(map(compat_chr, argvals))
|
||||
return ''.join(compat_chr(int(n)) for n in argvals)
|
||||
raise self.Exception('Unsupported string method ' + member, expr=expr)
|
||||
elif obj is float:
|
||||
if member == 'pow':
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue