3d103de4130457e1bb6738fddcd2788cb2bebb84
[cython.git] / Cython / Compiler / Errors.py
1 #
2 #   Pyrex - Errors
3 #
4
5 import sys
6 from Cython.Utils import open_new_file
7 from DebugFlags import debug_exception_on_error
8 import Options
9
10
11 class PyrexError(Exception):
12     pass
13
14 class PyrexWarning(Exception):
15     pass
16
17
18 def 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 = list(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
33 def format_position(position):
34     if position:
35         return u"%s:%d:%d: " % (position[0].get_description(),
36                                 position[1], position[2])
37     return u''
38
39 def 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
46 class CompileError(PyrexError):
47     
48     def __init__(self, position = None, message = u""):
49         self.position = position
50         self.message_only = message
51         self.reported = False
52     # Deprecated and withdrawn in 2.6:
53     #   self.message = message
54         Exception.__init__(self, format_error(message, position))
55
56 class CompileWarning(PyrexWarning):
57     
58     def __init__(self, position = None, message = ""):
59         self.position = position
60     # Deprecated and withdrawn in 2.6:
61     #   self.message = message
62         Exception.__init__(self, format_position(position) + message)
63
64
65 class InternalError(Exception):
66     # If this is ever raised, there is a bug in the compiler.
67     
68     def __init__(self, message):
69         self.message_only = message
70         Exception.__init__(self, u"Internal compiler error: %s"
71             % message)
72
73
74 class CompilerCrash(CompileError):
75     # raised when an unexpected exception occurs in a transform
76     def __init__(self, pos, context, message, cause, stacktrace=None):
77         if message:
78             message = u'\n' + message
79         else:
80             message = u'\n'
81         self.message_only = message
82         if context:
83             message = u"Compiler crash in %s%s" % (context, message)
84         if stacktrace:
85             import traceback
86             message += (
87                 u'\n\nCompiler crash traceback from this point on:\n' +
88                 u''.join(traceback.format_tb(stacktrace)))
89         if cause:
90             if not stacktrace:
91                 message += u'\n'
92             message += u'%s: %s' % (cause.__class__.__name__, cause)
93         CompileError.__init__(self, pos, message)
94
95 class NoElementTreeInstalledException(PyrexError):
96     """raised when the user enabled options.gdb_debug but no ElementTree 
97     implementation was found
98     """
99
100 listing_file = None
101 num_errors = 0
102 echo_file = None
103
104 def open_listing_file(path, echo_to_stderr = 1):
105     # Begin a new error listing. If path is None, no file
106     # is opened, the error counter is just reset.
107     global listing_file, num_errors, echo_file
108     if path is not None:
109         listing_file = open_new_file(path)
110     else:
111         listing_file = None
112     if echo_to_stderr:
113         echo_file = sys.stderr
114     else:
115         echo_file = None
116     num_errors = 0
117
118 def close_listing_file():
119     global listing_file
120     if listing_file:
121         listing_file.close()
122         listing_file = None
123
124 def report_error(err):
125     if error_stack:
126         error_stack[-1].append(err)
127     else:
128         global num_errors
129         # See Main.py for why dual reporting occurs. Quick fix for now.
130         if err.reported: return
131         err.reported = True
132         try: line = u"%s\n" % err
133         except UnicodeEncodeError:
134             # Python <= 2.5 does this for non-ASCII Unicode exceptions
135             line = format_error(getattr(err, 'message_only', "[unprintable exception message]"),
136                                 getattr(err, 'position', None)) + u'\n'
137         if listing_file:
138             try: listing_file.write(line)
139             except UnicodeEncodeError:
140                 listing_file.write(line.encode('ASCII', 'replace'))
141         if echo_file:
142             try: echo_file.write(line)
143             except UnicodeEncodeError:
144                 echo_file.write(line.encode('ASCII', 'replace'))
145         num_errors = num_errors + 1
146         if Options.fatal_errors:
147             raise InternalError, "abort"
148
149 def error(position, message):
150     #print "Errors.error:", repr(position), repr(message) ###
151     if position is None:
152         raise InternalError(message)
153     err = CompileError(position, message)    
154     if debug_exception_on_error: raise Exception(err) # debug
155     report_error(err)
156     return err
157
158 LEVEL=1 # warn about all errors level 1 or higher
159
160 def message(position, message, level=1):
161     if level < LEVEL:
162         return
163     warn = CompileWarning(position, message)
164     line = "note: %s\n" % warn
165     if listing_file:
166         listing_file.write(line)
167     if echo_file:
168         echo_file.write(line)
169     return warn
170
171 def warning(position, message, level=0):
172     if level < LEVEL:
173         return
174     warn = CompileWarning(position, message)
175     line = "warning: %s\n" % warn
176     if listing_file:
177         listing_file.write(line)
178     if echo_file:
179         echo_file.write(line)
180     return warn
181
182 _warn_once_seen = {}
183 def warn_once(position, message, level=0):
184     if level < LEVEL or message in _warn_once_seen:
185         return
186     warn = CompileWarning(position, message)
187     line = "warning: %s\n" % warn
188     if listing_file:
189         listing_file.write(line)
190     if echo_file:
191         echo_file.write(line)
192     _warn_once_seen[message] = True
193     return warn
194
195
196 # These functions can be used to momentarily suppress errors. 
197
198 error_stack = []
199
200 def hold_errors():
201     error_stack.append([])
202
203 def release_errors(ignore=False):
204     held_errors = error_stack.pop()
205     if not ignore:
206         for err in held_errors:
207             report_error(err)
208
209 def held_errors():
210     return error_stack[-1]
211
212
213 # this module needs a redesign to support parallel cythonisation, but
214 # for now, the following works at least in sequential compiler runs
215
216 def reset():
217     _warn_once_seen.clear()
218     del error_stack[:]