]> git.proxmox.com Git - mirror_frr.git/blob - lib/grammar_sandbox.c
Merge pull request #1038 from donaldsharp/zserv
[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 (%s)\n",
408 scannode - 1, node_names[scannode - 1]);
409 }
410
411 commands = cmd_graph_permutations(nodegraph);
412 prev = NULL;
413 for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) {
414 int same = prev && !strcmp(prev->cmd, cur->cmd);
415 if (printall && !same)
416 vty_out(vty, "'%s' [%x]\n", cur->cmd,
417 cur->el->daemon);
418 if (same) {
419 vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd);
420 vty_out(vty, " %s\n '%s'\n", prev->el->name,
421 prev->el->string);
422 vty_out(vty, " %s\n '%s'\n", cur->el->name,
423 cur->el->string);
424 vty_out(vty, "\n");
425 ambig++;
426 }
427 prev = cur;
428 }
429 list_delete(commands);
430
431 vty_out(vty, "\n");
432 } while (scan && scannode < LINK_PARAMS_NODE);
433
434 vty_out(vty, "%d ambiguous commands found.\n", ambig);
435
436 if (scan)
437 nodegraph = NULL;
438 return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED;
439 }
440
441 DEFUN (grammar_init_graph,
442 grammar_init_graph_cmd,
443 "grammar init",
444 GRAMMAR_STR
445 "(re)initialize graph\n")
446 {
447 if (nodegraph_free)
448 graph_delete_graph(nodegraph_free);
449 nodegraph_free = NULL;
450
451 init_cmdgraph(vty, &nodegraph);
452 return CMD_SUCCESS;
453 }
454
455 DEFUN (grammar_access,
456 grammar_access_cmd,
457 "grammar access (0-65535)",
458 GRAMMAR_STR
459 "access node graph\n"
460 "node number\n")
461 {
462 if (nodegraph_free)
463 graph_delete_graph(nodegraph_free);
464 nodegraph_free = NULL;
465
466 struct cmd_node *cnode;
467
468 cnode = vector_slot(cmdvec, atoi(argv[2]->arg));
469 if (!cnode) {
470 vty_out(vty, "%% no such node\n");
471 return CMD_WARNING_CONFIG_FAILED;
472 }
473
474 vty_out(vty, "node %d\n", (int)cnode->node);
475 nodegraph = cnode->cmdgraph;
476 return CMD_SUCCESS;
477 }
478
479 /* this is called in vtysh.c to set up the testing shim */
480 void grammar_sandbox_init(void)
481 {
482 // install all enable elements
483 install_element(ENABLE_NODE, &grammar_test_cmd);
484 install_element(ENABLE_NODE, &grammar_test_show_cmd);
485 install_element(ENABLE_NODE, &grammar_test_dot_cmd);
486 install_element(ENABLE_NODE, &grammar_test_match_cmd);
487 install_element(ENABLE_NODE, &grammar_test_complete_cmd);
488 install_element(ENABLE_NODE, &grammar_test_doc_cmd);
489 install_element(ENABLE_NODE, &grammar_findambig_cmd);
490 install_element(ENABLE_NODE, &grammar_init_graph_cmd);
491 install_element(ENABLE_NODE, &grammar_access_cmd);
492 }
493
494 #define item(x) { x, #x }
495 struct message tokennames[] = {item(WORD_TKN), // words
496 item(VARIABLE_TKN), // almost anything
497 item(RANGE_TKN), // integer range
498 item(IPV4_TKN), // IPV4 addresses
499 item(IPV4_PREFIX_TKN), // IPV4 network prefixes
500 item(IPV6_TKN), // IPV6 prefixes
501 item(IPV6_PREFIX_TKN), // IPV6 network prefixes
502 item(MAC_TKN), // MAC address
503 item(MAC_PREFIX_TKN), // MAC address w/ mask
504
505 /* plumbing types */
506 item(FORK_TKN),
507 item(JOIN_TKN),
508 item(START_TKN), // first token in line
509 item(END_TKN), // last token in line
510 {0}};
511
512 /**
513 * Pretty-prints a graph, assuming it is a tree.
514 *
515 * @param start the node to take as the root
516 * @param level indent level for recursive calls, always pass 0
517 */
518 void pretty_print_graph(struct vty *vty, struct graph_node *start, int level,
519 int desc, struct graph_node **stack, size_t stackpos)
520 {
521 // print this node
522 char tokennum[32];
523 struct cmd_token *tok = start->data;
524
525 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
526 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
527 if (tok->text)
528 vty_out(vty, ":\"%s\"", tok->text);
529 if (tok->varname)
530 vty_out(vty, " => %s", tok->varname);
531 if (desc)
532 vty_out(vty, " ?'%s'", tok->desc);
533 vty_out(vty, " ");
534
535 if (stackpos == MAXDEPTH) {
536 vty_out(vty, " -aborting! (depth limit)\n");
537 return;
538 }
539 stack[stackpos++] = start;
540
541 int numto = desc ? 2 : vector_active(start->to);
542 if (numto) {
543 if (numto > 1)
544 vty_out(vty, "\n");
545 for (unsigned int i = 0; i < vector_active(start->to); i++) {
546 struct graph_node *adj = vector_slot(start->to, i);
547 // if we're listing multiple children, indent!
548 if (numto > 1)
549 for (int j = 0; j < level + 1; j++)
550 vty_out(vty, " ");
551 // if this node is a vararg, just print *
552 if (adj == start)
553 vty_out(vty, "*");
554 else if (((struct cmd_token *)adj->data)->type
555 == END_TKN)
556 vty_out(vty, "--END\n");
557 else {
558 size_t k;
559 for (k = 0; k < stackpos; k++)
560 if (stack[k] == adj) {
561 vty_out(vty, "<<loop@%zu \n",
562 k);
563 break;
564 }
565 if (k == stackpos)
566 pretty_print_graph(
567 vty, adj,
568 numto > 1 ? level + 1 : level,
569 desc, stack, stackpos);
570 }
571 }
572 } else
573 vty_out(vty, "\n");
574 }
575
576 static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start,
577 struct graph_node **stack, size_t stackpos,
578 struct graph_node **visited, size_t *visitpos)
579 {
580 // print this node
581 char tokennum[32];
582 struct cmd_token *tok = start->data;
583 const char *color;
584
585 for (size_t i = 0; i < (*visitpos); i++)
586 if (visited[i] == start)
587 return;
588 visited[(*visitpos)++] = start;
589 if ((*visitpos) == MAXDEPTH * MAXDEPTH)
590 return;
591
592 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
593 fprintf(ofd, " n%p [ shape=box, label=<", start);
594
595 fprintf(ofd, "<b>%s</b>", lookup_msg(tokennames, tok->type, NULL));
596 if (tok->attr == CMD_ATTR_DEPRECATED)
597 fprintf(ofd, " (d)");
598 else if (tok->attr == CMD_ATTR_HIDDEN)
599 fprintf(ofd, " (h)");
600 if (tok->text) {
601 if (tok->type == WORD_TKN)
602 fprintf(ofd,
603 "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
604 tok->text);
605 else
606 fprintf(ofd, "<br/>%s", tok->text);
607 }
608 /* if (desc)
609 fprintf(ofd, " ?'%s'", tok->desc); */
610 switch (tok->type) {
611 case START_TKN:
612 color = "#ccffcc";
613 break;
614 case FORK_TKN:
615 color = "#aaddff";
616 break;
617 case JOIN_TKN:
618 color = "#ddaaff";
619 break;
620 case WORD_TKN:
621 color = "#ffffff";
622 break;
623 default:
624 color = "#ffffff";
625 break;
626 }
627 fprintf(ofd, ">, style = filled, fillcolor = \"%s\" ];\n", color);
628
629 if (stackpos == MAXDEPTH)
630 return;
631 stack[stackpos++] = start;
632
633 for (unsigned int i = 0; i < vector_active(start->to); i++) {
634 struct graph_node *adj = vector_slot(start->to, i);
635 // if this node is a vararg, just print *
636 if (adj == start) {
637 fprintf(ofd, " n%p -> n%p;\n", start, start);
638 } else if (((struct cmd_token *)adj->data)->type == END_TKN) {
639 // struct cmd_token *et = adj->data;
640 fprintf(ofd, " n%p -> end%p;\n", start, adj);
641 fprintf(ofd,
642 " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
643 adj);
644 } else {
645 fprintf(ofd, " n%p -> n%p;\n", start, adj);
646 size_t k;
647 for (k = 0; k < stackpos; k++)
648 if (stack[k] == adj)
649 break;
650 if (k == stackpos) {
651 pretty_print_dot(ofd, opts, adj, stack,
652 stackpos, visited, visitpos);
653 }
654 }
655 }
656 }
657
658
659 /** stuff that should go in command.c + command.h */
660 void init_cmdgraph(struct vty *vty, struct graph **graph)
661 {
662 // initialize graph, add start noe
663 *graph = graph_new();
664 nodegraph_free = *graph;
665 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
666 graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del);
667 if (vty)
668 vty_out(vty, "initialized graph\n");
669 }