diagnostics: modernize bison's syntax errors

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.
This commit is contained in:
Akim Demaille
2020-01-22 23:24:20 +01:00
parent 6fb362c87a
commit fc2191f137
5 changed files with 88 additions and 68 deletions

View File

@@ -23,6 +23,7 @@
#include "system.h" #include "system.h"
#include <argmatch.h> #include <argmatch.h>
#include <ctype.h>
#include <progname.h> #include <progname.h>
#include <stdarg.h> #include <stdarg.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -263,6 +264,20 @@ severity_prefix (severity s)
} }
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. | | complain. |
`-----------*/ `-----------*/
@@ -442,16 +457,7 @@ error_message (const location *loc, int *indent, warnings flags,
fprintf (stderr, "%*s", *indent - pos, ""); fprintf (stderr, "%*s", *indent - pos, "");
} }
const char* style = severity_style (sever); severity_print (sever, stderr);
if (sever != severity_disabled)
{
begin_use_class (style, stderr);
fprintf (stderr, "%s:", severity_prefix (sever));
end_use_class (style, stderr);
fputc (' ', stderr);
}
vfprintf (stderr, message, args); vfprintf (stderr, message, args);
/* Print the type of warning, only if this is not a sub message /* Print the type of warning, only if this is not a sub message
(in which case the prefix is null). */ (in which case the prefix is null). */
@@ -465,7 +471,7 @@ error_message (const location *loc, int *indent, warnings flags,
putc ('\n', stderr); putc ('\n', stderr);
flush (stderr); flush (stderr);
if (loc && !(flags & no_caret)) if (loc && !(flags & no_caret))
location_caret (*loc, style, stderr); location_caret (*loc, severity_style (sever), stderr);
} }
} }
flush (stderr); flush (stderr);
@@ -587,3 +593,53 @@ duplicate_rule_directive (char const *directive,
_("previous declaration")); _("previous declaration"));
fixits_register (&second, ""); 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);
}

View File

@@ -161,6 +161,11 @@ void duplicate_directive (char const *directive,
void duplicate_rule_directive (char const *directive, void duplicate_rule_directive (char const *directive,
location first, location second); location first, location second);
/** Report a syntax error, where argv[0] is the unexpected
token, and argv[1...argc] are the expected ones. */
void syntax_error (location loc,
int argc, const char* argv[]);
/** Warnings treated as errors shouldn't stop the execution as regular /** Warnings treated as errors shouldn't stop the execution as regular
errors should (because due to their nature, it is safe to go errors should (because due to their nature, it is safe to go
on). Thus, there are three possible execution statuses. */ on). Thus, there are three possible execution statuses. */

View File

@@ -804,58 +804,17 @@ epilogue.opt:
int int
yyreport_syntax_error (const yyparse_context_t *ctx) yyreport_syntax_error (const yyparse_context_t *ctx)
{ {
if (complaint_status < status_complaint)
complaint_status = status_complaint;
enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
/* Internationalized format string. */
const char *format = YY_NULLPTR;
/* Arguments of format: reported tokens (one for the "unexpected", /* Arguments of format: reported tokens (one for the "unexpected",
one per "expected"). */ one per "expected"). */
int arg[YYERROR_VERBOSE_ARGS_MAXIMUM]; int arg[YYERROR_VERBOSE_ARGS_MAXIMUM];
int n = yysyntax_error_arguments (ctx, arg, YYERROR_VERBOSE_ARGS_MAXIMUM); int n = yysyntax_error_arguments (ctx, arg, YYERROR_VERBOSE_ARGS_MAXIMUM);
switch (n) if (n == -2)
{ return 2;
case -2: const char *argv[YYERROR_VERBOSE_ARGS_MAXIMUM];
return 2; for (int i = 0; i < n; ++i)
# define YYCASE_(N, S) \ argv[i] = yysymbol_name (arg[i]);
case N: \ syntax_error (*yyparse_context_location (ctx), n, argv);
format = S; \
break
default: /* Avoid compiler warnings. */
YYCASE_(0, YY_("syntax error"));
YYCASE_(1, YY_("syntax error, unexpected %s"));
YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
# undef YYCASE_
}
location_print (*yyparse_context_location (ctx), stderr);
fputs (": ", stderr);
begin_use_class ("error", stderr);
fputs ("error:", stderr);
end_use_class ("error", stderr);
fputc (' ', stderr);
{
int i = 0;
while (*format)
if (format[0] == '%' && format[1] == 's' && i < n)
{
const char *style = i == 0 ? "unexpected" : "expected";
begin_use_class (style, stderr);
fputs (yysymbol_name (arg[i]), stderr);
end_use_class (style, stderr);
format += 2;
++i;
}
else
{
fputc (*format, stderr);
++format;
}
}
fputc ('\n', stderr);
location_caret (*yyparse_context_location (ctx), "error", stderr);
return 0; return 0;
} }

