mirror of
				https://github.com/ytdl-org/youtube-dl
				synced 2025-10-31 09:43:32 +00: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
							
								
									81e64cacf2
								
							
						
					
					
						commit
						5dee6213ed
					
				| @ -41,16 +41,27 @@ class TestJSInterpreter(unittest.TestCase): | ||||
|         self._test('function f(){return 42 + 7;}', 49) | ||||
|         self._test('function f(){return 42 + undefined;}', NaN) | ||||
|         self._test('function f(){return 42 + null;}', 42) | ||||
|         self._test('function f(){return 1 + "";}', '1') | ||||
|         self._test('function f(){return 42 + "7";}', '427') | ||||
|         self._test('function f(){return false + true;}', 1) | ||||
|         self._test('function f(){return "false" + true;}', 'falsetrue') | ||||
|         self._test('function f(){return ' | ||||
|                    '1 + "2" + [3,4] + {k: 56} + null + undefined + Infinity;}', | ||||
|                    '123,4[object Object]nullundefinedInfinity') | ||||
| 
 | ||||
|     def test_sub(self): | ||||
|         self._test('function f(){return 42 - 7;}', 35) | ||||
|         self._test('function f(){return 42 - undefined;}', NaN) | ||||
|         self._test('function f(){return 42 - null;}', 42) | ||||
|         self._test('function f(){return 42 - "7";}', 35) | ||||
|         self._test('function f(){return 42 - "spam";}', NaN) | ||||
| 
 | ||||
|     def test_mul(self): | ||||
|         self._test('function f(){return 42 * 7;}', 294) | ||||
|         self._test('function f(){return 42 * undefined;}', NaN) | ||||
|         self._test('function f(){return 42 * null;}', 0) | ||||
|         self._test('function f(){return 42 * "7";}', 294) | ||||
|         self._test('function f(){return 42 * "eggs";}', NaN) | ||||
| 
 | ||||
|     def test_div(self): | ||||
|         jsi = JSInterpreter('function f(a, b){return a / b;}') | ||||
| @ -58,17 +69,26 @@ class TestJSInterpreter(unittest.TestCase): | ||||
|         self._test(jsi, NaN, args=(JS_Undefined, 1)) | ||||
|         self._test(jsi, float('inf'), args=(2, 0)) | ||||
|         self._test(jsi, 0, args=(0, 3)) | ||||
|         self._test(jsi, 6, args=(42, 7)) | ||||
|         self._test(jsi, 0, args=(42, float('inf'))) | ||||
|         self._test(jsi, 6, args=("42", 7)) | ||||
|         self._test(jsi, NaN, args=("spam", 7)) | ||||
| 
 | ||||
|     def test_mod(self): | ||||
|         self._test('function f(){return 42 % 7;}', 0) | ||||
|         self._test('function f(){return 42 % 0;}', NaN) | ||||
|         self._test('function f(){return 42 % undefined;}', NaN) | ||||
|         self._test('function f(){return 42 % "7";}', 0) | ||||
|         self._test('function f(){return 42 % "beans";}', NaN) | ||||
| 
 | ||||
|     def test_exp(self): | ||||
|         self._test('function f(){return 42 ** 2;}', 1764) | ||||
|         self._test('function f(){return 42 ** undefined;}', NaN) | ||||
|         self._test('function f(){return 42 ** null;}', 1) | ||||
|         self._test('function f(){return undefined ** 0;}', 1) | ||||
|         self._test('function f(){return undefined ** 42;}', NaN) | ||||
|         self._test('function f(){return 42 ** "2";}', 1764) | ||||
|         self._test('function f(){return 42 ** "spam";}', NaN) | ||||
| 
 | ||||
|     def test_calc(self): | ||||
|         self._test('function f(a){return 2*a+1;}', 7, args=[3]) | ||||
|  | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 dirkf
						dirkf