]> git.proxmox.com Git - mirror_frr.git/blob - lib/command_parse.y
Merge pull request #9558 from LabNConsulting/ziemba/doc-cli-new-node
[mirror_frr.git] / lib / command_parse.y
1 /*
2 * Command format string parser for CLI backend.
3 *
4 * --
5 * Copyright (C) 2016 Cumulus Networks, Inc.
6 *
7 * This file is part of GNU Zebra.
8 *
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
12 * later version.
13 *
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.
18 *
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
22 * 02111-1307, USA.
23 */
24
25 %{
26 // compile with debugging facilities
27 #define YYDEBUG 1
28 %}
29
30 %locations
31 /* define parse.error verbose */
32 %define api.pure full
33 /* define api.prefix {cmd_yy} */
34
35 /* names for generated header and parser files */
36 %defines "lib/command_parse.h"
37 %output "lib/command_parse.c"
38
39 /* note: code blocks are output in order, to both .c and .h:
40 * 1. %code requires
41 * 2. %union + bison forward decls
42 * 3. %code provides
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.
45 */
46 %code requires {
47 #include "config.h"
48
49 #include <stdbool.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <ctype.h>
53
54 #include "command_graph.h"
55 #include "log.h"
56
57 DECLARE_MTYPE(LEX);
58
59 #define YYSTYPE CMD_YYSTYPE
60 #define YYLTYPE CMD_YYLTYPE
61 struct parser_ctx;
62
63 /* subgraph semantic value */
64 struct subgraph {
65 struct graph_node *start, *end;
66 };
67 }
68
69 %union {
70 long long number;
71 char *string;
72 struct graph_node *node;
73 struct subgraph subgraph;
74 }
75
76 %code provides {
77 #ifndef FLEX_SCANNER
78 #include "lib/command_lex.h"
79 #endif
80
81 extern void set_lexer_string (yyscan_t *scn, const char *string);
82 extern void cleanup_lexer (yyscan_t *scn);
83
84 struct parser_ctx {
85 yyscan_t scanner;
86
87 const struct cmd_element *el;
88
89 struct graph *graph;
90 struct graph_node *currnode;
91
92 /* pointers to copy of command docstring */
93 char *docstr_start, *docstr;
94 };
95 }
96
97 /* union types for lexed tokens */
98 %token <string> WORD
99 %token <string> IPV4
100 %token <string> IPV4_PREFIX
101 %token <string> IPV6
102 %token <string> IPV6_PREFIX
103 %token <string> VARIABLE
104 %token <string> RANGE
105 %token <string> MAC
106 %token <string> MAC_PREFIX
107
108 /* special syntax, value is irrelevant */
109 %token <string> EXCL_BRACKET
110
111 /* union types for parsed rules */
112 %type <node> start
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
121
122 %type <string> varname_token
123
124 %code {
125
126 /* bison declarations */
127 void
128 cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg);
129
130 /* helper functions for parser */
131 static const char *
132 doc_next (struct parser_ctx *ctx);
133
134 static struct graph_node *
135 new_token_node (struct parser_ctx *,
136 enum cmd_token_type type,
137 const char *text,
138 const char *doc);
139
140 static void
141 terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx,
142 struct graph_node *);
143
144 static void
145 cleanup (struct parser_ctx *ctx);
146
147 static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg);
148
149 #define scanner ctx->scanner
150 }
151
152 /* yyparse parameters */
153 %lex-param {yyscan_t scanner}
154 %parse-param {struct parser_ctx *ctx}
155
156 /* called automatically before yyparse */
157 %initial-action {
158 /* clear state pointers */
159 ctx->currnode = vector_slot (ctx->graph->nodes, 0);
160
161 /* copy docstring and keep a pointer to the copy */
162 if (ctx->el->doc)
163 {
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;
170 // null terminate
171 ctx->docstr[length - 1] = 0x00;
172 }
173 ctx->docstr_start = ctx->docstr;
174 }
175
176 %%
177
178 start:
179 cmd_token_seq
180 {
181 // tack on the command element
182 terminate_graph (&@1, ctx, ctx->currnode);
183 }
184 | cmd_token_seq placeholder_token '.' '.' '.'
185 {
186 if ((ctx->currnode = graph_add_edge (ctx->currnode, $2)) != $2)
187 graph_delete_node (ctx->graph, $2);
188
189 ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1;
190
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);
194
195 // tack on the command element
196 terminate_graph (&@1, ctx, ctx->currnode);
197 }
198 ;
199
200 varname_token: '$' WORD
201 {
202 $$ = $2;
203 }
204 | /* empty */
205 {
206 $$ = NULL;
207 }
208 ;
209
210 cmd_token_seq:
211 /* empty */
212 | cmd_token_seq cmd_token
213 ;
214
215 cmd_token:
216 simple_token
217 {
218 if ((ctx->currnode = graph_add_edge (ctx->currnode, $1)) != $1)
219 graph_delete_node (ctx->graph, $1);
220 }
221 | selector
222 {
223 graph_add_edge (ctx->currnode, $1.start);
224 ctx->currnode = $1.end;
225 }
226 ;
227
228 simple_token:
229 literal_token
230 | placeholder_token
231 ;
232
233 literal_token: WORD varname_token
234 {
235 $$ = new_token_node (ctx, WORD_TKN, $1, doc_next(ctx));
236 cmd_token_varname_set ($$->data, $2);
237 XFREE (MTYPE_LEX, $2);
238 XFREE (MTYPE_LEX, $1);
239 }
240 ;
241
242 placeholder_token_real:
243 IPV4
244 {
245 $$ = new_token_node (ctx, IPV4_TKN, $1, doc_next(ctx));
246 XFREE (MTYPE_LEX, $1);
247 }
248 | IPV4_PREFIX
249 {
250 $$ = new_token_node (ctx, IPV4_PREFIX_TKN, $1, doc_next(ctx));
251 XFREE (MTYPE_LEX, $1);
252 }
253 | IPV6
254 {
255 $$ = new_token_node (ctx, IPV6_TKN, $1, doc_next(ctx));
256 XFREE (MTYPE_LEX, $1);
257 }
258 | IPV6_PREFIX
259 {
260 $$ = new_token_node (ctx, IPV6_PREFIX_TKN, $1, doc_next(ctx));
261 XFREE (MTYPE_LEX, $1);
262 }
263 | VARIABLE
264 {
265 $$ = new_token_node (ctx, VARIABLE_TKN, $1, doc_next(ctx));
266 XFREE (MTYPE_LEX, $1);
267 }
268 | RANGE
269 {
270 $$ = new_token_node (ctx, RANGE_TKN, $1, doc_next(ctx));
271 struct cmd_token *token = $$->data;
272
273 // get the numbers out
274 yylval.string++;
275 token->min = strtoll (yylval.string, &yylval.string, 10);
276 strsep (&yylval.string, "-");
277 token->max = strtoll (yylval.string, &yylval.string, 10);
278
279 // validate range
280 if (token->min > token->max) cmd_yyerror (&@1, ctx, "Invalid range.");
281
282 XFREE (MTYPE_LEX, $1);
283 }
284 | MAC
285 {
286 $$ = new_token_node (ctx, MAC_TKN, $1, doc_next(ctx));
287 XFREE (MTYPE_LEX, $1);
288 }
289 | MAC_PREFIX
290 {
291 $$ = new_token_node (ctx, MAC_PREFIX_TKN, $1, doc_next(ctx));
292 XFREE (MTYPE_LEX, $1);
293 }
294
295 placeholder_token:
296 placeholder_token_real varname_token
297 {
298 struct cmd_token *token = $$->data;
299 $$ = $1;
300 cmd_token_varname_set (token, $2);
301 XFREE (MTYPE_LEX, $2);
302 };
303
304
305 /* <selector|set> productions */
306 selector: '<' selector_seq_seq '>' varname_token
307 {
308 $$ = $2;
309 cmd_token_varname_set ($2.end->data, $4);
310 XFREE (MTYPE_LEX, $4);
311 };
312
313 selector_seq_seq:
314 selector_seq_seq '|' selector_token_seq
315 {
316 $$ = $1;
317 graph_add_edge ($$.start, $3.start);
318 graph_add_edge ($3.end, $$.end);
319 }
320 | selector_token_seq
321 {
322 $$.start = new_token_node (ctx, FORK_TKN, NULL, NULL);
323 $$.end = new_token_node (ctx, JOIN_TKN, NULL, NULL);
324 ((struct cmd_token *)$$.start->data)->forkjoin = $$.end;
325 ((struct cmd_token *)$$.end->data)->forkjoin = $$.start;
326
327 graph_add_edge ($$.start, $1.start);
328 graph_add_edge ($1.end, $$.end);
329 }
330 ;
331
332 /* {keyword} productions */
333 selector: '{' selector_seq_seq '}' varname_token
334 {
335 $$ = $2;
336 graph_add_edge ($$.end, $$.start);
337 /* there is intentionally no start->end link, for two reasons:
338 * 1) this allows "at least 1 of" semantics, which are otherwise impossible
339 * 2) this would add a start->end->start loop in the graph that the current
340 * loop-avoidal fails to handle
341 * just use [{a|b}] if neccessary, that will work perfectly fine, and reason
342 * #1 is good enough to keep it this way. */
343
344 loopcheck(ctx, &$$);
345 cmd_token_varname_set ($2.end->data, $4);
346 XFREE (MTYPE_LEX, $4);
347 };
348
349
350 selector_token:
351 simple_token
352 {
353 $$.start = $$.end = $1;
354 }
355 | selector
356 ;
357
358 selector_token_seq:
359 selector_token_seq selector_token
360 {
361 graph_add_edge ($1.end, $2.start);
362 $$.start = $1.start;
363 $$.end = $2.end;
364 }
365 | selector_token
366 ;
367
368 /* [option] productions */
369 selector: '[' selector_seq_seq ']' varname_token
370 {
371 $$ = $2;
372 graph_add_edge ($$.start, $$.end);
373 cmd_token_varname_set ($2.end->data, $4);
374 XFREE (MTYPE_LEX, $4);
375 }
376 ;
377
378 /* ![option] productions */
379 selector: EXCL_BRACKET selector_seq_seq ']' varname_token
380 {
381 struct graph_node *neg_only = new_token_node (ctx, NEG_ONLY_TKN, NULL, NULL);
382
383 $$ = $2;
384 graph_add_edge ($$.start, neg_only);
385 graph_add_edge (neg_only, $$.end);
386 cmd_token_varname_set ($2.end->data, $4);
387 XFREE (MTYPE_LEX, $4);
388 }
389 ;
390
391 %%
392
393 #undef scanner
394
395 DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)");
396
397 void
398 cmd_graph_parse (struct graph *graph, const struct cmd_element *cmd)
399 {
400 struct parser_ctx ctx = { .graph = graph, .el = cmd };
401
402 // set to 1 to enable parser traces
403 yydebug = 0;
404
405 set_lexer_string (&ctx.scanner, cmd->string);
406
407 // parse command into DFA
408 cmd_yyparse (&ctx);
409
410 /* cleanup lexer */
411 cleanup_lexer (&ctx.scanner);
412
413 // cleanup
414 cleanup (&ctx);
415 }
416
417 /* parser helper functions */
418
419 static bool loopcheck_inner(struct graph_node *start, struct graph_node *node,
420 struct graph_node *end, size_t depth)
421 {
422 size_t i;
423 bool ret;
424
425 /* safety check */
426 if (depth++ == 64)
427 return true;
428
429 for (i = 0; i < vector_active(node->to); i++) {
430 struct graph_node *next = vector_slot(node->to, i);
431 struct cmd_token *tok = next->data;
432
433 if (next == end || next == start)
434 return true;
435 if (tok->type < SPECIAL_TKN)
436 continue;
437 ret = loopcheck_inner(start, next, end, depth);
438 if (ret)
439 return true;
440 }
441 return false;
442 }
443
444 static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg)
445 {
446 if (loopcheck_inner(sg->start, sg->start, sg->end, 0))
447 zlog_err("FATAL: '%s': {} contains an empty path! Use [{...}]",
448 ctx->el->string);
449 }
450
451 void
452 yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg)
453 {
454 char *tmpstr = strdup(ctx->el->string);
455 char *line, *eol;
456 char spacing[256];
457 int lineno = 0;
458
459 zlog_notice ("%s: FATAL parse error: %s", __func__, msg);
460 zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column);
461
462 line = tmpstr;
463 do {
464 lineno++;
465 eol = strchr(line, '\n');
466 if (eol)
467 *eol++ = '\0';
468
469 zlog_notice ("%s: | %s", __func__, line);
470 if (lineno == loc->first_line && lineno == loc->last_line
471 && loc->first_column < (int)sizeof(spacing) - 1
472 && loc->last_column < (int)sizeof(spacing) - 1) {
473
474 int len = loc->last_column - loc->first_column;
475 if (len == 0)
476 len = 1;
477
478 memset(spacing, ' ', loc->first_column - 1);
479 memset(spacing + loc->first_column - 1, '^', len);
480 spacing[loc->first_column - 1 + len] = '\0';
481 zlog_notice ("%s: | %s", __func__, spacing);
482 }
483 } while ((line = eol));
484 free(tmpstr);
485 }
486
487 static void
488 cleanup (struct parser_ctx *ctx)
489 {
490 /* free resources */
491 free (ctx->docstr_start);
492
493 /* clear state pointers */
494 ctx->currnode = NULL;
495 ctx->docstr_start = ctx->docstr = NULL;
496 }
497
498 static void
499 terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx,
500 struct graph_node *finalnode)
501 {
502 // end of graph should look like this
503 // * -> finalnode -> END_TKN -> cmd_element
504 const struct cmd_element *element = ctx->el;
505 struct graph_node *end_token_node =
506 new_token_node (ctx, END_TKN, CMD_CR_TEXT, "");
507 struct graph_node *end_element_node =
508 graph_new_node (ctx->graph, (void *)element, NULL);
509
510 if (ctx->docstr && strlen (ctx->docstr) > 1) {
511 zlog_err ("Excessive docstring while parsing '%s'", ctx->el->string);
512 zlog_err ("----------");
513 while (ctx->docstr && ctx->docstr[1] != '\0')
514 zlog_err ("%s", strsep(&ctx->docstr, "\n"));
515 zlog_err ("----------");
516 }
517
518 graph_add_edge (finalnode, end_token_node);
519 graph_add_edge (end_token_node, end_element_node);
520 }
521
522 static const char *
523 doc_next (struct parser_ctx *ctx)
524 {
525 const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : "";
526 if (*piece == 0x03)
527 {
528 zlog_err ("Ran out of docstring while parsing '%s'", ctx->el->string);
529 piece = "";
530 }
531
532 return piece;
533 }
534
535 static struct graph_node *
536 new_token_node (struct parser_ctx *ctx, enum cmd_token_type type,
537 const char *text, const char *doc)
538 {
539 struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc);
540 return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del);
541 }