e2ec1d9e674ed098fe6bc6f41d9d24683d739c59
[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
96 listing_file = None
97 num_errors = 0
98 echo_file = None
99
100 def open_listing_file(path, echo_to_stderr = 1):
101     # Begin a new error listing. If path is None, no file
102     # is opened, the error counter is just reset.
103     global listing_file, num_errors, echo_file
104     if path is not None:
105         listing_file = open_new_file(path)
106     else:
107         listing_file = None
108     if echo_to_stderr:
109         echo_file = sys.stderr
110     else:
111         echo_file = None
112     num_errors = 0
113
114 def close_listing_file():
115     global listing_file
116     if listing_file:
117         listing_file.close()
118         listing_file = None
119
120 def report_error(err):
121     if error_stack:
122         error_stack[-1].append(err)
123     else:
124         global num_errors
125         # See Main.py for why dual reporting occurs. Quick fix for now.
126         if err.reported: return
127         err.reported = True
128         try: line = u"%s\n" % err
129         except UnicodeEncodeError:
130             # Python <= 2.5 does this for non-ASCII Unicode exceptions
131             line = format_error(getattr(err, 'message_only', "[unprintable exception message]"),
132                                 getattr(err, 'position', None)) + u'\n'
133         if listing_file:
134             try: listing_file.write(line)
135             except UnicodeEncodeError:
136                 listing_file.write(line.encode('ASCII', 'replace'))
137         if echo_file:
138             try: echo_file.write(line)
139             except UnicodeEncodeError:
140                 echo_file.write(line.encode('ASCII', 'replace'))
141         num_errors = num_errors + 1
142         if Options.fatal_errors:
143             raise InternalError, "abort"
144
145 def error(position, message):
146     #print "Errors.error:", repr(position), repr(message) ###
147     if position is None:
148         raise InternalError(message)
149     err = CompileError(position, message)    
150     if debug_exception_on_error: raise Exception(err) # debug
151     report_error(err)
152     return err
153
154 LEVEL=1 # warn about all errors level 1 or higher
155
156 def message(position, message, level=1):
157     if level < LEVEL:
158         return
159     warn = CompileWarning(position, message)
160     line = "note: %s\n" % warn
161     if listing_file:
162         listing_file.write(line)
163     if echo_file:
164         echo_file.write(line)
165     return warn
166
167 def warning(position, message, level=0):
168     if level < LEVEL:
169         return
170     warn = CompileWarning(position, message)
171     line = "warning: %s\n" % warn
172     if listing_file:
173         listing_file.write(line)
174     if echo_file:
175         echo_file.write(line)
176     return warn
177
178 _warn_once_seen = {}
179 def warn_once(position, message, level=0):
180     if level < LEVEL or message in _warn_once_seen:
181         return
182     warn = CompileWarning(position, message)
183     line = "warning: %s\n" % warn
184     if listing_file:
185         listing_file.write(line)
186     if echo_file:
187         echo_file.write(line)
188     _warn_once_seen[message] = True
189     return warn
190
191
192 # These functions can be used to momentarily suppress errors. 
193
194 error_stack = []
195
196 def hold_errors():
197     error_stack.append([])
198
199 def release_errors(ignore=False):
200     held_errors = error_stack.pop()
201     if not ignore:
202         for err in held_errors:
203             report_error(err)
204
205 def held_errors():
206     return error_stack[-1]
207
208
209 # this module needs a redesign to support parallel cythonisation, but
210 # for now, the following works at least in sequential compiler runs
211
212 def reset():
213     _warn_once_seen.clear()
214     del error_stack[:]