| /* Support for fixing grammar files. | |
| Copyright (C) 2019-2022, 2025 Free Software Foundation, Inc. | |
| This file is part of Bison, the GNU Compiler Compiler. | |
| This program is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation, either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| #include <config.h> | |
| #include "fixits.h" | |
| #include <error.h> | |
| #include <get-errno.h> | |
| #include <gl_array_list.h> | |
| #include <gl_xlist.h> | |
| #include <progname.h> | |
| #include <quote.h> | |
| #include <quotearg.h> | |
| #include <vasnprintf.h> | |
| #include "system.h" | |
| #include "files.h" | |
| #include "getargs.h" | |
| typedef struct | |
| { | |
| location location; | |
| char *fix; | |
| } fixit; | |
| gl_list_t fixits = NULL; | |
| static fixit * | |
| fixit_new (location const *loc, char const* fix) | |
| { | |
| fixit *res = xmalloc (sizeof *res); | |
| res->location = *loc; | |
| res->fix = xstrdup (fix); | |
| return res; | |
| } | |
| static int | |
| fixit_cmp (void const *va, void const *vb) | |
| { | |
| fixit const *a = va; | |
| fixit const *b = vb; | |
| return location_cmp (a->location, b->location); | |
| } | |
| static void | |
| fixit_free (void const *vf) | |
| { | |
| fixit *f = deconst (vf); | |
| free (f->fix); | |
| free (f); | |
| } | |
| /* GCC and Clang follow the same pattern. | |
| https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Message-Formatting-Options.html | |
| https://clang.llvm.org/docs/UsersManual.html#cmdoption-fdiagnostics-parseable-fixits */ | |
| static void | |
| fixit_print (fixit const *f, FILE *out) | |
| { | |
| fprintf (out, "fix-it:%s:{%d:%d-%d:%d}:%s\n", | |
| quotearg_n_style (1, c_quoting_style, f->location.start.file), | |
| f->location.start.line, f->location.start.byte, | |
| f->location.end.line, f->location.end.byte, | |
| quotearg_n_style (2, c_quoting_style, f->fix)); | |
| } | |
| void | |
| fixits_register (location const *loc, char const* fix) | |
| { | |
| if (!fixits) | |
| fixits = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, | |
| fixit_free, true); | |
| fixit *f = fixit_new (loc, fix); | |
| gl_sortedlist_add (fixits, fixit_cmp, f); | |
| if (feature_flag & feature_fixit) | |
| fixit_print (f, stderr); | |
| } | |
| bool | |
| fixits_empty (void) | |
| { | |
| return !fixits; | |
| } | |
| void | |
| fixits_run (void) | |
| { | |
| if (!fixits) | |
| return; | |
| /* This is not unlike what is done in location_caret. */ | |
| uniqstr input = ((fixit *) gl_list_get_at (fixits, 0))->location.start.file; | |
| /* Backup the file. */ | |
| char buf[256]; | |
| size_t len = sizeof (buf); | |
| char *backup = asnprintf (buf, &len, "%s~", input); | |
| if (!backup) | |
| xalloc_die (); | |
| if (rename (input, backup)) | |
| error (EXIT_FAILURE, get_errno (), | |
| _("%s: cannot backup"), quotearg_colon (input)); | |
| FILE *in = xfopen (backup, "r"); | |
| FILE *out = xfopen (input, "w"); | |
| size_t line = 1; | |
| size_t offset = 1; | |
| void const *p = NULL; | |
| gl_list_iterator_t iter = gl_list_iterator (fixits); | |
| while (gl_list_iterator_next (&iter, &p, NULL)) | |
| { | |
| fixit const *f = p; | |
| /* Look for the correct line. */ | |
| while (line < f->location.start.line) | |
| { | |
| int c = getc (in); | |
| if (c == EOF) | |
| break; | |
| if (c == '\n') | |
| { | |
| ++line; | |
| offset = 1; | |
| } | |
| putc (c, out); | |
| } | |
| /* Look for the right offset. */ | |
| bool need_eol = false; | |
| while (offset < f->location.start.byte) | |
| { | |
| int c = getc (in); | |
| if (c == EOF) | |
| break; | |
| ++offset; | |
| if (c == '\n') | |
| /* The position we are asked for is beyond the actual | |
| line: pad with spaces, and remember we need a \n. */ | |
| need_eol = true; | |
| putc (need_eol ? ' ' : c, out); | |
| } | |
| /* Paste the fix instead. */ | |
| fputs (f->fix, out); | |
| /* Maybe install the eol afterwards. */ | |
| if (need_eol) | |
| putc ('\n', out); | |
| /* Skip the bad input. */ | |
| while (line < f->location.end.line) | |
| { | |
| int c = getc (in); | |
| if (c == EOF) | |
| break; | |
| if (c == '\n') | |
| { | |
| ++line; | |
| offset = 1; | |
| } | |
| } | |
| while (offset < f->location.end.byte) | |
| { | |
| int c = getc (in); | |
| if (c == EOF) | |
| break; | |
| ++offset; | |
| } | |
| /* If erasing the content of a full line, also remove the | |
| end-of-line. */ | |
| if (f->fix[0] == 0 && f->location.start.byte == 1) | |
| { | |
| int c = getc (in); | |
| if (c == EOF) | |
| break; | |
| else if (c == '\n') | |
| { | |
| ++line; | |
| offset = 1; | |
| } | |
| else | |
| ungetc (c, in); | |
| } | |
| } | |
| /* Paste the rest of the file. */ | |
| { | |
| int c; | |
| while ((c = getc (in)) != EOF) | |
| putc (c, out); | |
| } | |
| gl_list_iterator_free (&iter); | |
| xfclose (out); | |
| xfclose (in); | |
| fprintf (stderr, "%s: file %s was updated (backup: %s)\n", | |
| program_name, quote_n (0, input), quote_n (1, backup)); | |
| if (backup != buf) | |
| free (backup); | |
| } | |
| /* Free the registered fixits. */ | |
| void fixits_free (void) | |
| { | |
| if (fixits) | |
| { | |
| gl_list_free (fixits); | |
| fixits = NULL; | |
| } | |
| } |