tests: grep -E is not portable
[bison.git] / src / location.c
blob356f49477e2ea6d4906f60f8ea68af3a26865d95
1 /* Locations for Bison
3 Copyright (C) 2002, 2005-2015, 2018-2021 Free Software Foundation,
4 Inc.
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/>. */
21 #include <config.h>
22 #include "system.h"
24 #include <mbfile.h>
25 #include <mbswidth.h>
26 #include <quotearg.h>
27 #include <stdio.h> /* fileno */
28 #include <sys/ioctl.h>
29 #include <sys/stat.h> /* fstat */
30 #include <termios.h>
32 #ifdef WINSIZE_IN_PTEM
33 # include <sys/stream.h>
34 # include <sys/ptem.h>
35 #endif
37 #include "complain.h"
38 #include "getargs.h"
39 #include "location.h"
41 location const empty_loc = EMPTY_LOCATION_INIT;
43 /* The terminal width. Not less than 40. */
44 static int
45 columns (void)
47 const char *cp = getenv ("COLUMNS");
48 int res = 80;
49 if (cp && *cp)
51 long l = strtol (cp, NULL, 10);
52 res = 0 <= l && l <= INT_MAX ? l : INT_MAX;
54 else
56 #ifdef TIOCGWINSZ
57 struct winsize ws;
58 if (ioctl (STDERR_FILENO, TIOCGWINSZ, &ws) != -1
59 && 0 < ws.ws_col && ws.ws_col == (size_t) ws.ws_col)
60 res = ws.ws_col;
61 #endif
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. */
78 static inline int
79 add_column_width (int column, char const *buf, size_t bufsize)
81 int width
82 = buf ? mbsnwidth (buf, bufsize, 0)
83 : INT_MAX <= bufsize ? INT_MAX
84 : bufsize;
85 return column <= INT_MAX - width ? column + width : INT_MAX;
88 static void
89 boundary_compute (boundary *cur, char const *token, size_t size)
91 int line = cur->line;
92 int column = cur->column;
93 int byte = cur->byte;
94 char const *p0 = token;
95 char const *p = token;
96 char const *lim = token + size;
98 for (p = token; p < lim; ++p)
99 switch (*p)
101 case '\n':
102 line += line < INT_MAX;
103 column = 1;
104 byte = 1;
105 p0 = p + 1;
106 break;
108 case '\t':
109 column = add_column_width (column, p0, p - p0);
110 column = add_column_width (column, NULL, 8 - ((column - 1) & 7));
111 p0 = p + 1;
112 byte += byte < INT_MAX;
113 break;
115 default:
116 byte += byte < INT_MAX;
117 break;
119 column = add_column_width (column, p0, p - p0);
121 cur->line = line;
122 cur->column = column;
123 cur->byte = byte;
127 /* Set *LOC and adjust scanner cursor to account for token TOKEN of
128 size SIZE. */
130 void
131 location_compute (location *loc, boundary *cur, char const *token, size_t size)
133 loc->start = *cur;
134 boundary_compute (cur, token, size);
135 loc->end = *cur;
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"));
146 static int
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)
157 int res = 0;
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);
166 else
168 aver (loc.start.file);
169 aver (loc.end.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,
183 loc.end.file));
184 if (0 < loc.end.line)
186 res += fprintf (out, ":%d", loc.end.line);
187 if (0 <= end_col)
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);
196 if (0 <= end_col)
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);
204 return res;
208 /* Persistent data used by location_caret to avoid reopening and rereading the
209 same file all over for each error. */
210 static struct
212 /* Raw input file. */
213 FILE *file;
214 /* Input file as a stream of multibyte characters. */
215 mb_file_t mbfile;
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
218 not be quoted. */
219 boundary pos;
220 /* Offset in FILE of the current line (i.e., where line POS.LINE
221 starts). */
222 size_t offset;
223 /* Length of the current line. */
224 int line_len;
225 /* Given the initial column to display, the offset (number of
226 characters to skip at the beginning of the line). */
227 int skip;
229 /* Available width to quote the source file. Eight chars are
230 consumed by the left-margin (with line number). */
231 int width;
232 } caret_info;
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. */
242 ellipsis = _("...");
243 ellipsize = mbswidth (ellipsis, 0);
246 void
247 caret_free (void)
249 if (caret_info.file)
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. */
258 static bool
259 caret_set_file (const char *file)
261 /* If a different file than before, close and let the rest open
262 the new one. */
263 if (caret_info.pos.file && caret_info.pos.file != file)
265 caret_free ();
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. */
278 struct stat buf;
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);
285 else
286 caret_free ();
289 return !!caret_info.file;
292 /* Getc, but smash \r\n as \n. */
293 static void
294 caret_getc_internal (mbchar_t *res)
296 mbf_getc (*res, caret_info.mbfile);
297 if (mb_iseq (*res, '\r'))
299 mbchar_t c;
300 mbf_getc (c, caret_info.mbfile);
301 if (mb_iseq (c, '\n'))
302 mb_copy (res, &c);
303 else
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. */
313 static bool
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))
325 return false;
327 /* If this is the same line as the previous one, we are done. */
328 if (line < caret_info.pos.line)
329 return true;
331 /* Advance to the line's position, keeping track of the offset. */
332 while (caret_info.pos.line < line)
334 mbchar_t c;
335 caret_getc (c);
336 if (mb_iseof (c))
337 /* Something is wrong, that line number does not exist. */
338 return false;
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. */
348 while (true)
350 mbchar_t c;
351 caret_getc (c);
352 if (mb_iseof (c) || mb_iseq (c, '\n'))
353 break;
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))
359 return false;
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;
364 return true;
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. */
369 static bool
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;
375 caret_info.skip = 0;
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. */
383 if (caret_info.skip)
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;
389 return true;
392 void
393 location_caret (location loc, const char *style, FILE *out)
395 if (!(feature_flag & feature_caret))
396 return;
397 if (!loc.start.line)
398 return;
399 if (!caret_set_file (loc.start.file))
400 return;
401 if (!caret_set_line (loc.start.line))
402 return;
403 if (!caret_set_column (loc.start.column))
404 return;
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. */
412 mbchar_t c;
413 caret_getc (c);
414 if (!mb_iseof (c))
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. */
423 const int col_end
424 = loc.start.line == loc.end.line
425 ? loc.end.column
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
434 opening column. */
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.
441 if (state == before
442 && caret_info.pos.column == loc.start.column)
444 begin_use_class (style, out);
445 state = inside;
447 if (skip < caret_info.pos.column)
448 mb_putc (c, out);
449 boundary_compute (&caret_info.pos, mb_ptr (c), mb_len (c));
450 caret_getc (c);
451 if (state == inside
452 && (caret_info.pos.column == col_end
453 || width < caret_info.pos.column - skip))
455 end_use_class (style, out);
456 state = after;
458 if (width < caret_info.pos.column - skip)
460 fputs (ellipsis, out);
461 break;
464 if (state == inside)
466 // The line is shorter than expected.
467 end_use_class (style, out);
468 state = after;
470 putc ('\n', 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);
478 putc ('^', out);
479 /* Underlining a multiline location ends with the first
480 line. */
481 for (int i = loc.start.column - 1 - skip + 1,
482 i_end = min_int (col_end - 1 - skip, width);
483 i < i_end; ++i)
484 putc ('~', out);
485 end_use_class (style, out);
486 putc ('\n', out);
492 void
493 location_caret_suggestion (location loc, const char *s, FILE *out)
495 if (!(feature_flag & feature_caret))
496 return;
497 const char *style = "fixit-insert";
498 fprintf (out, " | %*s",
499 loc.start.column - 1 - caret_info.skip
500 + (caret_info.skip ? ellipsize : 0),
501 "");
502 begin_use_class (style, out);
503 fputs (s, out);
504 end_use_class (style, out);
505 putc ('\n', out);
508 bool
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;
515 void
516 boundary_set_from_string (boundary *bound, char *str)
518 /* Must search in reverse since the file name field may contain '.'
519 or ':'. */
520 char *at = strrchr (str, '@');
521 if (at)
523 *at = '\0';
524 bound->byte = atoi (at+1);
527 char *dot = strrchr (str, '.');
528 aver (dot);
529 *dot = '\0';
530 bound->column = atoi (dot+1);
531 if (!at)
532 bound->byte = bound->column;
535 char *colon = strrchr (str, ':');
536 aver (colon);
537 *colon = '\0';
538 bound->line = atoi (colon+1);
540 bound->file = uniqstr_new (str);