mirror of https://github.com/morpheus65535/bazarr
376 lines
8.6 KiB
Python
376 lines
8.6 KiB
Python
import binascii
|
|
|
|
from pyjsparser import PyJsParser
|
|
import six
|
|
if six.PY3:
|
|
basestring = str
|
|
long = int
|
|
xrange = range
|
|
unicode = str
|
|
|
|
REGEXP_CONVERTER = PyJsParser()
|
|
|
|
|
|
def to_hex(s):
|
|
return binascii.hexlify(s.encode('utf8')).decode(
|
|
'utf8') # fucking python 3, I hate it so much
|
|
|
|
|
|
# wtf was wrong with s.encode('hex') ???
|
|
def indent(lines, ind=4):
|
|
return ind * ' ' + lines.replace('\n', '\n' + ind * ' ').rstrip(' ')
|
|
|
|
|
|
def inject_before_lval(source, lval, code):
|
|
if source.count(lval) > 1:
|
|
print()
|
|
print(lval)
|
|
raise RuntimeError('To many lvals (%s)' % lval)
|
|
elif not source.count(lval):
|
|
print()
|
|
print(lval)
|
|
assert lval not in source
|
|
raise RuntimeError('No lval found "%s"' % lval)
|
|
end = source.index(lval)
|
|
inj = source.rfind('\n', 0, end)
|
|
ind = inj
|
|
while source[ind + 1] == ' ':
|
|
ind += 1
|
|
ind -= inj
|
|
return source[:inj + 1] + indent(code, ind) + source[inj + 1:]
|
|
|
|
|
|
def get_continue_label(label):
|
|
return CONTINUE_LABEL % to_hex(label)
|
|
|
|
|
|
def get_break_label(label):
|
|
return BREAK_LABEL % to_hex(label)
|
|
|
|
|
|
def is_valid_py_name(name):
|
|
try:
|
|
compile(name + ' = 11', 'a', 'exec')
|
|
except:
|
|
return False
|
|
return True
|
|
|
|
|
|
def indent(lines, ind=4):
|
|
return ind * ' ' + lines.replace('\n', '\n' + ind * ' ').rstrip(' ')
|
|
|
|
|
|
def compose_regex(val):
|
|
reg, flags = val
|
|
#reg = REGEXP_CONVERTER._unescape_string(reg)
|
|
return u'/%s/%s' % (reg, flags)
|
|
|
|
|
|
def float_repr(f):
|
|
if int(f) == f:
|
|
return repr(int(f))
|
|
return repr(f)
|
|
|
|
|
|
def argsplit(args, sep=','):
|
|
"""used to split JS args (it is not that simple as it seems because
|
|
sep can be inside brackets).
|
|
|
|
pass args *without* brackets!
|
|
|
|
Used also to parse array and object elements, and more"""
|
|
parsed_len = 0
|
|
last = 0
|
|
splits = []
|
|
for e in bracket_split(args, brackets=['()', '[]', '{}']):
|
|
if e[0] not in ('(', '[', '{'):
|
|
for i, char in enumerate(e):
|
|
if char == sep:
|
|
splits.append(args[last:parsed_len + i])
|
|
last = parsed_len + i + 1
|
|
parsed_len += len(e)
|
|
splits.append(args[last:])
|
|
return splits
|
|
|
|
|
|
def bracket_split(source, brackets=('()', '{}', '[]'), strip=False):
|
|
"""DOES NOT RETURN EMPTY STRINGS (can only return empty bracket content if strip=True)"""
|
|
starts = [e[0] for e in brackets]
|
|
in_bracket = 0
|
|
n = 0
|
|
last = 0
|
|
while n < len(source):
|
|
e = source[n]
|
|
if not in_bracket and e in starts:
|
|
in_bracket = 1
|
|
start = n
|
|
b_start, b_end = brackets[starts.index(e)]
|
|
elif in_bracket:
|
|
if e == b_start:
|
|
in_bracket += 1
|
|
elif e == b_end:
|
|
in_bracket -= 1
|
|
if not in_bracket:
|
|
if source[last:start]:
|
|
yield source[last:start]
|
|
last = n + 1
|
|
yield source[start + strip:n + 1 - strip]
|
|
n += 1
|
|
if source[last:]:
|
|
yield source[last:]
|
|
|
|
|
|
def js_comma(a, b):
|
|
return 'PyJsComma(' + a + ',' + b + ')'
|
|
|
|
|
|
def js_or(a, b):
|
|
return '(' + a + ' or ' + b + ')'
|
|
|
|
|
|
def js_bor(a, b):
|
|
return '(' + a + '|' + b + ')'
|
|
|
|
|
|
def js_bxor(a, b):
|
|
return '(' + a + '^' + b + ')'
|
|
|
|
|
|
def js_band(a, b):
|
|
return '(' + a + '&' + b + ')'
|
|
|
|
|
|
def js_and(a, b):
|
|
return '(' + a + ' and ' + b + ')'
|
|
|
|
|
|
def js_strict_eq(a, b):
|
|
return 'PyJsStrictEq(' + a + ',' + b + ')'
|
|
|
|
|
|
def js_strict_neq(a, b):
|
|
return 'PyJsStrictNeq(' + a + ',' + b + ')'
|
|
|
|
|
|
#Not handled by python in the same way like JS. For example 2==2==True returns false.
|
|
# In JS above would return true so we need brackets.
|
|
def js_abstract_eq(a, b):
|
|
return '(' + a + '==' + b + ')'
|
|
|
|
|
|
#just like ==
|
|
def js_abstract_neq(a, b):
|
|
return '(' + a + '!=' + b + ')'
|
|
|
|
|
|
def js_lt(a, b):
|
|
return '(' + a + '<' + b + ')'
|
|
|
|
|
|
def js_le(a, b):
|
|
return '(' + a + '<=' + b + ')'
|
|
|
|
|
|
def js_ge(a, b):
|
|
return '(' + a + '>=' + b + ')'
|
|
|
|
|
|
def js_gt(a, b):
|
|
return '(' + a + '>' + b + ')'
|
|
|
|
|
|
def js_in(a, b):
|
|
return b + '.contains(' + a + ')'
|
|
|
|
|
|
def js_instanceof(a, b):
|
|
return a + '.instanceof(' + b + ')'
|
|
|
|
|
|
def js_lshift(a, b):
|
|
return '(' + a + '<<' + b + ')'
|
|
|
|
|
|
def js_rshift(a, b):
|
|
return '(' + a + '>>' + b + ')'
|
|
|
|
|
|
def js_shit(a, b):
|
|
return 'PyJsBshift(' + a + ',' + b + ')'
|
|
|
|
|
|
def js_add(
|
|
a,
|
|
b): # To simplify later process of converting unary operators + and ++
|
|
return '(%s+%s)' % (a, b)
|
|
|
|
|
|
def js_sub(a, b): # To simplify
|
|
return '(%s-%s)' % (a, b)
|
|
|
|
|
|
def js_mul(a, b):
|
|
return '(' + a + '*' + b + ')'
|
|
|
|
|
|
def js_div(a, b):
|
|
return '(' + a + '/' + b + ')'
|
|
|
|
|
|
def js_mod(a, b):
|
|
return '(' + a + '%' + b + ')'
|
|
|
|
|
|
def js_typeof(a):
|
|
cand = list(bracket_split(a, ('()', )))
|
|
if len(cand) == 2 and cand[0] == 'var.get':
|
|
return cand[0] + cand[1][:-1] + ',throw=False).typeof()'
|
|
return a + '.typeof()'
|
|
|
|
|
|
def js_void(a):
|
|
# eval and return undefined
|
|
return 'PyJsComma(%s, Js(None))' % a
|
|
|
|
|
|
def js_new(a):
|
|
cands = list(bracket_split(a, ('()', )))
|
|
lim = len(cands)
|
|
if lim < 2:
|
|
return a + '.create()'
|
|
n = 0
|
|
while n < lim:
|
|
c = cands[n]
|
|
if c[0] == '(':
|
|
if cands[n - 1].endswith(
|
|
'.get') and n + 1 >= lim: # last get operation.
|
|
return a + '.create()'
|
|
elif cands[n - 1][0] == '(':
|
|
return ''.join(cands[:n]) + '.create' + c + ''.join(
|
|
cands[n + 1:])
|
|
elif cands[n - 1] == '.callprop':
|
|
beg = ''.join(cands[:n - 1])
|
|
args = argsplit(c[1:-1], ',')
|
|
prop = args[0]
|
|
new_args = ','.join(args[1:])
|
|
create = '.get(%s).create(%s)' % (prop, new_args)
|
|
return beg + create + ''.join(cands[n + 1:])
|
|
n += 1
|
|
return a + '.create()'
|
|
|
|
|
|
def js_delete(a):
|
|
#replace last get with delete.
|
|
c = list(bracket_split(a, ['()']))
|
|
beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip(
|
|
) #strips just to make sure... I will remove it later
|
|
if beg[-4:] != '.get':
|
|
print(a)
|
|
raise SyntaxError('Invalid delete operation')
|
|
return beg[:-3] + 'delete' + arglist
|
|
|
|
|
|
def js_neg(a):
|
|
return '(-' + a + ')'
|
|
|
|
|
|
def js_pos(a):
|
|
return '(+' + a + ')'
|
|
|
|
|
|
def js_inv(a):
|
|
return '(~' + a + ')'
|
|
|
|
|
|
def js_not(a):
|
|
return a + '.neg()'
|
|
|
|
|
|
def js_postfix(a, inc, post):
|
|
bra = list(bracket_split(a, ('()', )))
|
|
meth = bra[-2]
|
|
if not meth.endswith('get'):
|
|
raise SyntaxError('Invalid ++ or -- operation.')
|
|
bra[-2] = bra[-2][:-3] + 'put'
|
|
bra[-1] = '(%s,Js(%s.to_number())%sJs(1))' % (bra[-1][1:-1], a,
|
|
'+' if inc else '-')
|
|
res = ''.join(bra)
|
|
return res if not post else '(%s%sJs(1))' % (res, '-' if inc else '+')
|
|
|
|
|
|
def js_pre_inc(a):
|
|
return js_postfix(a, True, False)
|
|
|
|
|
|
def js_post_inc(a):
|
|
return js_postfix(a, True, True)
|
|
|
|
|
|
def js_pre_dec(a):
|
|
return js_postfix(a, False, False)
|
|
|
|
|
|
def js_post_dec(a):
|
|
return js_postfix(a, False, True)
|
|
|
|
|
|
CONTINUE_LABEL = 'JS_CONTINUE_LABEL_%s'
|
|
BREAK_LABEL = 'JS_BREAK_LABEL_%s'
|
|
PREPARE = '''HOLDER = var.own.get(NAME)\nvar.force_own_put(NAME, PyExceptionToJs(PyJsTempException))\n'''
|
|
RESTORE = '''if HOLDER is not None:\n var.own[NAME] = HOLDER\nelse:\n del var.own[NAME]\ndel HOLDER\n'''
|
|
TRY_CATCH = '''%stry:\nBLOCKfinally:\n%s''' % (PREPARE, indent(RESTORE))
|
|
|
|
OR = {'||': js_or}
|
|
AND = {'&&': js_and}
|
|
BOR = {'|': js_bor}
|
|
BXOR = {'^': js_bxor}
|
|
BAND = {'&': js_band}
|
|
|
|
EQS = {
|
|
'===': js_strict_eq,
|
|
'!==': js_strict_neq,
|
|
'==': js_abstract_eq, # we need == and != too. Read a note above method
|
|
'!=': js_abstract_neq
|
|
}
|
|
|
|
#Since JS does not have chained comparisons we need to implement all cmp methods.
|
|
COMPS = {
|
|
'<': js_lt,
|
|
'<=': js_le,
|
|
'>=': js_ge,
|
|
'>': js_gt,
|
|
'instanceof': js_instanceof, #todo change to validitate
|
|
'in': js_in
|
|
}
|
|
|
|
BSHIFTS = {'<<': js_lshift, '>>': js_rshift, '>>>': js_shit}
|
|
|
|
ADDS = {'+': js_add, '-': js_sub}
|
|
|
|
MULTS = {'*': js_mul, '/': js_div, '%': js_mod}
|
|
BINARY = {}
|
|
BINARY.update(ADDS)
|
|
BINARY.update(MULTS)
|
|
BINARY.update(BSHIFTS)
|
|
BINARY.update(COMPS)
|
|
BINARY.update(EQS)
|
|
BINARY.update(BAND)
|
|
BINARY.update(BXOR)
|
|
BINARY.update(BOR)
|
|
BINARY.update(AND)
|
|
BINARY.update(OR)
|
|
#Note they dont contain ++ and -- methods because they both have 2 different methods
|
|
# correct method will be found automatically in translate function
|
|
UNARY = {
|
|
'typeof': js_typeof,
|
|
'void': js_void,
|
|
'new': js_new,
|
|
'delete': js_delete,
|
|
'!': js_not,
|
|
'-': js_neg,
|
|
'+': js_pos,
|
|
'~': js_inv,
|
|
'++': None,
|
|
'--': None
|
|
}
|