diagnostics: truncate quoted sources to fit the screen

* src/location.c (min_int, columns): New.
(location_caret): Compute the line width.  Based on it, compute how
many columns must be skipped before the quoted location and truncated
after, to fit the sceen width.
* tests/local.at (AT_QUELL_VALGRIND): Transform into...
(AT_SET_ENV_IF, AT_SET_ENV): these.
Define COLUMNS to protect the test suite from the user's environment.
This commit is contained in:
Akim Demaille
2019-09-18 19:38:09 +02:00
parent 945b917da2
commit f716484627
3 changed files with 101 additions and 27 deletions

View File

@@ -33,6 +33,26 @@
location const empty_loc = EMPTY_LOCATION_INIT; location const empty_loc = EMPTY_LOCATION_INIT;
static int
min_int (int a, int b)
{
return a < b ? a : b;
}
/* The terminal width. */
static int
columns (void)
{
const char *cp = getenv ("COLUMNS");
int res = 80;
if (cp && *cp)
{
unsigned long ul = strtoul (cp, NULL, 10);
res = ul < INT_MAX ? ul : INT_MAX;
}
return res;
}
/* If BUF is null, add BUFSIZE (which in this case must be less than /* If BUF is null, add BUFSIZE (which in this case must be less than
INT_MAX) to COLUMN; otherwise, add mbsnwidth (BUF, BUFSIZE, 0) to INT_MAX) to COLUMN; otherwise, add mbsnwidth (BUF, BUFSIZE, 0) to
COLUMN. If an overflow occurs, return INT_MAX. */ COLUMN. If an overflow occurs, return INT_MAX. */
@@ -179,6 +199,8 @@ static struct
} caret_info; } caret_info;
/* Open FILE for quoting, if needed, and if possible. Return whether
the file can quoted. */
static bool static bool
caret_set_file (const char *file) caret_set_file (const char *file)
{ {
@@ -276,6 +298,38 @@ location_caret (location loc, const char *style, FILE *out)
FIXME: should be done in mbfile. */ FIXME: should be done in mbfile. */
caret_info.mbfile.eof_seen = 0; caret_info.mbfile.eof_seen = 0;
/* Find the number of columns of this line. */
while (true)
{
mbchar_t c;
caret_getc (c);
if (mb_iseof (c) || mb_iseq (c, '\n'))
break;
boundary_compute (&caret_info.pos, mb_ptr (c), mb_len (c));
}
int line_len = caret_info.pos.column;
/* Available width. Eight chars are consumed by the left-margin of
the quoting lines. */
int width = columns () - 8;
int skip = 0;
if (width < line_len)
{
/* We cannot quote the whole line. Make sure we can see the
beginning of the location. */
skip = width < loc.start.column ? loc.start.column - 10 : 0;
}
/* If we skip the initial part, we insert "..." before. */
if (skip)
width -= 3;
/* Go back to the beginning of line. */
fseek (caret_info.file, caret_info.offset, SEEK_SET);
/* Reset mbf's internal state.
FIXME: should be done in mbfile. */
caret_info.mbfile.eof_seen = 0;
caret_info.pos.column = 1;
/* Read the actual line. Don't update the offset, so that we keep a pointer /* Read the actual line. Don't update the offset, so that we keep a pointer
to the start of the line. */ to the start of the line. */
{ {
@@ -283,15 +337,19 @@ location_caret (location loc, const char *style, FILE *out)
caret_getc (c); caret_getc (c);
if (!mb_iseof (c)) if (!mb_iseof (c))
{ {
bool single_line = loc.start.line == loc.end.line; /* The last column to highlight. Only the first line of
multiline locations are quoted, in which case the ending
column is the end of line. Single point locations (with
equal boundaries) denote the character that they
follow. */
int col_end
= loc.start.line == loc.end.line
? loc.end.column + (loc.start.column == loc.end.column)
: line_len;
/* Quote the file (at most the first line in the case of /* Quote the file (at most the first line in the case of
multiline locations). */ multiline locations). */
{ {
fprintf (out, "%5d | ", loc.start.line); fprintf (out, "%5d | %s", loc.start.line, skip ? "..." : "");
/* Consider that single point location (with equal boundaries)
actually denote the character that they follow. */
int col_end = loc.end.column +
(single_line && loc.start.column == loc.end.column);
/* Whether we opened the style. If the line is not as /* Whether we opened the style. If the line is not as
expected (maybe the file was changed since the scanner expected (maybe the file was changed since the scanner
ran), we might reach the end before we actually saw the ran), we might reach the end before we actually saw the
@@ -304,29 +362,35 @@ location_caret (location loc, const char *style, FILE *out)
begin_use_class (style, out); begin_use_class (style, out);
opened = true; opened = true;
} }
mb_putc (c, out); if (skip < caret_info.pos.column)
mb_putc (c, out);
boundary_compute (&caret_info.pos, mb_ptr (c), mb_len (c)); boundary_compute (&caret_info.pos, mb_ptr (c), mb_len (c));
caret_getc (c); caret_getc (c);
if (opened if (opened
&& (single_line && (caret_info.pos.column == col_end
? caret_info.pos.column == col_end || width < caret_info.pos.column - skip))
: mb_iseq (c, '\n') || mb_iseof (c))) {
end_use_class (style, out); end_use_class (style, out);
opened = false;
}
if (width < caret_info.pos.column - skip)
break;
} }
putc ('\n', out); putc ('\n', out);
} }
/* Print the carets with the same indentation as above. */ /* Print the carets with the same indentation as above. */
{ {
fprintf (out, " | %*s", loc.start.column - 1, ""); fprintf (out, " | %s%*s",
skip ? "..." : "",
loc.start.column - 1 - skip, "");
begin_use_class (style, out); begin_use_class (style, out);
putc ('^', out); putc ('^', out);
/* Underlining a multiline location ends with the first /* Underlining a multiline location ends with the first
line. */ line. */
int len = single_line for (int i = loc.start.column - 1 - skip + 1,
? loc.end.column i_end = min_int (col_end - 1 - skip, width);
: ftell (caret_info.file) - caret_info.offset; i < i_end; ++i)
for (int i = loc.start.column + 1; i < len; ++i)
putc ('~', out); putc ('~', out);
end_use_class (style, out); end_use_class (style, out);
putc ('\n', out); putc ('\n', out);

View File

@@ -118,7 +118,8 @@ unsigned location_print (location loc, FILE *out);
left-over by the usage of location_caret. */ left-over by the usage of location_caret. */
void caret_free (void); void caret_free (void);
/* Output to OUT the line and caret corresponding to location LOC. */ /* Quote the line containing LOC onto OUT. Highlight the part of LOC
with the color STYLE. */
void location_caret (location loc, const char* style, FILE *out); void location_caret (location loc, const char* style, FILE *out);
/* Return -1, 0, 1, depending whether a is before, equal, or /* Return -1, 0, 1, depending whether a is before, equal, or

View File

@@ -828,7 +828,7 @@ AT_BISON_CHECK_NO_XML($@)])
# -------------------------------------------------- # --------------------------------------------------
# Low-level macro to run bison once. # Low-level macro to run bison once.
m4_define([AT_BISON_CHECK_], m4_define([AT_BISON_CHECK_],
[AT_CHECK(AT_QUELL_VALGRIND[[ bison --color=no -fno-caret ]]$@)]) [AT_CHECK(AT_SET_ENV[[ bison --color=no -fno-caret ]]$@)])
# AT_BISON_CHECK_WARNINGS(BISON_ARGS, [OTHER_AT_CHECK_ARGS]) # AT_BISON_CHECK_WARNINGS(BISON_ARGS, [OTHER_AT_CHECK_ARGS])
@@ -884,7 +884,7 @@ fi]dnl
# when a tortured grammar's XML is known to be too large for xsltproc to # when a tortured grammar's XML is known to be too large for xsltproc to
# handle. # handle.
m4_define([AT_BISON_CHECK_NO_XML], m4_define([AT_BISON_CHECK_NO_XML],
[AT_CHECK(m4_null_if([$2], [], [AT_QUELL_VALGRIND ])[[bison --color=no -fno-caret ]]$@) [AT_CHECK(AT_SET_ENV_IF([$2]) [[bison --color=no -fno-caret ]]$@)
AT_BISON_CHECK_WARNINGS($@)]) AT_BISON_CHECK_WARNINGS($@)])
# AT_BISON_CHECK_XML(BISON_ARGS, [OTHER_AT_CHECK_ARGS]) # AT_BISON_CHECK_XML(BISON_ARGS, [OTHER_AT_CHECK_ARGS])
@@ -911,20 +911,24 @@ m4_define([AT_BISON_CHECK_XML],
m4_popdef([AT_BISON_ARGS])dnl m4_popdef([AT_BISON_ARGS])dnl
[cp xml-tests/test.output expout] [cp xml-tests/test.output expout]
AT_CHECK([[$XSLTPROC \ AT_CHECK([[$XSLTPROC \
`]]AT_QUELL_VALGRIND[[ bison --print-datadir`/xslt/xml2text.xsl \ `]]AT_SET_ENV[[ bison --print-datadir`/xslt/xml2text.xsl \
xml-tests/test.xml]], [[0]], [expout]) xml-tests/test.xml]], [[0]], [expout])
[sort xml-tests/test.gv > expout] [sort xml-tests/test.gv > expout]
AT_CHECK([[$XSLTPROC \ AT_CHECK([[$XSLTPROC \
`]]AT_QUELL_VALGRIND[[ bison --print-datadir`/xslt/xml2dot.xsl \ `]]AT_SET_ENV[[ bison --print-datadir`/xslt/xml2dot.xsl \
xml-tests/test.xml | sort]], [[0]], [expout]) xml-tests/test.xml | sort]], [[0]], [expout])
[rm -rf xml-tests expout] [rm -rf xml-tests expout]
AT_RESTORE_SPECIAL_FILES AT_RESTORE_SPECIAL_FILES
[fi]]) [fi]])
# AT_QUELL_VALGRIND
# ----------------- # AT_SET_ENV_IF(EXIT-STATUS)
# Put this before a Bison invocation to keep Valgrind from complaining about # --------------------------
# reachable memory. # Put this before a Bison invocation to set the environment to:
# - define COLUMNS to make the test suite independant of the user's
# environment;
# - keep Valgrind from complaining about reachable memory (when
# EXIT-STATUS is not 0).
# #
# Do not quote invocations of this macro within the first argument of AT_CHECK. # Do not quote invocations of this macro within the first argument of AT_CHECK.
# The triple quoting below will cause test cases to fail if you do. If you do # The triple quoting below will cause test cases to fail if you do. If you do
@@ -932,10 +936,15 @@ m4_define([AT_BISON_CHECK_XML],
# will then fail to shell-escape its contents when attempting to print them. # will then fail to shell-escape its contents when attempting to print them.
# The testsuite verbose output, at least, will be incorrect, but nothing may # The testsuite verbose output, at least, will be incorrect, but nothing may
# fail to make sure you notice. # fail to make sure you notice.
m4_define([AT_QUELL_VALGRIND], m4_define([AT_SET_ENV_IF],
[[[VALGRIND_OPTS="$VALGRIND_OPTS --leak-check=summary --show-reachable=no"; export VALGRIND_OPTS;]]]) [[[COLUMNS=1000; export COLUMNS;]] m4_null_if($1, [], [[[VALGRIND_OPTS="$VALGRIND_OPTS --leak-check=summary --show-reachable=no"; export VALGRIND_OPTS; ]]])])
# AT_SET_ENV
# ----------
# See above.
m4_define([AT_SET_ENV],
[AT_SET_ENV_IF([1])])
## ------------------------ ## ## ------------------------ ##
## Compiling C, C++ Files. ## ## Compiling C, C++ Files. ##