SickGear/lib/js2py/internals/opcodes.py

806 lines
21 KiB
Python
Raw Normal View History

from .operations import *
from .base import get_member, get_member_dot, PyJsFunction, Scope
class OP_CODE(object):
_params = []
# def eval(self, ctx):
# raise
def __repr__(self):
return self.__class__.__name__ + str(
tuple([getattr(self, e) for e in self._params]))
# --------------------- UNARY ----------------------
class UNARY_OP(OP_CODE):
_params = ['operator']
def __init__(self, operator):
self.operator = operator
def eval(self, ctx):
val = ctx.stack.pop()
ctx.stack.append(UNARY_OPERATIONS[self.operator](val))
# special unary operations
class TYPEOF(OP_CODE):
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
def eval(self, ctx):
# typeof something_undefined does not throw reference error
val = ctx.get(self.identifier,
False) # <= this makes it slightly different!
ctx.stack.append(typeof_uop(val))
class POSTFIX(OP_CODE):
_params = ['cb', 'ca', 'identifier']
def __init__(self, post, incr, identifier):
self.identifier = identifier
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
def eval(self, ctx):
target = to_number(ctx.get(self.identifier)) + self.cb
ctx.put(self.identifier, target)
ctx.stack.append(target + self.ca)
class POSTFIX_MEMBER(OP_CODE):
_params = ['cb', 'ca']
def __init__(self, post, incr):
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
def eval(self, ctx):
name = ctx.stack.pop()
left = ctx.stack.pop()
target = to_number(get_member(left, name, ctx.space)) + self.cb
if type(left) not in PRIMITIVES:
left.put_member(name, target)
ctx.stack.append(target + self.ca)
class POSTFIX_MEMBER_DOT(OP_CODE):
_params = ['cb', 'ca', 'prop']
def __init__(self, post, incr, prop):
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
self.prop = prop
def eval(self, ctx):
left = ctx.stack.pop()
target = to_number(get_member_dot(left, self.prop,
ctx.space)) + self.cb
if type(left) not in PRIMITIVES:
left.put(self.prop, target)
ctx.stack.append(target + self.ca)
class DELETE(OP_CODE):
_params = ['name']
def __init__(self, name):
self.name = name
def eval(self, ctx):
ctx.stack.append(ctx.delete(self.name))
class DELETE_MEMBER(OP_CODE):
def eval(self, ctx):
prop = to_string(ctx.stack.pop())
obj = to_object(ctx.stack.pop(), ctx)
ctx.stack.append(obj.delete(prop, False))
# --------------------- BITWISE ----------------------
class BINARY_OP(OP_CODE):
_params = ['operator']
def __init__(self, operator):
self.operator = operator
def eval(self, ctx):
right = ctx.stack.pop()
left = ctx.stack.pop()
ctx.stack.append(BINARY_OPERATIONS[self.operator](left, right))
# &&, || and conditional are implemented in bytecode
# --------------------- JUMPS ----------------------
# simple label that will be removed from code after compilation. labels ID will be translated
# to source code position.
class LABEL(OP_CODE):
_params = ['num']
def __init__(self, num):
self.num = num
# I implemented interpreter in the way that when an integer is returned by eval operation the execution will jump
# to the location of the label (it is loc = label_locations[label])
class BASE_JUMP(OP_CODE):
_params = ['label']
def __init__(self, label):
self.label = label
class JUMP(BASE_JUMP):
def eval(self, ctx):
return self.label
class JUMP_IF_TRUE(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack.pop()
if to_boolean(val):
return self.label
class JUMP_IF_EQ(BASE_JUMP):
# this one is used in switch statement - compares last 2 values using === operator and jumps popping both if true else pops last.
def eval(self, ctx):
cmp = ctx.stack.pop()
if strict_equality_op(ctx.stack[-1], cmp):
ctx.stack.pop()
return self.label
class JUMP_IF_TRUE_WITHOUT_POP(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack[-1]
if to_boolean(val):
return self.label
class JUMP_IF_FALSE(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack.pop()
if not to_boolean(val):
return self.label
class JUMP_IF_FALSE_WITHOUT_POP(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack[-1]
if not to_boolean(val):
return self.label
class POP(OP_CODE):
def eval(self, ctx):
# todo remove this check later
assert len(ctx.stack), 'Popped from empty stack!'
del ctx.stack[-1]
# class REDUCE(OP_CODE):
# def eval(self, ctx):
# assert len(ctx.stack)==2
# ctx.stack[0] = ctx.stack[1]
# del ctx.stack[1]
# --------------- LOADING --------------
class LOAD_NONE(OP_CODE): # be careful with this :)
_params = []
def eval(self, ctx):
ctx.stack.append(None)
class LOAD_N_TUPLE(
OP_CODE
): # loads the tuple composed of n last elements on stack. elements are popped.
_params = ['n']
def __init__(self, n):
self.n = n
def eval(self, ctx):
tup = tuple(ctx.stack[-self.n:])
del ctx.stack[-self.n:]
ctx.stack.append(tup)
class LOAD_UNDEFINED(OP_CODE):
def eval(self, ctx):
ctx.stack.append(undefined)
class LOAD_NULL(OP_CODE):
def eval(self, ctx):
ctx.stack.append(null)
class LOAD_BOOLEAN(OP_CODE):
_params = ['val']
def __init__(self, val):
assert val in (0, 1)
self.val = bool(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_STRING(OP_CODE):
_params = ['val']
def __init__(self, val):
assert isinstance(val, basestring)
self.val = unicode(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_NUMBER(OP_CODE):
_params = ['val']
def __init__(self, val):
assert isinstance(val, (float, int, long))
self.val = float(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_REGEXP(OP_CODE):
_params = ['body', 'flags']
def __init__(self, body, flags):
self.body = body
self.flags = flags
def eval(self, ctx):
# we have to generate a new regexp - they are mutable
ctx.stack.append(ctx.space.NewRegExp(self.body, self.flags))
class LOAD_FUNCTION(OP_CODE):
_params = ['start', 'params', 'name', 'is_declaration', 'definitions']
def __init__(self, start, params, name, is_declaration, definitions):
assert type(start) == int
self.start = start # its an ID of label pointing to the beginning of the function bytecode
self.params = params
self.name = name
self.is_declaration = bool(is_declaration)
self.definitions = tuple(set(definitions + params))
def eval(self, ctx):
ctx.stack.append(
ctx.space.NewFunction(self.start, ctx, self.params, self.name,
self.is_declaration, self.definitions))
class LOAD_OBJECT(OP_CODE):
_params = [
'props'
] # props are py string pairs (prop_name, kind): kind can be either i, g or s. (init, get, set)
def __init__(self, props):
self.num = len(props)
self.props = props
def eval(self, ctx):
obj = ctx.space.NewObject()
if self.num:
obj._init(self.props, ctx.stack[-self.num:])
del ctx.stack[-self.num:]
ctx.stack.append(obj)
class LOAD_ARRAY(OP_CODE):
_params = ['num']
def __init__(self, num):
self.num = num
def eval(self, ctx):
arr = ctx.space.NewArray(self.num)
if self.num:
arr._init(ctx.stack[-self.num:])
del ctx.stack[-self.num:]
ctx.stack.append(arr)
class LOAD_THIS(OP_CODE):
def eval(self, ctx):
ctx.stack.append(ctx.THIS_BINDING)
class LOAD(OP_CODE): # todo check!
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
# 11.1.2
def eval(self, ctx):
ctx.stack.append(ctx.get(self.identifier, throw=True))
class LOAD_MEMBER(OP_CODE):
def eval(self, ctx):
prop = ctx.stack.pop()
obj = ctx.stack.pop()
ctx.stack.append(get_member(obj, prop, ctx.space))
class LOAD_MEMBER_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
obj = ctx.stack.pop()
ctx.stack.append(get_member_dot(obj, self.prop, ctx.space))
# --------------- STORING --------------
class STORE(OP_CODE):
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
def eval(self, ctx):
value = ctx.stack[-1] # don't pop
ctx.put(self.identifier, value)
class STORE_MEMBER(OP_CODE):
def eval(self, ctx):
value = ctx.stack.pop()
name = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
prop = to_string(name)
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % prop)
elif typ == UNDEFINED_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of undefined" % prop)
# just ignore...
else:
left.put_member(name, value)
ctx.stack.append(value)
class STORE_MEMBER_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
value = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % self.prop)
elif typ == UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % self.prop)
# just ignore...
else:
left.put(self.prop, value)
ctx.stack.append(value)
class STORE_OP(OP_CODE):
_params = ['identifier', 'op']
def __init__(self, identifier, op):
self.identifier = identifier
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
new_value = BINARY_OPERATIONS[self.op](ctx.get(self.identifier), value)
ctx.put(self.identifier, new_value)
ctx.stack.append(new_value)
class STORE_MEMBER_OP(OP_CODE):
_params = ['op']
def __init__(self, op):
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
name = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ is NULL_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of null" % to_string(name))
elif typ is UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % to_string(name))
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
left, name, ctx.space), value))
return
else:
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
left, name, ctx.space), value))
left.put_member(name, ctx.stack[-1])
class STORE_MEMBER_DOT_OP(OP_CODE):
_params = ['prop', 'op']
def __init__(self, prop, op):
self.prop = prop
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % self.prop)
elif typ == UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % self.prop)
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
left, self.prop, ctx.space), value))
return
else:
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
left, self.prop, ctx.space), value))
left.put(self.prop, ctx.stack[-1])
# --------------- CALLS --------------
def bytecode_call(ctx, func, this, args):
if type(func) is not PyJsFunction:
raise MakeError('TypeError', "%s is not a function" % Type(func))
if func.is_native: # call to built-in function or method
ctx.stack.append(func.call(this, args))
return None
# therefore not native. we have to return (new_context, function_label) to instruct interpreter to call
return func._generate_my_context(this, args), func.code
class CALL(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
func = ctx.stack.pop()
return bytecode_call(ctx, func, ctx.space.GlobalObj, args)
class CALL_METHOD(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
prop = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member(base, prop, ctx.space)
return bytecode_call(ctx, func, base, args)
class CALL_METHOD_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
args = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member_dot(base, self.prop, ctx.space)
return bytecode_call(ctx, func, base, args)
class CALL_NO_ARGS(OP_CODE):
def eval(self, ctx):
func = ctx.stack.pop()
return bytecode_call(ctx, func, ctx.space.GlobalObj, ())
class CALL_METHOD_NO_ARGS(OP_CODE):
def eval(self, ctx):
prop = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member(base, prop, ctx.space)
return bytecode_call(ctx, func, base, ())
class CALL_METHOD_DOT_NO_ARGS(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
base = ctx.stack.pop()
func = get_member_dot(base, self.prop, ctx.space)
return bytecode_call(ctx, func, base, ())
class NOP(OP_CODE):
def eval(self, ctx):
pass
class RETURN(OP_CODE):
def eval(
self, ctx
): # remember to load the return value on stack before using RETURN op.
return (None, None)
class NEW(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
constructor = ctx.stack.pop()
if type(constructor) in PRIMITIVES or not hasattr(
constructor, 'create'):
raise MakeError('TypeError',
'%s is not a constructor' % Type(constructor))
ctx.stack.append(constructor.create(args, space=ctx.space))
class NEW_NO_ARGS(OP_CODE):
def eval(self, ctx):
constructor = ctx.stack.pop()
if type(constructor) in PRIMITIVES or not hasattr(
constructor, 'create'):
raise MakeError('TypeError',
'%s is not a constructor' % Type(constructor))
ctx.stack.append(constructor.create((), space=ctx.space))
# --------------- EXCEPTIONS --------------
class THROW(OP_CODE):
def eval(self, ctx):
raise MakeError(None, None, ctx.stack.pop())
class TRY_CATCH_FINALLY(OP_CODE):
_params = [
'try_label', 'catch_label', 'catch_variable', 'finally_label',
'finally_present', 'end_label'
]
def __init__(self, try_label, catch_label, catch_variable, finally_label,
finally_present, end_label):
self.try_label = try_label
self.catch_label = catch_label
self.catch_variable = catch_variable
self.finally_label = finally_label
self.finally_present = finally_present
self.end_label = end_label
def eval(self, ctx):
# 4 different exectution results
# 0=normal, 1=return, 2=jump_outside, 3=errors
# execute_fragment_under_context returns:
# (return_value, typ, jump_loc/error)
ctx.stack.pop()
# execute try statement
try_status = ctx.space.exe.execute_fragment_under_context(
ctx, self.try_label, self.catch_label)
errors = try_status[1] == 3
# catch
if errors and self.catch_variable is not None:
# generate catch block context...
catch_context = Scope({
self.catch_variable:
try_status[2].get_thrown_value(ctx.space)
}, ctx.space, ctx)
catch_context.THIS_BINDING = ctx.THIS_BINDING
catch_status = ctx.space.exe.execute_fragment_under_context(
catch_context, self.catch_label, self.finally_label)
else:
catch_status = None
# finally
if self.finally_present:
finally_status = ctx.space.exe.execute_fragment_under_context(
ctx, self.finally_label, self.end_label)
else:
finally_status = None
# now return controls
other_status = catch_status or try_status
if finally_status is None or (finally_status[1] == 0
and other_status[1] != 0):
winning_status = other_status
else:
winning_status = finally_status
val, typ, spec = winning_status
if typ == 0: # normal
ctx.stack.append(val)
return
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
ctx.stack.append(val)
return spec
elif typ == 3:
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
# ------------ WITH + ITERATORS ----------
class WITH(OP_CODE):
_params = ['beg_label', 'end_label']
def __init__(self, beg_label, end_label):
self.beg_label = beg_label
self.end_label = end_label
def eval(self, ctx):
obj = to_object(ctx.stack.pop(), ctx.space)
with_context = Scope(
obj, ctx.space, ctx) # todo actually use the obj to modify the ctx
with_context.THIS_BINDING = ctx.THIS_BINDING
status = ctx.space.exe.execute_fragment_under_context(
with_context, self.beg_label, self.end_label)
val, typ, spec = status
if typ != 3: # exception
ctx.stack.pop()
if typ == 0: # normal
ctx.stack.append(val)
return
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
ctx.stack.append(val)
return spec
elif typ == 3: # exception
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
class FOR_IN(OP_CODE):
_params = ['name', 'body_start_label', 'continue_label', 'break_label']
def __init__(self, name, body_start_label, continue_label, break_label):
self.name = name
self.body_start_label = body_start_label
self.continue_label = continue_label
self.break_label = break_label
def eval(self, ctx):
iterable = ctx.stack.pop()
if is_null(iterable) or is_undefined(iterable):
ctx.stack.pop()
ctx.stack.append(undefined)
return self.break_label
obj = to_object(iterable, ctx.space)
for e in sorted(obj.own):
if not obj.own[e]['enumerable']:
continue
ctx.put(
self.name, e
) # JS would have been so much nicer if this was ctx.space.put(self.name, obj.get(e))
# evaluate the body
status = ctx.space.exe.execute_fragment_under_context(
ctx, self.body_start_label, self.break_label)
val, typ, spec = status
if typ != 3: # exception
ctx.stack.pop()
if typ == 0: # normal
ctx.stack.append(val)
continue
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
# now have to figure out whether this is a continue or something else...
ctx.stack.append(val)
if spec == self.continue_label:
# just a continue, perform next iteration as normal
continue
return spec # break or smth, go there and finish the iteration
elif typ == 3: # exception
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
return self.break_label
# all opcodes...
OP_CODES = {}
g = ''
for g in globals():
try:
if not issubclass(globals()[g], OP_CODE) or g == 'OP_CODE':
continue
except:
continue
OP_CODES[g] = globals()[g]