1/* Locations for Bison
2
3 Copyright (C) 2002, 2005-2015, 2018-2019 Free Software Foundation,
4 Inc.
5
6 This file is part of Bison, the GNU Compiler Compiler.
7
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.
12
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.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20
21#include <config.h>
22#include "system.h"
23
24#include <mbswidth.h>
25#include <quotearg.h>
26#include <stdio.h> /* fileno */
27#include <sys/stat.h> /* fstat */
28
29#include "complain.h"
30#include "getargs.h"
31#include "location.h"
32
33location const empty_loc = EMPTY_LOCATION_INIT;
34
35/* If BUF is null, add BUFSIZE (which in this case must be less than
36 INT_MAX) to COLUMN; otherwise, add mbsnwidth (BUF, BUFSIZE, 0) to
37 COLUMN. If an overflow occurs, return INT_MAX. */
38
39static inline int
40add_column_width (int column, char const *buf, size_t bufsize)
41{
42 int width
43 = buf ? mbsnwidth (buf, bufsize, 0)
44 : INT_MAX <= bufsize ? INT_MAX
45 : bufsize;
46 return column <= INT_MAX - width ? column + width : INT_MAX;
47}
48
49/* Set *LOC and adjust scanner cursor to account for token TOKEN of
50 size SIZE. */
51
52void
53location_compute (location *loc, boundary *cur, char const *token, size_t size)
54{
55 int line = cur->line;
56 int column = cur->column;
57 int byte = cur->byte;
58 char const *p0 = token;
59 char const *p = token;
60 char const *lim = token + size;
61
62 loc->start = *cur;
63
64 for (p = token; p < lim; ++p)
65 switch (*p)
66 {
67 case '\n':
68 line += line < INT_MAX;
69 column = 1;
70 byte = 1;
71 p0 = p + 1;
72 break;
73
74 case '\t':
75 column = add_column_width (column, p0, p - p0);
76 column = add_column_width (column, NULL, 8 - ((column - 1) & 7));
77 p0 = p + 1;
78 byte += byte < INT_MAX;
79 break;
80
81 default:
82 byte += byte < INT_MAX;
83 break;
84 }
85
86 cur->line = line;
87 cur->column = column = add_column_width (column, p0, p - p0);
88 cur->byte = byte;
89
90 loc->end = *cur;
91
92 if (line == INT_MAX && loc->start.line != INT_MAX)
93 complain (loc, Wother, _("line number overflow"));
94 if (column == INT_MAX && loc->start.column != INT_MAX)
95 complain (loc, Wother, _("column number overflow"));
96 if (byte == INT_MAX && loc->start.byte != INT_MAX)
97 complain (loc, Wother, _("byte number overflow"));
98}
99
100static unsigned
101boundary_print (boundary const *b, FILE *out)
102{
103 return fprintf (out, "%s:%d.%d@%d",
104 quotearg_n_style (3, escape_quoting_style, b->file),
105 b->line, b->column, b->byte);
106}
107
108unsigned
109location_print (location loc, FILE *out)
110{
111 unsigned res = 0;
112 if (trace_flag & trace_locations)
113 {
114 res += boundary_print (&loc.start, out);
115 res += fprintf (out, "-");
116 res += boundary_print (&loc.end, out);
117 }
118 else
119 {
120 int end_col = 0 != loc.end.column ? loc.end.column - 1 : 0;
121 res += fprintf (out, "%s",
122 quotearg_n_style (3, escape_quoting_style, loc.start.file));
123 if (0 <= loc.start.line)
124 {
125 res += fprintf (out, ":%d", loc.start.line);
126 if (0 <= loc.start.column)
127 res += fprintf (out, ".%d", loc.start.column);
128 }
129 if (loc.start.file != loc.end.file)
130 {
131 res += fprintf (out, "-%s",
132 quotearg_n_style (3, escape_quoting_style,
133 loc.end.file));
134 if (0 <= loc.end.line)
135 {
136 res += fprintf (out, ":%d", loc.end.line);
137 if (0 <= end_col)
138 res += fprintf (out, ".%d", end_col);
139 }
140 }
141 else if (0 <= loc.end.line)
142 {
143 if (loc.start.line < loc.end.line)
144 {
145 res += fprintf (out, "-%d", loc.end.line);
146 if (0 <= end_col)
147 res += fprintf (out, ".%d", end_col);
148 }
149 else if (0 <= end_col && loc.start.column < end_col)
150 res += fprintf (out, "-%d", end_col);
151 }
152 }
153
154 return res;
155}
156
157
158/* Persistent data used by location_caret to avoid reopening and rereading the
159 same file all over for each error. */
160static struct
161{
162 FILE *source;
163 /* The last file we tried to open. If non NULL, but SOURCE is NULL,
164 it means this file is special and should not be quoted. */
165 uniqstr file;
166 size_t line;
167 /* Offset in SOURCE where line LINE starts. */
168 size_t offset;
169} caret_info;
170
171void
172caret_free ()
173{
174 if (caret_info.source)
175 {
176 fclose (caret_info.source);
177 caret_info.source = NULL;
178 }
179}
180
181void
182location_caret (location loc, const char *style, FILE *out)
183{
184 if (loc.start.column == -1 || loc.start.line == -1)
185 return;
186 /* If a different source than before, close and let the rest open
187 the new one. */
188 if (caret_info.file && caret_info.file != loc.start.file)
189 {
190 caret_free ();
191 caret_info.file = NULL;
192 }
193 if (!caret_info.file)
194 {
195 caret_info.file = loc.start.file;
196 if ((caret_info.source = fopen (caret_info.file, "r")))
197 {
198 /* If the file is not regular (imagine #line 1 "/dev/stdin"
199 in the input file for instance), don't try to quote the
200 source. Keep caret_info.file set so that we don't try to
201 open it again, but leave caret_info.source NULL so that
202 we don't try to quote it. */
203 struct stat buf;
204 if (fstat (fileno (caret_info.source), &buf) == 0
205 && buf.st_mode & S_IFREG)
206 {
207 caret_info.line = 1;
208 caret_info.offset = 0;
209 }
210 else
211 caret_free ();
212 }
213 }
214 if (!caret_info.source)
215 return;
216
217
218 /* If the line we want to quote is seekable (the same line as the previous
219 location), just seek it. If it was a previous line, we lost track of it,
220 so return to the start of file. */
221 if (caret_info.line <= loc.start.line)
222 fseek (caret_info.source, caret_info.offset, SEEK_SET);
223 else
224 {
225 caret_info.line = 1;
226 caret_info.offset = 0;
227 fseek (caret_info.source, caret_info.offset, SEEK_SET);
228 }
229
230 /* Advance to the line's position, keeping track of the offset. */
231 while (caret_info.line < loc.start.line)
232 {
233 int c = getc (caret_info.source);
234 if (c == EOF)
235 /* Something is wrong, that line number does not exist. */
236 return;
237 caret_info.line += c == '\n';
238 }
239 caret_info.offset = ftell (caret_info.source);
240
241 /* Read the actual line. Don't update the offset, so that we keep a pointer
242 to the start of the line. */
243 {
244 int c = getc (caret_info.source);
245 if (c != EOF)
246 {
247 bool single_line = loc.start.line == loc.end.line;
248 /* Quote the file (at most the first line in the case of
249 multiline locations). */
250 {
251 fprintf (out, "%5d | ", loc.start.line);
252 /* Consider that single point location (with equal boundaries)
253 actually denote the character that they follow. */
254 int byte_end = loc.end.byte +
255 (single_line && loc.start.byte == loc.end.byte);
256 /* Byte number. */
257 int byte = 1;
258 /* Whether we opened the style. If the line is not as
259 expected (maybe the file was changed since the scanner
260 ran), we might reach the end before we actually saw the
261 opening column. */
262 bool opened = false;
263 while (c != EOF && c != '\n')
264 {
265 if (byte == loc.start.byte)
266 {
267 begin_use_class (style, out);
268 opened = true;
269 }
270 fputc (c, out);
271 c = getc (caret_info.source);
272 ++byte;
273 if (opened
274 && (single_line
275 ? byte == byte_end
276 : c == '\n' || c == EOF))
277 end_use_class (style, out);
278 }
279 putc ('\n', out);
280 }
281
282 /* Print the carets with the same indentation as above. */
283 {
284 fprintf (out, " | %*s", loc.start.column - 1, "");
285 begin_use_class (style, out);
286 putc ('^', out);
287 /* Underlining a multiline location ends with the first
288 line. */
289 int len = single_line
290 ? loc.end.column
291 : ftell (caret_info.source) - caret_info.offset;
292 for (int i = loc.start.column + 1; i < len; ++i)
293 putc ('~', out);
294 end_use_class (style, out);
295 putc ('\n', out);
296 }
297 }
298 }
299}
300
301bool
302location_empty (location loc)
303{
304 return !loc.start.file && !loc.start.line && !loc.start.column
305 && !loc.end.file && !loc.end.line && !loc.end.column;
306}
307
308void
309boundary_set_from_string (boundary *bound, char *str)
310{
311 /* Must search in reverse since the file name field may contain '.'
312 or ':'. */
313 char *at = strrchr (str, '@');
314 if (at)
315 {
316 *at = '\0';
317 bound->byte = atoi (at+1);
318 }
319 {
320 char *dot = strrchr (str, '.');
321 aver (dot);
322 *dot = '\0';
323 bound->column = atoi (dot+1);
324 if (!at)
325 bound->byte = bound->column;
326 }
327 {
328 char *colon = strrchr (str, ':');
329 aver (colon);
330 *colon = '\0';
331 bound->line = atoi (colon+1);
332 }
333 bound->file = uniqstr_new (str);
334}
335