Linux Netfilter development
 help / color / mirror / Atom feed
From: "Jan Kończak" <jan.konczak@cs.put.poznan.pl>
To: netfilter-devel@vger.kernel.org
Subject: [PATCH nft] parser_bison: on syntax errors, output expected tokens
Date: Thu, 04 Dec 2025 22:54:48 +0100	[thread overview]
Message-ID: <1950751.CQOukoFCf9@imladris> (raw)

Now, on syntax erros, 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'.

(Notice that the help which is already present in nft is also off,
e.g., 'nft add rule ip F I meta bogus bogus' constructs in meta.c
a list of all possible keywords that does not list all possible
keywords, for 'ibriport' gets recognized, but is not listed there.)


Signed-off-by: Jan Kończak <jan.konczak@cs.put.poznan.pl>
---
 src/parser_bison.y | 68 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 67 insertions(+), 1 deletion(-)

diff --git a/src/parser_bison.y b/src/parser_bison.y
index 3ceef794..55e95028 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -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 {
@@ -6603,3 +6604,68 @@ 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)
+{
+	struct location *loc = yypcontext_location(yyctx);
+	const char *badTok = yysymbol_name(yypcontext_token(yyctx));
+
+	char * msg;
+	int len = 1024;
+	int pos;
+	const char * const sep = ", ";
+
+	// get expected tokens
+	int expTokCnt = yypcontext_expected_tokens(yyctx, NULL, 0);
+	yysymbol_kind_t *expTokArr = malloc(sizeof(yysymbol_kind_t) * expTokCnt);
+	if (!expTokArr) return YYENOMEM;
+	yypcontext_expected_tokens(yyctx, expTokArr, expTokCnt);
+
+	// reserve space for the error message
+	msg = malloc(len);
+	if (!msg) { free(expTokArr); return YYENOMEM; }
+
+	// start building up the error message
+	pos = snprintf(msg, len, "syntax error, unexpected %s\n"
+	                         "expected any of: ", badTok);
+
+	// append expected tokens to the error message
+	for (int i = 0; i < expTokCnt; ++i) {
+		yysymbol_kind_t expTokKind = expTokArr[i];
+		const char * expTokName = yysymbol_name(expTokKind);
+
+		// tokens that name generic things shall be printed as <foo>; detect them
+		int isNotAKeyword = 0;
+		switch( expTokKind ){
+			case YYSYMBOL_NUM:      case YYSYMBOL_QUOTED_STRING:
+			case YYSYMBOL_STRING:   case YYSYMBOL_ASTERISK_STRING:
+				isNotAKeyword = 1;
+			default:
+		}
+
+		if ((size_t)len-pos-1 < strlen(expTokName)+strlen(sep)+isNotAKeyword*2) {
+			// need more space for the error message to fit all expected tokens
+			char * newMsg;
+			len += 1024;
+			newMsg = realloc(msg, len);
+			if (!newMsg) { free(msg); free(expTokArr); return YYENOMEM; }
+			msg = newMsg;
+		}
+
+		pos += snprintf(msg+pos, len-pos, "%s%s%s%s",
+		                                  (i ? sep : ""),
+		                                  (isNotAKeyword ? "<" : ""),
+		                                  expTokName,
+		                                  (isNotAKeyword ? ">" : ""));
+	}
+	free(expTokArr);
+	// notice no newline on the end of the error message; this is intended
+
+	// call the standard error function
+	yyerror(loc, nft, scanner, state, msg);
+
+	free(msg);
+	return 0;
+}
--



             reply	other threads:[~2025-12-04 21:54 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-04 21:54 Jan Kończak [this message]
2025-12-04 22:37 ` [PATCH nft] parser_bison: on syntax errors, output expected tokens Florian Westphal
2025-12-13 12:32   ` Jan Kończak
2026-01-16 13:07 ` Florian Westphal

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1950751.CQOukoFCf9@imladris \
    --to=jan.konczak@cs.put.poznan.pl \
    --cc=netfilter-devel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox