1#
2#   Errors
3#
4
5from __future__ import absolute_import
6
7try:
8    from __builtin__ import basestring as any_string_type
9except ImportError:
10    any_string_type = (bytes, str)
11
12import sys
13from contextlib import contextmanager
14
15from ..Utils import open_new_file
16from . import DebugFlags
17from . import Options
18
19
20class PyrexError(Exception):
21    pass
22
23
24class PyrexWarning(Exception):
25    pass
26
27
28def context(position):
29    source = position[0]
30    assert not (isinstance(source, any_string_type)), (
31        "Please replace filename strings with Scanning.FileSourceDescriptor instances %r" % source)
32    try:
33        F = source.get_lines()
34    except UnicodeDecodeError:
35        # file has an encoding problem
36        s = u"[unprintable code]\n"
37    else:
38        s = u''.join(F[max(0, position[1]-6):position[1]])
39        s = u'...\n%s%s^\n' % (s, u' '*(position[2]-1))
40    s = u'%s\n%s%s\n' % (u'-'*60, s, u'-'*60)
41    return s
42
43def format_position(position):
44    if position:
45        return u"%s:%d:%d: " % (position[0].get_error_description(),
46                                position[1], position[2])
47    return u''
48
49def format_error(message, position):
50    if position:
51        pos_str = format_position(position)
52        cont = context(position)
53        message = u'\nError compiling Cython file:\n%s\n%s%s' % (cont, pos_str, message or u'')
54    return message
55
56class CompileError(PyrexError):
57
58    def __init__(self, position = None, message = u""):
59        self.position = position
60        self.message_only = message
61        self.formatted_message = format_error(message, position)
62        self.reported = False
63    # Deprecated and withdrawn in 2.6:
64    #   self.message = message
65        Exception.__init__(self, self.formatted_message)
66        # Python Exception subclass pickling is broken,
67        # see http://bugs.python.org/issue1692335
68        self.args = (position, message)
69
70    def __str__(self):
71        return self.formatted_message
72
73class CompileWarning(PyrexWarning):
74
75    def __init__(self, position = None, message = ""):
76        self.position = position
77    # Deprecated and withdrawn in 2.6:
78    #   self.message = message
79        Exception.__init__(self, format_position(position) + message)
80
81class InternalError(Exception):
82    # If this is ever raised, there is a bug in the compiler.
83
84    def __init__(self, message):
85        self.message_only = message
86        Exception.__init__(self, u"Internal compiler error: %s"
87            % message)
88
89class AbortError(Exception):
90    # Throw this to stop the compilation immediately.
91
92    def __init__(self, message):
93        self.message_only = message
94        Exception.__init__(self, u"Abort error: %s" % message)
95
96class CompilerCrash(CompileError):
97    # raised when an unexpected exception occurs in a transform
98    def __init__(self, pos, context, message, cause, stacktrace=None):
99        if message:
100            message = u'\n' + message
101        else:
102            message = u'\n'
103        self.message_only = message
104        if context:
105            message = u"Compiler crash in %s%s" % (context, message)
106        if stacktrace:
107            import traceback
108            message += (
109                u'\n\nCompiler crash traceback from this point on:\n' +
110                u''.join(traceback.format_tb(stacktrace)))
111        if cause:
112            if not stacktrace:
113                message += u'\n'
114            message += u'%s: %s' % (cause.__class__.__name__, cause)
115        CompileError.__init__(self, pos, message)
116        # Python Exception subclass pickling is broken,
117        # see http://bugs.python.org/issue1692335
118        self.args = (pos, context, message, cause, stacktrace)
119
120class NoElementTreeInstalledException(PyrexError):
121    """raised when the user enabled options.gdb_debug but no ElementTree
122    implementation was found
123    """
124
125listing_file = None
126num_errors = 0
127echo_file = None
128
129def open_listing_file(path, echo_to_stderr = 1):
130    # Begin a new error listing. If path is None, no file
131    # is opened, the error counter is just reset.
132    global listing_file, num_errors, echo_file
133    if path is not None:
134        listing_file = open_new_file(path)
135    else:
136        listing_file = None
137    if echo_to_stderr:
138        echo_file = sys.stderr
139    else:
140        echo_file = None
141    num_errors = 0
142
143def close_listing_file():
144    global listing_file
145    if listing_file:
146        listing_file.close()
147        listing_file = None
148
149def report_error(err, use_stack=True):
150    if error_stack and use_stack:
151        error_stack[-1].append(err)
152    else:
153        global num_errors
154        # See Main.py for why dual reporting occurs. Quick fix for now.
155        if err.reported: return
156        err.reported = True
157        try: line = u"%s\n" % err
158        except UnicodeEncodeError:
159            # Python <= 2.5 does this for non-ASCII Unicode exceptions
160            line = format_error(getattr(err, 'message_only', "[unprintable exception message]"),
161                                getattr(err, 'position', None)) + u'\n'
162        if listing_file:
163            try: listing_file.write(line)
164            except UnicodeEncodeError:
165                listing_file.write(line.encode('ASCII', 'replace'))
166        if echo_file:
167            try: echo_file.write(line)
168            except UnicodeEncodeError:
169                echo_file.write(line.encode('ASCII', 'replace'))
170        num_errors += 1
171        if Options.fast_fail:
172            raise AbortError("fatal errors")
173
174
175def error(position, message):
176    #print("Errors.error:", repr(position), repr(message)) ###
177    if position is None:
178        raise InternalError(message)
179    err = CompileError(position, message)
180    if DebugFlags.debug_exception_on_error: raise Exception(err) # debug
181    report_error(err)
182    return err
183
184
185LEVEL = 1 # warn about all errors level 1 or higher
186
187
188def message(position, message, level=1):
189    if level < LEVEL:
190        return
191    warn = CompileWarning(position, message)
192    line = "note: %s\n" % warn
193    if listing_file:
194        listing_file.write(line)
195    if echo_file:
196        echo_file.write(line)
197    return warn
198
199
200def warning(position, message, level=0):
201    if level < LEVEL:
202        return
203    if Options.warning_errors and position:
204        return error(position, message)
205    warn = CompileWarning(position, message)
206    line = "warning: %s\n" % warn
207    if listing_file:
208        listing_file.write(line)
209    if echo_file:
210        echo_file.write(line)
211    return warn
212
213
214_warn_once_seen = {}
215def warn_once(position, message, level=0):
216    if level < LEVEL or message in _warn_once_seen:
217        return
218    warn = CompileWarning(position, message)
219    line = "warning: %s\n" % warn
220    if listing_file:
221        listing_file.write(line)
222    if echo_file:
223        echo_file.write(line)
224    _warn_once_seen[message] = True
225    return warn
226
227
228# These functions can be used to momentarily suppress errors.
229
230error_stack = []
231
232
233def hold_errors():
234    error_stack.append([])
235
236
237def release_errors(ignore=False):
238    held_errors = error_stack.pop()
239    if not ignore:
240        for err in held_errors:
241            report_error(err)
242
243
244def held_errors():
245    return error_stack[-1]
246
247
248# same as context manager:
249
250@contextmanager
251def local_errors(ignore=False):
252    errors = []
253    error_stack.append(errors)
254    try:
255        yield errors
256    finally:
257        release_errors(ignore=ignore)
258
259
260# this module needs a redesign to support parallel cythonisation, but
261# for now, the following works at least in sequential compiler runs
262
263def reset():
264    _warn_once_seen.clear()
265    del error_stack[:]
266