Mercurial > scratch
diff pydershell/pydershell.py @ 12:f024e41d0fb9
More refactoring, a bit more documentation.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Sun, 06 Sep 2009 14:25:10 -0700 |
parents | 74f27983a350 |
children | 2a3313bfe574 |
line wrap: on
line diff
--- a/pydershell/pydershell.py Sun Sep 06 20:12:48 2009 +0000 +++ b/pydershell/pydershell.py Sun Sep 06 14:25:10 2009 -0700 @@ -4,19 +4,74 @@ import time import threading import traceback +import weakref + import pydermonkey +class ContextWatchdogThread(threading.Thread): + """ + Watches active JS contexts and triggers their operation callbacks + at a regular interval. + """ + + def __init__(self, interval=0.25): + threading.Thread.__init__(self) + self._lock = threading.Lock() + self._stop = threading.Event() + self._contexts = [] + self.interval = interval + self.setDaemon(True) + + def add_context(self, cx): + self._lock.acquire() + try: + self._contexts.append(weakref.ref(cx)) + finally: + self._lock.release() + + def join(self): + self._stop.set() + threading.Thread.join(self) + + def run(self): + while not self._stop.isSet(): + time.sleep(self.interval) + new_list = [] + self._lock.acquire() + try: + for weakcx in self._contexts: + cx = weakcx() + if cx: + new_list.append(weakcx) + cx.trigger_operation_callback() + self._contexts = new_list + finally: + self._lock.release() + +# Create a global watchdog. +watchdog = ContextWatchdogThread() +watchdog.start() + class InternalError(BaseException): + """ + Represents an error in a JS-wrapped Python function that wasn't + expected to happen; because it's derived from BaseException, it + unrolls the whole JS/Python stack so that the error can be + reported to the outermost calling code. + """ + def __init__(self): BaseException.__init__(self) self.exc_info = sys.exc_info() -rt = pydermonkey.Runtime() -cx = rt.new_context() -globalobj = cx.new_object() -cx.init_standard_classes(globalobj) +def safejsfunc(cx, on_obj, name=None): + """ + Exposes the decorated Python function on the given JS object. -def safejsfunc(cx, on_obj, name=None): + Any unexpected exceptions raised by the function will be + re-raised as InternalError exceptions. + """ + def make_wrapper(func): if name is None: func_name = func.__name__ @@ -37,77 +92,91 @@ return func return make_wrapper -@safejsfunc(cx, globalobj) -def foo(cx, this, args): - return cx.call_function(this, args[0], ()) - -@safejsfunc(cx, globalobj, 'print') -def jsprint(cx, this, args): - if len(args) > 0: - print args[0] - -def opcb(cx): - # Don't do anything; if a keyboard interrupt was triggered, - # it'll get raised here automatically. - pass - -cx.set_operation_callback(opcb) - -class State(object): - def __init__(self): - self.curr_exc = None - self.curr_tb = None - self.curr_js_stack = None +def format_stack(js_stack): + """ + Returns a formatted Python-esque stack traceback of the given + JS stack. + """ -state = State() - -def throwhook(cx): - curr_exc = cx.get_pending_exception() - if state.curr_exc != curr_exc: - state.curr_exc = curr_exc - state.py_stack = traceback.extract_stack() - state.js_stack = cx.get_stack() - -cx.set_throw_hook(throwhook) + STACK_LINE =" File \"%(filename)s\", line %(lineno)d, in %(name)s" -def watchdog(): - while 1: - time.sleep(0.25) - cx.trigger_operation_callback() - -thread = threading.Thread(target=watchdog) -thread.setDaemon(True) -thread.start() - -filename = 'test.js' - -def make_stack(js_stack): lines = [] while js_stack: - if js_stack['script']: - script = js_stack['script'] - thing = dict(filename = script.filename, + script = js_stack['script'] + function = js_stack['function'] + if script: + frameinfo = dict(filename = script.filename, lineno = js_stack['lineno'], name = '<module>') - elif js_stack['function'] and not js_stack['function'].is_python: - func = js_stack['function'] - thing = dict(filename = func.filename, + elif function and not function.is_python: + frameinfo = dict(filename = function.filename, lineno = js_stack['lineno'], - name = func.name) + name = function.name) else: - thing = None - if thing: - lines.insert(0, " File \"%(filename)s\", line %(lineno)d, in %(name)s" % thing) + frameinfo = None + if frameinfo: + lines.insert(0, STACK_LINE % frameinfo) js_stack = js_stack['caller'] lines.insert(0, "Traceback (most recent call last):") return '\n'.join(lines) -try: - cx.evaluate_script(globalobj, open(filename).read(), filename, 1) -except pydermonkey.error, e: - print make_stack(state.js_stack) - print e.args[1] -except InternalError, e: - print "An internal error occurred." - traceback.print_tb(e.exc_info[2]) - print e.exc_info[1] +class JSRuntime(object): + """ + A JS runtime capable of loading and executing scripts. + """ + + def __init__(self, watchdog=watchdog): + rt = pydermonkey.Runtime() + cx = rt.new_context() + globalobj = cx.new_object() + cx.init_standard_classes(globalobj) + + @safejsfunc(cx, globalobj) + def foo(cx, this, args): + return cx.call_function(this, args[0], ()) + + @safejsfunc(cx, globalobj, 'print') + def jsprint(cx, this, args): + if len(args) > 0: + print args[0] + + cx.set_operation_callback(self._opcb) + cx.set_throw_hook(self._throwhook) + watchdog.add_context(cx) + + self.rt = rt + self.cx = cx + self.globalobj = globalobj + self.curr_exc = None + self.py_stack = None + self.js_stack = None + + def _opcb(self, cx): + # Don't do anything; if a keyboard interrupt was triggered, + # it'll get raised here automatically. + pass + + def _throwhook(self, cx): + curr_exc = cx.get_pending_exception() + if self.curr_exc != curr_exc: + self.curr_exc = curr_exc + self.py_stack = traceback.extract_stack() + self.js_stack = cx.get_stack() + + def run_script(self, filename): + contents = open(filename).read() + cx = self.cx + try: + cx.evaluate_script(self.globalobj, contents, + filename, 1) + except pydermonkey.error, e: + print format_stack(self.js_stack) + print e.args[1] + except InternalError, e: + print "An internal error occurred." + traceback.print_tb(e.exc_info[2]) + print e.exc_info[1] + +if __name__ == '__main__': + runtime = JSRuntime() + runtime.run_script('test.js')