You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
228 lines
8.7 KiB
228 lines
8.7 KiB
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, debug_mode=False):
|
|
self.tape = []
|
|
self.compiled = False
|
|
self.label_locs = None
|
|
self.is_strict = is_strict
|
|
self.debug_mode = debug_mode
|
|
|
|
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
|
|
|
|
# dbg
|
|
self.ctx_depth = 0
|
|
|
|
|
|
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)
|
|
# IMPARTANT: It is guaranteed that the length of the ctx.stack is unchanged.
|
|
'''
|
|
old_curr_ctx = self.current_ctx
|
|
self.ctx_depth += 1
|
|
old_stack_len = len(ctx.stack)
|
|
old_ret_len = len(self.return_locs)
|
|
old_ctx_len = len(self.contexts)
|
|
try:
|
|
self.current_ctx = ctx
|
|
return self._execute_fragment_under_context(
|
|
ctx, start_label, end_label)
|
|
except JsException as err:
|
|
if self.debug_mode:
|
|
self._on_fragment_exit("js errors")
|
|
# undo the things that were put on the stack (if any) to ensure a proper error recovery
|
|
del ctx.stack[old_stack_len:]
|
|
del self.return_locs[old_ret_len:]
|
|
del self.contexts[old_ctx_len :]
|
|
return undefined, 3, err
|
|
finally:
|
|
self.ctx_depth -= 1
|
|
self.current_ctx = old_curr_ctx
|
|
assert old_stack_len == len(ctx.stack)
|
|
|
|
def _get_dbg_indent(self):
|
|
return self.ctx_depth * ' '
|
|
|
|
def _on_fragment_exit(self, mode):
|
|
print(self._get_dbg_indent() + 'ctx exit (%s)' % mode)
|
|
|
|
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
|
|
if self.debug_mode:
|
|
print(self._get_dbg_indent() + 'ctx entry (from:%d, to:%d)' % (start, end))
|
|
while loc < len(self.tape):
|
|
if len(self.contexts) == entry_level and loc >= end:
|
|
if self.debug_mode:
|
|
self._on_fragment_exit('normal')
|
|
assert loc == end
|
|
delta_stack = len(ctx.stack) - initial_len
|
|
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
|
|
return ctx.stack.pop(), 0, None # means normal return
|
|
|
|
# execute instruction
|
|
if self.debug_mode:
|
|
print(self._get_dbg_indent() + str(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]
|
|
if len(self.contexts) == entry_level:
|
|
# check if jumped outside of the fragment and break if so
|
|
if not start <= loc < end:
|
|
if self.debug_mode:
|
|
self._on_fragment_exit('jump outside loc:%d label:%d' % (loc, status))
|
|
delta_stack = len(ctx.stack) - initial_len
|
|
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
|
|
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:
|
|
if self.debug_mode:
|
|
self._on_fragment_exit('return')
|
|
delta_stack = len(ctx.stack) - initial_len
|
|
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
|
|
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
|
|
if self.debug_mode:
|
|
self._on_fragment_exit('internal error - unexpected end of tape, will crash')
|
|
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
|
|
if self.debug_mode:
|
|
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 = []
|