View File

@@ -273,7 +273,7 @@ AT_TEST([[Carriage return]],
[[input.y:10.8-11.0: <error>error:</error> missing '"' at end of line [[input.y:10.8-11.0: <error>error:</error> missing '"' at end of line
10 | %token <error>"</error> 10 | %token <error>"</error>
| <error>^</error> | <error>^</error>
input.y:10.8-11.0: <error>error:</error> syntax error, unexpected <unexpected>string</unexpected>, expecting <expected>character literal</expected> or <expected>identifier</expected> or <expected><tag></expected> input.y:10.8-11.0: <error>error:</error> expected <expected>character literal</expected> or <expected>identifier</expected> or <expected><tag></expected> before <unexpected>string</unexpected>
10 | %token <error>"</error> 10 | %token <error>"</error>
| <error>^</error> | <error>^</error>
]]) ]])

View File

@@ -102,7 +102,7 @@ input.y:6.1-17: error: invalid directive: '%a-does-not-exist'
input.y:7.1: error: invalid character: '%' input.y:7.1: error: invalid character: '%'
input.y:7.2: error: invalid character: '-' input.y:7.2: error: invalid character: '-'
input.y:8.1-9.0: error: missing '%}' at end of file input.y:8.1-9.0: error: missing '%}' at end of file
input.y:8.1-9.0: error: syntax error, unexpected %{...%} input.y:8.1-9.0: error: unexpected %{...%}
]]) ]])
AT_CLEANUP AT_CLEANUP
@@ -124,7 +124,7 @@ AT_DATA([input.y],
]]) ]])
AT_BISON_CHECK([input.y], [1], [], AT_BISON_CHECK([input.y], [1], [],
[[input.y:3.1-15: error: syntax error, unexpected %initial-action, expecting {...} [[input.y:3.1-15: error: expected {...} before %initial-action
]]) ]])
AT_CLEANUP AT_CLEANUP
@@ -281,16 +281,16 @@ input.y:3.17-24: error: nonterminals cannot be given a string alias
input.y:4.8-10: error: character literals cannot be nonterminals input.y:4.8-10: error: character literals cannot be nonterminals
4 | %nterm '+' '*'; 4 | %nterm '+' '*';
| ^~~ | ^~~
input.y:5.8-15: error: syntax error, unexpected string, expecting character literal or identifier or <tag> input.y:5.8-15: error: expected character literal or identifier or <tag> before string
5 | %nterm "number"; 5 | %nterm "number";
| ^~~~~~~~ | ^~~~~~~~
input.y:6.8-13: error: syntax error, unexpected string, expecting character literal or identifier or <tag> input.y:6.8-13: error: expected character literal or identifier or <tag> before string
6 | %token "tok1" 1; 6 | %token "tok1" 1;
| ^~~~~~ | ^~~~~~
input.y:7.14: error: syntax error, unexpected integer literal input.y:7.14: error: unexpected integer literal
7 | %left "tok2" 2; 7 | %left "tok2" 2;
| ^ | ^
input.y:8.14: error: syntax error, unexpected integer literal input.y:8.14: error: unexpected integer literal
8 | %type "tok3" 3; 8 | %type "tok3" 3;
| ^ | ^
]]) ]])
@@ -1277,7 +1277,7 @@ AT_SETUP([Torturing the Scanner])
AT_BISON_OPTION_PUSHDEFS AT_BISON_OPTION_PUSHDEFS
AT_DATA([input.y], []) AT_DATA([input.y], [])
AT_BISON_CHECK([input.y], [1], [], AT_BISON_CHECK([input.y], [1], [],
[[input.y:1.1: error: syntax error, unexpected end of file [[input.y:1.1: error: unexpected end of file
]]) ]])
@@ -1285,7 +1285,7 @@ AT_DATA([input.y],
[{} [{}
]) ])
AT_BISON_CHECK([-fcaret input.y], [1], [], AT_BISON_CHECK([-fcaret input.y], [1], [],
[[input.y:1.1-2: error: syntax error, unexpected {...} [[input.y:1.1-2: error: unexpected {...}
1 | {} 1 | {}
| ^~ | ^~
]]) ]])
@@ -1655,7 +1655,7 @@ input.y:16.11-17.0: error: missing '"' at end of line
input.y:19.13-20.0: error: missing '}' at end of file input.y:19.13-20.0: error: missing '}' at end of file
19 | %destructor { free ($$) 19 | %destructor { free ($$)
| ^~~~~~~~~~~ | ^~~~~~~~~~~
input.y:20.1: error: syntax error, unexpected end of file input.y:20.1: error: unexpected end of file
]]) ]])
AT_CLEANUP AT_CLEANUP