]> git.proxmox.com Git - mirror_frr.git/blame - lib/grammar_sandbox.c
*: auto-convert to SPDX License IDs
[mirror_frr.git] / lib / grammar_sandbox.c
CommitLineData
acddc0ed 1// SPDX-License-Identifier: GPL-2.0-or-later
1ab84bf3
QY
2/*
3 * Testing shim and API examples for the new CLI backend.
4 *
5 * This unit defines a number of commands in the old engine that can
6 * be used to test and interact with the new engine.
1ab84bf3
QY
7 * --
8 * Copyright (C) 2016 Cumulus Networks, Inc.
1ab84bf3
QY
9 */
10
b45ac5f5
DL
11#ifdef HAVE_CONFIG_H
12#include "config.h"
13#endif
14
51e156b3 15#include "command.h"
1eb5e8dc 16#include "graph.h"
0a22b979 17#include "linklist.h"
9d0662e0 18#include "command_match.h"
51e156b3
QY
19
20#define GRAMMAR_STR "CLI grammar sandbox\n"
21
bf8d3d6a 22DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc");
fa133e00 23
97c45dae 24/** headers **/
d62a17ae 25void grammar_sandbox_init(void);
eb51bb9b
DL
26static void pretty_print_graph(struct vty *vty, struct graph_node *, int, int,
27 struct graph_node **, size_t);
28static void init_cmdgraph(struct vty *, struct graph **);
1ab84bf3 29
97c45dae 30/** shim interface commands **/
c17faa4b 31static struct graph *nodegraph = NULL, *nodegraph_free = NULL;
340a2b4a 32
d62a17ae 33#define check_nodegraph() \
34 do { \
35 if (!nodegraph) { \
36 vty_out(vty, "nodegraph not initialized\n"); \
37 return CMD_WARNING; \
38 } \
39 } while (0)
45082064 40
9d0662e0
QY
41DEFUN (grammar_test,
42 grammar_test_cmd,
fa133e00 43 "grammar parse LINE...",
9d0662e0 44 GRAMMAR_STR
fa133e00 45 "parse a command\n"
9d0662e0
QY
46 "command to pass to new parser\n")
47{
d62a17ae 48 check_nodegraph();
49
50 int idx_command = 2;
51 // make a string from tokenized command line
52 char *command = argv_concat(argv, argc, idx_command);
53
54 // create cmd_element for parser
55 struct cmd_element *cmd =
56 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
57 cmd->string = command;
58 cmd->doc =
59 "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
60 cmd->func = NULL;
61
62 // parse the command and install it into the command graph
63 struct graph *graph = graph_new();
9eebf97e 64 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
d62a17ae 65 graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
66
67 cmd_graph_parse(graph, cmd);
68 cmd_graph_merge(nodegraph, graph, +1);
69
70 return CMD_SUCCESS;
9d0662e0
QY
71}
72
eceb1066
QY
73DEFUN (grammar_test_complete,
74 grammar_test_complete_cmd,
fa133e00 75 "grammar complete COMMAND...",
9d0662e0 76 GRAMMAR_STR
eceb1066 77 "attempt to complete input on DFA\n"
fa133e00 78 "command to complete\n")
9d0662e0 79{
d62a17ae 80 check_nodegraph();
81
82 int idx_command = 2;
83 char *cmdstr = argv_concat(argv, argc, idx_command);
84 if (!cmdstr)
85 return CMD_SUCCESS;
86
87 vector command = cmd_make_strvec(cmdstr);
88 if (!command) {
89 XFREE(MTYPE_TMP, cmdstr);
90 return CMD_SUCCESS;
91 }
92
93 // generate completions of user input
94 struct list *completions;
95 enum matcher_rv result =
96 command_complete(nodegraph, command, &completions);
97
98 // print completions or relevant error message
99 if (!MATCHER_ERROR(result)) {
100 vector comps = completions_to_vec(completions);
101 struct cmd_token *tkn;
102
103 // calculate length of longest tkn->text in completions
104 unsigned int width = 0, i = 0;
105 for (i = 0; i < vector_active(comps); i++) {
106 tkn = vector_slot(comps, i);
107 unsigned int len = strlen(tkn->text);
108 width = len > width ? len : width;
109 }
110
111 // print completions
112 for (i = 0; i < vector_active(comps); i++) {
113 tkn = vector_slot(comps, i);
114 vty_out(vty, " %-*s %s\n", width, tkn->text,
115 tkn->desc);
116 }
117
118 for (i = 0; i < vector_active(comps); i++)
119 cmd_token_del(
120 (struct cmd_token *)vector_slot(comps, i));
121 vector_free(comps);
122 } else
123 vty_out(vty, "%% No match\n");
124
125 // free resources
6a154c88 126 list_delete(&completions);
d62a17ae 127 cmd_free_strvec(command);
128 XFREE(MTYPE_TMP, cmdstr);
129
130 return CMD_SUCCESS;
340a2b4a
QY
131}
132
eceb1066
QY
133DEFUN (grammar_test_match,
134 grammar_test_match_cmd,
fa133e00 135 "grammar match COMMAND...",
eceb1066
QY
136 GRAMMAR_STR
137 "attempt to match input on DFA\n"
fa133e00 138 "command to match\n")
eceb1066 139{
d62a17ae 140 check_nodegraph();
141
142 int idx_command = 2;
143 if (argv[2]->arg[0] == '#')
144 return CMD_SUCCESS;
145
146 char *cmdstr = argv_concat(argv, argc, idx_command);
147 if (!cmdstr)
148 return CMD_SUCCESS;
149 vector command = cmd_make_strvec(cmdstr);
150 if (!command) {
151 XFREE(MTYPE_TMP, cmdstr);
152 return CMD_SUCCESS;
153 }
154
155 struct list *argvv = NULL;
156 const struct cmd_element *element = NULL;
157 enum matcher_rv result =
158 command_match(nodegraph, command, &argvv, &element);
159
160 // print completions or relevant error message
161 if (element) {
162 vty_out(vty, "Matched: %s\n", element->string);
163 struct listnode *ln;
164 struct cmd_token *token;
165 for (ALL_LIST_ELEMENTS_RO(argvv, ln, token))
166 vty_out(vty, "%s -- %s\n", token->text, token->arg);
167
168 vty_out(vty, "func: %p\n", element->func);
169
6a154c88 170 list_delete(&argvv);
d62a17ae 171 } else {
172 assert(MATCHER_ERROR(result));
173 switch (result) {
174 case MATCHER_NO_MATCH:
175 vty_out(vty, "%% Unknown command\n");
176 break;
177 case MATCHER_INCOMPLETE:
178 vty_out(vty, "%% Incomplete command\n");
179 break;
180 case MATCHER_AMBIGUOUS:
181 vty_out(vty, "%% Ambiguous command\n");
182 break;
bde30e78 183 case MATCHER_OK:
d62a17ae 184 vty_out(vty, "%% Unknown error\n");
185 break;
186 }
187 }
188
189 // free resources
190 cmd_free_strvec(command);
191 XFREE(MTYPE_TMP, cmdstr);
192
193 return CMD_SUCCESS;
eceb1066
QY
194}
195
1ab84bf3
QY
196/**
197 * Testing shim to test docstrings
198 */
199DEFUN (grammar_test_doc,
200 grammar_test_doc_cmd,
201 "grammar test docstring",
202 GRAMMAR_STR
203 "Test function for docstring\n"
204 "Command end\n")
205{
d62a17ae 206 check_nodegraph();
207
208 // create cmd_element with docstring
209 struct cmd_element *cmd =
210 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
211 cmd->string = XSTRDUP(
212 MTYPE_CMD_TOKENS,
213 "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
214 cmd->doc = XSTRDUP(MTYPE_CMD_TOKENS,
215 "Test stuff\n"
216 "docstring thing\n"
217 "first example\n"
218 "second example\n"
219 "follow\n"
220 "random range\n"
221 "end thingy\n"
222 "variable\n"
223 "optional variable\n"
224 "optional set\n"
225 "optional lol\n"
226 "vararg!\n");
227 cmd->func = NULL;
228
229 // parse element
230 cmd_graph_parse(nodegraph, cmd);
231
232 return CMD_SUCCESS;
1ab84bf3
QY
233}
234
235/**
236 * Debugging command to print command graph
237 */
238DEFUN (grammar_test_show,
239 grammar_test_show_cmd,
fa133e00 240 "grammar show [doc]",
1ab84bf3 241 GRAMMAR_STR
fa133e00
DL
242 "print current accumulated DFA\n"
243 "include docstrings\n")
1ab84bf3 244{
d62a17ae 245 check_nodegraph();
fa133e00 246
09f6d019 247 struct graph_node *stack[CMD_ARGC_MAX];
d62a17ae 248 pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3,
249 stack, 0);
250 return CMD_SUCCESS;
1ab84bf3 251}
51e156b3 252
818a5168
DL
253DEFUN (grammar_test_dot,
254 grammar_test_dot_cmd,
255 "grammar dotfile OUTNAME",
256 GRAMMAR_STR
257 "print current graph for dot\n"
258 ".dot filename\n")
259{
d62a17ae 260 check_nodegraph();
d62a17ae 261 FILE *ofd = fopen(argv[2]->arg, "w");
26fbe472 262
d62a17ae 263 if (!ofd) {
264 vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
265 return CMD_SUCCESS;
266 }
267
26fbe472
QY
268 char *dot = cmd_graph_dump_dot(nodegraph);
269
270 fprintf(ofd, "%s", dot);
d62a17ae 271 fclose(ofd);
26fbe472
QY
272 XFREE(MTYPE_TMP, dot);
273
d62a17ae 274 return CMD_SUCCESS;
818a5168
DL
275}
276
d62a17ae 277struct cmd_permute_item {
278 char *cmd;
279 struct cmd_element *el;
0a22b979
DL
280};
281
d62a17ae 282static void cmd_permute_free(void *arg)
0a22b979 283{
d62a17ae 284 struct cmd_permute_item *i = arg;
285 XFREE(MTYPE_TMP, i->cmd);
286 XFREE(MTYPE_TMP, i);
0a22b979
DL
287}
288
d62a17ae 289static int cmd_permute_cmp(void *a, void *b)
0a22b979 290{
d62a17ae 291 struct cmd_permute_item *aa = a, *bb = b;
292 return strcmp(aa->cmd, bb->cmd);
0a22b979
DL
293}
294
d62a17ae 295static void cmd_graph_permute(struct list *out, struct graph_node **stack,
296 size_t stackpos, char *cmd)
0a22b979 297{
d62a17ae 298 struct graph_node *gn = stack[stackpos];
299 struct cmd_token *tok = gn->data;
300 char *appendp = cmd + strlen(cmd);
c683bd44 301 size_t j;
d62a17ae 302
303 if (tok->type < SPECIAL_TKN) {
304 sprintf(appendp, "%s ", tok->text);
305 appendp += strlen(appendp);
306 } else if (tok->type == END_TKN) {
307 struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i));
308 i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data;
309 i->cmd = XSTRDUP(MTYPE_TMP, cmd);
310 i->cmd[strlen(cmd) - 1] = '\0';
311 listnode_add_sort(out, i);
312 return;
313 }
314
09f6d019 315 if (++stackpos == CMD_ARGC_MAX)
d62a17ae 316 return;
317
c683bd44 318 for (size_t i = 0; i < vector_active(gn->to); i++) {
d62a17ae 319 struct graph_node *gnext = vector_slot(gn->to, i);
320 for (j = 0; j < stackpos; j++)
321 if (stack[j] == gnext)
322 break;
323 if (j != stackpos)
324 continue;
325
326 stack[stackpos] = gnext;
327 *appendp = '\0';
328 cmd_graph_permute(out, stack, stackpos, cmd);
329 }
0a22b979
DL
330}
331
d62a17ae 332static struct list *cmd_graph_permutations(struct graph *graph)
0a22b979 333{
d62a17ae 334 char accumulate[2048] = "";
09f6d019 335 struct graph_node *stack[CMD_ARGC_MAX];
d62a17ae 336
337 struct list *rv = list_new();
338 rv->cmp = cmd_permute_cmp;
339 rv->del = cmd_permute_free;
340 stack[0] = vector_slot(graph->nodes, 0);
341 cmd_graph_permute(rv, stack, 0, accumulate);
342 return rv;
0a22b979
DL
343}
344
345extern vector cmdvec;
346
347DEFUN (grammar_findambig,
348 grammar_findambig_cmd,
349 "grammar find-ambiguous [{printall|nodescan}]",
350 GRAMMAR_STR
351 "Find ambiguous commands\n"
352 "Print all permutations\n"
353 "Scan all nodes\n")
354{
d62a17ae 355 struct list *commands;
356 struct cmd_permute_item *prev = NULL, *cur = NULL;
357 struct listnode *ln;
358 int i, printall, scan, scannode = 0;
359 int ambig = 0;
360
361 i = 0;
362 printall = argv_find(argv, argc, "printall", &i);
363 i = 0;
364 scan = argv_find(argv, argc, "nodescan", &i);
365
366 if (scan && nodegraph_free) {
367 graph_delete_graph(nodegraph_free);
368 nodegraph_free = NULL;
369 }
370
371 if (!scan && !nodegraph) {
372 vty_out(vty, "nodegraph uninitialized\r\n");
373 return CMD_WARNING_CONFIG_FAILED;
374 }
375
376 do {
377 if (scan) {
378 struct cmd_node *cnode =
379 vector_slot(cmdvec, scannode++);
380 if (!cnode)
381 continue;
0e06eb8b 382 cmd_finalize_node(cnode);
d62a17ae 383 nodegraph = cnode->cmdgraph;
384 if (!nodegraph)
385 continue;
996c9314 386 vty_out(vty, "scanning node %d (%s)\n", scannode - 1,
f4b8291f 387 cnode->name);
d62a17ae 388 }
389
390 commands = cmd_graph_permutations(nodegraph);
391 prev = NULL;
392 for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) {
393 int same = prev && !strcmp(prev->cmd, cur->cmd);
394 if (printall && !same)
395 vty_out(vty, "'%s' [%x]\n", cur->cmd,
396 cur->el->daemon);
397 if (same) {
398 vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd);
399 vty_out(vty, " %s\n '%s'\n", prev->el->name,
400 prev->el->string);
401 vty_out(vty, " %s\n '%s'\n", cur->el->name,
402 cur->el->string);
403 vty_out(vty, "\n");
404 ambig++;
405 }
406 prev = cur;
407 }
6a154c88 408 list_delete(&commands);
d62a17ae 409
410 vty_out(vty, "\n");
411 } while (scan && scannode < LINK_PARAMS_NODE);
412
413 vty_out(vty, "%d ambiguous commands found.\n", ambig);
414
415 if (scan)
416 nodegraph = NULL;
417 return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED;
0a22b979
DL
418}
419
500b1a5b
QY
420DEFUN (grammar_init_graph,
421 grammar_init_graph_cmd,
fa133e00 422 "grammar init",
500b1a5b
QY
423 GRAMMAR_STR
424 "(re)initialize graph\n")
425{
d62a17ae 426 if (nodegraph_free)
427 graph_delete_graph(nodegraph_free);
428 nodegraph_free = NULL;
af2567b6 429
d62a17ae 430 init_cmdgraph(vty, &nodegraph);
431 return CMD_SUCCESS;
500b1a5b
QY
432}
433
af2567b6
DL
434DEFUN (grammar_access,
435 grammar_access_cmd,
436 "grammar access (0-65535)",
437 GRAMMAR_STR
438 "access node graph\n"
439 "node number\n")
440{
d62a17ae 441 if (nodegraph_free)
442 graph_delete_graph(nodegraph_free);
443 nodegraph_free = NULL;
444
445 struct cmd_node *cnode;
446
447 cnode = vector_slot(cmdvec, atoi(argv[2]->arg));
448 if (!cnode) {
449 vty_out(vty, "%% no such node\n");
450 return CMD_WARNING_CONFIG_FAILED;
451 }
452
453 vty_out(vty, "node %d\n", (int)cnode->node);
0e06eb8b 454 cmd_finalize_node(cnode);
d62a17ae 455 nodegraph = cnode->cmdgraph;
456 return CMD_SUCCESS;
af2567b6
DL
457}
458
1ab84bf3 459/* this is called in vtysh.c to set up the testing shim */
d62a17ae 460void grammar_sandbox_init(void)
461{
462 // install all enable elements
463 install_element(ENABLE_NODE, &grammar_test_cmd);
464 install_element(ENABLE_NODE, &grammar_test_show_cmd);
465 install_element(ENABLE_NODE, &grammar_test_dot_cmd);
466 install_element(ENABLE_NODE, &grammar_test_match_cmd);
467 install_element(ENABLE_NODE, &grammar_test_complete_cmd);
468 install_element(ENABLE_NODE, &grammar_test_doc_cmd);
469 install_element(ENABLE_NODE, &grammar_findambig_cmd);
470 install_element(ENABLE_NODE, &grammar_init_graph_cmd);
471 install_element(ENABLE_NODE, &grammar_access_cmd);
51e156b3 472}
1ab84bf3 473
97f13f08
QY
474/**
475 * Pretty-prints a graph, assuming it is a tree.
476 *
477 * @param start the node to take as the root
478 * @param level indent level for recursive calls, always pass 0
479 */
eb51bb9b
DL
480static void pretty_print_graph(struct vty *vty, struct graph_node *start,
481 int level, int desc, struct graph_node **stack,
482 size_t stackpos)
1ab84bf3 483{
d62a17ae 484 // print this node
485 char tokennum[32];
486 struct cmd_token *tok = start->data;
487
488 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
489 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
490 if (tok->text)
491 vty_out(vty, ":\"%s\"", tok->text);
492 if (tok->varname)
493 vty_out(vty, " => %s", tok->varname);
494 if (desc)
495 vty_out(vty, " ?'%s'", tok->desc);
496 vty_out(vty, " ");
497
09f6d019 498 if (stackpos == CMD_ARGC_MAX) {
d62a17ae 499 vty_out(vty, " -aborting! (depth limit)\n");
500 return;
501 }
502 stack[stackpos++] = start;
503
504 int numto = desc ? 2 : vector_active(start->to);
505 if (numto) {
506 if (numto > 1)
507 vty_out(vty, "\n");
508 for (unsigned int i = 0; i < vector_active(start->to); i++) {
509 struct graph_node *adj = vector_slot(start->to, i);
510 // if we're listing multiple children, indent!
511 if (numto > 1)
512 for (int j = 0; j < level + 1; j++)
513 vty_out(vty, " ");
514 // if this node is a vararg, just print *
515 if (adj == start)
516 vty_out(vty, "*");
517 else if (((struct cmd_token *)adj->data)->type
518 == END_TKN)
519 vty_out(vty, "--END\n");
520 else {
521 size_t k;
522 for (k = 0; k < stackpos; k++)
523 if (stack[k] == adj) {
524 vty_out(vty, "<<loop@%zu \n",
525 k);
526 break;
527 }
528 if (k == stackpos)
529 pretty_print_graph(
530 vty, adj,
531 numto > 1 ? level + 1 : level,
532 desc, stack, stackpos);
533 }
534 }
535 } else
536 vty_out(vty, "\n");
1eb5e8dc
QY
537}
538
539/** stuff that should go in command.c + command.h */
eb51bb9b 540static void init_cmdgraph(struct vty *vty, struct graph **graph)
97c45dae 541{
d62a17ae 542 // initialize graph, add start noe
543 *graph = graph_new();
544 nodegraph_free = *graph;
545 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
546 graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del);
547 if (vty)
548 vty_out(vty, "initialized graph\n");
97c45dae 549}