mirror of https://github.com/morpheus65535/bazarr
806 lines
21 KiB
Python
806 lines
21 KiB
Python
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 is 'OP_CODE':
|
|
continue
|
|
except:
|
|
continue
|
|
OP_CODES[g] = globals()[g]
|