-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.py
More file actions
319 lines (247 loc) · 10.4 KB
/
Copy patherrors.py
File metadata and controls
319 lines (247 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
import os
import pkgutil
import re
import sys
import traceback
from contextlib import contextmanager
from functools import reduce
from colorama import Fore
from hy import _initialize_env_var
from hy._compat import PYPY
_hy_filter_internal_errors = _initialize_env_var("HY_FILTER_INTERNAL_ERRORS", True)
COLORED = _initialize_env_var("HY_COLORED_ERRORS", False)
class HyError(Exception):
pass
class HyInternalError(HyError):
"""Unexpected errors occurring during compilation or parsing of Hy code.
Errors sub-classing this are not intended to be user-facing, and will,
hopefully, never be seen by users!
"""
class HyLanguageError(HyError):
"""Errors caused by invalid use of the Hy language.
This, and any errors inheriting from this, are user-facing.
"""
def __init__(
self,
message,
expression=None,
filename=None,
source=None,
lineno=1,
colno=1,
):
"""
Args:
message (str): The message to display for this error.
expression (Optional[Object]): The Hy expression generating this error.
filename (Optional[str]): The filename for the source code generating this error.
Expression-provided information will take precedence of this value. Defaults to `None`.
source (Optional[str]): The actual source code generating this error. Expression-provided
information will take precedence of this value. Defaults to `None`.
lineno (int): The line number of the error. Expression-provided information will
take precedence of this value. Defaults to `1`.
colno (int): The column number of the error. Expression-provided information
will take precedence of this value. Defaults to `1`.
"""
self.msg = message
self.compute_lineinfo(expression, filename, source, lineno, colno)
if isinstance(self, SyntaxError):
syntax_error_args = (self.filename, self.lineno, self.offset, self.text)
super().__init__(message, syntax_error_args)
else:
super().__init__(message)
def compute_lineinfo(self, expression, filename, source, lineno, colno):
# NOTE: We use `SyntaxError`'s field names (i.e. `text`, `offset`,
# `msg`) for compatibility and print-outs.
self.text = getattr(expression, "source", source)
self.filename = getattr(expression, "filename", filename)
if self.text:
lines = self.text.splitlines()
self.lineno = getattr(expression, "start_line", lineno)
self.offset = getattr(expression, "start_column", colno)
end_column = getattr(expression, "end_column", len(lines[self.lineno - 1]))
end_line = getattr(expression, "end_line", self.lineno)
# Trim the source down to the essentials.
self.text = "\n".join(lines[self.lineno - 1 : end_line])
if end_column:
if self.lineno == end_line:
self.arrow_offset = end_column
else:
self.arrow_offset = len(self.text[0])
self.arrow_offset -= self.offset
else:
self.arrow_offset = None
else:
# We could attempt to extract the source given a filename, but we
# don't.
self.lineno = lineno
self.offset = colno
self.arrow_offset = None
def __str__(self):
"""Provide an exception message that includes SyntaxError-like source
line information when available.
"""
# Syntax errors are special and annotate the traceback (instead of what
# we would do in the message that follows the traceback).
if isinstance(self, SyntaxError):
return super().__str__()
# When there isn't extra source information, use the normal message.
elif not self.text:
return super().__str__()
# Re-purpose Python's builtin syntax error formatting.
output = traceback.format_exception_only(
SyntaxError,
SyntaxError(self.msg, (self.filename, self.lineno, self.offset, self.text)),
)
arrow_idx, _ = next(
((i, x) for i, x in enumerate(output) if x.strip() == "^"), (None, None)
)
if arrow_idx:
msg_idx = arrow_idx + 1
else:
msg_idx, _ = next(
(i, x) for i, x in enumerate(output) if x.startswith("SyntaxError: ")
)
# Get rid of erroneous error-type label.
output[msg_idx] = re.sub("^SyntaxError: ", "", output[msg_idx])
# Extend the text arrow, when given enough source info.
if arrow_idx and self.arrow_offset:
output[arrow_idx] = "{}{}^\n".format(
output[arrow_idx].rstrip("\n"), "-" * (self.arrow_offset - 1)
)
if COLORED:
output[msg_idx:] = [Fore.YELLOW + o + Fore.RESET for o in output[msg_idx:]]
if arrow_idx:
output[arrow_idx] = Fore.GREEN + output[arrow_idx] + Fore.RESET
for idx, line in enumerate(output[::msg_idx]):
if line.strip().startswith('File "{}", line'.format(self.filename)):
output[idx] = Fore.RED + line + Fore.RESET
# This resulting string will come after a "<class-name>:" prompt, so
# put it down a line.
output.insert(0, "\n")
# Avoid "...expected str instance, ColoredString found"
return reduce(lambda x, y: x + y, output)
class HyCompileError(HyInternalError):
"""Unexpected errors occurring within the compiler."""
class HyTypeError(HyLanguageError, TypeError):
"""TypeError occurring during the normal use of Hy."""
class HyNameError(HyLanguageError, NameError):
"""NameError occurring during the normal use of Hy."""
class HyRequireError(HyLanguageError):
"""Errors arising during the use of `require`
This, and any errors inheriting from this, are user-facing.
"""
class HyMacroExpansionError(HyLanguageError):
"""Errors caused by invalid use of Hy macros.
This, and any errors inheriting from this, are user-facing.
"""
class HyEvalError(HyLanguageError):
"""Errors occurring during code evaluation at compile-time.
These errors distinguish unexpected errors within the compilation process
(i.e. `HyInternalError`s) from unrelated errors in user code evaluated by
the compiler (e.g. in `eval-and-compile`).
This, and any errors inheriting from this, are user-facing.
"""
class HyIOError(HyInternalError, IOError):
"""Subclass used to distinguish between IOErrors raised by Hy itself as
opposed to Hy programs.
"""
class HySyntaxError(HyLanguageError, SyntaxError):
"""Error during the Lexing of a Hython expression."""
class HyWrapperError(HyError, TypeError):
"""Errors caused by language model object wrapping.
These can be caused by improper user-level use of a macro, so they're
not really "internal". If they arise due to anything else, they're an
internal/compiler problem, though.
"""
def _module_filter_name(module_name):
try:
compiler_loader = pkgutil.get_loader(module_name)
if not compiler_loader:
return None
filename = compiler_loader.get_filename(module_name)
if not filename:
return None
if compiler_loader.is_package(module_name):
# Use the package directory (e.g. instead of `.../__init__.py`) so
# that we can filter all modules in a package.
return os.path.dirname(filename)
else:
# Normalize filename endings, because tracebacks will use `pyc` when
# the loader says `py`.
return filename.replace(".pyc", ".py")
except Exception:
return None
_tb_hidden_modules = {
m
for m in map(
_module_filter_name,
[
"hy.compiler",
"hy.reader",
"hy.cmdline",
"hy.reader.parser",
"hy.importer",
"hy._compat",
"hy.macros",
"hy.models",
"hy.core.result_macros",
],
)
if m is not None
}
# We can't derive these easily from just their module names due
# to missing magic attributes in internal importlib modules
_tb_hidden_modules.update(
f"<builtin>/frozen {x}" if PYPY else f"<frozen {x}>"
for x in ("importlib._bootstrap", "importlib._bootstrap_external")
)
def hy_exc_filter(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.
"""
# frame = (filename, line number, function name*, text)
new_tb = []
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
):
new_tb += [frame]
lines = traceback.format_list(new_tb)
lines.insert(0, "Traceback (most recent call last):\n")
lines.extend(traceback.format_exception_only(exc_type, exc_value))
output = "".join(lines)
return output
def hy_exc_handler(exc_type, exc_value, exc_traceback):
"""A `sys.excepthook` handler that uses `hy_exc_filter` to
remove internal Hy frames from a traceback print-out.
"""
if os.environ.get("HY_DEBUG", False):
return sys.__excepthook__(exc_type, exc_value, exc_traceback)
try:
output = hy_exc_filter(exc_type, exc_value, exc_traceback)
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