]>
Commit | Line | Data |
---|---|---|
9d0662e0 | 1 | /* |
1ab84bf3 | 2 | * Command format string parser for CLI backend. |
9d0662e0 | 3 | * |
1ab84bf3 | 4 | * -- |
9547b5d0 | 5 | * Copyright (C) 2016 Cumulus Networks, Inc. |
9d0662e0 | 6 | * |
1ab84bf3 QY |
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. | |
9d0662e0 QY |
23 | */ |
24 | ||
92055a92 | 25 | %{ |
1ab84bf3 QY |
26 | // compile with debugging facilities |
27 | #define YYDEBUG 1 | |
51fc9379 | 28 | %} |
782d9789 | 29 | |
8bb647a8 | 30 | %locations |
05dbb7df | 31 | /* define parse.error verbose */ |
e9484f70 | 32 | %define api.pure full |
0d37f9f3 | 33 | /* define api.prefix {cmd_yy} */ |
e9484f70 | 34 | |
51fc9379 | 35 | /* names for generated header and parser files */ |
4a121f99 DL |
36 | %defines "lib/command_parse.h" |
37 | %output "lib/command_parse.c" | |
5a8bbed0 | 38 | |
afc3a6ce DL |
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 | */ | |
eceb1066 | 46 | %code requires { |
92e50261 DL |
47 | #include "config.h" |
48 | ||
5894e76d DL |
49 | #include <stdlib.h> |
50 | #include <string.h> | |
51 | #include <ctype.h> | |
52 | ||
53 | #include "command_graph.h" | |
a9958674 | 54 | #include "log.h" |
51fc9379 | 55 | |
55b7f20f DL |
56 | DECLARE_MTYPE(LEX) |
57 | ||
afc3a6ce | 58 | #define YYSTYPE CMD_YYSTYPE |
8bb647a8 | 59 | #define YYLTYPE CMD_YYLTYPE |
afc3a6ce | 60 | struct parser_ctx; |
2020b1c8 DL |
61 | |
62 | /* subgraph semantic value */ | |
63 | struct subgraph { | |
64 | struct graph_node *start, *end; | |
65 | }; | |
afc3a6ce DL |
66 | } |
67 | ||
68 | %union { | |
69 | long long number; | |
70 | char *string; | |
71 | struct graph_node *node; | |
2020b1c8 | 72 | struct subgraph subgraph; |
afc3a6ce DL |
73 | } |
74 | ||
75 | %code provides { | |
76 | #ifndef FLEX_SCANNER | |
77 | #include "command_lex.h" | |
78 | #endif | |
79 | ||
80 | extern void set_lexer_string (yyscan_t *scn, const char *string); | |
81 | extern void cleanup_lexer (yyscan_t *scn); | |
82 | ||
b07a15f8 | 83 | struct parser_ctx { |
e9484f70 DL |
84 | yyscan_t scanner; |
85 | ||
b07a15f8 DL |
86 | struct cmd_element *el; |
87 | ||
88 | struct graph *graph; | |
fd19e7a2 | 89 | struct graph_node *currnode; |
b07a15f8 DL |
90 | |
91 | /* pointers to copy of command docstring */ | |
92 | char *docstr_start, *docstr; | |
93 | }; | |
92055a92 QY |
94 | } |
95 | ||
51fc9379 | 96 | /* union types for lexed tokens */ |
5a5d576b QY |
97 | %token <string> WORD |
98 | %token <string> IPV4 | |
99 | %token <string> IPV4_PREFIX | |
100 | %token <string> IPV6 | |
101 | %token <string> IPV6_PREFIX | |
102 | %token <string> VARIABLE | |
103 | %token <string> RANGE | |
9779e3f1 QY |
104 | %token <string> MAC |
105 | %token <string> MAC_PREFIX | |
782d9789 | 106 | |
51fc9379 | 107 | /* union types for parsed rules */ |
782d9789 | 108 | %type <node> start |
782d9789 QY |
109 | %type <node> literal_token |
110 | %type <node> placeholder_token | |
16705ecc | 111 | %type <node> placeholder_token_real |
9286efab | 112 | %type <node> simple_token |
9286efab QY |
113 | %type <subgraph> selector |
114 | %type <subgraph> selector_token | |
115 | %type <subgraph> selector_token_seq | |
116 | %type <subgraph> selector_seq_seq | |
782d9789 | 117 | |
16705ecc DL |
118 | %type <string> varname_token |
119 | ||
51fc9379 | 120 | %code { |
e9484f70 | 121 | |
51fc9379 QY |
122 | /* bison declarations */ |
123 | void | |
8bb647a8 | 124 | cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg); |
1eb5e8dc | 125 | |
51fc9379 | 126 | /* helper functions for parser */ |
83364d20 | 127 | static const char * |
b07a15f8 | 128 | doc_next (struct parser_ctx *ctx); |
51fc9379 | 129 | |
1eb5e8dc | 130 | static struct graph_node * |
b07a15f8 | 131 | new_token_node (struct parser_ctx *, |
d0bfb22c | 132 | enum cmd_token_type type, |
83364d20 DL |
133 | const char *text, |
134 | const char *doc); | |
51fc9379 QY |
135 | |
136 | static void | |
8bb647a8 | 137 | terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, |
b07a15f8 | 138 | struct graph_node *); |
51fc9379 QY |
139 | |
140 | static void | |
b07a15f8 | 141 | cleanup (struct parser_ctx *ctx); |
e9484f70 DL |
142 | |
143 | #define scanner ctx->scanner | |
51fc9379 QY |
144 | } |
145 | ||
146 | /* yyparse parameters */ | |
e9484f70 DL |
147 | %lex-param {yyscan_t scanner} |
148 | %parse-param {struct parser_ctx *ctx} | |
51fc9379 QY |
149 | |
150 | /* called automatically before yyparse */ | |
151 | %initial-action { | |
152 | /* clear state pointers */ | |
fd19e7a2 | 153 | ctx->currnode = vector_slot (ctx->graph->nodes, 0); |
51fc9379 | 154 | |
51fc9379 | 155 | /* copy docstring and keep a pointer to the copy */ |
b07a15f8 | 156 | if (ctx->el->doc) |
a9958674 QY |
157 | { |
158 | // allocate a new buffer, making room for a flag | |
b07a15f8 DL |
159 | size_t length = (size_t) strlen (ctx->el->doc) + 2; |
160 | ctx->docstr = malloc (length); | |
161 | memcpy (ctx->docstr, ctx->el->doc, strlen (ctx->el->doc)); | |
a9958674 | 162 | // set the flag so doc_next knows when to print a warning |
b07a15f8 | 163 | ctx->docstr[length - 2] = 0x03; |
a9958674 | 164 | // null terminate |
b07a15f8 | 165 | ctx->docstr[length - 1] = 0x00; |
a9958674 | 166 | } |
b07a15f8 | 167 | ctx->docstr_start = ctx->docstr; |
51fc9379 | 168 | } |
92055a92 | 169 | |
92055a92 QY |
170 | %% |
171 | ||
88255c7c | 172 | start: |
fd19e7a2 | 173 | cmd_token_seq |
880e24a1 | 174 | { |
51fc9379 | 175 | // tack on the command element |
8bb647a8 | 176 | terminate_graph (&@1, ctx, ctx->currnode); |
880e24a1 | 177 | } |
fd19e7a2 | 178 | | cmd_token_seq placeholder_token '.' '.' '.' |
88255c7c | 179 | { |
6432969d | 180 | if ((ctx->currnode = graph_add_edge (ctx->currnode, $2)) != $2) |
fd19e7a2 | 181 | graph_delete_node (ctx->graph, $2); |
88255c7c | 182 | |
4d94b292 | 183 | ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1; |
7d5718c1 | 184 | |
1ab84bf3 | 185 | // adding a node as a child of itself accepts any number |
a9958674 | 186 | // of the same token, which is what we want for variadics |
6432969d | 187 | graph_add_edge (ctx->currnode, ctx->currnode); |
88255c7c | 188 | |
51fc9379 | 189 | // tack on the command element |
8bb647a8 | 190 | terminate_graph (&@1, ctx, ctx->currnode); |
88255c7c | 191 | } |
9286efab | 192 | ; |
92055a92 | 193 | |
16705ecc DL |
194 | varname_token: '$' WORD |
195 | { | |
4d85f868 | 196 | $$ = $2; |
16705ecc DL |
197 | } |
198 | | /* empty */ | |
199 | { | |
200 | $$ = NULL; | |
201 | } | |
202 | ; | |
203 | ||
9286efab | 204 | cmd_token_seq: |
0d4aa1b1 | 205 | /* empty */ |
9286efab QY |
206 | | cmd_token_seq cmd_token |
207 | ; | |
92055a92 | 208 | |
782d9789 | 209 | cmd_token: |
9286efab | 210 | simple_token |
5a5d576b | 211 | { |
6432969d | 212 | if ((ctx->currnode = graph_add_edge (ctx->currnode, $1)) != $1) |
b07a15f8 | 213 | graph_delete_node (ctx->graph, $1); |
5a5d576b | 214 | } |
663982cd | 215 | | selector |
5a5d576b | 216 | { |
2020b1c8 DL |
217 | graph_add_edge (ctx->currnode, $1.start); |
218 | ctx->currnode = $1.end; | |
478bdaeb | 219 | } |
9286efab QY |
220 | ; |
221 | ||
222 | simple_token: | |
223 | literal_token | |
224 | | placeholder_token | |
225 | ; | |
226 | ||
16705ecc | 227 | literal_token: WORD varname_token |
9286efab | 228 | { |
83364d20 | 229 | $$ = new_token_node (ctx, WORD_TKN, $1, doc_next(ctx)); |
5894e76d | 230 | cmd_token_varname_set ($$->data, $2); |
16705ecc | 231 | XFREE (MTYPE_LEX, $2); |
55b7f20f | 232 | XFREE (MTYPE_LEX, $1); |
9286efab | 233 | } |
782d9789 QY |
234 | ; |
235 | ||
16705ecc | 236 | placeholder_token_real: |
478bdaeb | 237 | IPV4 |
4b0abf24 | 238 | { |
83364d20 | 239 | $$ = new_token_node (ctx, IPV4_TKN, $1, doc_next(ctx)); |
55b7f20f | 240 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 241 | } |
478bdaeb | 242 | | IPV4_PREFIX |
340a2b4a | 243 | { |
83364d20 | 244 | $$ = new_token_node (ctx, IPV4_PREFIX_TKN, $1, doc_next(ctx)); |
55b7f20f | 245 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 246 | } |
478bdaeb | 247 | | IPV6 |
340a2b4a | 248 | { |
83364d20 | 249 | $$ = new_token_node (ctx, IPV6_TKN, $1, doc_next(ctx)); |
55b7f20f | 250 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 251 | } |
478bdaeb | 252 | | IPV6_PREFIX |
340a2b4a | 253 | { |
83364d20 | 254 | $$ = new_token_node (ctx, IPV6_PREFIX_TKN, $1, doc_next(ctx)); |
55b7f20f | 255 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 256 | } |
478bdaeb | 257 | | VARIABLE |
340a2b4a | 258 | { |
83364d20 | 259 | $$ = new_token_node (ctx, VARIABLE_TKN, $1, doc_next(ctx)); |
55b7f20f | 260 | XFREE (MTYPE_LEX, $1); |
4b0abf24 | 261 | } |
478bdaeb QY |
262 | | RANGE |
263 | { | |
83364d20 | 264 | $$ = new_token_node (ctx, RANGE_TKN, $1, doc_next(ctx)); |
d0bfb22c | 265 | struct cmd_token *token = $$->data; |
478bdaeb QY |
266 | |
267 | // get the numbers out | |
b3899769 | 268 | yylval.string++; |
7a6ded40 | 269 | token->min = strtoll (yylval.string, &yylval.string, 10); |
ef955a80 | 270 | strsep (&yylval.string, "-"); |
7a6ded40 | 271 | token->max = strtoll (yylval.string, &yylval.string, 10); |
ef955a80 QY |
272 | |
273 | // validate range | |
8bb647a8 | 274 | if (token->min > token->max) cmd_yyerror (&@1, ctx, "Invalid range."); |
5a5d576b | 275 | |
55b7f20f | 276 | XFREE (MTYPE_LEX, $1); |
478bdaeb | 277 | } |
9779e3f1 QY |
278 | | MAC |
279 | { | |
280 | $$ = new_token_node (ctx, MAC_TKN, $1, doc_next(ctx)); | |
281 | XFREE (MTYPE_LEX, $1); | |
282 | } | |
283 | | MAC_PREFIX | |
284 | { | |
285 | $$ = new_token_node (ctx, MAC_PREFIX_TKN, $1, doc_next(ctx)); | |
286 | XFREE (MTYPE_LEX, $1); | |
287 | } | |
782d9789 | 288 | |
16705ecc DL |
289 | placeholder_token: |
290 | placeholder_token_real varname_token | |
291 | { | |
292 | struct cmd_token *token = $$->data; | |
293 | $$ = $1; | |
5894e76d | 294 | cmd_token_varname_set (token, $2); |
16705ecc DL |
295 | XFREE (MTYPE_LEX, $2); |
296 | }; | |
297 | ||
298 | ||
4b0abf24 | 299 | /* <selector|set> productions */ |
16705ecc | 300 | selector: '<' selector_seq_seq '>' varname_token |
2a23ca6e | 301 | { |
663982cd | 302 | $$ = $2; |
5894e76d | 303 | cmd_token_varname_set ($2.end->data, $4); |
16705ecc | 304 | XFREE (MTYPE_LEX, $4); |
2a23ca6e | 305 | }; |
782d9789 | 306 | |
9286efab QY |
307 | selector_seq_seq: |
308 | selector_seq_seq '|' selector_token_seq | |
478bdaeb | 309 | { |
663982cd | 310 | $$ = $1; |
2020b1c8 DL |
311 | graph_add_edge ($$.start, $3.start); |
312 | graph_add_edge ($3.end, $$.end); | |
9286efab | 313 | } |
663982cd | 314 | | selector_token_seq |
9286efab | 315 | { |
663982cd DL |
316 | $$.start = new_token_node (ctx, FORK_TKN, NULL, NULL); |
317 | $$.end = new_token_node (ctx, JOIN_TKN, NULL, NULL); | |
318 | ((struct cmd_token *)$$.start->data)->forkjoin = $$.end; | |
319 | ((struct cmd_token *)$$.end->data)->forkjoin = $$.start; | |
320 | ||
2020b1c8 DL |
321 | graph_add_edge ($$.start, $1.start); |
322 | graph_add_edge ($1.end, $$.end); | |
478bdaeb | 323 | } |
9286efab | 324 | ; |
782d9789 | 325 | |
7d5718c1 | 326 | /* {keyword} productions */ |
16705ecc | 327 | selector: '{' selector_seq_seq '}' varname_token |
7d5718c1 | 328 | { |
663982cd DL |
329 | $$ = $2; |
330 | graph_add_edge ($$.end, $$.start); | |
331 | /* there is intentionally no start->end link, for two reasons: | |
332 | * 1) this allows "at least 1 of" semantics, which are otherwise impossible | |
333 | * 2) this would add a start->end->start loop in the graph that the current | |
334 | * loop-avoidal fails to handle | |
335 | * just use [{a|b}] if neccessary, that will work perfectly fine, and reason | |
336 | * #1 is good enough to keep it this way. */ | |
16705ecc | 337 | |
5894e76d | 338 | cmd_token_varname_set ($2.end->data, $4); |
16705ecc | 339 | XFREE (MTYPE_LEX, $4); |
7d5718c1 DL |
340 | }; |
341 | ||
342 | ||
535ef556 | 343 | selector_token: |
9286efab | 344 | simple_token |
4b0abf24 | 345 | { |
2020b1c8 | 346 | $$.start = $$.end = $1; |
9286efab | 347 | } |
4d12266b | 348 | | selector |
9286efab | 349 | ; |
782d9789 | 350 | |
663982cd DL |
351 | selector_token_seq: |
352 | selector_token_seq selector_token | |
2a23ca6e | 353 | { |
2020b1c8 DL |
354 | graph_add_edge ($1.end, $2.start); |
355 | $$.start = $1.start; | |
356 | $$.end = $2.end; | |
2a23ca6e | 357 | } |
663982cd | 358 | | selector_token |
9286efab | 359 | ; |
478bdaeb | 360 | |
663982cd | 361 | /* [option] productions */ |
16705ecc | 362 | selector: '[' selector_seq_seq ']' varname_token |
9286efab | 363 | { |
663982cd DL |
364 | $$ = $2; |
365 | graph_add_edge ($$.start, $$.end); | |
5894e76d | 366 | cmd_token_varname_set ($2.end->data, $4); |
16705ecc | 367 | XFREE (MTYPE_LEX, $4); |
9286efab | 368 | } |
782d9789 QY |
369 | ; |
370 | ||
92055a92 | 371 | %% |
4b0abf24 | 372 | |
e9484f70 DL |
373 | #undef scanner |
374 | ||
55b7f20f DL |
375 | DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)") |
376 | ||
1eb5e8dc | 377 | void |
5894e76d | 378 | cmd_graph_parse (struct graph *graph, struct cmd_element *cmd) |
51fc9379 | 379 | { |
b07a15f8 DL |
380 | struct parser_ctx ctx = { .graph = graph, .el = cmd }; |
381 | ||
1ab84bf3 | 382 | // set to 1 to enable parser traces |
51fc9379 QY |
383 | yydebug = 0; |
384 | ||
e9484f70 DL |
385 | set_lexer_string (&ctx.scanner, cmd->string); |
386 | ||
51fc9379 | 387 | // parse command into DFA |
e9484f70 DL |
388 | cmd_yyparse (&ctx); |
389 | ||
390 | /* cleanup lexer */ | |
391 | cleanup_lexer (&ctx.scanner); | |
07079d78 | 392 | |
7a6ded40 | 393 | // cleanup |
b07a15f8 | 394 | cleanup (&ctx); |
51fc9379 QY |
395 | } |
396 | ||
397 | /* parser helper functions */ | |
398 | ||
399 | void | |
8bb647a8 | 400 | yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg) |
51fc9379 | 401 | { |
8bb647a8 DL |
402 | char *tmpstr = strdup(ctx->el->string); |
403 | char *line, *eol; | |
404 | char spacing[256]; | |
405 | int lineno = 0; | |
406 | ||
b87478cb DS |
407 | zlog_notice ("%s: FATAL parse error: %s", __func__, msg); |
408 | zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column); | |
8bb647a8 DL |
409 | |
410 | line = tmpstr; | |
411 | do { | |
412 | lineno++; | |
413 | eol = strchr(line, '\n'); | |
414 | if (eol) | |
415 | *eol++ = '\0'; | |
416 | ||
b87478cb | 417 | zlog_notice ("%s: | %s", __func__, line); |
8bb647a8 DL |
418 | if (lineno == loc->first_line && lineno == loc->last_line |
419 | && loc->first_column < (int)sizeof(spacing) - 1 | |
420 | && loc->last_column < (int)sizeof(spacing) - 1) { | |
421 | ||
422 | int len = loc->last_column - loc->first_column; | |
423 | if (len == 0) | |
424 | len = 1; | |
425 | ||
426 | memset(spacing, ' ', loc->first_column - 1); | |
427 | memset(spacing + loc->first_column - 1, '^', len); | |
428 | spacing[loc->first_column - 1 + len] = '\0'; | |
b87478cb | 429 | zlog_notice ("%s: | %s", __func__, spacing); |
8bb647a8 DL |
430 | } |
431 | } while ((line = eol)); | |
432 | free(tmpstr); | |
92055a92 | 433 | } |
782d9789 | 434 | |
51fc9379 | 435 | static void |
b07a15f8 | 436 | cleanup (struct parser_ctx *ctx) |
782d9789 | 437 | { |
51fc9379 | 438 | /* free resources */ |
b07a15f8 | 439 | free (ctx->docstr_start); |
478bdaeb | 440 | |
4b0abf24 | 441 | /* clear state pointers */ |
b07a15f8 DL |
442 | ctx->currnode = NULL; |
443 | ctx->docstr_start = ctx->docstr = NULL; | |
51fc9379 | 444 | } |
4b0abf24 | 445 | |
51fc9379 | 446 | static void |
8bb647a8 DL |
447 | terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, |
448 | struct graph_node *finalnode) | |
51fc9379 | 449 | { |
1eb5e8dc QY |
450 | // end of graph should look like this |
451 | // * -> finalnode -> END_TKN -> cmd_element | |
b07a15f8 | 452 | struct cmd_element *element = ctx->el; |
d8074cc0 | 453 | struct graph_node *end_token_node = |
83364d20 | 454 | new_token_node (ctx, END_TKN, CMD_CR_TEXT, ""); |
1eb5e8dc | 455 | struct graph_node *end_element_node = |
b5a1e9ef | 456 | graph_new_node (ctx->graph, element, NULL); |
7a6ded40 | 457 | |
ebb08130 QY |
458 | if (ctx->docstr && strlen (ctx->docstr) > 1) { |
459 | zlog_debug ("Excessive docstring while parsing '%s'", ctx->el->string); | |
460 | zlog_debug ("----------"); | |
461 | while (ctx->docstr && ctx->docstr[1] != '\0') | |
462 | zlog_debug ("%s", strsep(&ctx->docstr, "\n")); | |
463 | zlog_debug ("----------\n"); | |
464 | } | |
465 | ||
1eb5e8dc QY |
466 | graph_add_edge (finalnode, end_token_node); |
467 | graph_add_edge (end_token_node, end_element_node); | |
782d9789 | 468 | } |
f4b0c72e | 469 | |
83364d20 | 470 | static const char * |
b07a15f8 | 471 | doc_next (struct parser_ctx *ctx) |
51fc9379 | 472 | { |
b07a15f8 | 473 | const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : ""; |
a9958674 QY |
474 | if (*piece == 0x03) |
475 | { | |
b07a15f8 | 476 | zlog_debug ("Ran out of docstring while parsing '%s'", ctx->el->string); |
a9958674 QY |
477 | piece = ""; |
478 | } | |
479 | ||
83364d20 | 480 | return piece; |
51fc9379 QY |
481 | } |
482 | ||
483 | static struct graph_node * | |
b07a15f8 | 484 | new_token_node (struct parser_ctx *ctx, enum cmd_token_type type, |
83364d20 | 485 | const char *text, const char *doc) |
7a6ded40 | 486 | { |
5894e76d DL |
487 | struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc); |
488 | return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del); | |
7a6ded40 | 489 | } |