Refactor REPL error handling and filter Hy internal trace output
These changes make the Hy REPL more closely follow `code.InteractiveConsole`'s class interface and provide minimally intrusive traceback print-out filtering via a context manager that temporarily alters `sys.excepthook`. In other words, exception messages from the REPL will no longer show Hy internal code (e.g. importer, compiler and parsing functions). The boolean variable `hy.errors._hy_filter_internal_errors` dynamically enables/disables trace filtering, and the env variable `HY_FILTER_INTERNAL_ERRORS` can be used as the initial value.
This commit is contained in:
parent
51c7efe6e8
commit
e468d5f081
|
@ -48,12 +48,6 @@ Command Line Options
|
||||||
`--spy` only works on REPL mode.
|
`--spy` only works on REPL mode.
|
||||||
.. versionadded:: 0.9.11
|
.. versionadded:: 0.9.11
|
||||||
|
|
||||||
.. cmdoption:: --show-tracebacks
|
|
||||||
|
|
||||||
Print extended tracebacks for Hy exceptions.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9.12
|
|
||||||
|
|
||||||
.. cmdoption:: --repl-output-fn
|
.. cmdoption:: --repl-output-fn
|
||||||
|
|
||||||
Format REPL output using specific function (e.g., hy.contrib.hy-repr.hy-repr)
|
Format REPL output using specific function (e.g., hy.contrib.hy-repr.hy-repr)
|
||||||
|
|
|
@ -5,6 +5,16 @@ except ImportError:
|
||||||
__version__ = 'unknown'
|
__version__ = 'unknown'
|
||||||
|
|
||||||
|
|
||||||
|
def _initialize_env_var(env_var, default_val):
|
||||||
|
import os, distutils.util
|
||||||
|
try:
|
||||||
|
res = bool(distutils.util.strtobool(
|
||||||
|
os.environ.get(env_var, str(default_val))))
|
||||||
|
except ValueError as e:
|
||||||
|
res = default_val
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
from hy.models import HyExpression, HyInteger, HyKeyword, HyComplex, HyString, HyBytes, HySymbol, HyFloat, HyDict, HyList, HySet # NOQA
|
from hy.models import HyExpression, HyInteger, HyKeyword, HyComplex, HyString, HyBytes, HySymbol, HyFloat, HyDict, HyList, HySet # NOQA
|
||||||
|
|
||||||
|
|
||||||
|
|
125
hy/cmdline.py
125
hy/cmdline.py
|
@ -21,7 +21,7 @@ import hy
|
||||||
from hy.lex import hy_parse, mangle
|
from hy.lex import hy_parse, mangle
|
||||||
from hy.lex.exceptions import PrematureEndOfInput
|
from hy.lex.exceptions import PrematureEndOfInput
|
||||||
from hy.compiler import HyASTCompiler, hy_compile, hy_eval
|
from hy.compiler import HyASTCompiler, hy_compile, hy_eval
|
||||||
from hy.errors import HyTypeError, HyLanguageError, HySyntaxError
|
from hy.errors import HySyntaxError, filtered_hy_exceptions
|
||||||
from hy.importer import runhy
|
from hy.importer import runhy
|
||||||
from hy.completer import completion, Completer
|
from hy.completer import completion, Completer
|
||||||
from hy.macros import macro, require
|
from hy.macros import macro, require
|
||||||
|
@ -90,47 +90,58 @@ class HyREPL(code.InteractiveConsole, object):
|
||||||
self._repl_results_symbols = [mangle("*{}".format(i + 1)) for i in range(3)]
|
self._repl_results_symbols = [mangle("*{}".format(i + 1)) for i in range(3)]
|
||||||
self.locals.update({sym: None for sym in self._repl_results_symbols})
|
self.locals.update({sym: None for sym in self._repl_results_symbols})
|
||||||
|
|
||||||
def runsource(self, source, filename='<input>', symbol='single'):
|
def ast_callback(self, main_ast, expr_ast):
|
||||||
global SIMPLE_TRACEBACKS
|
if self.spy:
|
||||||
|
# Mush the two AST chunks into a single module for
|
||||||
|
# conversion into Python.
|
||||||
|
new_ast = ast.Module(main_ast.body +
|
||||||
|
[ast.Expr(expr_ast.body)])
|
||||||
|
print(astor.to_source(new_ast))
|
||||||
|
|
||||||
def error_handler(e, use_simple_traceback=False):
|
def _error_wrap(self, error_fn, *args, **kwargs):
|
||||||
self.locals[mangle("*e")] = e
|
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
|
||||||
if use_simple_traceback:
|
|
||||||
print(e, file=sys.stderr)
|
# Sadly, this method in Python 2.7 ignores an overridden
|
||||||
else:
|
# `sys.excepthook`.
|
||||||
self.showtraceback()
|
if sys.excepthook is sys.__excepthook__:
|
||||||
|
error_fn(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
sys.excepthook(sys.last_type, sys.last_value, sys.last_traceback)
|
||||||
|
|
||||||
|
self.locals[mangle("*e")] = sys.last_value
|
||||||
|
|
||||||
|
def showsyntaxerror(self, filename=None):
|
||||||
|
if filename is None:
|
||||||
|
filename = self.filename
|
||||||
|
|
||||||
|
self._error_wrap(super(HyREPL, self).showsyntaxerror,
|
||||||
|
filename=filename)
|
||||||
|
|
||||||
|
def showtraceback(self):
|
||||||
|
self._error_wrap(super(HyREPL, self).showtraceback)
|
||||||
|
|
||||||
|
def runsource(self, source, filename='<input>', symbol='single'):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
do = hy_parse(source, filename=filename)
|
do = hy_parse(source, filename=filename)
|
||||||
except PrematureEndOfInput:
|
except PrematureEndOfInput:
|
||||||
return True
|
return True
|
||||||
except HySyntaxError as e:
|
except HySyntaxError as e:
|
||||||
error_handler(e, use_simple_traceback=SIMPLE_TRACEBACKS)
|
self.showsyntaxerror(filename=filename)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
def ast_callback(main_ast, expr_ast):
|
# Our compiler doesn't correspond to a real, fixed source file, so
|
||||||
if self.spy:
|
# we need to [re]set these.
|
||||||
# Mush the two AST chunks into a single module for
|
self.hy_compiler.filename = filename
|
||||||
# conversion into Python.
|
self.hy_compiler.source = source
|
||||||
new_ast = ast.Module(main_ast.body +
|
value = hy_eval(do, self.locals, self.module, self.ast_callback,
|
||||||
[ast.Expr(expr_ast.body)])
|
compiler=self.hy_compiler, filename=filename,
|
||||||
print(astor.to_source(new_ast))
|
|
||||||
|
|
||||||
value = hy_eval(do, self.locals, self.module,
|
|
||||||
ast_callback=ast_callback,
|
|
||||||
compiler=self.hy_compiler,
|
|
||||||
filename=filename,
|
|
||||||
source=source)
|
source=source)
|
||||||
|
except SystemExit:
|
||||||
except HyTypeError as e:
|
raise
|
||||||
if e.source is None:
|
|
||||||
e.source = source
|
|
||||||
e.filename = filename
|
|
||||||
error_handler(e, use_simple_traceback=SIMPLE_TRACEBACKS)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_handler(e, use_simple_traceback=SIMPLE_TRACEBACKS)
|
self.showtraceback()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
@ -142,10 +153,12 @@ class HyREPL(code.InteractiveConsole, object):
|
||||||
# Print the value.
|
# Print the value.
|
||||||
try:
|
try:
|
||||||
output = self.output_fn(value)
|
output = self.output_fn(value)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
error_handler(e)
|
self.showtraceback()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(output)
|
print(output)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,25 +214,12 @@ def ideas_macro(ETname):
|
||||||
""")])
|
""")])
|
||||||
|
|
||||||
|
|
||||||
SIMPLE_TRACEBACKS = True
|
|
||||||
|
|
||||||
|
|
||||||
def pretty_error(func, *args, **kw):
|
|
||||||
try:
|
|
||||||
return func(*args, **kw)
|
|
||||||
except HyLanguageError as e:
|
|
||||||
if SIMPLE_TRACEBACKS:
|
|
||||||
print(e, file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def run_command(source, filename=None):
|
def run_command(source, filename=None):
|
||||||
tree = hy_parse(source, filename=filename)
|
tree = hy_parse(source, filename=filename)
|
||||||
__main__ = importlib.import_module('__main__')
|
__main__ = importlib.import_module('__main__')
|
||||||
require("hy.cmdline", __main__, assignments="ALL")
|
require("hy.cmdline", __main__, assignments="ALL")
|
||||||
pretty_error(hy_eval, tree, None, __main__, filename=filename,
|
with filtered_hy_exceptions():
|
||||||
source=source)
|
hy_eval(tree, None, __main__, filename=filename, source=source)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,9 +232,7 @@ def run_repl(hr=None, **kwargs):
|
||||||
hr = HyREPL(**kwargs)
|
hr = HyREPL(**kwargs)
|
||||||
|
|
||||||
namespace = hr.locals
|
namespace = hr.locals
|
||||||
|
with filtered_hy_exceptions(), completion(Completer(namespace)):
|
||||||
with completion(Completer(namespace)):
|
|
||||||
|
|
||||||
hr.interact("{appname} {version} using "
|
hr.interact("{appname} {version} using "
|
||||||
"{py}({build}) {pyversion} on {os}".format(
|
"{py}({build}) {pyversion} on {os}".format(
|
||||||
appname=hy.__appname__,
|
appname=hy.__appname__,
|
||||||
|
@ -263,9 +261,10 @@ def run_icommand(source, **kwargs):
|
||||||
else:
|
else:
|
||||||
filename = '<input>'
|
filename = '<input>'
|
||||||
|
|
||||||
hr = HyREPL(**kwargs)
|
with filtered_hy_exceptions():
|
||||||
hr.runsource(source, filename=filename, symbol='single')
|
hr = HyREPL(**kwargs)
|
||||||
return run_repl(hr)
|
hr.runsource(source, filename=filename, symbol='single')
|
||||||
|
return run_repl(hr)
|
||||||
|
|
||||||
|
|
||||||
USAGE = "%(prog)s [-h | -i cmd | -c cmd | -m module | file | -] [arg] ..."
|
USAGE = "%(prog)s [-h | -i cmd | -c cmd | -m module | file | -] [arg] ..."
|
||||||
|
@ -301,9 +300,6 @@ def cmdline_handler(scriptname, argv):
|
||||||
"(e.g., hy.contrib.hy-repr.hy-repr)")
|
"(e.g., hy.contrib.hy-repr.hy-repr)")
|
||||||
parser.add_argument("-v", "--version", action="version", version=VERSION)
|
parser.add_argument("-v", "--version", action="version", version=VERSION)
|
||||||
|
|
||||||
parser.add_argument("--show-tracebacks", action="store_true",
|
|
||||||
help="show complete tracebacks for Hy exceptions")
|
|
||||||
|
|
||||||
# this will contain the script/program name and any arguments for it.
|
# this will contain the script/program name and any arguments for it.
|
||||||
parser.add_argument('args', nargs=argparse.REMAINDER,
|
parser.add_argument('args', nargs=argparse.REMAINDER,
|
||||||
help=argparse.SUPPRESS)
|
help=argparse.SUPPRESS)
|
||||||
|
@ -328,10 +324,6 @@ def cmdline_handler(scriptname, argv):
|
||||||
|
|
||||||
options = parser.parse_args(argv[1:])
|
options = parser.parse_args(argv[1:])
|
||||||
|
|
||||||
if options.show_tracebacks:
|
|
||||||
global SIMPLE_TRACEBACKS
|
|
||||||
SIMPLE_TRACEBACKS = False
|
|
||||||
|
|
||||||
if options.E:
|
if options.E:
|
||||||
# User did "hy -E ..."
|
# User did "hy -E ..."
|
||||||
_remove_python_envs()
|
_remove_python_envs()
|
||||||
|
@ -372,7 +364,8 @@ def cmdline_handler(scriptname, argv):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sys.argv = options.args
|
sys.argv = options.args
|
||||||
runhy.run_path(filename, run_name='__main__')
|
with filtered_hy_exceptions():
|
||||||
|
runhy.run_path(filename, run_name='__main__')
|
||||||
return 0
|
return 0
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print("hy: Can't open file '{0}': [Errno {1}] {2}".format(
|
print("hy: Can't open file '{0}': [Errno {1}] {2}".format(
|
||||||
|
@ -448,9 +441,11 @@ def hy2py_main():
|
||||||
|
|
||||||
if options.FILE is None or options.FILE == '-':
|
if options.FILE is None or options.FILE == '-':
|
||||||
source = sys.stdin.read()
|
source = sys.stdin.read()
|
||||||
hst = pretty_error(hy_parse, source, filename='<stdin>')
|
with filtered_hy_exceptions():
|
||||||
|
hst = hy_parse(source, filename='<stdin>')
|
||||||
else:
|
else:
|
||||||
with io.open(options.FILE, 'r', encoding='utf-8') as source_file:
|
with filtered_hy_exceptions(), \
|
||||||
|
io.open(options.FILE, 'r', encoding='utf-8') as source_file:
|
||||||
source = source_file.read()
|
source = source_file.read()
|
||||||
hst = hy_parse(source, filename=options.FILE)
|
hst = hy_parse(source, filename=options.FILE)
|
||||||
|
|
||||||
|
@ -468,7 +463,9 @@ def hy2py_main():
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
|
|
||||||
_ast = pretty_error(hy_compile, hst, '__main__')
|
with filtered_hy_exceptions():
|
||||||
|
_ast = hy_compile(hst, '__main__')
|
||||||
|
|
||||||
if options.with_ast:
|
if options.with_ast:
|
||||||
if PY3 and platform.system() == "Windows":
|
if PY3 and platform.system() == "Windows":
|
||||||
_print_for_windows(astor.dump_tree(_ast))
|
_print_for_windows(astor.dump_tree(_ast))
|
||||||
|
|
|
@ -1834,7 +1834,7 @@ def get_compiler_module(module=None, compiler=None, calling_frame=False):
|
||||||
|
|
||||||
|
|
||||||
def hy_eval(hytree, locals=None, module=None, ast_callback=None,
|
def hy_eval(hytree, locals=None, module=None, ast_callback=None,
|
||||||
compiler=None, filename='<string>', source=None):
|
compiler=None, filename=None, source=None):
|
||||||
"""Evaluates a quoted expression and returns the value.
|
"""Evaluates a quoted expression and returns the value.
|
||||||
|
|
||||||
If you're evaluating hand-crafted AST trees, make sure the line numbers
|
If you're evaluating hand-crafted AST trees, make sure the line numbers
|
||||||
|
|
82
hy/errors.py
82
hy/errors.py
|
@ -2,13 +2,20 @@
|
||||||
# Copyright 2019 the authors.
|
# Copyright 2019 the authors.
|
||||||
# This file is part of Hy, which is free software licensed under the Expat
|
# This file is part of Hy, which is free software licensed under the Expat
|
||||||
# license. See the LICENSE.
|
# license. See the LICENSE.
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from hy import _initialize_env_var
|
||||||
|
|
||||||
from clint.textui import colored
|
from clint.textui import colored
|
||||||
|
|
||||||
|
_hy_filter_internal_errors = _initialize_env_var('HY_FILTER_INTERNAL_ERRORS',
|
||||||
|
True)
|
||||||
|
|
||||||
|
|
||||||
class HyError(Exception):
|
class HyError(Exception):
|
||||||
def __init__(self, message, *args):
|
def __init__(self, message, *args):
|
||||||
|
@ -193,7 +200,8 @@ class HySyntaxError(HyLanguageError, SyntaxError):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
||||||
output = traceback.format_exception_only(SyntaxError, self)
|
output = traceback.format_exception_only(SyntaxError,
|
||||||
|
SyntaxError(*self.args))
|
||||||
|
|
||||||
output[-1] = colored.yellow(output[-1])
|
output[-1] = colored.yellow(output[-1])
|
||||||
if len(self.source) > 0:
|
if len(self.source) > 0:
|
||||||
|
@ -206,3 +214,73 @@ class HySyntaxError(HyLanguageError, SyntaxError):
|
||||||
|
|
||||||
# Avoid "...expected str instance, ColoredString found"
|
# Avoid "...expected str instance, ColoredString found"
|
||||||
return reduce(lambda x, y: x + y, output)
|
return reduce(lambda x, y: x + y, output)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_module_info(module):
|
||||||
|
compiler_loader = pkgutil.get_loader(module)
|
||||||
|
is_pkg = compiler_loader.is_package(module)
|
||||||
|
filename = compiler_loader.get_filename()
|
||||||
|
if is_pkg:
|
||||||
|
# Use package directory
|
||||||
|
return os.path.dirname(filename)
|
||||||
|
else:
|
||||||
|
# Normalize filename endings, because tracebacks will use `pyc` when
|
||||||
|
# the loader says `py`.
|
||||||
|
return filename.replace('.pyc', '.py')
|
||||||
|
|
||||||
|
|
||||||
|
_tb_hidden_modules = {_get_module_info(m)
|
||||||
|
for m in ['hy.compiler', 'hy.lex',
|
||||||
|
'hy.cmdline', 'hy.lex.parser',
|
||||||
|
'hy.importer', 'hy._compat',
|
||||||
|
'hy.macros', 'hy.models',
|
||||||
|
'rply']}
|
||||||
|
|
||||||
|
|
||||||
|
def hy_exc_handler(exc_type, exc_value, exc_traceback):
|
||||||
|
"""Produce exceptions print-outs with all frames originating from the
|
||||||
|
modules in `_tb_hidden_modules` filtered out.
|
||||||
|
|
||||||
|
The frames are actually filtered by each module's filename and only when a
|
||||||
|
subclass of `HyLanguageError` is emitted.
|
||||||
|
|
||||||
|
This does not remove the frames from the actual tracebacks, so debugging
|
||||||
|
will show everything.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# frame = (filename, line number, function name*, text)
|
||||||
|
new_tb = [frame for frame in traceback.extract_tb(exc_traceback)
|
||||||
|
if not (frame[0].replace('.pyc', '.py') in _tb_hidden_modules or
|
||||||
|
os.path.dirname(frame[0]) in _tb_hidden_modules)]
|
||||||
|
|
||||||
|
lines = traceback.format_list(new_tb)
|
||||||
|
|
||||||
|
if lines:
|
||||||
|
lines.insert(0, "Traceback (most recent call last):\n")
|
||||||
|
|
||||||
|
lines.extend(traceback.format_exception_only(exc_type, exc_value))
|
||||||
|
output = ''.join(lines)
|
||||||
|
|
||||||
|
sys.stderr.write(output)
|
||||||
|
sys.stderr.flush()
|
||||||
|
except Exception:
|
||||||
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def filtered_hy_exceptions():
|
||||||
|
"""Temporarily apply a `sys.excepthook` that filters Hy internal frames
|
||||||
|
from tracebacks.
|
||||||
|
|
||||||
|
Filtering can be controlled by the variable
|
||||||
|
`hy.errors._hy_filter_internal_errors` and environment variable
|
||||||
|
`HY_FILTER_INTERNAL_ERRORS`.
|
||||||
|
"""
|
||||||
|
global _hy_filter_internal_errors
|
||||||
|
if _hy_filter_internal_errors:
|
||||||
|
current_hook = sys.excepthook
|
||||||
|
sys.excepthook = hy_exc_handler
|
||||||
|
yield
|
||||||
|
sys.excepthook = current_hook
|
||||||
|
else:
|
||||||
|
yield
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright 2019 the authors.
|
# Copyright 2019 the authors.
|
||||||
# This file is part of Hy, which is free software licensed under the Expat
|
# This file is part of Hy, which is free software licensed under the Expat
|
||||||
# license. See the LICENSE.
|
# license. See the LICENSE.
|
||||||
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -10,11 +11,36 @@ from hy.models import (HyExpression, HyInteger, HyFloat, HyComplex, HySymbol,
|
||||||
HyString, HyDict, HyList, HySet, HyKeyword)
|
HyString, HyDict, HyList, HySet, HyKeyword)
|
||||||
from hy.lex import tokenize
|
from hy.lex import tokenize
|
||||||
from hy.lex.exceptions import LexException, PrematureEndOfInput
|
from hy.lex.exceptions import LexException, PrematureEndOfInput
|
||||||
|
from hy.errors import hy_exc_handler
|
||||||
|
|
||||||
def peoi(): return pytest.raises(PrematureEndOfInput)
|
def peoi(): return pytest.raises(PrematureEndOfInput)
|
||||||
def lexe(): return pytest.raises(LexException)
|
def lexe(): return pytest.raises(LexException)
|
||||||
|
|
||||||
|
|
||||||
|
def check_ex(execinfo, expected):
|
||||||
|
output = traceback.format_exception_only(execinfo.type, execinfo.value)
|
||||||
|
assert output[:-1] == expected[:-1]
|
||||||
|
# Python 2.7 doesn't give the full exception name, so we compensate.
|
||||||
|
assert output[-1].endswith(expected[-1])
|
||||||
|
|
||||||
|
|
||||||
|
def check_trace_output(capsys, execinfo, expected):
|
||||||
|
sys.__excepthook__(execinfo.type, execinfo.value, execinfo.tb)
|
||||||
|
captured_wo_filtering = capsys.readouterr()[-1].strip('\n')
|
||||||
|
|
||||||
|
hy_exc_handler(execinfo.type, execinfo.value, execinfo.tb)
|
||||||
|
captured_w_filtering = capsys.readouterr()[-1].strip('\n')
|
||||||
|
|
||||||
|
output = captured_w_filtering.split('\n')
|
||||||
|
|
||||||
|
# Make sure the filtered frames aren't the same as the unfiltered ones.
|
||||||
|
assert output[:-1] != captured_wo_filtering.split('\n')[:-1]
|
||||||
|
# Remove the origin frame lines.
|
||||||
|
assert output[3:-1] == expected[:-1]
|
||||||
|
# Python 2.7 doesn't give the full exception name, so we compensate.
|
||||||
|
assert output[-1].endswith(expected[-1])
|
||||||
|
|
||||||
|
|
||||||
def test_lex_exception():
|
def test_lex_exception():
|
||||||
""" Ensure tokenize throws a fit on a partial input """
|
""" Ensure tokenize throws a fit on a partial input """
|
||||||
with peoi(): tokenize("(foo")
|
with peoi(): tokenize("(foo")
|
||||||
|
@ -32,8 +58,13 @@ def test_unbalanced_exception():
|
||||||
def test_lex_single_quote_err():
|
def test_lex_single_quote_err():
|
||||||
"Ensure tokenizing \"' \" throws a LexException that can be stringified"
|
"Ensure tokenizing \"' \" throws a LexException that can be stringified"
|
||||||
# https://github.com/hylang/hy/issues/1252
|
# https://github.com/hylang/hy/issues/1252
|
||||||
with lexe() as e: tokenize("' ")
|
with lexe() as execinfo:
|
||||||
assert "Could not identify the next token" in str(e.value)
|
tokenize("' ")
|
||||||
|
check_ex(execinfo, [
|
||||||
|
' File "<string>", line -1\n',
|
||||||
|
" '\n",
|
||||||
|
' ^\n',
|
||||||
|
'LexException: Could not identify the next token.\n'])
|
||||||
|
|
||||||
|
|
||||||
def test_lex_expression_symbols():
|
def test_lex_expression_symbols():
|
||||||
|
@ -76,7 +107,11 @@ def test_lex_strings_exception():
|
||||||
""" Make sure tokenize throws when codec can't decode some bytes"""
|
""" Make sure tokenize throws when codec can't decode some bytes"""
|
||||||
with lexe() as execinfo:
|
with lexe() as execinfo:
|
||||||
tokenize('\"\\x8\"')
|
tokenize('\"\\x8\"')
|
||||||
assert "Can't convert \"\\x8\" to a HyString" in str(execinfo.value)
|
check_ex(execinfo, [
|
||||||
|
' File "<string>", line 1\n',
|
||||||
|
' "\\x8"\n',
|
||||||
|
' ^\n',
|
||||||
|
'LexException: Can\'t convert "\\x8" to a HyString\n'])
|
||||||
|
|
||||||
|
|
||||||
def test_lex_bracket_strings():
|
def test_lex_bracket_strings():
|
||||||
|
@ -184,20 +219,13 @@ def test_lex_digit_separators():
|
||||||
def test_lex_bad_attrs():
|
def test_lex_bad_attrs():
|
||||||
with lexe() as execinfo:
|
with lexe() as execinfo:
|
||||||
tokenize("1.foo")
|
tokenize("1.foo")
|
||||||
|
check_ex(execinfo, [
|
||||||
expected = [
|
|
||||||
' File "<string>", line 1\n',
|
' File "<string>", line 1\n',
|
||||||
' 1.foo\n',
|
' 1.foo\n',
|
||||||
' ^\n',
|
' ^\n',
|
||||||
('LexException: Cannot access attribute on anything other'
|
'LexException: Cannot access attribute on anything other'
|
||||||
' than a name (in order to get attributes of expressions,'
|
' than a name (in order to get attributes of expressions,'
|
||||||
' use `(. <expression> <attr>)` or `(.<attr> <expression>)`)\n')
|
' use `(. <expression> <attr>)` or `(.<attr> <expression>)`)\n'])
|
||||||
]
|
|
||||||
output = traceback.format_exception_only(execinfo.type, execinfo.value)
|
|
||||||
|
|
||||||
assert output[:-1:1] == expected[:-1:1]
|
|
||||||
# Python 2.7 doesn't give the full exception name, so we compensate.
|
|
||||||
assert output[-1].endswith(expected[-1])
|
|
||||||
|
|
||||||
with lexe(): tokenize("0.foo")
|
with lexe(): tokenize("0.foo")
|
||||||
with lexe(): tokenize("1.5.foo")
|
with lexe(): tokenize("1.5.foo")
|
||||||
|
@ -437,3 +465,27 @@ def test_discard():
|
||||||
assert tokenize("a '#_b c") == [HySymbol("a"), HyExpression([HySymbol("quote"), HySymbol("c")])]
|
assert tokenize("a '#_b c") == [HySymbol("a"), HyExpression([HySymbol("quote"), HySymbol("c")])]
|
||||||
assert tokenize("a '#_b #_c d") == [HySymbol("a"), HyExpression([HySymbol("quote"), HySymbol("d")])]
|
assert tokenize("a '#_b #_c d") == [HySymbol("a"), HyExpression([HySymbol("quote"), HySymbol("d")])]
|
||||||
assert tokenize("a '#_ #_b c d") == [HySymbol("a"), HyExpression([HySymbol("quote"), HySymbol("d")])]
|
assert tokenize("a '#_ #_b c d") == [HySymbol("a"), HyExpression([HySymbol("quote"), HySymbol("d")])]
|
||||||
|
|
||||||
|
|
||||||
|
def test_lex_exception_filtering(capsys):
|
||||||
|
"""Confirm that the exception filtering works for lexer errors."""
|
||||||
|
|
||||||
|
# First, test for PrematureEndOfInput
|
||||||
|
with peoi() as execinfo:
|
||||||
|
tokenize(" \n (foo")
|
||||||
|
check_trace_output(capsys, execinfo, [
|
||||||
|
' File "<string>", line 2',
|
||||||
|
' (foo',
|
||||||
|
' ^',
|
||||||
|
'PrematureEndOfInput: Premature end of input'])
|
||||||
|
|
||||||
|
# Now, for a generic LexException
|
||||||
|
with lexe() as execinfo:
|
||||||
|
tokenize(" \n\n 1.foo ")
|
||||||
|
check_trace_output(capsys, execinfo, [
|
||||||
|
' File "<string>", line 3',
|
||||||
|
' 1.foo',
|
||||||
|
' ^',
|
||||||
|
'LexException: Cannot access attribute on anything other'
|
||||||
|
' than a name (in order to get attributes of expressions,'
|
||||||
|
' use `(. <expression> <attr>)` or `(.<attr> <expression>)`)'])
|
||||||
|
|
Loading…
Reference in New Issue