Coverage for trlc/errors.py: 95%
124 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-27 00:52 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-27 00:52 +0000
1#!/usr/bin/env python3
2#
3# TRLC - Treat Requirements Like Code
4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5#
6# This file is part of the TRLC Python Reference Implementation.
7#
8# TRLC is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# TRLC is distributed in the hope that it will be useful, but WITHOUT
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
16# License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
21import sys
22import enum
24from trlc import version
27class Location:
28 """Reference to a source or virtual location
30 Any message raised by the :class:`Message_Handler` will be
31 attached to a given location. This location can be real
32 (i.e. something in a file) or virtual (i.e. a builtin function).
34 :attribute file_name: the name of the file or virtual location
35 :type: str
37 :attribute line_no: an optional line number, starting at 1
38 :type: int
40 :attribute col_no: an optional column number, starting at 1
41 :type: int:
42 """
43 def __init__(self, file_name, line_no=None, col_no=None):
44 assert isinstance(file_name, str)
45 if line_no is not None:
46 assert isinstance(line_no, int)
47 assert line_no >= 1
48 if col_no is not None:
49 assert isinstance(col_no, int)
50 assert col_no >= 1
51 self.file_name = file_name
52 self.line_no = line_no
53 self.col_no = col_no
55 def to_string(self, include_column=True):
56 """Return a nice string representation
58 The style is the gcc-style file:line:column format. Note that
59 the filename is stripped of its path in order to make the
60 final message smaller.
62 :param include_column: If set, include the column location (if \
63 there is one)
64 :type include_column: bool
66 :returns: a formatted location
67 :rtype: str
69 """
70 rv = self.file_name
71 if self.line_no:
72 rv += ":%u" % self.line_no
73 if self.col_no and include_column:
74 rv += ":%u" % self.col_no
75 return rv
77 def context_lines(self):
78 return []
80 def get_end_location(self):
81 """Get location point to the end of this location
83 When we generate a location for a longer sequence then this
84 function gets the "end" of it::
86 for example here
87 ^^^^^^^ this is the whole range
88 ^ file/line/col points here
89 ^ file/line/col of end_location points here
91 :returns: a pointer to the last character in a location
92 :rtype: Location
94 """
95 return self
98@enum.unique
99class Kind(enum.Enum):
100 SYS_ERROR = enum.auto()
101 SYS_CHECK = enum.auto()
102 SYS_WARNING = enum.auto()
103 USER_ERROR = enum.auto()
104 USER_WARNING = enum.auto()
106 def __str__(self):
107 return {"SYS_ERROR" : "error",
108 "SYS_CHECK" : "issue",
109 "SYS_WARNING" : "warning",
110 "USER_ERROR" : "check error",
111 "USER_WARNING" : "check warning"}[self.name]
114class TRLC_Error(Exception):
115 """ The universal exception that TRLC raises if something goes wrong
117 :attribute location: Where the issue originates from
118 :type: Location
120 :attribute kind: The kind of problem (e.g. lex error, error, warning, etc.)
121 :type: str
123 :attribute message: Description of the problem
124 :type: str
125 """
126 def __init__(self, location, kind, message):
127 assert isinstance(location, Location)
128 assert isinstance(kind, Kind)
129 assert isinstance(message, str)
131 super().__init__()
132 self.location = location
133 self.kind = kind
134 self.message = message
137class Message_Handler:
138 """Universal message handler
140 All messages from TRLC are processed by this class. If you want to
141 write a tool that emits additional messages then it would be a
142 really good idea to also use this class. Do not use your own print
143 statements.
145 If the location comes from the location attribute of
146 :class:`~trlc.ast.Node` then you also get context provided for
147 free.
149 :attribute brief: When true displays as much context as possible
150 :type: Boolean
152 :attribute warnings: Number of system or user warnings raised
153 :type: int
155 :attribute errors: Number of system or user errors raised
156 :type: int
158 :attribute supressed: Number of messages supressed by policy
159 :type: int
161 """
162 def __init__(self, brief=False, detailed_info=True):
163 assert isinstance(brief, bool)
164 self.brief = brief
165 self.show_details = detailed_info
166 self.warnings = 0
167 self.errors = 0
168 self.suppressed = 0
169 self.sm = None
170 self.suppress_kind = set()
172 def suppress(self, kind):
173 assert isinstance(kind, Kind)
174 self.suppress_kind.add(kind)
176 def cross_file_reference(self, location):
177 assert isinstance(location, Location)
179 if self.sm is None: 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true
180 return location.to_string(include_column=False)
181 else:
182 return self.sm.cross_file_reference(location)
184 def emit(self,
185 location,
186 kind,
187 message,
188 fatal=True,
189 extrainfo=None,
190 category=None):
191 assert isinstance(location, Location)
192 assert isinstance(kind, Kind)
193 assert isinstance(message, str)
194 assert isinstance(fatal, bool)
195 assert isinstance(extrainfo, str) or extrainfo is None
196 assert isinstance(category, str) or category is None
198 if self.brief:
199 context = None
200 msg = "%s: trlc %s: %s" % (location.to_string(),
201 str(kind),
202 message)
204 else:
205 context = location.context_lines()
206 msg = "%s: %s: %s" % (location.to_string(len(context) == 0),
207 str(kind),
208 message)
210 if category:
211 msg += " [%s]" % category
213 if kind in self.suppress_kind: 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true
214 self.suppressed += 1
216 else:
217 if context:
218 assert len(context) == 2
219 print(context[0].replace("\t", " "))
220 print(context[1].replace("\t", " "), msg)
221 else:
222 print(msg)
224 if not self.brief \
225 and self.show_details \
226 and extrainfo:
227 if context:
228 indent = len(context[1]) - 1
229 else:
230 indent = 0
231 for line in extrainfo.splitlines():
232 print("%s| %s" % (" " * indent,
233 line.rstrip()))
235 if fatal:
236 raise TRLC_Error(location, kind, message)
238 def lex_error(self, location, message):
239 assert isinstance(location, Location)
240 assert isinstance(message, str)
242 self.errors += 1
243 self.emit(location = location,
244 kind = Kind.SYS_ERROR,
245 message = message)
247 def error(self,
248 location,
249 message,
250 explanation=None,
251 fatal=True,
252 user=False):
253 """ Create an error message
255 For example::
257 mh.error(my_expr.location, "potato")
259 Might generate this output::
261 x = 5 + 2
262 ^ foo.check:5: error: potato
264 :param location: where to attach the message
265 :type location: Location
267 :param message: the message to print
268 :type message: str
270 :param fatal: should we raise an exception in addition to printing \
271 the error?
272 :type fatal: bool
274 :param user: if set print "check error:" instead of "error:"
275 :type user: bool
277 :raises TRLC_Error: if fatal is true
278 """
279 assert isinstance(location, Location)
280 assert isinstance(message, str)
281 assert isinstance(explanation, str) or explanation is None
282 assert isinstance(fatal, bool)
283 assert isinstance(user, bool)
285 if user:
286 kind = Kind.USER_ERROR
287 else:
288 kind = Kind.SYS_ERROR
290 self.errors += 1
291 self.emit(location = location,
292 kind = kind,
293 message = message,
294 fatal = fatal,
295 extrainfo = explanation)
297 def warning(self, location, message, explanation=None, user=False):
298 """ Create a warning message
300 :param location: where to attach the message
301 :type location: Location
302 :param message: the message to print
303 :type message: str
304 :param user: if set print "check warning:" instead of "warning:"
305 :type user: bool
306 """
307 assert isinstance(location, Location)
308 assert isinstance(message, str)
309 assert isinstance(explanation, str) or explanation is None
310 assert isinstance(user, bool)
312 if user:
313 kind = Kind.USER_WARNING
314 else:
315 kind = Kind.SYS_WARNING
317 self.warnings += 1
318 self.emit(location = location,
319 kind = kind,
320 message = message,
321 extrainfo = explanation,
322 fatal = False)
324 def check(self, location, message, check, explanation=None):
325 assert isinstance(location, Location)
326 assert isinstance(message, str)
327 assert isinstance(check, str)
328 assert isinstance(explanation, str) or explanation is None
330 self.warnings += 1
331 self.emit(location = location,
332 kind = Kind.SYS_CHECK,
333 message = message,
334 fatal = False,
335 extrainfo = explanation,
336 category = check)
338 def ice_loc(self, location, message): # pragma: no cover
339 assert isinstance(location, Location)
340 assert isinstance(message, str)
342 self.errors += 1
343 self.emit(location = location,
344 kind = Kind.SYS_ERROR,
345 message = message,
346 extrainfo = "please report this to %s" % version.BUGS_URL,
347 fatal = False)
348 sys.exit(1)