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 | |
33 | location 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 | |
39 | static inline int |
40 | add_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 | |
52 | void |
53 | location_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 | |
100 | static unsigned |
101 | boundary_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 | |
108 | unsigned |
109 | location_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. */ |
160 | static 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 | |
171 | void |
172 | caret_free () |
173 | { |
174 | if (caret_info.source) |
175 | { |
176 | fclose (caret_info.source); |
177 | caret_info.source = NULL; |
178 | } |
179 | } |
180 | |
181 | void |
182 | location_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 | |
301 | bool |
302 | location_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 | |
308 | void |
309 | boundary_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 | |