mirror of
https://git.savannah.gnu.org/git/bison.git
synced 2026-03-10 04:43:03 +00:00
We used to display the unexpected token first:
$ bison foo.y
foo.y:1.8-13: error: syntax error, unexpected %token, expecting character literal or identifier or <tag>
1 | %token %token
| ^~~~~~
GCC uses a different format:
$ gcc-mp-9 foo.c
foo.c:1:5: error: expected identifier or '(' before ')' token
1 | int()()()
| ^
and so does Clang:
$ clang-mp-9.0 foo.c
foo.c:1:5: error: expected identifier or '('
int()()()
^
1 error generated.
They display the unexpected token last (or not at all). Also, they
don't waste width with "syntax error". Let's try that. It gives, for
the same example as above:
$ bison foo.y
foo.y:1.8-13: error: expected character literal or identifier or <tag> before %token
1 | %token %token
| ^~~~~~
* src/complain.h, src/complain.c (syntax_error): New.
* src/parse-gram.y (yyreport_syntax_error): Use it.
646 lines
17 KiB
C
646 lines
17 KiB
C
/* Declaration for error-reporting function for Bison.
|
|
|
|
Copyright (C) 2000-2002, 2004-2006, 2009-2015, 2018-2020 Free
|
|
Software Foundation, Inc.
|
|
|
|
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 <http://www.gnu.org/licenses/>. */
|
|
|
|
/* Based on error.c and error.h,
|
|
written by David MacKenzie <djm@gnu.ai.mit.edu>. */
|
|
|
|
#include <config.h>
|
|
#include "system.h"
|
|
|
|
#include <argmatch.h>
|
|
#include <ctype.h>
|
|
#include <progname.h>
|
|
#include <stdarg.h>
|
|
#include <sys/stat.h>
|
|
#include <textstyle.h>
|
|
|
|
#include "complain.h"
|
|
#include "files.h"
|
|
#include "fixits.h"
|
|
#include "getargs.h"
|
|
#include "quote.h"
|
|
|
|
err_status complaint_status = status_none;
|
|
|
|
bool warnings_are_errors = false;
|
|
|
|
/** Whether -Werror/-Wno-error was applied to a warning. */
|
|
typedef enum
|
|
{
|
|
errority_unset = 0, /** No explict status. */
|
|
errority_disabled = 1, /** Explictly disabled with -Wno-error=foo. */
|
|
errority_enabled = 2 /** Explictly enabled with -Werror=foo. */
|
|
} errority;
|
|
|
|
/** For each warning type, its errority. */
|
|
static errority errority_flag[warnings_size];
|
|
|
|
/** Diagnostics severity. */
|
|
typedef enum
|
|
{
|
|
severity_disabled = 0, /**< Explicitly disabled via -Wno-foo. */
|
|
severity_unset = 1, /**< Unspecified status. */
|
|
severity_warning = 2, /**< A warning. */
|
|
severity_error = 3, /**< An error (continue, but die soon). */
|
|
severity_fatal = 4 /**< Fatal error (die now). */
|
|
} severity;
|
|
|
|
|
|
/** For each warning type, its severity. */
|
|
static severity warnings_flag[warnings_size];
|
|
|
|
styled_ostream_t errstream = NULL;
|
|
|
|
void
|
|
begin_use_class (const char *s, FILE *out)
|
|
{
|
|
if (out == stderr)
|
|
{
|
|
if (color_debug)
|
|
fprintf (out, "<%s>", s);
|
|
else
|
|
{
|
|
styled_ostream_begin_use_class (errstream, s);
|
|
styled_ostream_flush_to_current_style (errstream);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
end_use_class (const char *s, FILE *out)
|
|
{
|
|
if (out == stderr)
|
|
{
|
|
if (color_debug)
|
|
fprintf (out, "</%s>", s);
|
|
else
|
|
{
|
|
styled_ostream_end_use_class (errstream, s);
|
|
styled_ostream_flush_to_current_style (errstream);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
flush (FILE *out)
|
|
{
|
|
if (out == stderr)
|
|
ostream_flush (errstream, FLUSH_THIS_STREAM);
|
|
fflush (out);
|
|
}
|
|
|
|
/*------------------------.
|
|
| --warnings's handling. |
|
|
`------------------------*/
|
|
|
|
ARGMATCH_DEFINE_GROUP (warning, warnings)
|
|
|
|
static const argmatch_warning_doc argmatch_warning_docs[] =
|
|
{
|
|
{ "conflicts-sr", N_("S/R conflicts (enabled by default)") },
|
|
{ "conflicts-rr", N_("R/R conflicts (enabled by default)") },
|
|
{ "dangling-alias", N_("string aliases not attached to a symbol") },
|
|
{ "deprecated", N_("obsolete constructs") },
|
|
{ "empty-rule", N_("empty rules without %empty") },
|
|
{ "midrule-values", N_("unset or unused midrule values") },
|
|
{ "precedence", N_("useless precedence and associativity") },
|
|
{ "yacc", N_("incompatibilities with POSIX Yacc") },
|
|
{ "other", N_("all other warnings (enabled by default)") },
|
|
{ "all", N_("all the warnings except 'dangling-alias' and 'yacc'") },
|
|
{ "no-CATEGORY", N_("turn off warnings in CATEGORY") },
|
|
{ "none", N_("turn off all the warnings") },
|
|
{ "error[=CATEGORY]", N_("treat warnings as errors") },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static const argmatch_warning_arg argmatch_warning_args[] =
|
|
{
|
|
{ "all", Wall },
|
|
{ "conflicts-rr", Wconflicts_rr },
|
|
{ "conflicts-sr", Wconflicts_sr },
|
|
{ "dangling-alias", Wdangling_alias },
|
|
{ "deprecated", Wdeprecated },
|
|
{ "empty-rule", Wempty_rule },
|
|
{ "everything", Weverything },
|
|
{ "midrule-values", Wmidrule_values },
|
|
{ "none", Wnone },
|
|
{ "other", Wother },
|
|
{ "precedence", Wprecedence },
|
|
{ "yacc", Wyacc },
|
|
{ NULL, Wnone }
|
|
};
|
|
|
|
const argmatch_warning_group_type argmatch_warning_group =
|
|
{
|
|
argmatch_warning_args,
|
|
argmatch_warning_docs,
|
|
N_("Warning categories include:"),
|
|
NULL
|
|
};
|
|
|
|
void
|
|
warning_usage (FILE *out)
|
|
{
|
|
argmatch_warning_usage (out);
|
|
}
|
|
|
|
void
|
|
warning_argmatch (char const *arg, size_t no, size_t err)
|
|
{
|
|
int value = *argmatch_warning_value ("--warning", arg + no + err);
|
|
|
|
/* -Wnone == -Wno-everything, and -Wno-none == -Weverything. */
|
|
if (!value)
|
|
{
|
|
value = Weverything;
|
|
no = !no;
|
|
}
|
|
|
|
for (size_t b = 0; b < warnings_size; ++b)
|
|
if (value & 1 << b)
|
|
{
|
|
if (err && no)
|
|
/* -Wno-error=foo. */
|
|
errority_flag[b] = errority_disabled;
|
|
else if (err && !no)
|
|
{
|
|
/* -Werror=foo: enables -Wfoo. */
|
|
errority_flag[b] = errority_enabled;
|
|
warnings_flag[b] = severity_warning;
|
|
}
|
|
else if (no)
|
|
/* -Wno-foo. */
|
|
warnings_flag[b] = severity_disabled;
|
|
else
|
|
/* -Wfoo. */
|
|
warnings_flag[b] = severity_warning;
|
|
}
|
|
}
|
|
|
|
/** Decode a comma-separated list of arguments from -W.
|
|
*
|
|
* \param args comma separated list of effective subarguments to decode.
|
|
* If 0, then activate all the flags.
|
|
*/
|
|
|
|
void
|
|
warnings_argmatch (char *args)
|
|
{
|
|
if (!args)
|
|
warning_argmatch ("all", 0, 0);
|
|
else if (STREQ (args, "help"))
|
|
{
|
|
warning_usage (stdout);
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
else
|
|
for (args = strtok (args, ","); args; args = strtok (NULL, ","))
|
|
if (STREQ (args, "error"))
|
|
warnings_are_errors = true;
|
|
else if (STREQ (args, "no-error"))
|
|
warnings_are_errors = false;
|
|
else
|
|
{
|
|
/* The length of the possible 'no-' prefix: 3, or 0. */
|
|
size_t no = STRPREFIX_LIT ("no-", args) ? 3 : 0;
|
|
/* The length of the possible 'error=' (possibly after
|
|
'no-') prefix: 6, or 0. */
|
|
size_t err = STRPREFIX_LIT ("error=", args + no) ? 6 : 0;
|
|
|
|
warning_argmatch (args, no, err);
|
|
}
|
|
}
|
|
|
|
/* Color style for this type of message. */
|
|
static const char*
|
|
severity_style (severity s)
|
|
{
|
|
switch (s)
|
|
{
|
|
case severity_disabled:
|
|
case severity_unset:
|
|
return "note";
|
|
case severity_warning:
|
|
return "warning";
|
|
case severity_error:
|
|
case severity_fatal:
|
|
return "error";
|
|
}
|
|
abort ();
|
|
}
|
|
|
|
/* Prefix for this type of message. */
|
|
static const char*
|
|
severity_prefix (severity s)
|
|
{
|
|
switch (s)
|
|
{
|
|
case severity_disabled:
|
|
case severity_unset:
|
|
return "";
|
|
case severity_warning:
|
|
return _("warning");
|
|
case severity_error:
|
|
return _("error");
|
|
case severity_fatal:
|
|
return _("fatal error");
|
|
}
|
|
abort ();
|
|
}
|
|
|
|
|
|
static void
|
|
severity_print (severity s, FILE *out)
|
|
{
|
|
if (s != severity_disabled)
|
|
{
|
|
const char* style = severity_style (s);
|
|
begin_use_class (style, out);
|
|
fprintf (out, "%s:", severity_prefix (s));
|
|
end_use_class (style, out);
|
|
fputc (' ', out);
|
|
}
|
|
}
|
|
|
|
|
|
/*-----------.
|
|
| complain. |
|
|
`-----------*/
|
|
|
|
void
|
|
complain_init_color (void)
|
|
{
|
|
#if HAVE_LIBTEXTSTYLE
|
|
if (color_mode == color_yes
|
|
|| color_mode == color_html
|
|
|| (color_mode == color_tty && isatty (STDERR_FILENO)))
|
|
{
|
|
style_file_prepare ("BISON_STYLE", "BISON_STYLEDIR", pkgdatadir (),
|
|
"bison-default.css");
|
|
/* As a fallback, use the default in the current directory. */
|
|
struct stat statbuf;
|
|
if ((style_file_name == NULL || stat (style_file_name, &statbuf) < 0)
|
|
&& stat ("bison-default.css", &statbuf) == 0)
|
|
style_file_name = "bison-default.css";
|
|
}
|
|
else
|
|
/* No styling. */
|
|
style_file_name = NULL;
|
|
#endif
|
|
|
|
/* Workaround clang's warning (starting at Clang 3.5) about the stub
|
|
code of html_styled_ostream_create:
|
|
|
|
| src/complain.c:274:7: error: code will never be executed [-Werror,-Wunreachable-code]
|
|
| ? html_styled_ostream_create (file_ostream_create (stderr),
|
|
| ^~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
#if defined __clang__
|
|
# pragma clang diagnostic push
|
|
# pragma clang diagnostic ignored "-Wunreachable-code"
|
|
#endif
|
|
errstream =
|
|
color_mode == color_html
|
|
? html_styled_ostream_create (file_ostream_create (stderr),
|
|
style_file_name)
|
|
: styled_ostream_create (STDERR_FILENO, "(stderr)", TTYCTL_AUTO,
|
|
style_file_name);
|
|
#if defined __clang__
|
|
# pragma clang diagnostic pop
|
|
#endif
|
|
}
|
|
|
|
void
|
|
complain_init (void)
|
|
{
|
|
caret_init ();
|
|
|
|
warnings warnings_default =
|
|
Wconflicts_sr | Wconflicts_rr | Wdeprecated | Wother;
|
|
|
|
for (size_t b = 0; b < warnings_size; ++b)
|
|
{
|
|
warnings_flag[b] = (1 << b & warnings_default
|
|
? severity_warning
|
|
: severity_unset);
|
|
errority_flag[b] = errority_unset;
|
|
}
|
|
}
|
|
|
|
void
|
|
complain_free (void)
|
|
{
|
|
caret_free ();
|
|
styled_ostream_free (errstream);
|
|
}
|
|
|
|
/* A diagnostic with FLAGS is about to be issued. With what severity?
|
|
(severity_fatal, severity_error, severity_disabled, or
|
|
severity_warning.) */
|
|
|
|
static severity
|
|
warning_severity (warnings flags)
|
|
{
|
|
if (flags & fatal)
|
|
/* Diagnostics about fatal errors. */
|
|
return severity_fatal;
|
|
else if (flags & complaint)
|
|
/* Diagnostics about errors. */
|
|
return severity_error;
|
|
else
|
|
{
|
|
/* Diagnostics about warnings. */
|
|
severity res = severity_disabled;
|
|
for (size_t b = 0; b < warnings_size; ++b)
|
|
if (flags & 1 << b)
|
|
{
|
|
res = res < warnings_flag[b] ? warnings_flag[b] : res;
|
|
/* If the diagnostic is enabled, and -Werror is enabled,
|
|
and -Wno-error=foo was not explicitly requested, this
|
|
is an error. */
|
|
if (res == severity_warning
|
|
&& (errority_flag[b] == errority_enabled
|
|
|| (warnings_are_errors
|
|
&& errority_flag[b] != errority_disabled)))
|
|
res = severity_error;
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
bool
|
|
warning_is_unset (warnings flags)
|
|
{
|
|
for (size_t b = 0; b < warnings_size; ++b)
|
|
if (flags & 1 << b && warnings_flag[b] != severity_unset)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
warning_is_enabled (warnings flags)
|
|
{
|
|
return severity_warning <= warning_severity (flags);
|
|
}
|
|
|
|
/** Display a "[-Wyacc]" like message on \a out. */
|
|
|
|
static void
|
|
warnings_print_categories (warnings warn_flags, FILE *out)
|
|
{
|
|
for (int wbit = 0; wbit < warnings_size; ++wbit)
|
|
if (warn_flags & (1 << wbit))
|
|
{
|
|
warnings w = 1 << wbit;
|
|
severity s = warning_severity (w);
|
|
const char* style = severity_style (s);
|
|
fputs (" [", out);
|
|
begin_use_class (style, out);
|
|
fprintf (out, "-W%s%s",
|
|
s == severity_error ? "error=" : "",
|
|
argmatch_warning_argument (&w));
|
|
end_use_class (style, out);
|
|
fputc (']', out);
|
|
/* Display only the first match, the second is "-Wall". */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** Report an error message.
|
|
*
|
|
* \param loc the location, defaulting to the current file,
|
|
* or the program name.
|
|
* \param indent optional indentation for the error message.
|
|
* \param flags the category for this message.
|
|
* \param sever to decide the prefix to put before the message
|
|
* (e.g., "warning").
|
|
* \param message the error message, a printf format string. Iff it
|
|
* ends with ": ", then no trailing newline is printed,
|
|
* and the caller should print the remaining
|
|
* newline-terminated message to stderr.
|
|
* \param args the arguments of the format string.
|
|
*/
|
|
static
|
|
void
|
|
error_message (const location *loc, int *indent, warnings flags,
|
|
severity sever, const char *message, va_list args)
|
|
{
|
|
int pos = 0;
|
|
|
|
if (loc)
|
|
pos += location_print (*loc, stderr);
|
|
else
|
|
pos += fprintf (stderr, "%s", grammar_file ? grammar_file : program_name);
|
|
pos += fprintf (stderr, ": ");
|
|
|
|
if (indent)
|
|
{
|
|
if (*indent)
|
|
sever = severity_disabled;
|
|
if (!*indent)
|
|
*indent = pos;
|
|
else if (*indent > pos)
|
|
fprintf (stderr, "%*s", *indent - pos, "");
|
|
}
|
|
|
|
severity_print (sever, stderr);
|
|
vfprintf (stderr, message, args);
|
|
/* Print the type of warning, only if this is not a sub message
|
|
(in which case the prefix is null). */
|
|
if (! (flags & silent) && sever != severity_disabled)
|
|
warnings_print_categories (flags, stderr);
|
|
|
|
{
|
|
size_t l = strlen (message);
|
|
if (l < 2 || message[l - 2] != ':' || message[l - 1] != ' ')
|
|
{
|
|
putc ('\n', stderr);
|
|
flush (stderr);
|
|
if (loc && !(flags & no_caret))
|
|
location_caret (*loc, severity_style (sever), stderr);
|
|
}
|
|
}
|
|
flush (stderr);
|
|
}
|
|
|
|
/** Raise a complaint (fatal error, error or just warning). */
|
|
|
|
static void
|
|
complains (const location *loc, int *indent, warnings flags,
|
|
const char *message, va_list args)
|
|
{
|
|
if ((flags & complaint) && complaint_status < status_complaint)
|
|
complaint_status = status_complaint;
|
|
|
|
severity s = warning_severity (flags);
|
|
if (severity_warning <= s)
|
|
{
|
|
if (severity_error <= s && ! complaint_status)
|
|
complaint_status = status_warning_as_error;
|
|
error_message (loc, indent, flags, s, message, args);
|
|
}
|
|
|
|
if (flags & fatal)
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
void
|
|
complain (location const *loc, warnings flags, const char *message, ...)
|
|
{
|
|
va_list args;
|
|
va_start (args, message);
|
|
complains (loc, NULL, flags, message, args);
|
|
va_end (args);
|
|
}
|
|
|
|
void
|
|
complain_indent (location const *loc, warnings flags, int *indent,
|
|
const char *message, ...)
|
|
{
|
|
va_list args;
|
|
va_start (args, message);
|
|
complains (loc, indent, flags, message, args);
|
|
va_end (args);
|
|
}
|
|
|
|
void
|
|
complain_args (location const *loc, warnings w, int *indent,
|
|
int argc, char *argv[])
|
|
{
|
|
switch (argc)
|
|
{
|
|
case 1:
|
|
complain_indent (loc, w, indent, "%s", _(argv[0]));
|
|
break;
|
|
case 2:
|
|
complain_indent (loc, w, indent, _(argv[0]), argv[1]);
|
|
break;
|
|
case 3:
|
|
complain_indent (loc, w, indent, _(argv[0]), argv[1], argv[2]);
|
|
break;
|
|
case 4:
|
|
complain_indent (loc, w, indent, _(argv[0]), argv[1], argv[2], argv[3]);
|
|
break;
|
|
case 5:
|
|
complain_indent (loc, w, indent, _(argv[0]), argv[1], argv[2], argv[3],
|
|
argv[4]);
|
|
break;
|
|
default:
|
|
complain (loc, fatal, "too many arguments for complains");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
bison_directive (location const *loc, char const *directive)
|
|
{
|
|
complain (loc, Wyacc,
|
|
_("POSIX Yacc does not support %s"), directive);
|
|
}
|
|
|
|
void
|
|
deprecated_directive (location const *loc, char const *old, char const *upd)
|
|
{
|
|
if (warning_is_enabled (Wdeprecated))
|
|
{
|
|
complain (loc, Wdeprecated,
|
|
_("deprecated directive: %s, use %s"),
|
|
quote (old), quote_n (1, upd));
|
|
location_caret_suggestion (*loc, upd, stderr);
|
|
/* Register updates only if -Wdeprecated is enabled. */
|
|
fixits_register (loc, upd);
|
|
}
|
|
}
|
|
|
|
void
|
|
duplicate_directive (char const *directive,
|
|
location first, location second)
|
|
{
|
|
int i = 0;
|
|
if (feature_flag & feature_caret)
|
|
complain_indent (&second, Wother, &i, _("duplicate directive"));
|
|
else
|
|
complain_indent (&second, Wother, &i, _("duplicate directive: %s"), quote (directive));
|
|
i += SUB_INDENT;
|
|
complain_indent (&first, Wother, &i, _("previous declaration"));
|
|
fixits_register (&second, "");
|
|
}
|
|
|
|
void
|
|
duplicate_rule_directive (char const *directive,
|
|
location first, location second)
|
|
{
|
|
int i = 0;
|
|
complain_indent (&second, complaint, &i,
|
|
_("only one %s allowed per rule"), directive);
|
|
i += SUB_INDENT;
|
|
complain_indent (&first, complaint, &i,
|
|
_("previous declaration"));
|
|
fixits_register (&second, "");
|
|
}
|
|
|
|
void
|
|
syntax_error (location loc,
|
|
int argc, const char* argv[])
|
|
{
|
|
if (complaint_status < status_complaint)
|
|
complaint_status = status_complaint;
|
|
assert (argc <= 5);
|
|
const char *format = NULL;
|
|
switch (argc)
|
|
{
|
|
# define CASE(N, S) \
|
|
case N: \
|
|
format = S; \
|
|
break
|
|
default: /* Avoid compiler warnings. */
|
|
CASE (0, _("syntax error"));
|
|
CASE (1, _("unexpected %0$s"));
|
|
CASE (2, _("expected %1$s before %0$s"));
|
|
CASE (3, _("expected %1$s or %2$s before %0$s"));
|
|
CASE (4, _("expected %1$s or %2$s or %3$s before %0$s"));
|
|
CASE (5, _("expected %1$s or %2$s or %4$s or %5$s before %0$s"));
|
|
# undef CASE
|
|
}
|
|
location_print (loc, stderr);
|
|
fputs (": ", stderr);
|
|
severity_print (severity_error, stderr);
|
|
|
|
while (*format)
|
|
if (format[0] == '%'
|
|
&& isdigit (format[1])
|
|
&& format[2] == '$'
|
|
&& format[3] == 's'
|
|
&& (format[1] - '0') < argc)
|
|
{
|
|
int i = format[1] - '0';
|
|
const char *style = i == 0 ? "unexpected" : "expected";
|
|
begin_use_class (style, stderr);
|
|
fputs (argv[i], stderr);
|
|
end_use_class (style, stderr);
|
|
format += 4;
|
|
}
|
|
else
|
|
{
|
|
fputc (*format, stderr);
|
|
++format;
|
|
}
|
|
fputc ('\n', stderr);
|
|
location_caret (loc, "error", stderr);
|
|
}
|