Improve error handling in the RS REPL

This commit is contained in:
Kovid Goyal 2015-06-26 23:25:01 +05:30
parent cea314f0d9
commit 8aaa1d0cdd
2 changed files with 53 additions and 10 deletions

View File

@ -10,7 +10,7 @@ import os, json, sys, re, atexit, errno
from threading import local from threading import local
from functools import partial from functools import partial
from threading import Thread from threading import Thread
from Queue import Queue from Queue import Queue, Empty
from duktape import Context, JSError, to_python from duktape import Context, JSError, to_python
from calibre.constants import cache_dir from calibre.constants import cache_dir
@ -223,6 +223,14 @@ class Repl(Thread):
'lib_path': self.libdir or os.path.dirname(P(COMPILER_PATH)) # TODO: Change this to load pyj files from the src code 'lib_path': self.libdir or os.path.dirname(P(COMPILER_PATH)) # TODO: Change this to load pyj files from the src code
} }
def get_from_repl(self):
while True:
try:
return self.from_repl.get(True, 1)
except Empty:
if not self.is_alive():
raise SystemExit(1)
def run(self): def run(self):
self.init_ctx() self.init_ctx()
rl = None rl = None
@ -252,6 +260,8 @@ class Repl(Thread):
''') ''')
rl = self.ctx.g.rl rl = self.ctx.g.rl
self.ctx.eval('module.exports(repl_options)') self.ctx.eval('module.exports(repl_options)')
completer = to_python(rl.completer)
while True: while True:
ev, line = self.to_repl.get() ev, line = self.to_repl.get()
try: try:
@ -261,12 +271,11 @@ class Repl(Thread):
elif ev == 'line': elif ev == 'line':
rl.send_line(line) rl.send_line(line)
else: else:
val = rl.completer(line) val = completer(line)
val = to_python(val) val = to_python(val)
self.from_repl.put(val[0]) self.from_repl.put(val[0])
except Exception as e: except Exception as e:
if 'JSError' in e.__class__.__name__: if isinstance(e, JSError):
e = JSError(e) # A bare JSError
print (e.stack or e.message, file=sys.stderr) print (e.stack or e.message, file=sys.stderr)
else: else:
import traceback import traceback
@ -290,7 +299,7 @@ class Repl(Thread):
def completer(text, num): def completer(text, num):
if self.completions is None: if self.completions is None:
self.to_repl.put(('complete', text)) self.to_repl.put(('complete', text))
self.completions = self.from_repl.get() self.completions = filter(None, self.get_from_repl())
if self.completions is None: if self.completions is None:
return None return None
try: try:
@ -302,7 +311,7 @@ class Repl(Thread):
self.readline.set_completer(completer) self.readline.set_completer(completer)
while True: while True:
lw = self.from_repl.get() lw = self.get_from_repl()
if lw is None: if lw is None:
raise SystemExit(1) raise SystemExit(1)
q = self.prompt q = self.prompt

View File

@ -24,7 +24,22 @@ exports.readFileSync = Duktape.readfile;
''' '''
vm = ''' vm = '''
exports.createContext = Duktape.create_context; exports.createContext = Duktape.create_context;
exports.runInContext = Duktape.run_in_context; exports.runInContext = function(code, ctx) {
var result = Duktape.run_in_context(code, ctx);
if (result[0]) return result[1];
var cls = Error;
var e = result[1];
if (e.name) {
try {
cls = eval(e.name);
} catch(e) {}
}
var err = cls(e.message);
err.fileName = e.fileName;
err.lineNumber = e.lineNumber;
err.stack = e.stack;
throw err;
};
''' '''
path = ''' path = '''
exports.join = function () { return arguments[0] + '/' + arguments[1]; } exports.join = function () { return arguments[0] + '/' + arguments[1]; }
@ -74,7 +89,13 @@ class Function(object):
return str('[Function: %s(...) from file: %s]' % (x.name, x.fileName)) return str('[Function: %s(...) from file: %s]' % (x.name, x.fileName))
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs) try:
return self.func(*args, **kwargs)
except dukpy.JSError as e:
self.reraise(e)
def reraise(self, e):
raise JSError(e), None, sys.exc_info()[2]
def to_python(x): def to_python(x):
try: try:
@ -113,6 +134,15 @@ class JSError(Exception):
Exception.__init__(self, type('')(e)) Exception.__init__(self, type('')(e))
self.name = self.js_message = self.fileName = self.lineNumber = self.stack = None self.name = self.js_message = self.fileName = self.lineNumber = self.stack = None
def as_dict(self):
return {
'name':self.name or undefined,
'message': self.js_message or self.message,
'fileName': self.fileName or undefined,
'lineNumber': self.lineNumber or undefined,
'stack': self.stack or undefined
}
contexts = {} contexts = {}
def create_context(base_dirs, *args): def create_context(base_dirs, *args):
@ -125,8 +155,12 @@ def create_context(base_dirs, *args):
return key return key
def run_in_context(code, ctx, options=None): def run_in_context(code, ctx, options=None):
ans = contexts[ctx].eval(code) c = contexts[ctx]
return to_python(ans) try:
ans = c.eval(code)
except JSError as e:
return [False, e.as_dict()]
return [True, to_python(ans)]
class Context(object): class Context(object):