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