This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
[PATCH 4b] diagnostic-show-locus.c changes: Insertions
- From: David Malcolm <dmalcolm at redhat dot com>
- To: law at redhat dot com
- Cc: gcc-patches at gcc dot gnu dot org, David Malcolm <dmalcolm at redhat dot com>
- Date: Wed, 28 Oct 2015 14:09:23 -0400
- Subject: [PATCH 4b] diagnostic-show-locus.c changes: Insertions
- Authentication-results: sourceware.org; auth=none
- References: <56300286 dot 4040103 at redhat dot com> <1446055764-23753-1-git-send-email-dmalcolm at redhat dot com>
gcc/ChangeLog:
* diagnostic-show-locus.c (struct point_state): New struct.
(class colorizer): New class.
(class layout_point): New class.
(class layout_range): New class.
(class layout): New class.
(colorizer::colorizer): New ctor.
(colorizer::~colorizer): New dtor.
(layout::layout): New ctor.
(layout::print_line): New method.
(layout::get_state_at_point): New method.
(layout::get_x_bound_for_row): New method.
(show_ruler): New function.
(diagnostic_show_locus): Reimplement in terms of class layout.
---
gcc/diagnostic-show-locus.c | 708 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 695 insertions(+), 13 deletions(-)
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index fdf73de..6865209 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -36,11 +36,682 @@ along with GCC; see the file COPYING3. If not see
# include <sys/ioctl.h>
#endif
-/* Print the physical source line corresponding to the location of
- this diagnostic, and a caret indicating the precise column. This
- function only prints two caret characters if the two locations
- given by DIAGNOSTIC are on the same line according to
- diagnostic_same_line(). */
+static void
+show_ruler (diagnostic_context *context, int max_width, int x_offset);
+
+/* Classes for rendering source code and diagnostics, within an
+ anonymous namespace.
+ The work is done by "class layout", which embeds and uses
+ "class colorizer" and "class layout_range" to get things done. */
+
+namespace {
+
+/* The state at a given point of the source code, assuming that we're
+ in a range: which range are we in, and whether we should draw a caret at
+ this point. */
+
+struct point_state
+{
+ int range_idx;
+ bool draw_caret_p;
+};
+
+/* A class to inject colorization codes when printing the diagnostic locus.
+
+ It has one kind of colorization for each of:
+ - normal text
+ - range 0 (the "primary location")
+ - range 1
+ - range 2
+
+ The class caches the lookup of the color codes for the above.
+
+ The class also has responsibility for tracking which of the above is
+ active, filtering out unnecessary changes. This allows layout::print_line
+ to simply request a colorization code for *every* character it prints
+ through this class, and have the filtering be done for it here. */
+
+class colorizer
+{
+ public:
+ colorizer (diagnostic_context *context,
+ const diagnostic_info *diagnostic);
+ ~colorizer ();
+
+ void set_range (int range_idx) { set_state (range_idx); }
+ void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
+
+ private:
+ void set_state (int state);
+ void begin_state (int state);
+ void finish_state (int state);
+
+ private:
+ static const int STATE_NORMAL_TEXT = -1;
+
+ diagnostic_context *m_context;
+ const diagnostic_info *m_diagnostic;
+ int m_current_state;
+ const char *m_caret_cs;
+ const char *m_caret_ce;
+ const char *m_range1_cs;
+ const char *m_range2_cs;
+ const char *m_range_ce;
+};
+
+/* A point within a layout_range; similar to an expanded_location,
+ but after filtering on file. */
+
+class layout_point
+{
+ public:
+ layout_point (const expanded_location &exploc)
+ : m_line (exploc.line),
+ m_column (exploc.column) {}
+
+ int m_line;
+ int m_column;
+};
+
+/* A class for use by "class layout" below: a filtered location_range. */
+
+class layout_range
+{
+ public:
+ layout_range (const location_range *loc_range);
+
+ bool contains_point (int row, int column) const;
+
+ layout_point m_start;
+ layout_point m_finish;
+ bool m_show_caret_p;
+ layout_point m_caret;
+};
+
+/* A class to control the overall layout when printing a diagnostic.
+
+ The layout is determined within the constructor.
+ It is then printed by repeatedly calling the "print_line" method.
+ Each such call can print two lines: one for the source line itself,
+ and potentially an "annotation" line, containing carets/underlines.
+
+ We assume we have disjoint ranges. */
+
+class layout
+{
+ public:
+ layout (diagnostic_context *context,
+ const diagnostic_info *diagnostic);
+
+ int get_first_line () const { return m_first_line; }
+ int get_last_line () const { return m_last_line; }
+
+ void print_line (int row);
+
+ private:
+ bool
+ get_state_at_point (/* Inputs. */
+ int row, int column,
+ int first_non_ws, int last_non_ws,
+ /* Outputs. */
+ point_state *out_state);
+
+ int
+ get_x_bound_for_row (int row, int caret_column,
+ int last_non_ws);
+
+ private:
+ diagnostic_context *m_context;
+ pretty_printer *m_pp;
+ diagnostic_t m_diagnostic_kind;
+ expanded_location m_exploc;
+ colorizer m_colorizer;
+ bool m_colorize_source_p;
+ auto_vec <layout_range> m_layout_ranges;
+ int m_first_line;
+ int m_last_line;
+ int m_x_offset;
+};
+
+/* Implementation of "class colorizer". */
+
+/* The constructor for "colorizer". Lookup and store color codes for the
+ different kinds of things we might need to print. */
+
+colorizer::colorizer (diagnostic_context *context,
+ const diagnostic_info *diagnostic) :
+ m_context (context),
+ m_diagnostic (diagnostic),
+ m_current_state (STATE_NORMAL_TEXT)
+{
+ m_caret_ce = colorize_stop (pp_show_color (context->printer));
+ m_range1_cs = colorize_start (pp_show_color (context->printer), "range1");
+ m_range2_cs = colorize_start (pp_show_color (context->printer), "range2");
+ m_range_ce = colorize_stop (pp_show_color (context->printer));
+}
+
+/* The destructor for "colorize". If colorization is on, print a code to
+ turn it off. */
+
+colorizer::~colorizer ()
+{
+ finish_state (m_current_state);
+}
+
+/* Update state, printing color codes if necessary if there's a state
+ change. */
+
+void
+colorizer::set_state (int new_state)
+{
+ if (m_current_state != new_state)
+ {
+ finish_state (m_current_state);
+ m_current_state = new_state;
+ begin_state (new_state);
+ }
+}
+
+/* Turn on any colorization for STATE. */
+
+void
+colorizer::begin_state (int state)
+{
+ switch (state)
+ {
+ case STATE_NORMAL_TEXT:
+ break;
+
+ case 0:
+ /* Make range 0 be the same color as the "kind" text
+ (error vs warning vs note). */
+ pp_string
+ (m_context->printer,
+ colorize_start (pp_show_color (m_context->printer),
+ diagnostic_get_color_for_kind (m_diagnostic->kind)));
+ break;
+
+ case 1:
+ pp_string (m_context->printer, m_range1_cs);
+ break;
+
+ case 2:
+ pp_string (m_context->printer, m_range2_cs);
+ break;
+
+ default:
+ /* We don't expect more than 3 ranges per diagnostic. */
+ gcc_unreachable ();
+ break;
+ }
+}
+
+/* Turn off any colorization for STATE. */
+
+void
+colorizer::finish_state (int state)
+{
+ switch (state)
+ {
+ case STATE_NORMAL_TEXT:
+ break;
+
+ case 0:
+ pp_string (m_context->printer, m_caret_ce);
+ break;
+
+ default:
+ /* Within a range. */
+ gcc_assert (state > 0);
+ pp_string (m_context->printer, m_range_ce);
+ break;
+ }
+}
+
+/* Implementation of class layout_range. */
+
+/* The constructor for class layout_range.
+ Initialize various layout_point fields from expanded_location
+ equivalents; we've already filtered on file. */
+
+layout_range::layout_range (const location_range *loc_range)
+: m_start (loc_range->m_start),
+ m_finish (loc_range->m_finish),
+ m_show_caret_p (loc_range->m_show_caret_p),
+ m_caret (loc_range->m_caret)
+{
+}
+
+/* Is (column, row) within the given range?
+ We've already filtered on the file.
+
+ Ranges are closed (both limits are within the range).
+
+ Example A: a single-line range:
+ start: (col=22, line=2)
+ finish: (col=38, line=2)
+
+ |00000011111111112222222222333333333344444444444
+ |34567890123456789012345678901234567890123456789
+--+-----------------------------------------------
+01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+02|bbbbbbbbbbbbbbbbbbbSwwwwwwwwwwwwwwwFaaaaaaaaaaa
+03|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+ Example B: a multiline range with
+ start: (col=14, line=3)
+ finish: (col=08, line=5)
+
+ |00000011111111112222222222333333333344444444444
+ |34567890123456789012345678901234567890123456789
+--+-----------------------------------------------
+01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+02|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+03|bbbbbbbbbbbSwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+04|wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+05|wwwwwFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+06|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+--+-----------------------------------------------
+
+ Legend:
+ - 'b' indicates a point *before* the range
+ - 'S' indicates the start of the range
+ - 'w' indicates a point within the range
+ - 'F' indicates the finish of the range (which is
+ within it).
+ - 'a' indicates a subsequent point *after* the range. */
+
+bool
+layout_range::contains_point (int row, int column) const
+{
+ gcc_assert (m_start.m_line <= m_finish.m_line);
+ /* ...but the equivalent isn't true for the columns;
+ consider example B in the comment above. */
+
+ if (row < m_start.m_line)
+ /* Points before the first line of the range are
+ outside it (corresponding to line 01 in example A
+ and lines 01 and 02 in example B above). */
+ return false;
+
+ if (row == m_start.m_line)
+ /* On same line as start of range (corresponding
+ to line 02 in example A and line 03 in example B). */
+ {
+ if (column < m_start.m_column)
+ /* Points on the starting line of the range, but
+ before the column in which it begins. */
+ return false;
+
+ if (row < m_finish.m_line)
+ /* This is a multiline range; the point
+ is within it (corresponds to line 03 in example B
+ from column 14 onwards) */
+ return true;
+ else
+ {
+ /* This is a single-line range. */
+ gcc_assert (row == m_finish.m_line);
+ return column <= m_finish.m_column;
+ }
+ }
+
+ /* The point is in a line beyond that containing the
+ start of the range: lines 03 onwards in example A,
+ and lines 04 onwards in example B. */
+ gcc_assert (row > m_start.m_line);
+
+ if (row > m_finish.m_line)
+ /* The point is beyond the final line of the range
+ (lines 03 onwards in example A, and lines 06 onwards
+ in example B). */
+ return false;
+
+ if (row < m_finish.m_line)
+ {
+ /* The point is in a line that's fully within a multiline
+ range (e.g. line 04 in example B). */
+ gcc_assert (m_start.m_line < m_finish.m_line);
+ return true;
+ }
+
+ gcc_assert (row == m_finish.m_line);
+
+ return column <= m_finish.m_column;
+}
+
+/* Given a source line LINE of length LINE_WIDTH, determine the width
+ without any trailing whitespace. */
+
+static int
+get_line_width_without_trailing_whitespace (const char *line, int line_width)
+{
+ int result = line_width;
+ while (result > 0)
+ {
+ char ch = line[result - 1];
+ if (ch == ' ' || ch == '\t')
+ result--;
+ else
+ break;
+ }
+ gcc_assert (result >= 0);
+ gcc_assert (result <= line_width);
+ gcc_assert (result == 0 ||
+ (line[result - 1] != ' '
+ && line[result -1] != '\t'));
+ return result;
+}
+
+/* Implementation of class layout. */
+
+/* Constructor for class layout.
+
+ Filter the ranges from the rich_location to those that we can
+ sanely print, populating m_layout_ranges.
+ Determine the range of lines that we will print.
+ Determine m_x_offset, to ensure that the primary caret
+ will fit within the max_width provided by the diagnostic_context. */
+
+layout::layout (diagnostic_context * context,
+ const diagnostic_info *diagnostic)
+: m_context (context),
+ m_pp (context->printer),
+ m_diagnostic_kind (diagnostic->kind),
+ m_exploc (diagnostic->richloc->lazily_expand_location ()),
+ m_colorizer (context, diagnostic),
+ m_colorize_source_p (context->colorize_source_p),
+ m_layout_ranges (rich_location::MAX_RANGES),
+ m_first_line (m_exploc.line),
+ m_last_line (m_exploc.line),
+ m_x_offset (0)
+{
+ rich_location *richloc = diagnostic->richloc;
+ for (unsigned int idx = 0; idx < richloc->get_num_locations (); idx++)
+ {
+ /* This diagnostic printer can only cope with "sufficiently sane" ranges.
+ Ignore any ranges that are awkward to handle. */
+ location_range *loc_range = richloc->get_range (idx);
+
+ /* If any part of the range isn't in the same file as the primary
+ location of this diagnostic, ignore the range. */
+ if (loc_range->m_start.file != m_exploc.file)
+ continue;
+ if (loc_range->m_finish.file != m_exploc.file)
+ continue;
+ if (loc_range->m_show_caret_p)
+ if (loc_range->m_caret.file != m_exploc.file)
+ continue;
+
+ /* Passed all the tests; add the range to m_layout_ranges so that
+ it will be printed. */
+ layout_range ri (loc_range);
+ m_layout_ranges.safe_push (ri);
+
+ /* Update m_first_line/m_last_line if necessary. */
+ if (loc_range->m_start.line < m_first_line)
+ m_first_line = loc_range->m_start.line;
+ if (loc_range->m_finish.line > m_last_line)
+ m_last_line = loc_range->m_finish.line;
+ }
+
+ /* Adjust m_x_offset.
+ Center the primary caret to fit in max_width; all columns
+ will be adjusted accordingly. */
+ int max_width = m_context->caret_max_width;
+ int line_width;
+ const char *line = location_get_source_line (m_exploc.file, m_exploc.line,
+ &line_width);
+ if (line && m_exploc.column <= line_width)
+ {
+ int right_margin = CARET_LINE_MARGIN;
+ int column = m_exploc.column;
+ right_margin = MIN (line_width - column, right_margin);
+ right_margin = max_width - right_margin;
+ if (line_width >= max_width && column > right_margin)
+ m_x_offset = column - right_margin;
+ gcc_assert (m_x_offset >= 0);
+ }
+
+ if (0)
+ show_ruler (context, line_width, m_x_offset);
+}
+
+/* Print text describing a line of source code.
+ This typically prints two lines:
+
+ (1) the source code itself, potentially colorized at any ranges, and
+ (2) an annotation line containing any carets/underlines
+ describing the ranges. */
+
+void
+layout::print_line (int row)
+{
+ int line_width;
+ const char *line = location_get_source_line (m_exploc.file, row,
+ &line_width);
+ if (!line)
+ return;
+
+ line += m_x_offset;
+
+ m_colorizer.set_normal_text ();
+
+ /* Step 1: print the source code line. */
+
+ /* We will stop printing at any trailing whitespace. */
+ line_width
+ = get_line_width_without_trailing_whitespace (line,
+ line_width);
+ pp_space (m_pp);
+ int first_non_ws = INT_MAX;
+ int last_non_ws = 0;
+ int column;
+ for (column = 1 + m_x_offset; column <= line_width; column++)
+ {
+ /* Assuming colorization is enabled for the caret and underline
+ characters, we may also colorize the associated characters
+ within the source line.
+
+ For frontends that generate range information, we color the
+ associated characters in the source line the same as the
+ carets and underlines in the annotation line, to make it easier
+ for the reader to see the pertinent code.
+
+ For frontends that only generate carets, we don't colorize the
+ characters above them, since this would look strange (e.g.
+ colorizing just the first character in a token). */
+ if (m_colorize_source_p)
+ {
+ bool in_range_p;
+ point_state state;
+ in_range_p = get_state_at_point (row, column,
+ 0, INT_MAX,
+ &state);
+ if (in_range_p)
+ m_colorizer.set_range (state.range_idx);
+ else
+ m_colorizer.set_normal_text ();
+ }
+ char c = *line == '\t' ? ' ' : *line;
+ if (c == '\0')
+ c = ' ';
+ if (c != ' ')
+ {
+ last_non_ws = column;
+ if (first_non_ws == INT_MAX)
+ first_non_ws = column;
+ }
+ pp_character (m_pp, c);
+ line++;
+ }
+ pp_newline (m_pp);
+
+ /* Step 2: print a line consisting of the caret/underlines for the
+ given source line. */
+ int x_bound = get_x_bound_for_row (row, m_exploc.column,
+ last_non_ws);
+
+ pp_space (m_pp);
+ for (int column = 1 + m_x_offset; column < x_bound; column++)
+ {
+ bool in_range_p;
+ point_state state;
+ in_range_p = get_state_at_point (row, column,
+ first_non_ws, last_non_ws,
+ &state);
+ if (in_range_p)
+ {
+ /* Within a range. Draw either the caret or an underline. */
+ m_colorizer.set_range (state.range_idx);
+ if (state.draw_caret_p)
+ /* Draw the caret. */
+ pp_character (m_pp, m_context->caret_chars[state.range_idx]);
+ else
+ pp_character (m_pp, '~');
+ }
+ else
+ {
+ /* Not in a range. */
+ m_colorizer.set_normal_text ();
+ pp_character (m_pp, ' ');
+ }
+ }
+ pp_newline (m_pp);
+}
+
+/* Return true if (ROW/COLUMN) is within a range of the layout.
+ If it returns true, OUT_STATE is written to, with the
+ range index, and whether we should draw the caret at
+ (ROW/COLUMN) (as opposed to an underline). */
+
+bool
+layout::get_state_at_point (/* Inputs. */
+ int row, int column,
+ int first_non_ws, int last_non_ws,
+ /* Outputs. */
+ point_state *out_state)
+{
+ layout_range *range;
+ int i;
+ FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+ {
+ if (0)
+ fprintf (stderr,
+ "range ( (%i, %i), (%i, %i))->contains_point (%i, %i): %s\n",
+ range->m_start.m_line,
+ range->m_start.m_column,
+ range->m_finish.m_line,
+ range->m_finish.m_column,
+ row,
+ column,
+ range->contains_point (row, column) ? "true" : "false");
+
+ if (range->contains_point (row, column))
+ {
+ out_state->range_idx = i;
+
+ /* Are we at the range's caret? is it visible? */
+ out_state->draw_caret_p = false;
+ if (row == range->m_caret.m_line
+ && column == range->m_caret.m_column)
+ out_state->draw_caret_p = range->m_show_caret_p;
+
+ /* Within a multiline range, don't display any underline
+ in any leading or trailing whitespace on a line.
+ We do display carets, however. */
+ if (!out_state->draw_caret_p)
+ if (column < first_non_ws || column > last_non_ws)
+ return false;
+
+ /* We are within a range. */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Helper function for use by layout::print_line when printing the
+ annotation line under the source line.
+ Get the column beyond the rightmost one that could contain a caret or
+ range marker, given that we stop rendering at trailing whitespace.
+ ROW is the source line within the given file.
+ CARET_COLUMN is the column of range 0's caret.
+ LAST_NON_WS_COLUMN is the last column containing a non-whitespace
+ character of source (as determined when printing the source line). */
+
+int
+layout::get_x_bound_for_row (int row, int caret_column,
+ int last_non_ws_column)
+{
+ int result = caret_column + 1;
+
+ layout_range *range;
+ int i;
+ FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+ {
+ if (row >= range->m_start.m_line)
+ {
+ if (range->m_finish.m_line == row)
+ {
+ /* On the final line within a range; ensure that
+ we render up to the end of the range. */
+ if (result <= range->m_finish.m_column)
+ result = range->m_finish.m_column + 1;
+ }
+ else if (row < range->m_finish.m_line)
+ {
+ /* Within a multiline range; ensure that we render up to the
+ last non-whitespace column. */
+ if (result <= last_non_ws_column)
+ result = last_non_ws_column + 1;
+ }
+ }
+ }
+
+ return result;
+}
+
+} /* End of anonymous namespace. */
+
+/* For debugging layout issues in diagnostic_show_locus and friends,
+ render a ruler giving column numbers (after the 1-column indent). */
+
+static void
+show_ruler (diagnostic_context *context, int max_width, int x_offset)
+{
+ /* Hundreds. */
+ if (max_width > 99)
+ {
+ pp_space (context->printer);
+ for (int column = 1 + x_offset; column < max_width; column++)
+ if (0 == column % 10)
+ pp_character (context->printer, '0' + (column / 100) % 10);
+ else
+ pp_space (context->printer);
+ pp_newline (context->printer);
+ }
+
+ /* Tens. */
+ pp_space (context->printer);
+ for (int column = 1 + x_offset; column < max_width; column++)
+ if (0 == column % 10)
+ pp_character (context->printer, '0' + (column / 10) % 10);
+ else
+ pp_space (context->printer);
+ pp_newline (context->printer);
+
+ /* Units. */
+ pp_space (context->printer);
+ for (int column = 1 + x_offset; column < max_width; column++)
+ pp_character (context->printer, '0' + (column % 10));
+ pp_newline (context->printer);
+}
+
+/* Print the physical source code corresponding to the location of
+ this diagnostic, with additional annotations. */
+
void
diagnostic_show_locus (diagnostic_context * context,
const diagnostic_info *diagnostic)
@@ -51,14 +722,25 @@ diagnostic_show_locus (diagnostic_context * context,
return;
context->last_location = diagnostic_location (diagnostic, 0);
- expanded_location s0 = diagnostic_expand_location (diagnostic, 0);
- expanded_location s1 = { };
- /* Zero-initialized. This is checked later by diagnostic_print_caret_line. */
- if (diagnostic_location (diagnostic, 1) > BUILTINS_LOCATION)
- s1 = diagnostic_expand_location (diagnostic, 1);
+ pp_newline (context->printer);
+
+ const char *saved_prefix = pp_get_prefix (context->printer);
+ pp_set_prefix (context->printer, NULL);
+
+ {
+ layout layout (context, diagnostic);
+ int last_line = layout.get_last_line ();
+ for (int row = layout.get_first_line ();
+ row <= last_line;
+ row++)
+ layout.print_line (row);
+
+ /* The closing scope here leads to the dtor for layout and thus
+ colorizer being called here, which affects the precise
+ place where colorization is turned off in the unittest
+ for colorized output. */
+ }
- diagnostic_print_caret_line (context, s0, s1,
- context->caret_chars[0],
- context->caret_chars[1]);
+ pp_set_prefix (context->printer, saved_prefix);
}
--
1.8.5.3