xref: /aosp_15_r20/external/emboss/compiler/util/error.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1*99e0aae7SDavid Rees# Copyright 2019 Google LLC
2*99e0aae7SDavid Rees#
3*99e0aae7SDavid Rees# Licensed under the Apache License, Version 2.0 (the "License");
4*99e0aae7SDavid Rees# you may not use this file except in compliance with the License.
5*99e0aae7SDavid Rees# You may obtain a copy of the License at
6*99e0aae7SDavid Rees#
7*99e0aae7SDavid Rees#     https://www.apache.org/licenses/LICENSE-2.0
8*99e0aae7SDavid Rees#
9*99e0aae7SDavid Rees# Unless required by applicable law or agreed to in writing, software
10*99e0aae7SDavid Rees# distributed under the License is distributed on an "AS IS" BASIS,
11*99e0aae7SDavid Rees# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*99e0aae7SDavid Rees# See the License for the specific language governing permissions and
13*99e0aae7SDavid Rees# limitations under the License.
14*99e0aae7SDavid Rees
15*99e0aae7SDavid Rees"""Error and warning message support for Emboss.
16*99e0aae7SDavid Rees
17*99e0aae7SDavid ReesThis module exports the error, warn, and note functions, which return a _Message
18*99e0aae7SDavid Reesrepresenting the error, warning, or note, respectively.  The format method of
19*99e0aae7SDavid Reesthe returned object can be used to render the message with source code snippets.
20*99e0aae7SDavid Rees
21*99e0aae7SDavid ReesThroughout Emboss, messages are passed around as lists of lists of _Messages.
22*99e0aae7SDavid ReesEach inner list represents a group of messages which should either all be
23*99e0aae7SDavid Reesprinted, or not printed; i.e., an error message and associated informational
24*99e0aae7SDavid Reesmessages.  For example, to indicate both a duplicate definition error and a
25*99e0aae7SDavid Reeswarning that a field is a reserved word, one might return:
26*99e0aae7SDavid Rees
27*99e0aae7SDavid Rees    return [
28*99e0aae7SDavid Rees        [
29*99e0aae7SDavid Rees            error.error(file_name, location, "Duplicate definition),
30*99e0aae7SDavid Rees            error.note(original_file_name, original_location,
31*99e0aae7SDavid Rees                       "Original definition"),
32*99e0aae7SDavid Rees        ],
33*99e0aae7SDavid Rees        [
34*99e0aae7SDavid Rees            error.warn(file_name, location, "Field name is a C reserved word.")
35*99e0aae7SDavid Rees        ],
36*99e0aae7SDavid Rees    ]
37*99e0aae7SDavid Rees"""
38*99e0aae7SDavid Rees
39*99e0aae7SDavid Reesfrom compiler.util import ir_data_utils
40*99e0aae7SDavid Reesfrom compiler.util import parser_types
41*99e0aae7SDavid Rees
42*99e0aae7SDavid Rees# Error levels; represented by the strings that will be included in messages.
43*99e0aae7SDavid ReesERROR = "error"
44*99e0aae7SDavid ReesWARNING = "warning"
45*99e0aae7SDavid ReesNOTE = "note"
46*99e0aae7SDavid Rees
47*99e0aae7SDavid Rees# Colors; represented by the terminal escape sequences used to switch to them.
48*99e0aae7SDavid Rees# These work out-of-the-box on Unix derivatives (Linux, *BSD, Mac OS X), and
49*99e0aae7SDavid Rees# work on Windows using colorify.
50*99e0aae7SDavid ReesBLACK = "\033[0;30m"
51*99e0aae7SDavid ReesRED = "\033[0;31m"
52*99e0aae7SDavid ReesGREEN = "\033[0;32m"
53*99e0aae7SDavid ReesYELLOW = "\033[0;33m"
54*99e0aae7SDavid ReesBLUE = "\033[0;34m"
55*99e0aae7SDavid ReesMAGENTA = "\033[0;35m"
56*99e0aae7SDavid ReesCYAN = "\033[0;36m"
57*99e0aae7SDavid ReesWHITE = "\033[0;37m"
58*99e0aae7SDavid ReesBRIGHT_BLACK = "\033[0;1;30m"
59*99e0aae7SDavid ReesBRIGHT_RED = "\033[0;1;31m"
60*99e0aae7SDavid ReesBRIGHT_GREEN = "\033[0;1;32m"
61*99e0aae7SDavid ReesBRIGHT_YELLOW = "\033[0;1;33m"
62*99e0aae7SDavid ReesBRIGHT_BLUE = "\033[0;1;34m"
63*99e0aae7SDavid ReesBRIGHT_MAGENTA = "\033[0;1;35m"
64*99e0aae7SDavid ReesBRIGHT_CYAN = "\033[0;1;36m"
65*99e0aae7SDavid ReesBRIGHT_WHITE = "\033[0;1;37m"
66*99e0aae7SDavid ReesBOLD = "\033[0;1m"
67*99e0aae7SDavid ReesRESET = "\033[0m"
68*99e0aae7SDavid Rees
69*99e0aae7SDavid Reesdef _copy(location):
70*99e0aae7SDavid Rees  location = ir_data_utils.copy(location)
71*99e0aae7SDavid Rees  if not location:
72*99e0aae7SDavid Rees    location = parser_types.make_location((0,0), (0,0))
73*99e0aae7SDavid Rees  return location
74*99e0aae7SDavid Rees
75*99e0aae7SDavid Rees
76*99e0aae7SDavid Reesdef error(source_file, location, message):
77*99e0aae7SDavid Rees  """Returns an object representing an error message."""
78*99e0aae7SDavid Rees  return _Message(source_file, _copy(location), ERROR, message)
79*99e0aae7SDavid Rees
80*99e0aae7SDavid Rees
81*99e0aae7SDavid Reesdef warn(source_file, location, message):
82*99e0aae7SDavid Rees  """Returns an object representing a warning."""
83*99e0aae7SDavid Rees  return _Message(source_file, _copy(location), WARNING, message)
84*99e0aae7SDavid Rees
85*99e0aae7SDavid Rees
86*99e0aae7SDavid Reesdef note(source_file, location, message):
87*99e0aae7SDavid Rees  """Returns and object representing an informational note."""
88*99e0aae7SDavid Rees  return _Message(source_file, _copy(location), NOTE, message)
89*99e0aae7SDavid Rees
90*99e0aae7SDavid Rees
91*99e0aae7SDavid Reesclass _Message(object):
92*99e0aae7SDavid Rees  """_Message holds a human-readable message."""
93*99e0aae7SDavid Rees  __slots__ = ("location", "source_file", "severity", "message")
94*99e0aae7SDavid Rees
95*99e0aae7SDavid Rees  def __init__(self, source_file, location, severity, message):
96*99e0aae7SDavid Rees    self.location = location
97*99e0aae7SDavid Rees    self.source_file = source_file
98*99e0aae7SDavid Rees    self.severity = severity
99*99e0aae7SDavid Rees    self.message = message
100*99e0aae7SDavid Rees
101*99e0aae7SDavid Rees  def format(self, source_code):
102*99e0aae7SDavid Rees    """Formats the _Message for display.
103*99e0aae7SDavid Rees
104*99e0aae7SDavid Rees    Arguments:
105*99e0aae7SDavid Rees      source_code: A dict of file names to source texts.  This is used to
106*99e0aae7SDavid Rees        render source snippets.
107*99e0aae7SDavid Rees
108*99e0aae7SDavid Rees    Returns:
109*99e0aae7SDavid Rees      A list of tuples.
110*99e0aae7SDavid Rees
111*99e0aae7SDavid Rees      The first element of each tuple is an escape sequence used to put a Unix
112*99e0aae7SDavid Rees      terminal into a particular color mode.  For use in non-Unix-terminal
113*99e0aae7SDavid Rees      output, the string will match one of the color names exported by this
114*99e0aae7SDavid Rees      module.
115*99e0aae7SDavid Rees
116*99e0aae7SDavid Rees      The second element is a string containing text to show to the user.
117*99e0aae7SDavid Rees
118*99e0aae7SDavid Rees      The text will not end with a newline character, nor will it include a
119*99e0aae7SDavid Rees      RESET color element.
120*99e0aae7SDavid Rees
121*99e0aae7SDavid Rees      To show non-colorized output, simply write the second element of each
122*99e0aae7SDavid Rees      tuple, then a newline at the end.
123*99e0aae7SDavid Rees
124*99e0aae7SDavid Rees      To show colorized output, write both the first and second element of each
125*99e0aae7SDavid Rees      tuple, then a newline at the end.  Before exiting to the operating system,
126*99e0aae7SDavid Rees      a RESET sequence should be emitted.
127*99e0aae7SDavid Rees    """
128*99e0aae7SDavid Rees    # TODO(bolms): Figure out how to get Vim, Emacs, etc. to parse Emboss error
129*99e0aae7SDavid Rees    #     messages.
130*99e0aae7SDavid Rees    severity_colors = {
131*99e0aae7SDavid Rees        ERROR: (BRIGHT_RED, BOLD),
132*99e0aae7SDavid Rees        WARNING: (BRIGHT_MAGENTA, BOLD),
133*99e0aae7SDavid Rees        NOTE: (BRIGHT_BLACK, WHITE)
134*99e0aae7SDavid Rees    }
135*99e0aae7SDavid Rees
136*99e0aae7SDavid Rees    result = []
137*99e0aae7SDavid Rees    if self.location.is_synthetic:
138*99e0aae7SDavid Rees      pos = "[compiler bug]"
139*99e0aae7SDavid Rees    else:
140*99e0aae7SDavid Rees      pos = parser_types.format_position(self.location.start)
141*99e0aae7SDavid Rees    source_name = self.source_file or "[prelude]"
142*99e0aae7SDavid Rees    if not self.location.is_synthetic and self.source_file in source_code:
143*99e0aae7SDavid Rees      source_lines = source_code[self.source_file].splitlines()
144*99e0aae7SDavid Rees      source_line = source_lines[self.location.start.line - 1]
145*99e0aae7SDavid Rees    else:
146*99e0aae7SDavid Rees      source_line = ""
147*99e0aae7SDavid Rees    lines = self.message.splitlines()
148*99e0aae7SDavid Rees    for i in range(len(lines)):
149*99e0aae7SDavid Rees      line = lines[i]
150*99e0aae7SDavid Rees      # This is a little awkward, but we want to suppress the final newline in
151*99e0aae7SDavid Rees      # the message.  This newline is final if and only if it is the last line
152*99e0aae7SDavid Rees      # of the message and there is no source snippet.
153*99e0aae7SDavid Rees      if i != len(lines) - 1 or source_line:
154*99e0aae7SDavid Rees        line += "\n"
155*99e0aae7SDavid Rees      result.append((BOLD, "{}:{}: ".format(source_name, pos)))
156*99e0aae7SDavid Rees      if i == 0:
157*99e0aae7SDavid Rees        severity = self.severity
158*99e0aae7SDavid Rees      else:
159*99e0aae7SDavid Rees        severity = NOTE
160*99e0aae7SDavid Rees      result.append((severity_colors[severity][0], "{}: ".format(severity)))
161*99e0aae7SDavid Rees      result.append((severity_colors[severity][1], line))
162*99e0aae7SDavid Rees    if source_line:
163*99e0aae7SDavid Rees      result.append((WHITE, source_line + "\n"))
164*99e0aae7SDavid Rees      indicator_indent = " " * (self.location.start.column - 1)
165*99e0aae7SDavid Rees      if self.location.start.line == self.location.end.line:
166*99e0aae7SDavid Rees        indicator_caret = "^" * max(
167*99e0aae7SDavid Rees            1, self.location.end.column - self.location.start.column)
168*99e0aae7SDavid Rees      else:
169*99e0aae7SDavid Rees        indicator_caret = "^"
170*99e0aae7SDavid Rees      result.append((BRIGHT_GREEN, indicator_indent + indicator_caret))
171*99e0aae7SDavid Rees    return result
172*99e0aae7SDavid Rees
173*99e0aae7SDavid Rees  def __repr__(self):
174*99e0aae7SDavid Rees    return ("Message({source_file!r}, make_location(({start_line!r}, "
175*99e0aae7SDavid Rees            "{start_column!r}), ({end_line!r}, {end_column!r}), "
176*99e0aae7SDavid Rees            "{is_synthetic!r}), {severity!r}, {message!r})").format(
177*99e0aae7SDavid Rees                source_file=self.source_file,
178*99e0aae7SDavid Rees                start_line=self.location.start.line,
179*99e0aae7SDavid Rees                start_column=self.location.start.column,
180*99e0aae7SDavid Rees                end_line=self.location.end.line,
181*99e0aae7SDavid Rees                end_column=self.location.end.column,
182*99e0aae7SDavid Rees                is_synthetic=self.location.is_synthetic,
183*99e0aae7SDavid Rees                severity=self.severity,
184*99e0aae7SDavid Rees                message=self.message)
185*99e0aae7SDavid Rees
186*99e0aae7SDavid Rees  def __eq__(self, other):
187*99e0aae7SDavid Rees    return (
188*99e0aae7SDavid Rees        self.__class__ == other.__class__ and self.location == other.location
189*99e0aae7SDavid Rees        and self.source_file == other.source_file and
190*99e0aae7SDavid Rees        self.severity == other.severity and self.message == other.message)
191*99e0aae7SDavid Rees
192*99e0aae7SDavid Rees  def __ne__(self, other):
193*99e0aae7SDavid Rees    return not self == other
194*99e0aae7SDavid Rees
195*99e0aae7SDavid Rees
196*99e0aae7SDavid Reesdef split_errors(errors):
197*99e0aae7SDavid Rees  """Splits errors into (user_errors, synthetic_errors).
198*99e0aae7SDavid Rees
199*99e0aae7SDavid Rees  Arguments:
200*99e0aae7SDavid Rees      errors: A list of lists of _Message, which is a list of bundles of
201*99e0aae7SDavid Rees          associated messages.
202*99e0aae7SDavid Rees
203*99e0aae7SDavid Rees  Returns:
204*99e0aae7SDavid Rees      (user_errors, synthetic_errors), where both user_errors and
205*99e0aae7SDavid Rees      synthetic_errors are lists of lists of _Message.  synthetic_errors will
206*99e0aae7SDavid Rees      contain all bundles that reference any synthetic source_location, and
207*99e0aae7SDavid Rees      user_errors will contain the rest.
208*99e0aae7SDavid Rees
209*99e0aae7SDavid Rees      The intent is that user_errors can be shown to end users, while
210*99e0aae7SDavid Rees      synthetic_errors should generally be suppressed.
211*99e0aae7SDavid Rees  """
212*99e0aae7SDavid Rees  synthetic_errors = []
213*99e0aae7SDavid Rees  user_errors = []
214*99e0aae7SDavid Rees  for error_block in errors:
215*99e0aae7SDavid Rees    if any(message.location.is_synthetic for message in error_block):
216*99e0aae7SDavid Rees      synthetic_errors.append(error_block)
217*99e0aae7SDavid Rees    else:
218*99e0aae7SDavid Rees      user_errors.append(error_block)
219*99e0aae7SDavid Rees  return user_errors, synthetic_errors
220*99e0aae7SDavid Rees
221*99e0aae7SDavid Rees
222*99e0aae7SDavid Reesdef filter_errors(errors):
223*99e0aae7SDavid Rees  """Returns the non-synthetic errors from `errors`."""
224*99e0aae7SDavid Rees  return split_errors(errors)[0]
225*99e0aae7SDavid Rees
226*99e0aae7SDavid Rees
227*99e0aae7SDavid Reesdef format_errors(errors, source_codes, use_color=False):
228*99e0aae7SDavid Rees  """Formats error messages with source code snippets."""
229*99e0aae7SDavid Rees  result = []
230*99e0aae7SDavid Rees  for error_group in errors:
231*99e0aae7SDavid Rees    assert error_group, "Found empty error_group!"
232*99e0aae7SDavid Rees    for message in error_group:
233*99e0aae7SDavid Rees      if use_color:
234*99e0aae7SDavid Rees        result.append("".join(e[0] + e[1] + RESET
235*99e0aae7SDavid Rees                              for e in message.format(source_codes)))
236*99e0aae7SDavid Rees      else:
237*99e0aae7SDavid Rees        result.append("".join(e[1] for e in message.format(source_codes)))
238*99e0aae7SDavid Rees  return "\n".join(result)
239*99e0aae7SDavid Rees
240*99e0aae7SDavid Rees
241*99e0aae7SDavid Reesdef make_error_from_parse_error(file_name, parse_error):
242*99e0aae7SDavid Rees  return [error(file_name,
243*99e0aae7SDavid Rees                parse_error.token.source_location,
244*99e0aae7SDavid Rees                "{code}\n"
245*99e0aae7SDavid Rees                "Found {text!r} ({symbol}), expected {expected}.".format(
246*99e0aae7SDavid Rees                    code=parse_error.code or "Syntax error",
247*99e0aae7SDavid Rees                    text=parse_error.token.text,
248*99e0aae7SDavid Rees                    symbol=parse_error.token.symbol,
249*99e0aae7SDavid Rees                    expected=", ".join(parse_error.expected_tokens)))]
250*99e0aae7SDavid Rees
251*99e0aae7SDavid Rees
252