]> git.proxmox.com Git - mirror_frr.git/blob - lib/grammar_sandbox.c
*: reindent
[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
502 /* plumbing types */
503 item(FORK_TKN),
504 item(JOIN_TKN),
505 item(START_TKN), // first token in line
506 item(END_TKN), // last token in line
507 {0}};
508
509 /**
510 * Pretty-prints a graph, assuming it is a tree.
511 *
512 * @param start the node to take as the root
513 * @param level indent level for recursive calls, always pass 0
514 */
515 void pretty_print_graph(struct vty *vty, struct graph_node *start, int level,
516 int desc, struct graph_node **stack, size_t stackpos)
517 {
518 // print this node
519 char tokennum[32];
520 struct cmd_token *tok = start->data;
521
522 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
523 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
524 if (tok->text)
525 vty_out(vty, ":\"%s\"", tok->text);
526 if (tok->varname)
527 vty_out(vty, " => %s", tok->varname);
528 if (desc)
529 vty_out(vty, " ?'%s'", tok->desc);
530 vty_out(vty, " ");
531
532 if (stackpos == MAXDEPTH) {
533 vty_out(vty, " -aborting! (depth limit)\n");
534 return;
535 }
536 stack[stackpos++] = start;
537
538 int numto = desc ? 2 : vector_active(start->to);
539 if (numto) {
540 if (numto > 1)
541 vty_out(vty, "\n");
542 for (unsigned int i = 0; i < vector_active(start->to); i++) {
543 struct graph_node *adj = vector_slot(start->to, i);
544 // if we're listing multiple children, indent!
545 if (numto > 1)
546 for (int j = 0; j < level + 1; j++)
547 vty_out(vty, " ");
548 // if this node is a vararg, just print *
549 if (adj == start)
550 vty_out(vty, "*");
551 else if (((struct cmd_token *)adj->data)->type
552 == END_TKN)
553 vty_out(vty, "--END\n");
554 else {
555 size_t k;
556 for (k = 0; k < stackpos; k++)
557 if (stack[k] == adj) {
558 vty_out(vty, "<<loop@%zu \n",
559 k);
560 break;
561 }
562 if (k == stackpos)
563 pretty_print_graph(
564 vty, adj,
565 numto > 1 ? level + 1 : level,
566 desc, stack, stackpos);
567 }
568 }
569 } else
570 vty_out(vty, "\n");
571 }
572
573 static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start,
574 struct graph_node **stack, size_t stackpos,
575 struct graph_node **visited, size_t *visitpos)
576 {
577 // print this node
578 char tokennum[32];
579 struct cmd_token *tok = start->data;
580 const char *color;
581
582 for (size_t i = 0; i < (*visitpos); i++)
583 if (visited[i] == start)
584 return;
585 visited[(*visitpos)++] = start;
586 if ((*visitpos) == MAXDEPTH * MAXDEPTH)
587 return;
588
589 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
590 fprintf(ofd, " n%p [ shape=box, label=<", start);
591
592 fprintf(ofd, "<b>%s</b>", lookup_msg(tokennames, tok->type, NULL));
593 if (tok->attr == CMD_ATTR_DEPRECATED)
594 fprintf(ofd, " (d)");
595 else if (tok->attr == CMD_ATTR_HIDDEN)
596 fprintf(ofd, " (h)");
597 if (tok->text) {
598 if (tok->type == WORD_TKN)
599 fprintf(ofd,
600 "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
601 tok->text);
602 else
603 fprintf(ofd, "<br/>%s", tok->text);
604 }
605 /* if (desc)
606 fprintf(ofd, " ?'%s'", tok->desc); */
607 switch (tok->type) {
608 case START_TKN:
609 color = "#ccffcc";
610 break;
611 case FORK_TKN:
612 color = "#aaddff";
613 break;
614 case JOIN_TKN:
615 color = "#ddaaff";
616 break;
617 case WORD_TKN:
618 color = "#ffffff";
619 break;
620 default:
621 color = "#ffffff";
622 break;
623 }
624 fprintf(ofd, ">, style = filled, fillcolor = \"%s\" ];\n", color);
625
626 if (stackpos == MAXDEPTH)
627 return;
628 stack[stackpos++] = start;
629
630 for (unsigned int i = 0; i < vector_active(start->to); i++) {
631 struct graph_node *adj = vector_slot(start->to, i);
632 // if this node is a vararg, just print *
633 if (adj == start) {
634 fprintf(ofd, " n%p -> n%p;\n", start, start);
635 } else if (((struct cmd_token *)adj->data)->type == END_TKN) {
636 // struct cmd_token *et = adj->data;
637 fprintf(ofd, " n%p -> end%p;\n", start, adj);
638 fprintf(ofd,
639 " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
640 adj);
641 } else {
642 fprintf(ofd, " n%p -> n%p;\n", start, adj);
643 size_t k;
644 for (k = 0; k < stackpos; k++)
645 if (stack[k] == adj)
646 break;
647 if (k == stackpos) {
648 pretty_print_dot(ofd, opts, adj, stack,
649 stackpos, visited, visitpos);
650 }
651 }
652 }
653 }
654
655
656 /** stuff that should go in command.c + command.h */
657 void init_cmdgraph(struct vty *vty, struct graph **graph)
658 {
659 // initialize graph, add start noe
660 *graph = graph_new();
661 nodegraph_free = *graph;
662 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
663 graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del);
664 if (vty)
665 vty_out(vty, "initialized graph\n");
666 }