+def _js_bit_op(op):
+ def zeroise(x):
+ if x in (None, JS_Undefined):
+ return 0
+ with contextlib.suppress(TypeError):
+ if math.isnan(x): # NB: NaN cannot be checked by membership
+ return 0
+ return x
+
+ def wrapped(a, b):
+ return op(zeroise(a), zeroise(b)) & 0xffffffff
+
+ return wrapped
+
+
+def _js_arith_op(op):
+
+ def wrapped(a, b):
+ if JS_Undefined in (a, b):
+ return float('nan')
+ return op(a or 0, b or 0)
+
+ return wrapped
+
+
+def _js_div(a, b):
+ if JS_Undefined in (a, b) or not (a or b):
+ return float('nan')
+ return (a or 0) / b if b else float('inf')
+
+
+def _js_mod(a, b):
+ if JS_Undefined in (a, b) or not b:
+ return float('nan')
+ return (a or 0) % b
+
+
+def _js_exp(a, b):
+ if not b:
+ return 1 # even 0 ** 0 !!
+ elif JS_Undefined in (a, b):
+ return float('nan')
+ return (a or 0) ** b
+
+
+def _js_eq_op(op):
+
+ def wrapped(a, b):
+ if {a, b} <= {None, JS_Undefined}:
+ return op(a, a)
+ return op(a, b)
+
+ return wrapped
+
+
+def _js_comp_op(op):
+
+ def wrapped(a, b):
+ if JS_Undefined in (a, b):
+ return False
+ if isinstance(a, str) or isinstance(b, str):
+ return op(str(a or 0), str(b or 0))
+ return op(a or 0, b or 0)
+
+ return wrapped
+
+
+def _js_ternary(cndn, if_true=True, if_false=False):
+ """Simulate JS's ternary operator (cndn?if_true:if_false)"""
+ if cndn in (False, None, 0, '', JS_Undefined):
+ return if_false
+ with contextlib.suppress(TypeError):
+ if math.isnan(cndn): # NB: NaN cannot be checked by membership
+ return if_false
+ return if_true
+
+
+# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
+_OPERATORS = { # None => Defined in JSInterpreter._operator
+ '?': None,
+ '??': None,
+ '||': None,
+ '&&': None,
+
+ '|': _js_bit_op(operator.or_),
+ '^': _js_bit_op(operator.xor),
+ '&': _js_bit_op(operator.and_),
+
+ '===': operator.is_,
+ '!==': operator.is_not,
+ '==': _js_eq_op(operator.eq),
+ '!=': _js_eq_op(operator.ne),
+
+ '<=': _js_comp_op(operator.le),
+ '>=': _js_comp_op(operator.ge),
+ '<': _js_comp_op(operator.lt),
+ '>': _js_comp_op(operator.gt),
+
+ '>>': _js_bit_op(operator.rshift),
+ '<<': _js_bit_op(operator.lshift),
+
+ '+': _js_arith_op(operator.add),
+ '-': _js_arith_op(operator.sub),
+
+ '*': _js_arith_op(operator.mul),
+ '%': _js_mod,
+ '/': _js_div,
+ '**': _js_exp,
+}
+
+_COMP_OPERATORS = {'===', '!==', '==', '!=', '<=', '>=', '<', '>'}
+
+_NAME_RE = r'[a-zA-Z_$][\w$]*'
+_MATCHING_PARENS = dict(zip(*zip('()', '{}', '[]')))
+_QUOTES = '\'"/'
+
+
+class JS_Undefined:
+ pass