]> git.proxmox.com Git - mirror_frr.git/blob - lib/grammar_sandbox.c
debianpkg: Remove -werror from Ubuntu 14.04 and 12.04 build to skip warnings from...
[mirror_frr.git] / lib / grammar_sandbox.c
1 /*
2 * Testing shim and API examples for the new CLI backend.
3 *
4 * This unit defines a number of commands in the old engine that can
5 * be used to test and interact with the new engine.
6 * --
7 * Copyright (C) 2016 Cumulus Networks, Inc.
8 *
9 * This file is part of GNU Zebra.
10 *
11 * GNU Zebra is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2, or (at your option) any
14 * later version.
15 *
16 * GNU Zebra is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with GNU Zebra; see the file COPYING. If not, write to the Free
23 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
24 * 02111-1307, USA.
25 */
26
27 #include "command.h"
28 #include "memory_vty.h"
29 #include "graph.h"
30 #include "linklist.h"
31 #include "command_match.h"
32
33 #define GRAMMAR_STR "CLI grammar sandbox\n"
34
35 DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc")
36
37 #define MAXDEPTH 64
38
39 /** headers **/
40 void grammar_sandbox_init(void);
41 void pretty_print_graph(struct vty *vty, struct graph_node *, int, int,
42 struct graph_node **, size_t);
43 static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start,
44 struct graph_node **stack, size_t stackpos,
45 struct graph_node **visited, size_t *visitpos);
46 void init_cmdgraph(struct vty *, struct graph **);
47
48 /** shim interface commands **/
49 struct graph *nodegraph = NULL, *nodegraph_free = NULL;
50
51 DEFUN (grammar_test,
52 grammar_test_cmd,
53 "grammar parse LINE...",
54 GRAMMAR_STR
55 "parse a command\n"
56 "command to pass to new parser\n")
57 {
58 int idx_command = 2;
59 // make a string from tokenized command line
60 char *command = argv_concat(argv, argc, idx_command);
61
62 // create cmd_element for parser
63 struct cmd_element *cmd =
64 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
65 cmd->string = command;
66 cmd->doc =
67 "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
68 cmd->func = NULL;
69
70 // parse the command and install it into the command graph
71 struct graph *graph = graph_new();
72 struct cmd_token *token =
73 new_cmd_token(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
74 graph_new_node(graph, token, (void (*)(void *)) & del_cmd_token);
75
76 command_parse_format(graph, cmd);
77 cmd_merge_graphs(nodegraph, graph, +1);
78
79 return CMD_SUCCESS;
80 }
81
82 DEFUN (grammar_test_complete,
83 grammar_test_complete_cmd,
84 "grammar complete COMMAND...",
85 GRAMMAR_STR
86 "attempt to complete input on DFA\n"
87 "command to complete\n")
88 {
89 int idx_command = 2;
90 char *cmdstr = argv_concat(argv, argc, idx_command);
91 if (!cmdstr)
92 return CMD_SUCCESS;
93
94 vector command = cmd_make_strvec(cmdstr);
95 if (!command) {
96 XFREE(MTYPE_TMP, cmdstr);
97 return CMD_SUCCESS;
98 }
99
100 // generate completions of user input
101 struct list *completions;
102 enum matcher_rv result =
103 command_complete(nodegraph, command, &completions);
104
105 // print completions or relevant error message
106 if (!MATCHER_ERROR(result)) {
107 vector comps = completions_to_vec(completions);
108 struct cmd_token *tkn;
109
110 // calculate length of longest tkn->text in completions
111 unsigned int width = 0, i = 0;
112 for (i = 0; i < vector_active(comps); i++) {
113 tkn = vector_slot(comps, i);
114 unsigned int len = strlen(tkn->text);
115 width = len > width ? len : width;
116 }
117
118 // print completions
119 for (i = 0; i < vector_active(comps); i++) {
120 tkn = vector_slot(comps, i);
121 vty_out(vty, " %-*s %s%s", width, tkn->text,
122 tkn->desc, VTY_NEWLINE);
123 }
124
125 for (i = 0; i < vector_active(comps); i++)
126 del_cmd_token(
127 (struct cmd_token *)vector_slot(comps, i));
128 vector_free(comps);
129 } else
130 vty_out(vty, "%% No match%s", VTY_NEWLINE);
131
132 // free resources
133 list_delete(completions);
134 cmd_free_strvec(command);
135 XFREE(MTYPE_TMP, cmdstr);
136
137 return CMD_SUCCESS;
138 }
139
140 DEFUN (grammar_test_match,
141 grammar_test_match_cmd,
142 "grammar match COMMAND...",
143 GRAMMAR_STR
144 "attempt to match input on DFA\n"
145 "command to match\n")
146 {
147 int idx_command = 2;
148 if (argv[2]->arg[0] == '#')
149 return CMD_SUCCESS;
150
151 char *cmdstr = argv_concat(argv, argc, idx_command);
152 if (!cmdstr)
153 return CMD_SUCCESS;
154 vector command = cmd_make_strvec(cmdstr);
155 if (!command) {
156 XFREE(MTYPE_TMP, cmdstr);
157 return CMD_SUCCESS;
158 }
159
160 struct list *argvv = NULL;
161 const struct cmd_element *element = NULL;
162 enum matcher_rv result =
163 command_match(nodegraph, command, &argvv, &element);
164
165 // print completions or relevant error message
166 if (element) {
167 vty_out(vty, "Matched: %s%s", element->string, VTY_NEWLINE);
168 struct listnode *ln;
169 struct cmd_token *token;
170 for (ALL_LIST_ELEMENTS_RO(argvv, ln, token))
171 vty_out(vty, "%s -- %s%s", token->text, token->arg,
172 VTY_NEWLINE);
173
174 vty_out(vty, "func: %p%s", element->func, VTY_NEWLINE);
175
176 list_delete(argvv);
177 } else {
178 assert(MATCHER_ERROR(result));
179 switch (result) {
180 case MATCHER_NO_MATCH:
181 vty_out(vty, "%% Unknown command%s", VTY_NEWLINE);
182 break;
183 case MATCHER_INCOMPLETE:
184 vty_out(vty, "%% Incomplete command%s", VTY_NEWLINE);
185 break;
186 case MATCHER_AMBIGUOUS:
187 vty_out(vty, "%% Ambiguous command%s", VTY_NEWLINE);
188 break;
189 default:
190 vty_out(vty, "%% Unknown error%s", VTY_NEWLINE);
191 break;
192 }
193 }
194
195 // free resources
196 cmd_free_strvec(command);
197 XFREE(MTYPE_TMP, cmdstr);
198
199 return CMD_SUCCESS;
200 }
201
202 /**
203 * Testing shim to test docstrings
204 */
205 DEFUN (grammar_test_doc,
206 grammar_test_doc_cmd,
207 "grammar test docstring",
208 GRAMMAR_STR
209 "Test function for docstring\n"
210 "Command end\n")
211 {
212 // create cmd_element with docstring
213 struct cmd_element *cmd =
214 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
215 cmd->string = XSTRDUP(
216 MTYPE_CMD_TOKENS,
217 "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
218 cmd->doc = XSTRDUP(MTYPE_CMD_TOKENS,
219 "Test stuff\n"
220 "docstring thing\n"
221 "first example\n"
222 "second example\n"
223 "follow\n"
224 "random range\n"
225 "end thingy\n"
226 "variable\n"
227 "optional variable\n"
228 "optional set\n"
229 "optional lol\n"
230 "vararg!\n");
231 cmd->func = NULL;
232
233 // parse element
234 command_parse_format(nodegraph, cmd);
235
236 return CMD_SUCCESS;
237 }
238
239 /**
240 * Debugging command to print command graph
241 */
242 DEFUN (grammar_test_show,
243 grammar_test_show_cmd,
244 "grammar show [doc]",
245 GRAMMAR_STR
246 "print current accumulated DFA\n"
247 "include docstrings\n")
248 {
249 struct graph_node *stack[MAXDEPTH];
250
251 if (!nodegraph)
252 vty_out(vty, "nodegraph uninitialized\r\n");
253 else
254 pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0,
255 argc >= 3, stack, 0);
256 return CMD_SUCCESS;
257 }
258
259 DEFUN (grammar_test_dot,
260 grammar_test_dot_cmd,
261 "grammar dotfile OUTNAME",
262 GRAMMAR_STR
263 "print current graph for dot\n"
264 ".dot filename\n")
265 {
266 struct graph_node *stack[MAXDEPTH];
267 struct graph_node *visited[MAXDEPTH * MAXDEPTH];
268 size_t vpos = 0;
269
270 if (!nodegraph) {
271 vty_out(vty, "nodegraph uninitialized\r\n");
272 return CMD_SUCCESS;
273 }
274 FILE *ofd = fopen(argv[2]->arg, "w");
275 if (!ofd) {
276 vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
277 return CMD_SUCCESS;
278 }
279
280 fprintf(ofd,
281 "digraph {\n graph [ rankdir = LR ];\n node [ fontname = \"Fira Mono\", fontsize = 9 ];\n\n");
282 pretty_print_dot(ofd, 0, vector_slot(nodegraph->nodes, 0), stack, 0,
283 visited, &vpos);
284 fprintf(ofd, "}\n");
285 fclose(ofd);
286 return CMD_SUCCESS;
287 }
288
289 struct cmd_permute_item {
290 char *cmd;
291 struct cmd_element *el;
292 };
293
294 static void cmd_permute_free(void *arg)
295 {
296 struct cmd_permute_item *i = arg;
297 XFREE(MTYPE_TMP, i->cmd);
298 XFREE(MTYPE_TMP, i);
299 }
300
301 static int cmd_permute_cmp(void *a, void *b)
302 {
303 struct cmd_permute_item *aa = a, *bb = b;
304 return strcmp(aa->cmd, bb->cmd);
305 }
306
307 static void cmd_graph_permute(struct list *out, struct graph_node **stack,
308 size_t stackpos, char *cmd)
309 {
310 struct graph_node *gn = stack[stackpos];
311 struct cmd_token *tok = gn->data;
312 char *appendp = cmd + strlen(cmd);
313 size_t i, j;
314
315 if (tok->type < SPECIAL_TKN) {
316 sprintf(appendp, "%s ", tok->text);
317 appendp += strlen(appendp);
318 } else if (tok->type == END_TKN) {
319 struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i));
320 i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data;
321 i->cmd = XSTRDUP(MTYPE_TMP, cmd);
322 i->cmd[strlen(cmd) - 1] = '\0';
323 listnode_add_sort(out, i);
324 return;
325 }
326
327 if (++stackpos == MAXDEPTH)
328 return;
329
330 for (i = 0; i < vector_active(gn->to); i++) {
331 struct graph_node *gnext = vector_slot(gn->to, i);
332 for (j = 0; j < stackpos; j++)
333 if (stack[j] == gnext)
334 break;
335 if (j != stackpos)
336 continue;
337
338 stack[stackpos] = gnext;
339 *appendp = '\0';
340 cmd_graph_permute(out, stack, stackpos, cmd);
341 }
342 }
343
344 static struct list *cmd_graph_permutations(struct graph *graph)
345 {
346 char accumulate[2048] = "";
347 struct graph_node *stack[MAXDEPTH];
348
349 struct list *rv = list_new();
350 rv->cmp = cmd_permute_cmp;
351 rv->del = cmd_permute_free;
352 stack[0] = vector_slot(graph->nodes, 0);
353 cmd_graph_permute(rv, stack, 0, accumulate);
354 return rv;
355 }
356
357 extern vector cmdvec;
358
359 DEFUN (grammar_findambig,
360 grammar_findambig_cmd,
361 "grammar find-ambiguous [{printall|nodescan}]",
362 GRAMMAR_STR
363 "Find ambiguous commands\n"
364 "Print all permutations\n"
365 "Scan all nodes\n")
366 {
367 struct list *commands;
368 struct cmd_permute_item *prev = NULL, *cur = NULL;
369 struct listnode *ln;
370 int i, printall, scan, scannode = 0;
371 int ambig = 0;
372
373 i = 0;
374 printall = argv_find(argv, argc, "printall", &i);
375 i = 0;
376 scan = argv_find(argv, argc, "nodescan", &i);
377
378 if (scan && nodegraph_free) {
379 graph_delete_graph(nodegraph_free);
380 nodegraph_free = NULL;
381 }
382
383 if (!scan && !nodegraph) {
384 vty_out(vty, "nodegraph uninitialized\r\n");
385 return CMD_WARNING;
386 }
387
388 do {
389 if (scan) {
390 struct cmd_node *cnode =
391 vector_slot(cmdvec, scannode++);
392 if (!cnode)
393 continue;
394 nodegraph = cnode->cmdgraph;
395 if (!nodegraph)
396 continue;
397 vty_out(vty, "scanning node %d%s", scannode - 1,
398 VTY_NEWLINE);
399 }
400
401 commands = cmd_graph_permutations(nodegraph);
402 prev = NULL;
403 for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) {
404 int same = prev && !strcmp(prev->cmd, cur->cmd);
405 if (printall && !same)
406 vty_out(vty, "'%s' [%x]%s", cur->cmd,
407 cur->el->daemon, VTY_NEWLINE);
408 if (same) {
409 vty_out(vty, "'%s' AMBIGUOUS:%s", cur->cmd,
410 VTY_NEWLINE);
411 vty_out(vty, " %s%s '%s'%s", prev->el->name,
412 VTY_NEWLINE, prev->el->string,
413 VTY_NEWLINE);
414 vty_out(vty, " %s%s '%s'%s", cur->el->name,
415 VTY_NEWLINE, cur->el->string,
416 VTY_NEWLINE);
417 vty_out(vty, "%s", VTY_NEWLINE);
418 ambig++;
419 }
420 prev = cur;
421 }
422 list_delete(commands);
423
424 vty_out(vty, "%s", VTY_NEWLINE);
425 } while (scan && scannode < LINK_PARAMS_NODE);
426
427 vty_out(vty, "%d ambiguous commands found.%s", ambig, VTY_NEWLINE);
428
429 if (scan)
430 nodegraph = NULL;
431 return ambig == 0 ? CMD_SUCCESS : CMD_WARNING;
432 }
433
434 DEFUN (grammar_init_graph,
435 grammar_init_graph_cmd,
436 "grammar init",
437 GRAMMAR_STR
438 "(re)initialize graph\n")
439 {
440 if (nodegraph_free)
441 graph_delete_graph(nodegraph_free);
442 nodegraph_free = NULL;
443
444 init_cmdgraph(vty, &nodegraph);
445 return CMD_SUCCESS;
446 }
447
448 DEFUN (grammar_access,
449 grammar_access_cmd,
450 "grammar access (0-65535)",
451 GRAMMAR_STR
452 "access node graph\n"
453 "node number\n")
454 {
455 if (nodegraph_free)
456 graph_delete_graph(nodegraph_free);
457 nodegraph_free = NULL;
458
459 struct cmd_node *cnode;
460
461 cnode = vector_slot(cmdvec, atoi(argv[2]->arg));
462 if (!cnode) {
463 vty_out(vty, "%% no such node%s", VTY_NEWLINE);
464 return CMD_WARNING;
465 }
466
467 vty_out(vty, "node %d%s", (int)cnode->node, VTY_NEWLINE);
468 nodegraph = cnode->cmdgraph;
469 return CMD_SUCCESS;
470 }
471
472 /* this is called in vtysh.c to set up the testing shim */
473 void grammar_sandbox_init(void)
474 {
475 init_cmdgraph(NULL, &nodegraph);
476
477 // install all enable elements
478 install_element(ENABLE_NODE, &grammar_test_cmd);
479 install_element(ENABLE_NODE, &grammar_test_show_cmd);
480 install_element(ENABLE_NODE, &grammar_test_dot_cmd);
481 install_element(ENABLE_NODE, &grammar_test_match_cmd);
482 install_element(ENABLE_NODE, &grammar_test_complete_cmd);
483 install_element(ENABLE_NODE, &grammar_test_doc_cmd);
484 install_element(ENABLE_NODE, &grammar_findambig_cmd);
485 install_element(ENABLE_NODE, &grammar_init_graph_cmd);
486 install_element(ENABLE_NODE, &grammar_access_cmd);
487 }
488
489 #define item(x) { x, #x }
490 struct message tokennames[] = {item(WORD_TKN), // words
491 item(VARIABLE_TKN), // almost anything
492 item(RANGE_TKN), // integer range
493 item(IPV4_TKN), // IPV4 addresses
494 item(IPV4_PREFIX_TKN), // IPV4 network prefixes
495 item(IPV6_TKN), // IPV6 prefixes
496 item(IPV6_PREFIX_TKN), // IPV6 network prefixes
497
498 /* plumbing types */
499 item(FORK_TKN),
500 item(JOIN_TKN),
501 item(START_TKN), // first token in line
502 item(END_TKN), // last token in line
503 {0}};
504
505 /**
506 * Pretty-prints a graph, assuming it is a tree.
507 *
508 * @param start the node to take as the root
509 * @param level indent level for recursive calls, always pass 0
510 */
511 void pretty_print_graph(struct vty *vty, struct graph_node *start, int level,
512 int desc, struct graph_node **stack, size_t stackpos)
513 {
514 // print this node
515 char tokennum[32];
516 struct cmd_token *tok = start->data;
517
518 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
519 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
520 if (tok->text)
521 vty_out(vty, ":\"%s\"", tok->text);
522 if (desc)
523 vty_out(vty, " ?'%s'", tok->desc);
524 vty_out(vty, " ");
525
526 if (stackpos == MAXDEPTH) {
527 vty_out(vty, " -aborting! (depth limit)%s", VTY_NEWLINE);
528 return;
529 }
530 stack[stackpos++] = start;
531
532 int numto = desc ? 2 : vector_active(start->to);
533 if (numto) {
534 if (numto > 1)
535 vty_out(vty, "%s", VTY_NEWLINE);
536 for (unsigned int i = 0; i < vector_active(start->to); i++) {
537 struct graph_node *adj = vector_slot(start->to, i);
538 // if we're listing multiple children, indent!
539 if (numto > 1)
540 for (int j = 0; j < level + 1; j++)
541 vty_out(vty, " ");
542 // if this node is a vararg, just print *
543 if (adj == start)
544 vty_out(vty, "*");
545 else if (((struct cmd_token *)adj->data)->type
546 == END_TKN)
547 vty_out(vty, "--END%s", VTY_NEWLINE);
548 else {
549 size_t k;
550 for (k = 0; k < stackpos; k++)
551 if (stack[k] == adj) {
552 vty_out(vty, "<<loop@%zu %s", k,
553 VTY_NEWLINE);
554 break;
555 }
556 if (k == stackpos)
557 pretty_print_graph(
558 vty, adj,
559 numto > 1 ? level + 1 : level,
560 desc, stack, stackpos);
561 }
562 }
563 } else
564 vty_out(vty, "%s", VTY_NEWLINE);
565 }
566
567 static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start,
568 struct graph_node **stack, size_t stackpos,
569 struct graph_node **visited, size_t *visitpos)
570 {
571 // print this node
572 char tokennum[32];
573 struct cmd_token *tok = start->data;
574 const char *color;
575
576 for (size_t i = 0; i < (*visitpos); i++)
577 if (visited[i] == start)
578 return;
579 visited[(*visitpos)++] = start;
580 if ((*visitpos) == MAXDEPTH * MAXDEPTH)
581 return;
582
583 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
584 fprintf(ofd, " n%p [ shape=box, label=<", start);
585
586 fprintf(ofd, "<b>%s</b>", lookup_msg(tokennames, tok->type, NULL));
587 if (tok->attr == CMD_ATTR_DEPRECATED)
588 fprintf(ofd, " (d)");
589 else if (tok->attr == CMD_ATTR_HIDDEN)
590 fprintf(ofd, " (h)");
591 if (tok->text) {
592 if (tok->type == WORD_TKN)
593 fprintf(ofd,
594 "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
595 tok->text);
596 else
597 fprintf(ofd, "<br/>%s", tok->text);
598 }
599 /* if (desc)
600 fprintf(ofd, " ?'%s'", tok->desc); */
601 switch (tok->type) {
602 case START_TKN:
603 color = "#ccffcc";
604 break;
605 case FORK_TKN:
606 color = "#aaddff";
607 break;
608 case JOIN_TKN:
609 color = "#ddaaff";
610 break;
611 case WORD_TKN:
612 color = "#ffffff";
613 break;
614 default:
615 color = "#ffffff";
616 break;
617 }
618 fprintf(ofd, ">, style = filled, fillcolor = \"%s\" ];\n", color);
619
620 if (stackpos == MAXDEPTH)
621 return;
622 stack[stackpos++] = start;
623
624 for (unsigned int i = 0; i < vector_active(start->to); i++) {
625 struct graph_node *adj = vector_slot(start->to, i);
626 // if this node is a vararg, just print *
627 if (adj == start) {
628 fprintf(ofd, " n%p -> n%p;\n", start, start);
629 } else if (((struct cmd_token *)adj->data)->type == END_TKN) {
630 // struct cmd_token *et = adj->data;
631 fprintf(ofd, " n%p -> end%p;\n", start, adj);
632 fprintf(ofd,
633 " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
634 adj);
635 } else {
636 fprintf(ofd, " n%p -> n%p;\n", start, adj);
637 size_t k;
638 for (k = 0; k < stackpos; k++)
639 if (stack[k] == adj)
640 break;
641 if (k == stackpos) {
642 pretty_print_dot(ofd, opts, adj, stack,
643 stackpos, visited, visitpos);
644 }
645 }
646 }
647 }
648
649
650 /** stuff that should go in command.c + command.h */
651 void init_cmdgraph(struct vty *vty, struct graph **graph)
652 {
653 // initialize graph, add start noe
654 *graph = graph_new();
655 nodegraph_free = *graph;
656 struct cmd_token *token = new_cmd_token(START_TKN, 0, NULL, NULL);
657 graph_new_node(*graph, token, (void (*)(void *)) & del_cmd_token);
658 if (vty)
659 vty_out(vty, "initialized graph%s", VTY_NEWLINE);
660 }