comparison 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
comparison
equal deleted inserted replaced
11:74f27983a350 12:f024e41d0fb9
2 2
3 import sys 3 import sys
4 import time 4 import time
5 import threading 5 import threading
6 import traceback 6 import traceback
7 import weakref
8
7 import pydermonkey 9 import pydermonkey
8 10
11 class ContextWatchdogThread(threading.Thread):
12 """
13 Watches active JS contexts and triggers their operation callbacks
14 at a regular interval.
15 """
16
17 def __init__(self, interval=0.25):
18 threading.Thread.__init__(self)
19 self._lock = threading.Lock()
20 self._stop = threading.Event()
21 self._contexts = []
22 self.interval = interval
23 self.setDaemon(True)
24
25 def add_context(self, cx):
26 self._lock.acquire()
27 try:
28 self._contexts.append(weakref.ref(cx))
29 finally:
30 self._lock.release()
31
32 def join(self):
33 self._stop.set()
34 threading.Thread.join(self)
35
36 def run(self):
37 while not self._stop.isSet():
38 time.sleep(self.interval)
39 new_list = []
40 self._lock.acquire()
41 try:
42 for weakcx in self._contexts:
43 cx = weakcx()
44 if cx:
45 new_list.append(weakcx)
46 cx.trigger_operation_callback()
47 self._contexts = new_list
48 finally:
49 self._lock.release()
50
51 # Create a global watchdog.
52 watchdog = ContextWatchdogThread()
53 watchdog.start()
54
9 class InternalError(BaseException): 55 class InternalError(BaseException):
56 """
57 Represents an error in a JS-wrapped Python function that wasn't
58 expected to happen; because it's derived from BaseException, it
59 unrolls the whole JS/Python stack so that the error can be
60 reported to the outermost calling code.
61 """
62
10 def __init__(self): 63 def __init__(self):
11 BaseException.__init__(self) 64 BaseException.__init__(self)
12 self.exc_info = sys.exc_info() 65 self.exc_info = sys.exc_info()
13 66
14 rt = pydermonkey.Runtime() 67 def safejsfunc(cx, on_obj, name=None):
15 cx = rt.new_context() 68 """
16 globalobj = cx.new_object() 69 Exposes the decorated Python function on the given JS object.
17 cx.init_standard_classes(globalobj)
18 70
19 def safejsfunc(cx, on_obj, name=None): 71 Any unexpected exceptions raised by the function will be
72 re-raised as InternalError exceptions.
73 """
74
20 def make_wrapper(func): 75 def make_wrapper(func):
21 if name is None: 76 if name is None:
22 func_name = func.__name__ 77 func_name = func.__name__
23 else: 78 else:
24 func_name = name 79 func_name = name
35 cx.new_function(wrapper, func_name) 90 cx.new_function(wrapper, func_name)
36 ) 91 )
37 return func 92 return func
38 return make_wrapper 93 return make_wrapper
39 94
40 @safejsfunc(cx, globalobj) 95 def format_stack(js_stack):
41 def foo(cx, this, args): 96 """
42 return cx.call_function(this, args[0], ()) 97 Returns a formatted Python-esque stack traceback of the given
98 JS stack.
99 """
43 100
44 @safejsfunc(cx, globalobj, 'print') 101 STACK_LINE =" File \"%(filename)s\", line %(lineno)d, in %(name)s"
45 def jsprint(cx, this, args):
46 if len(args) > 0:
47 print args[0]
48 102
49 def opcb(cx):
50 # Don't do anything; if a keyboard interrupt was triggered,
51 # it'll get raised here automatically.
52 pass
53
54 cx.set_operation_callback(opcb)
55
56 class State(object):
57 def __init__(self):
58 self.curr_exc = None
59 self.curr_tb = None
60 self.curr_js_stack = None
61
62 state = State()
63
64 def throwhook(cx):
65 curr_exc = cx.get_pending_exception()
66 if state.curr_exc != curr_exc:
67 state.curr_exc = curr_exc
68 state.py_stack = traceback.extract_stack()
69 state.js_stack = cx.get_stack()
70
71 cx.set_throw_hook(throwhook)
72
73 def watchdog():
74 while 1:
75 time.sleep(0.25)
76 cx.trigger_operation_callback()
77
78 thread = threading.Thread(target=watchdog)
79 thread.setDaemon(True)
80 thread.start()
81
82 filename = 'test.js'
83
84 def make_stack(js_stack):
85 lines = [] 103 lines = []
86 while js_stack: 104 while js_stack:
87 if js_stack['script']: 105 script = js_stack['script']
88 script = js_stack['script'] 106 function = js_stack['function']
89 thing = dict(filename = script.filename, 107 if script:
108 frameinfo = dict(filename = script.filename,
90 lineno = js_stack['lineno'], 109 lineno = js_stack['lineno'],
91 name = '<module>') 110 name = '<module>')
92 elif js_stack['function'] and not js_stack['function'].is_python: 111 elif function and not function.is_python:
93 func = js_stack['function'] 112 frameinfo = dict(filename = function.filename,
94 thing = dict(filename = func.filename,
95 lineno = js_stack['lineno'], 113 lineno = js_stack['lineno'],
96 name = func.name) 114 name = function.name)
97 else: 115 else:
98 thing = None 116 frameinfo = None
99 if thing: 117 if frameinfo:
100 lines.insert(0, " File \"%(filename)s\", line %(lineno)d, in %(name)s" % thing) 118 lines.insert(0, STACK_LINE % frameinfo)
101 js_stack = js_stack['caller'] 119 js_stack = js_stack['caller']
102 lines.insert(0, "Traceback (most recent call last):") 120 lines.insert(0, "Traceback (most recent call last):")
103 return '\n'.join(lines) 121 return '\n'.join(lines)
104 122
105 try: 123 class JSRuntime(object):
106 cx.evaluate_script(globalobj, open(filename).read(), filename, 1) 124 """
107 except pydermonkey.error, e: 125 A JS runtime capable of loading and executing scripts.
108 print make_stack(state.js_stack) 126 """
109 print e.args[1] 127
110 except InternalError, e: 128 def __init__(self, watchdog=watchdog):
111 print "An internal error occurred." 129 rt = pydermonkey.Runtime()
112 traceback.print_tb(e.exc_info[2]) 130 cx = rt.new_context()
113 print e.exc_info[1] 131 globalobj = cx.new_object()
132 cx.init_standard_classes(globalobj)
133
134 @safejsfunc(cx, globalobj)
135 def foo(cx, this, args):
136 return cx.call_function(this, args[0], ())
137
138 @safejsfunc(cx, globalobj, 'print')
139 def jsprint(cx, this, args):
140 if len(args) > 0:
141 print args[0]
142
143 cx.set_operation_callback(self._opcb)
144 cx.set_throw_hook(self._throwhook)
145 watchdog.add_context(cx)
146
147 self.rt = rt
148 self.cx = cx
149 self.globalobj = globalobj
150 self.curr_exc = None
151 self.py_stack = None
152 self.js_stack = None
153
154 def _opcb(self, cx):
155 # Don't do anything; if a keyboard interrupt was triggered,
156 # it'll get raised here automatically.
157 pass
158
159 def _throwhook(self, cx):
160 curr_exc = cx.get_pending_exception()
161 if self.curr_exc != curr_exc:
162 self.curr_exc = curr_exc
163 self.py_stack = traceback.extract_stack()
164 self.js_stack = cx.get_stack()
165
166 def run_script(self, filename):
167 contents = open(filename).read()
168 cx = self.cx
169 try:
170 cx.evaluate_script(self.globalobj, contents,
171 filename, 1)
172 except pydermonkey.error, e:
173 print format_stack(self.js_stack)
174 print e.args[1]
175 except InternalError, e:
176 print "An internal error occurred."
177 traceback.print_tb(e.exc_info[2])
178 print e.exc_info[1]
179
180 if __name__ == '__main__':
181 runtime = JSRuntime()
182 runtime.run_script('test.js')