SickGear/lib/js2py/translators/friendly_nodes.py
2023-02-09 13:41:15 +00:00

375 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
}