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