1/* Support for fixing grammar files.
2
3 Copyright (C) 2019 Free Software Foundation, Inc.
4
5 This file is part of Bison, the GNU Compiler Compiler.
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20#include <config.h>
21
22#include "fixits.h"
23
24#include "system.h"
25
26#include "error.h"
27#include "get-errno.h"
28#include "getargs.h"
29#include "gl_array_list.h"
30#include "gl_xlist.h"
31#include "progname.h"
32#include "quote.h"
33#include "quotearg.h"
34#include "vasnprintf.h"
35
36#include "files.h"
37
38typedef struct
39{
40 location location;
41 char *fix;
42} fixit;
43
44gl_list_t fixits = NULL;
45
46static fixit *
47fixit_new (location const *loc, char const* fix)
48{
49 fixit *res = xmalloc (sizeof *res);
50 res->location = *loc;
51 res->fix = xstrdup (fix);
52 return res;
53}
54
55static int
56fixit_cmp (const fixit *a, const fixit *b)
57{
58 return location_cmp (a->location, b->location);
59}
60
61static void
62fixit_free (fixit *f)
63{
64 free (f->fix);
65 free (f);
66}
67
68
69/* GCC and Clang follow the same pattern.
70 https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Message-Formatting-Options.html
71 http://clang.llvm.org/docs/UsersManual.html#cmdoption-fdiagnostics-parseable-fixits */
72static void
73fixit_print (fixit const *f, FILE *out)
74{
75 fprintf (out, "fix-it:%s:{%d:%d-%d:%d}:%s\n",
76 quotearg_n_style (1, c_quoting_style, f->location.start.file),
77 f->location.start.line, f->location.start.byte,
78 f->location.end.line, f->location.end.byte,
79 quotearg_n_style (2, c_quoting_style, f->fix));
80}
81
82
83void
84fixits_register (location const *loc, char const* fix)
85{
86 if (!fixits)
87 fixits = gl_list_create_empty (GL_ARRAY_LIST,
88 /* equals */ NULL,
89 /* hashcode */ NULL,
90 (gl_listelement_dispose_fn) fixit_free,
91 true);
92 fixit *f = fixit_new (loc, fix);
93 gl_sortedlist_add (fixits, (gl_listelement_compar_fn) fixit_cmp, f);
94 if (feature_flag & feature_fixit_parsable)
95 fixit_print (f, stderr);
96}
97
98
99bool
100fixits_empty (void)
101{
102 return !fixits;
103}
104
105
106void
107fixits_run (void)
108{
109 if (!fixits)
110 return;
111
112 /* This is not unlike what is done in location_caret. */
113 uniqstr input = ((fixit *) gl_list_get_at (fixits, 0))->location.start.file;
114 /* Backup the file. */
115 char buf[256];
116 size_t len = sizeof (buf);
117 char *backup = asnprintf (buf, &len, "%s~", input);
118 if (!backup)
119 xalloc_die ();
120 if (rename (input, backup))
121 error (EXIT_FAILURE, get_errno (),
122 _("%s: cannot backup"), quotearg_colon (input));
123 FILE *in = xfopen (backup, "r");
124 FILE *out = xfopen (input, "w");
125 size_t line = 1;
126 size_t offset = 1;
127 fixit const *f = NULL;
128 gl_list_iterator_t iter = gl_list_iterator (fixits);
129 while (gl_list_iterator_next (&iter, (const void**) &f, NULL))
130 {
131 /* Look for the correct line. */
132 while (line < f->location.start.line)
133 {
134 int c = getc (in);
135 if (c == EOF)
136 break;
137 if (c == '\n')
138 {
139 ++line;
140 offset = 1;
141 }
142 putc (c, out);
143 }
144
145 /* Look for the right offset. */
146 bool need_eol = false;
147 while (offset < f->location.start.byte)
148 {
149 int c = getc (in);
150 if (c == EOF)
151 break;
152 ++offset;
153 if (c == '\n')
154 /* The position we are asked for is beyond the actual
155 line: pad with spaces, and remember we need a \n. */
156 need_eol = true;
157 putc (need_eol ? ' ' : c, out);
158 }
159
160 /* Paste the fix instead. */
161 fputs (f->fix, out);
162
163 /* Maybe install the eol afterwards. */
164 if (need_eol)
165 putc ('\n', out);
166
167 /* Skip the bad input. */
168 while (line < f->location.end.line)
169 {
170 int c = getc (in);
171 if (c == EOF)
172 break;
173 if (c == '\n')
174 {
175 ++line;
176 offset = 1;
177 }
178 }
179 while (offset < f->location.end.byte)
180 {
181 int c = getc (in);
182 if (c == EOF)
183 break;
184 ++offset;
185 }
186
187 /* If erasing the content of a full line, also remove the
188 end-of-line. */
189 if (f->fix[0] == 0 && f->location.start.byte == 1)
190 {
191 int c = getc (in);
192 if (c == EOF)
193 break;
194 else if (c == '\n')
195 {
196 ++line;
197 offset = 1;
198 }
199 else
200 ungetc (c, in);
201 }
202 }
203 /* Paste the rest of the file. */
204 {
205 int c;
206 while ((c = getc (in)) != EOF)
207 putc (c, out);
208 }
209
210 gl_list_iterator_free (&iter);
211 xfclose (out);
212 xfclose (in);
213 fprintf (stderr, "%s: file %s was updated (backup: %s)\n",
214 program_name, quote_n (0, input), quote_n (1, backup));
215 if (backup != buf)
216 free (backup);
217}
218
219
220/* Free the registered fixits. */
221void fixits_free (void)
222{
223 if (fixits)
224 {
225 gl_list_free (fixits);
226 fixits = NULL;
227 }
228}
229