]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
parser_bison: on syntax errors, output expected tokens
authorJan Kończak <jan.konczak@cs.put.poznan.pl>
Thu, 4 Dec 2025 21:54:48 +0000 (22:54 +0100)
committerFlorian Westphal <fw@strlen.de>
Tue, 20 Jan 2026 19:01:30 +0000 (20:01 +0100)
Now, on syntax errors, e.g., 'nft create fable filter', the user sees:
 Error: syntax error, unexpected string
 create fable filter
          ^^^^^
The patch builds an error message that lists what the parser expects
to see, in that case it would print:
 Error: syntax error, unexpected string
 expected any of: synproxy, table, chain, set, element, map,
 flowtable, ct, counter, limit, quota, secmark
 create fable filter
        ^^^^^
The obvious purpose of this is to help people who learn nft syntax.

The messages are still not as explanatory as one wishes, for it may
list parser token names such as 'string', but it's still better than
no hints at all.

Heed that the list of possible items on the parser's side is not
always consistent with expectations.

For instance, lexer/parser recognizes 'l4proto' in this command:
nft add rule ip F I meta l4proto tcp
as a generic '%token <string> STRING', while 'iifname' in
   nft add rule ip F I meta iifname eth0

is recognized as a '%token IIFNAME'

In such case the parser is only able to say that right after 'meta'
it expects 'iifname' or 'string', rather than 'iifname' and 'l4proto'.

This 'meta STRING' is a historic wart and can be resolved in
a followup patch.

[ fw@strlen.de: minor coding style changes and rewordings ]

Signed-off-by: Jan Kończak <jan.konczak@cs.put.poznan.pl>
Signed-off-by: Florian Westphal <fw@strlen.de>
src/parser_bison.y

index 18174859434c82435386a01dfdab03f28a693ca6..33e2e3eaea73440aa59b98fedccbe4a35bf9b0db 100644 (file)
@@ -221,7 +221,8 @@ int nft_lex(void *, void *, void *);
 %parse-param           { void *scanner }
 %parse-param           { struct parser_state *state }
 %lex-param             { scanner }
-%define parse.error verbose
+%define parse.error custom
+%define parse.lac full
 %locations
 
 %initial-action {
@@ -6529,3 +6530,59 @@ exthdr_key               :       HBH     close_scope_hbh { $$ = IPPROTO_HOPOPTS; }
                        ;
 
 %%
+
+static int
+yyreport_syntax_error(const yypcontext_t *yyctx, struct nft_ctx *nft,
+                      void *scanner, struct parser_state *state)
+{
+       const char *bad_token = yysymbol_name(yypcontext_token(yyctx));
+       struct location *loc = yypcontext_location(yyctx);
+       yysymbol_kind_t *exp_tokens;
+       int exp_tokens_cnt;
+       size_t errbufsz;
+       FILE *errfp;
+       char *msg;
+
+       errfp = open_memstream(&msg, &errbufsz);
+       if (!errfp)
+               memory_allocation_error();
+
+       exp_tokens_cnt = yypcontext_expected_tokens(yyctx, NULL, 0);
+       exp_tokens = xmalloc_array(exp_tokens_cnt, sizeof(yysymbol_kind_t));
+       yypcontext_expected_tokens(yyctx, exp_tokens, exp_tokens_cnt);
+
+       fprintf(errfp, "syntax error, unexpected %s\nexpected any of: ", bad_token);
+
+       for (int i = 0; i < exp_tokens_cnt; i++) {
+               const char *token_name = yysymbol_name(exp_tokens[i]);
+               bool is_keyword = true;
+
+               /* tokens that name generic things shall be printed as <foo>; detect them */
+               switch (exp_tokens[i]) {
+               case YYSYMBOL_NUM:
+               case YYSYMBOL_STRING:
+               case YYSYMBOL_QUOTED_STRING:
+               case YYSYMBOL_ASTERISK_STRING:
+                       is_keyword = false;
+                       break;
+               default:
+                       break;
+               }
+
+               if (i > 0)
+                       fputs(", ", errfp);
+               if (!is_keyword)
+                       fputc('<', errfp);
+               fputs(token_name, errfp);
+               if (!is_keyword)
+                       fputc('>', errfp);
+       }
+
+       free(exp_tokens);
+       fclose(errfp);
+       /* no newline on the end of the error message; this is intended */
+       yyerror(loc, nft, scanner, state, msg);
+
+       free(msg);
+       return 0;
+}