3 Copyright (C) 2002, 2005-2015, 2018-2021 Free Software Foundation,
6 This file is part of Bison, the GNU Compiler Compiler.
8 This program is free software: you can redistribute it and/or modify
9 it 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.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <https://www.gnu.org/licenses/>. */
27 #include <stdio.h> /* fileno */
28 #include <sys/ioctl.h>
29 #include <sys/stat.h> /* fstat */
32 #ifdef WINSIZE_IN_PTEM
33 # include <sys/stream.h>
34 # include <sys/ptem.h>
41 location
const empty_loc
= EMPTY_LOCATION_INIT
;
43 /* The terminal width. Not less than 40. */
47 const char *cp
= getenv ("COLUMNS");
51 long l
= strtol (cp
, NULL
, 10);
52 res
= 0 <= l
&& l
<= INT_MAX
? l
: INT_MAX
;
58 if (ioctl (STDERR_FILENO
, TIOCGWINSZ
, &ws
) != -1
59 && 0 < ws
.ws_col
&& ws
.ws_col
== (size_t) ws
.ws_col
)
63 return max_int (res
, 40);
66 /* Available screen width. */
67 static int screen_width
= 80;
69 /* The ellipsis symbol to use for this locale, and the number of
70 screen-columns it uses. */
71 static const char *ellipsis
= "...";
72 static int ellipsize
= 3;
74 /* If BUF is null, add BUFSIZE (which in this case must be less than
75 INT_MAX) to COLUMN; otherwise, add mbsnwidth (BUF, BUFSIZE, 0) to
76 COLUMN. If an overflow occurs, return INT_MAX. */
79 add_column_width (int column
, char const *buf
, size_t bufsize
)
82 = buf
? mbsnwidth (buf
, bufsize
, 0)
83 : INT_MAX
<= bufsize
? INT_MAX
85 return column
<= INT_MAX
- width
? column
+ width
: INT_MAX
;
89 boundary_compute (boundary
*cur
, char const *token
, size_t size
)
92 int column
= cur
->column
;
94 char const *p0
= token
;
95 char const *p
= token
;
96 char const *lim
= token
+ size
;
98 for (p
= token
; p
< lim
; ++p
)
102 line
+= line
< INT_MAX
;
109 column
= add_column_width (column
, p0
, p
- p0
);
110 column
= add_column_width (column
, NULL
, 8 - ((column
- 1) & 7));
112 byte
+= byte
< INT_MAX
;
116 byte
+= byte
< INT_MAX
;
119 column
= add_column_width (column
, p0
, p
- p0
);
122 cur
->column
= column
;
127 /* Set *LOC and adjust scanner cursor to account for token TOKEN of
131 location_compute (location
*loc
, boundary
*cur
, char const *token
, size_t size
)
134 boundary_compute (cur
, token
, size
);
137 if (loc
->end
.line
== INT_MAX
&& loc
->start
.line
!= INT_MAX
)
138 complain (loc
, Wother
, _("line number overflow"));
139 if (loc
->end
.column
== INT_MAX
&& loc
->start
.column
!= INT_MAX
)
140 complain (loc
, Wother
, _("column number overflow"));
141 /* TRANSLATORS: we are counting bytes, and there are too many. */
142 if (loc
->end
.byte
== INT_MAX
&& loc
->start
.byte
!= INT_MAX
)
143 complain (loc
, Wother
, _("byte number overflow"));
147 boundary_print (boundary
const *b
, FILE *out
)
149 return fprintf (out
, "%s:%d.%d@%d",
150 quotearg_n_style (3, escape_quoting_style
, b
->file
),
151 b
->line
, b
->column
, b
->byte
);
155 location_print (location loc
, FILE *out
)
158 if (location_empty (loc
))
159 res
+= fprintf (out
, "(empty location)");
160 else if (trace_flag
& trace_locations
)
162 res
+= boundary_print (&loc
.start
, out
);
163 res
+= fprintf (out
, "-");
164 res
+= boundary_print (&loc
.end
, out
);
168 aver (loc
.start
.file
);
170 int end_col
= 0 != loc
.end
.column
? loc
.end
.column
- 1 : 0;
171 res
+= fprintf (out
, "%s",
172 quotearg_n_style (3, escape_quoting_style
, loc
.start
.file
));
173 if (0 < loc
.start
.line
)
175 res
+= fprintf (out
, ":%d", loc
.start
.line
);
176 if (0 < loc
.start
.column
)
177 res
+= fprintf (out
, ".%d", loc
.start
.column
);
179 if (loc
.start
.file
!= loc
.end
.file
)
181 res
+= fprintf (out
, "-%s",
182 quotearg_n_style (3, escape_quoting_style
,
184 if (0 < loc
.end
.line
)
186 res
+= fprintf (out
, ":%d", loc
.end
.line
);
188 res
+= fprintf (out
, ".%d", end_col
);
191 else if (0 < loc
.end
.line
)
193 if (loc
.start
.line
< loc
.end
.line
)
195 res
+= fprintf (out
, "-%d", loc
.end
.line
);
197 res
+= fprintf (out
, ".%d", end_col
);
199 else if (0 <= end_col
&& loc
.start
.column
< end_col
)
200 res
+= fprintf (out
, "-%d", end_col
);
208 /* Persistent data used by location_caret to avoid reopening and rereading the
209 same file all over for each error. */
212 /* Raw input file. */
214 /* Input file as a stream of multibyte characters. */
216 /* The position within the last file we quoted. If POS.FILE is non
217 NULL, but FILE is NULL, it means this file is special and should
220 /* Offset in FILE of the current line (i.e., where line POS.LINE
223 /* Length of the current line. */
225 /* Given the initial column to display, the offset (number of
226 characters to skip at the beginning of the line). */
229 /* Available width to quote the source file. Eight chars are
230 consumed by the left-margin (with line number). */
234 void caret_init (void)
236 screen_width
= columns ();
237 /* TRANSLATORS: This is used when a line is too long, and is
238 displayed truncated. Use an ellipsis appropriate for your
239 language, remembering that "…" (U+2026 HORIZONTAL ELLIPSIS)
240 sometimes misdisplays and that "..." (three ASCII periods) is a
241 safer choice in some locales. */
243 ellipsize
= mbswidth (ellipsis
, 0);
251 fclose (caret_info
.file
);
252 caret_info
.file
= NULL
;
256 /* Open FILE for quoting, if needed, and if possible. Return whether
257 the file can quoted. */
259 caret_set_file (const char *file
)
261 /* If a different file than before, close and let the rest open
263 if (caret_info
.pos
.file
&& caret_info
.pos
.file
!= file
)
266 caret_info
.pos
.file
= NULL
;
268 if (!caret_info
.pos
.file
)
270 caret_info
.pos
.file
= file
;
271 if ((caret_info
.file
= fopen (caret_info
.pos
.file
, "r")))
273 /* If the file is not regular (imagine #line 1 "/dev/stdin"
274 in the input file for instance), don't try to quote the
275 file. Keep caret_info.file set so that we don't try to
276 open it again, but leave caret_info.file NULL so that we
277 don't try to quote it. */
279 if (fstat (fileno (caret_info
.file
), &buf
) == 0
280 && buf
.st_mode
& S_IFREG
)
282 caret_info
.pos
.line
= 1;
283 mbf_init (caret_info
.mbfile
, caret_info
.file
);
289 return !!caret_info
.file
;
292 /* Getc, but smash \r\n as \n. */
294 caret_getc_internal (mbchar_t
*res
)
296 mbf_getc (*res
, caret_info
.mbfile
);
297 if (mb_iseq (*res
, '\r'))
300 mbf_getc (c
, caret_info
.mbfile
);
301 if (mb_iseq (c
, '\n'))
304 mbf_ungetc (c
, caret_info
.mbfile
);
308 #define caret_getc(Var) caret_getc_internal(&Var)
310 /* Move CARET_INFO (which has a valid FILE) to the line number LINE.
311 Compute and cache that line's length in CARET_INFO.LINE_LEN.
312 Return whether successful. */
314 caret_set_line (int line
)
316 /* If the line we want to quote is seekable (the same line as the previous
317 location), just seek it. If it was a previous line, we lost track of it,
318 so return to the start of file. */
319 if (line
< caret_info
.pos
.line
)
321 caret_info
.pos
.line
= 1;
322 caret_info
.offset
= 0;
324 if (fseek (caret_info
.file
, caret_info
.offset
, SEEK_SET
))
327 /* If this is the same line as the previous one, we are done. */
328 if (line
< caret_info
.pos
.line
)
331 /* Advance to the line's position, keeping track of the offset. */
332 while (caret_info
.pos
.line
< line
)
337 /* Something is wrong, that line number does not exist. */
339 caret_info
.pos
.line
+= mb_iseq (c
, '\n');
341 caret_info
.offset
= ftell (caret_info
.file
);
342 caret_info
.pos
.column
= 1;
343 /* Reset mbf's internal state.
344 FIXME: should be done in mbfile. */
345 caret_info
.mbfile
.eof_seen
= 0;
347 /* Find the number of columns of this line. */
352 if (mb_iseof (c
) || mb_iseq (c
, '\n'))
354 boundary_compute (&caret_info
.pos
, mb_ptr (c
), mb_len (c
));
356 caret_info
.line_len
= caret_info
.pos
.column
;
357 /* Go back to the beginning of line. */
358 if (fseek (caret_info
.file
, caret_info
.offset
, SEEK_SET
))
360 /* Reset mbf's internal state.
361 FIXME: should be done in mbfile. */
362 caret_info
.mbfile
.eof_seen
= 0;
363 caret_info
.pos
.column
= 1;
367 /* Compute CARET_INFO.WIDTH and CARET_INFO.SKIP based on the fact that
368 the first column to display in the current line is COL. */
370 caret_set_column (int col
)
372 /* Available width. Eight chars are consumed by the left-margin
373 (with line number). */
374 caret_info
.width
= screen_width
- 8;
376 if (caret_info
.width
< caret_info
.line_len
)
378 /* We cannot quote the whole line. Make sure we can see the
379 beginning of the location. */
380 caret_info
.skip
= caret_info
.width
< col
? col
- 10 : 0;
382 /* If we skip the initial part, we insert "..." before. */
384 caret_info
.width
-= ellipsize
;
385 /* If the end of line does not fit, we also need to truncate the
386 end, and leave "..." there. */
387 if (caret_info
.width
< caret_info
.line_len
- caret_info
.skip
)
388 caret_info
.width
-= ellipsize
;
393 location_caret (location loc
, const char *style
, FILE *out
)
395 if (!(feature_flag
& feature_caret
))
399 if (!caret_set_file (loc
.start
.file
))
401 if (!caret_set_line (loc
.start
.line
))
403 if (!caret_set_column (loc
.start
.column
))
406 const int width
= caret_info
.width
;
407 const int skip
= caret_info
.skip
;
409 /* Read the actual line. Don't update the offset, so that we keep a pointer
410 to the start of the line. */
416 /* The last column to highlight. Only the first line of
417 multiline locations are quoted, in which case the ending
418 column is the end of line.
420 We used to work with byte offsets, and that was much
421 easier. However, we went back to using (visual) columns to
422 support truncating of long lines. */
424 = loc
.start
.line
== loc
.end
.line
426 : caret_info
.line_len
;
427 /* Quote the file (at most the first line in the case of
428 multiline locations). */
430 fprintf (out
, "%5d | %s", loc
.start
.line
, skip
? ellipsis
: "");
431 /* Whether we opened the style. If the line is not as
432 expected (maybe the file was changed since the scanner
433 ran), we might reach the end before we actually saw the
435 enum { before
, inside
, after
} state
= before
;
436 while (!mb_iseof (c
) && !mb_iseq (c
, '\n'))
438 // We might have already opened (and even closed!) the
439 // style and yet have the equality of the columns if we
440 // just saw zero-width characters.
442 && caret_info
.pos
.column
== loc
.start
.column
)
444 begin_use_class (style
, out
);
447 if (skip
< caret_info
.pos
.column
)
449 boundary_compute (&caret_info
.pos
, mb_ptr (c
), mb_len (c
));
452 && (caret_info
.pos
.column
== col_end
453 || width
< caret_info
.pos
.column
- skip
))
455 end_use_class (style
, out
);
458 if (width
< caret_info
.pos
.column
- skip
)
460 fputs (ellipsis
, out
);
466 // The line is shorter than expected.
467 end_use_class (style
, out
);
473 /* Print the carets with the same indentation as above. */
475 fprintf (out
, " | %*s",
476 loc
.start
.column
- 1 - skip
+ (skip
? ellipsize
: 0), "");
477 begin_use_class (style
, out
);
479 /* Underlining a multiline location ends with the first
481 for (int i
= loc
.start
.column
- 1 - skip
+ 1,
482 i_end
= min_int (col_end
- 1 - skip
, width
);
485 end_use_class (style
, out
);
493 location_caret_suggestion (location loc
, const char *s
, FILE *out
)
495 if (!(feature_flag
& feature_caret
))
497 const char *style
= "fixit-insert";
498 fprintf (out
, " | %*s",
499 loc
.start
.column
- 1 - caret_info
.skip
500 + (caret_info
.skip
? ellipsize
: 0),
502 begin_use_class (style
, out
);
504 end_use_class (style
, out
);
509 location_empty (location loc
)
511 return !loc
.start
.file
&& !loc
.start
.line
&& !loc
.start
.column
512 && !loc
.end
.file
&& !loc
.end
.line
&& !loc
.end
.column
;
516 boundary_set_from_string (boundary
*bound
, char *str
)
518 /* Must search in reverse since the file name field may contain '.'
520 char *at
= strrchr (str
, '@');
524 bound
->byte
= atoi (at
+1);
527 char *dot
= strrchr (str
, '.');
530 bound
->column
= atoi (dot
+1);
532 bound
->byte
= bound
->column
;
535 char *colon
= strrchr (str
, ':');
538 bound
->line
= atoi (colon
+1);
540 bound
->file
= uniqstr_new (str
);