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