2 * Command format string parser for CLI backend.
5 * Copyright (C) 2016 Cumulus Networks, Inc.
7 * This file is part of GNU Zebra.
9 * GNU Zebra is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2, or (at your option) any
14 * GNU Zebra is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with GNU Zebra; see the file COPYING. If not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
26 // compile with debugging facilities
31 /* define parse.error verbose */
33 /* define api.prefix {cmd_yy} */
35 /* names for generated header and parser files */
36 %defines "lib/command_parse.h"
37 %output "lib/command_parse.c"
39 /* note: code blocks are output in order, to both .c and .h:
41 * 2. %union + bison forward decls
43 * command_lex.h needs to be included at 3.; it needs the union and YYSTYPE.
44 * struct parser_ctx is needed for the bison forward decls.
54 #include "command_graph.h"
59 #define YYSTYPE CMD_YYSTYPE
60 #define YYLTYPE CMD_YYLTYPE
63 /* subgraph semantic value */
65 struct graph_node *start, *end;
72 struct graph_node *node;
73 struct subgraph subgraph;
78 #include "lib/command_lex.h"
81 extern void set_lexer_string (yyscan_t *scn, const char *string);
82 extern void cleanup_lexer (yyscan_t *scn);
87 const struct cmd_element *el;
90 struct graph_node *currnode;
92 /* pointers to copy of command docstring */
93 char *docstr_start, *docstr;
97 /* union types for lexed tokens */
100 %token <string> IPV4_PREFIX
102 %token <string> IPV6_PREFIX
103 %token <string> VARIABLE
104 %token <string> RANGE
106 %token <string> MAC_PREFIX
108 /* special syntax, value is irrelevant */
109 %token <string> EXCL_BRACKET
111 /* union types for parsed rules */
113 %type <node> literal_token
114 %type <node> placeholder_token
115 %type <node> placeholder_token_real
116 %type <node> simple_token
117 %type <subgraph> selector
118 %type <subgraph> selector_token
119 %type <subgraph> selector_token_seq
120 %type <subgraph> selector_seq_seq
122 %type <string> varname_token
126 /* bison declarations */
128 cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg);
130 /* helper functions for parser */
132 doc_next (struct parser_ctx *ctx);
134 static struct graph_node *
135 new_token_node (struct parser_ctx *,
136 enum cmd_token_type type,
141 terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx,
142 struct graph_node *);
145 cleanup (struct parser_ctx *ctx);
147 static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg);
149 #define scanner ctx->scanner
152 /* yyparse parameters */
153 %lex-param {yyscan_t scanner}
154 %parse-param {struct parser_ctx *ctx}
156 /* called automatically before yyparse */
158 /* clear state pointers */
159 ctx->currnode = vector_slot (ctx->graph->nodes, 0);
161 /* copy docstring and keep a pointer to the copy */
164 // allocate a new buffer, making room for a flag
165 size_t length = (size_t) strlen (ctx->el->doc) + 2;
166 ctx->docstr = malloc (length);
167 memcpy (ctx->docstr, ctx->el->doc, strlen (ctx->el->doc));
168 // set the flag so doc_next knows when to print a warning
169 ctx->docstr[length - 2] = 0x03;
171 ctx->docstr[length - 1] = 0x00;
173 ctx->docstr_start = ctx->docstr;
181 // tack on the command element
182 terminate_graph (&@1, ctx, ctx->currnode);
184 | cmd_token_seq placeholder_token '.' '.' '.'
186 if ((ctx->currnode = graph_add_edge (ctx->currnode, $2)) != $2)
187 graph_delete_node (ctx->graph, $2);
189 ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1;
191 // adding a node as a child of itself accepts any number
192 // of the same token, which is what we want for variadics
193 graph_add_edge (ctx->currnode, ctx->currnode);
195 // tack on the command element
196 terminate_graph (&@1, ctx, ctx->currnode);
200 varname_token: '$' WORD
212 | cmd_token_seq cmd_token
218 if ((ctx->currnode = graph_add_edge (ctx->currnode, $1)) != $1)
219 graph_delete_node (ctx->graph, $1);
220 cmd_token_varname_seqappend($1);
224 graph_add_edge (ctx->currnode, $1.start);
225 cmd_token_varname_seqappend($1.start);
226 ctx->currnode = $1.end;
235 literal_token: WORD varname_token
237 $$ = new_token_node (ctx, WORD_TKN, $1, doc_next(ctx));
238 cmd_token_varname_set ($$->data, $2);
239 XFREE (MTYPE_LEX, $2);
240 XFREE (MTYPE_LEX, $1);
244 placeholder_token_real:
247 $$ = new_token_node (ctx, IPV4_TKN, $1, doc_next(ctx));
248 XFREE (MTYPE_LEX, $1);
252 $$ = new_token_node (ctx, IPV4_PREFIX_TKN, $1, doc_next(ctx));
253 XFREE (MTYPE_LEX, $1);
257 $$ = new_token_node (ctx, IPV6_TKN, $1, doc_next(ctx));
258 XFREE (MTYPE_LEX, $1);
262 $$ = new_token_node (ctx, IPV6_PREFIX_TKN, $1, doc_next(ctx));
263 XFREE (MTYPE_LEX, $1);
267 $$ = new_token_node (ctx, VARIABLE_TKN, $1, doc_next(ctx));
268 XFREE (MTYPE_LEX, $1);
272 $$ = new_token_node (ctx, RANGE_TKN, $1, doc_next(ctx));
273 struct cmd_token *token = $$->data;
275 // get the numbers out
277 token->min = strtoll (yylval.string, &yylval.string, 10);
278 strsep (&yylval.string, "-");
279 token->max = strtoll (yylval.string, &yylval.string, 10);
282 if (token->min > token->max) cmd_yyerror (&@1, ctx, "Invalid range.");
284 XFREE (MTYPE_LEX, $1);
288 $$ = new_token_node (ctx, MAC_TKN, $1, doc_next(ctx));
289 XFREE (MTYPE_LEX, $1);
293 $$ = new_token_node (ctx, MAC_PREFIX_TKN, $1, doc_next(ctx));
294 XFREE (MTYPE_LEX, $1);
298 placeholder_token_real varname_token
301 cmd_token_varname_set ($$->data, $2);
302 XFREE (MTYPE_LEX, $2);
306 /* <selector|set> productions */
307 selector: '<' selector_seq_seq '>' varname_token
310 cmd_token_varname_join ($2.end, $4);
311 XFREE (MTYPE_LEX, $4);
315 selector_seq_seq '|' selector_token_seq
318 graph_add_edge ($$.start, $3.start);
319 graph_add_edge ($3.end, $$.end);
323 $$.start = new_token_node (ctx, FORK_TKN, NULL, NULL);
324 $$.end = new_token_node (ctx, JOIN_TKN, NULL, NULL);
325 ((struct cmd_token *)$$.start->data)->forkjoin = $$.end;
326 ((struct cmd_token *)$$.end->data)->forkjoin = $$.start;
328 graph_add_edge ($$.start, $1.start);
329 graph_add_edge ($1.end, $$.end);
333 /* {keyword} productions */
334 selector: '{' selector_seq_seq '}' varname_token
337 graph_add_edge ($$.end, $$.start);
338 /* there is intentionally no start->end link, for two reasons:
339 * 1) this allows "at least 1 of" semantics, which are otherwise impossible
340 * 2) this would add a start->end->start loop in the graph that the current
341 * loop-avoidal fails to handle
342 * just use [{a|b}] if necessary, that will work perfectly fine, and reason
343 * #1 is good enough to keep it this way. */
346 cmd_token_varname_join ($2.end, $4);
347 XFREE (MTYPE_LEX, $4);
354 $$.start = $$.end = $1;
360 selector_token_seq selector_token
362 graph_add_edge ($1.end, $2.start);
363 cmd_token_varname_seqappend($2.start);
370 /* [option] productions */
371 selector: '[' selector_seq_seq ']' varname_token
374 graph_add_edge ($$.start, $$.end);
375 cmd_token_varname_join ($2.end, $4);
376 XFREE (MTYPE_LEX, $4);
380 /* ![option] productions */
381 selector: EXCL_BRACKET selector_seq_seq ']' varname_token
383 struct graph_node *neg_only = new_token_node (ctx, NEG_ONLY_TKN, NULL, NULL);
386 graph_add_edge ($$.start, neg_only);
387 graph_add_edge (neg_only, $$.end);
388 cmd_token_varname_join ($2.end, $4);
389 XFREE (MTYPE_LEX, $4);
397 DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)");
400 cmd_graph_parse (struct graph *graph, const struct cmd_element *cmd)
402 struct parser_ctx ctx = { .graph = graph, .el = cmd };
404 // set to 1 to enable parser traces
407 set_lexer_string (&ctx.scanner, cmd->string);
409 // parse command into DFA
413 cleanup_lexer (&ctx.scanner);
419 /* parser helper functions */
421 static bool loopcheck_inner(struct graph_node *start, struct graph_node *node,
422 struct graph_node *end, size_t depth)
431 for (i = 0; i < vector_active(node->to); i++) {
432 struct graph_node *next = vector_slot(node->to, i);
433 struct cmd_token *tok = next->data;
435 if (next == end || next == start)
437 if (tok->type < SPECIAL_TKN)
439 ret = loopcheck_inner(start, next, end, depth);
446 static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg)
448 if (loopcheck_inner(sg->start, sg->start, sg->end, 0))
449 zlog_err("FATAL: '%s': {} contains an empty path! Use [{...}]",
454 yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg)
456 char *tmpstr = strdup(ctx->el->string);
461 zlog_notice ("%s: FATAL parse error: %s", __func__, msg);
462 zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column);
467 eol = strchr(line, '\n');
471 zlog_notice ("%s: | %s", __func__, line);
472 if (lineno == loc->first_line && lineno == loc->last_line
473 && loc->first_column < (int)sizeof(spacing) - 1
474 && loc->last_column < (int)sizeof(spacing) - 1) {
476 int len = loc->last_column - loc->first_column;
480 memset(spacing, ' ', loc->first_column - 1);
481 memset(spacing + loc->first_column - 1, '^', len);
482 spacing[loc->first_column - 1 + len] = '\0';
483 zlog_notice ("%s: | %s", __func__, spacing);
485 } while ((line = eol));
490 cleanup (struct parser_ctx *ctx)
493 free (ctx->docstr_start);
495 /* clear state pointers */
496 ctx->currnode = NULL;
497 ctx->docstr_start = ctx->docstr = NULL;
501 terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx,
502 struct graph_node *finalnode)
504 // end of graph should look like this
505 // * -> finalnode -> END_TKN -> cmd_element
506 const struct cmd_element *element = ctx->el;
507 struct graph_node *end_token_node =
508 new_token_node (ctx, END_TKN, CMD_CR_TEXT, "");
509 struct graph_node *end_element_node =
510 graph_new_node (ctx->graph, (void *)element, NULL);
512 if (ctx->docstr && strlen (ctx->docstr) > 1) {
513 zlog_err ("Excessive docstring while parsing '%s'", ctx->el->string);
514 zlog_err ("----------");
515 while (ctx->docstr && ctx->docstr[1] != '\0')
516 zlog_err ("%s", strsep(&ctx->docstr, "\n"));
517 zlog_err ("----------");
520 graph_add_edge (finalnode, end_token_node);
521 graph_add_edge (end_token_node, end_element_node);
525 doc_next (struct parser_ctx *ctx)
527 const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : "";
530 zlog_err ("Ran out of docstring while parsing '%s'", ctx->el->string);
537 static struct graph_node *
538 new_token_node (struct parser_ctx *ctx, enum cmd_token_type type,
539 const char *text, const char *doc)
541 struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc);
542 return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del);