bazarr/libs/js2py/internals/code.py

198 lines
7.3 KiB
Python
Raw Normal View History

from opcodes import *
from space import *
from base import *
class Code:
'''Can generate, store and run sequence of ops representing js code'''
def __init__(self, is_strict=False):
self.tape = []
self.compiled = False
self.label_locs = None
self.is_strict = is_strict
self.contexts = []
self.current_ctx = None
self.return_locs = []
self._label_count = 0
self.label_locs = None
# useful references
self.GLOBAL_THIS = None
self.space = None
def get_new_label(self):
self._label_count += 1
return self._label_count
def emit(self, op_code, *args):
''' Adds op_code with specified args to tape '''
self.tape.append(OP_CODES[op_code](*args))
def compile(self, start_loc=0):
''' Records locations of labels and compiles the code '''
self.label_locs = {} if self.label_locs is None else self.label_locs
loc = start_loc
while loc < len(self.tape):
if type(self.tape[loc]) == LABEL:
self.label_locs[self.tape[loc].num] = loc
del self.tape[loc]
continue
loc += 1
self.compiled = True
def _call(self, func, this, args):
''' Calls a bytecode function func
NOTE: use !ONLY! when calling functions from native methods! '''
assert not func.is_native
# fake call - the the runner to return to the end of the file
old_contexts = self.contexts
old_return_locs = self.return_locs
old_curr_ctx = self.current_ctx
self.contexts = [FakeCtx()]
self.return_locs = [len(self.tape)] # target line after return
# prepare my ctx
my_ctx = func._generate_my_context(this, args)
self.current_ctx = my_ctx
# execute dunction
ret = self.run(my_ctx, starting_loc=self.label_locs[func.code])
# bring back old execution
self.current_ctx = old_curr_ctx
self.contexts = old_contexts
self.return_locs = old_return_locs
return ret
def execute_fragment_under_context(self, ctx, start_label, end_label):
''' just like run but returns if moved outside of the specified fragment
# 4 different exectution results
# 0=normal, 1=return, 2=jump_outside, 3=errors
# execute_fragment_under_context returns:
# (return_value, typ, return_value/jump_loc/py_error)
# ctx.stack must be len 1 and its always empty after the call.
'''
old_curr_ctx = self.current_ctx
try:
self.current_ctx = ctx
return self._execute_fragment_under_context(
ctx, start_label, end_label)
except JsException as err:
# undo the things that were put on the stack (if any)
# don't worry, I know the recovery is possible through try statement and for this reason try statement
# has its own context and stack so it will not delete the contents of the outer stack
del ctx.stack[:]
return undefined, 3, err
finally:
self.current_ctx = old_curr_ctx
def _execute_fragment_under_context(self, ctx, start_label, end_label):
start, end = self.label_locs[start_label], self.label_locs[end_label]
initial_len = len(ctx.stack)
loc = start
entry_level = len(self.contexts)
# for e in self.tape[start:end]:
# print e
while loc < len(self.tape):
#print loc, self.tape[loc]
if len(self.contexts) == entry_level and loc >= end:
assert loc == end
assert len(ctx.stack) == (
1 + initial_len), 'Stack change must be equal to +1!'
return ctx.stack.pop(), 0, None # means normal return
# execute instruction
status = self.tape[loc].eval(ctx)
# check status for special actions
if status is not None:
if type(status) == int: # jump to label
loc = self.label_locs[status]
if len(self.contexts) == entry_level:
# check if jumped outside of the fragment and break if so
if not start <= loc < end:
assert len(ctx.stack) == (
1 + initial_len
), 'Stack change must be equal to +1!'
return ctx.stack.pop(), 2, status # jump outside
continue
elif len(status) == 2: # a call or a return!
# call: (new_ctx, func_loc_label_num)
if status[0] is not None:
# append old state to the stack
self.contexts.append(ctx)
self.return_locs.append(loc + 1)
# set new state
loc = self.label_locs[status[1]]
ctx = status[0]
self.current_ctx = ctx
continue
# return: (None, None)
else:
if len(self.contexts) == entry_level:
assert len(ctx.stack) == 1 + initial_len
return undefined, 1, ctx.stack.pop(
) # return signal
return_value = ctx.stack.pop()
ctx = self.contexts.pop()
self.current_ctx = ctx
ctx.stack.append(return_value)
loc = self.return_locs.pop()
continue
# next instruction
loc += 1
assert False, 'Remember to add NOP at the end!'
def run(self, ctx, starting_loc=0):
loc = starting_loc
self.current_ctx = ctx
while loc < len(self.tape):
# execute instruction
#print loc, self.tape[loc]
status = self.tape[loc].eval(ctx)
# check status for special actions
if status is not None:
if type(status) == int: # jump to label
loc = self.label_locs[status]
continue
elif len(status) == 2: # a call or a return!
# call: (new_ctx, func_loc_label_num)
if status[0] is not None:
# append old state to the stack
self.contexts.append(ctx)
self.return_locs.append(loc + 1)
# set new state
loc = self.label_locs[status[1]]
ctx = status[0]
self.current_ctx = ctx
continue
# return: (None, None)
else:
return_value = ctx.stack.pop()
ctx = self.contexts.pop()
self.current_ctx = ctx
ctx.stack.append(return_value)
loc = self.return_locs.pop()
continue
# next instruction
loc += 1
assert len(ctx.stack) == 1, ctx.stack
return ctx.stack.pop()
class FakeCtx(object):
def __init__(self):
self.stack = []