From: Quentin Young Date: Mon, 8 Aug 2016 21:11:14 +0000 (+0000) Subject: lib: Code cleanup, formatting, & headers X-Git-Tag: frr-3.0-branchpoint~129^2~258 X-Git-Url: https://git.proxmox.com/?a=commitdiff_plain;h=1ab84bf32f891c9aa62e1d2a42501a0df7d6aec0;p=mirror_frr.git lib: Code cleanup, formatting, & headers Gnu-style code, add copyright headers, cleanup some random style issues, shuffle around code into relevant units, add docs. Signed-off-by: Quentin Young --- diff --git a/lib/command_graph.c b/lib/command_graph.c index 0a866de33..f24993577 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -1,46 +1,52 @@ /* - * Command DFA module. - * Provides a DFA data structure and associated functions for manipulating it. - * Used to match user command line input. + * Graph data structure and companion routines for CLI backend. * - * @author Quentin Young + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. */ - -#include "command_graph.h" #include +#include "command_graph.h" #include "memory.h" struct graph_node * -add_node(struct graph_node *parent, struct graph_node *child) +add_node (struct graph_node *parent, struct graph_node *child) { - vector_set(parent->children, child); + vector_set (parent->children, child); child->refs++; return child; } struct graph_node * -new_node(enum graph_node_type type) +new_node (enum graph_node_type type) { struct graph_node *node = - XMALLOC(MTYPE_CMD_TOKENS, sizeof(struct graph_node)); + XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct graph_node)); node->type = type; node->children = vector_init(VECTOR_MIN_SIZE); - node->end = NULL; - node->text = NULL; - node->element = NULL; - node->arg = NULL; - node->is_start = 0; - node->value = 0; - node->min = 0; - node->max = 0; - node->refs = 0; return node; } void -free_node (struct graph_node *node) +delete_node (struct graph_node *node) { if (!node) return; if (node->children) vector_free (node->children); @@ -51,100 +57,17 @@ free_node (struct graph_node *node) } void -free_graph (struct graph_node *start) +delete_graph (struct graph_node *start) { - if (start && start->children && vector_active(start->children) > 0) { - for (unsigned int i = 0; i < vector_active(start->children); i++) { - free_graph (vector_slot(start->children, i)); - vector_unset(start->children, i); + if (start && start->children && vector_active (start->children) > 0) + { + for (unsigned int i = 0; i < vector_active (start->children); i++) + { + delete_graph (vector_slot(start->children, i)); + vector_unset (start->children, i); + } } - } if (--(start->refs) == 0) - free_node (start); -} - -char * -describe_node(struct graph_node *node, char* buffer, unsigned int bufsize) -{ - if (node == NULL) { - snprintf(buffer, bufsize, "(null node)"); - return buffer; - } - - // print this node - switch (node->type) { - case WORD_GN: - case IPV4_GN: - case IPV4_PREFIX_GN: - case IPV6_GN: - case IPV6_PREFIX_GN: - case VARIABLE_GN: - case RANGE_GN: - snprintf(buffer, bufsize, node->text); - break; - case NUMBER_GN: - snprintf(buffer, bufsize, "%lld", node->value); - break; - case SELECTOR_GN: - snprintf(buffer, bufsize, "<>"); - break; - case OPTION_GN: - snprintf(buffer, bufsize, "[]"); - break; - case NUL_GN: - snprintf(buffer, bufsize, "NUL"); - break; - case END_GN: - snprintf(buffer, bufsize, "END"); - break; - case START_GN: - snprintf(buffer, bufsize, "START"); - break; - default: - snprintf(buffer, bufsize, "ERROR"); - } - - return buffer; -} - -void -walk_graph(struct graph_node *start, int level) -{ - char* desc = malloc(50); - // print this node - fprintf(stderr, "%s[%d] ", describe_node(start, desc, 50), vector_active(start->children)); - free(desc); - - if (vector_active(start->children)) { - if (vector_active(start->children) == 1) - walk_graph(vector_slot(start->children, 0), level); - else { - fprintf(stderr, "\n"); - for (unsigned int i = 0; i < vector_active(start->children); i++) { - struct graph_node *r = vector_slot(start->children, i); - for (int j = 0; j < level+1; j++) - fprintf(stderr, " "); - walk_graph(r, level+1); - } - } - } - else - fprintf(stderr, "\n"); -} - -void -dump_node (struct graph_node *node) -{ - char buf[50]; - describe_node(node, buf, 50); - fprintf(stderr, "%s[%d]\n", buf, node->type); - fprintf(stderr, "\t->text: %s\n", node->text); - fprintf(stderr, "\t->value: %lld\n", node->value); - fprintf(stderr, "\t->is_start: %d\n", node->is_start); - fprintf(stderr, "\t->element: %p\n", node->element); - fprintf(stderr, "\t->min: %lld\n->max: %lld\n", node->min, node->max); - fprintf(stderr, "\t->arg: %s\n", node->arg); - fprintf(stderr, "\t->refs: %d\n", node->refs); - fprintf(stderr, "\tnum children: %d\n", vector_active(node->children)); + delete_node (start); } diff --git a/lib/command_graph.h b/lib/command_graph.h index 718ce6655..48c3d9cd0 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -1,39 +1,74 @@ -#ifndef COMMAND_GRAPH_H -#define COMMAND_GRAPH_H +/* + * Graph data structure and companion routines for CLI backend. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_COMMAND_GRAPH_H +#define _ZEBRA_COMMAND_GRAPH_H #include "command.h" +/** + * Types for graph nodes. + * + * The node type determines what kind of data the node can match (in the + * matching use case) or hold (in the argv use case). + */ enum graph_node_type { - IPV4_GN, - IPV4_PREFIX_GN, - IPV6_GN, - IPV6_PREFIX_GN, - WORD_GN, - RANGE_GN, - NUMBER_GN, - VARIABLE_GN, - SELECTOR_GN, - OPTION_GN, - NUL_GN, - START_GN, - END_GN + IPV4_GN, // IPV4 addresses + IPV4_PREFIX_GN, // IPV4 network prefixes + IPV6_GN, // IPV6 prefixes + IPV6_PREFIX_GN, // IPV6 network prefixes + WORD_GN, // words + RANGE_GN, // integer ranges + NUMBER_GN, // numbers + VARIABLE_GN, // almost anything + /* plumbing types */ + SELECTOR_GN, // marks beginning of selector subgraph + OPTION_GN, // marks beginning of option subgraph + NUL_GN, // transparent node with various uses + START_GN, // first node in the graph (has no parents) + END_GN // leaf node in the graph, has pointer to cmd_element }; +/** + * Command graph node. + * Used for matching and passing arguments to vtysh commands. + */ struct graph_node { - enum graph_node_type type;// data type this node matches or holds - unsigned int is_start; // whether this node is a start node - vector children; // this node's children - struct graph_node * end; // pointer to end for SELECTOR_GN & OPTION_GN + enum graph_node_type type; // data type this node matches or holds + vector children; // this node's children + struct graph_node *end; // pointer to end for SELECTOR_GN & OPTION_GN - char *text; // original format text - char *doc; // docstring for this node - long long value; // for NUMBER_GN - long long min, max; // for RANGE_GN + char *text; // original format text + char *doc; // docstring for this node + long long value; // for NUMBER_GN + long long min, max; // for RANGE_GN /* cmd_element struct pointer, only valid for END_GN */ struct cmd_element *element; + /* used for passing arguments to command functions */ char *arg; @@ -49,52 +84,33 @@ struct graph_node * @return child node */ struct graph_node * -add_node(struct graph_node *, struct graph_node *); +add_node (struct graph_node *parent, struct graph_node *child); -/* - * Create a new node. - * Initializes all fields to default values and sets the node type. +/** + * Creates a new node, initializes all fields to default values and sets the + * node type. * - * @param[in] node type - * @return pointer to the newly allocated node + * @param[in] type node type + * @return pointer to the created node */ struct graph_node * -new_node(enum graph_node_type); - -/** - * Frees the data associated with a graph_node. - * @param[out] pointer to graph_node to free - */ -void -free_node(struct graph_node *); +new_node (enum graph_node_type type); /** - * Recursively calls free_node on a graph node - * and all its children. - * @param[out] graph to free + * Deletes a graph node without deleting its children. + * + * @param[out] node pointer to node to delete */ void -free_graph(struct graph_node *); +delete_node (struct graph_node *node); /** - * Walks a command DFA, printing structure to stdout. - * For debugging. + * Deletes a graph node and recursively deletes all its direct and indirect + * children. * - * @param[in] start node of graph to walk - * @param[in] graph depth for recursion, caller passes 0 + * @param[out] node start node of graph to free */ void -walk_graph(struct graph_node *, int); - -/** - * Returns a string representation of the given node. - * @param[in] the node to describe - * @param[out] the buffer to write the description into - * @return pointer to description string - */ -char * -describe_node(struct graph_node *, char *, unsigned int); +delete_graph (struct graph_node *node); -void -dump_node (struct graph_node *); -#endif +#endif /* _ZEBRA_COMMAND_GRAPH_H */ diff --git a/lib/command_lex.l b/lib/command_lex.l index 600c92d23..920c03db8 100644 --- a/lib/command_lex.l +++ b/lib/command_lex.l @@ -1,3 +1,27 @@ +/* + * Command format string lexer for CLI backend. + * + * -- + * Copyright (C) 2015 Cumulus Networks, Inc. + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + %{ #include "command_parse.h" diff --git a/lib/command_match.c b/lib/command_match.c index e4b95d597..011ac698c 100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@ -1,11 +1,35 @@ +/* + * Input matching routines for CLI backend. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include #include "command_match.h" #include "command_parse.h" -#include #include "memory.h" /* matcher helper prototypes */ static int -add_nexthops(struct list *, struct graph_node *); +add_nexthops (struct list *, struct graph_node *); static struct list * match_command_r (struct graph_node *, vector, unsigned int); @@ -14,7 +38,7 @@ static int score_precedence (enum graph_node_type); static enum match_type -min_match_level(enum node_type); +min_match_level (enum node_type); static struct graph_node * copy_node (struct graph_node *); @@ -23,10 +47,10 @@ static void delete_nodelist (void *); static struct graph_node * -disambiguate (struct graph_node *, struct graph_node *, char *); +disambiguate_nodes (struct graph_node *, struct graph_node *, char *); static struct list * -disambiguate_nodelist (struct list *, struct list *, vector, unsigned int); +disambiguate (struct list *, struct list *, vector, unsigned int); /* token matcher prototypes */ static enum match_type @@ -54,40 +78,33 @@ static enum match_type match_number (struct graph_node *, const char *); static enum match_type -match_variable (struct graph_node *, const char *); +match_variable (struct graph_node *node, const char *word); /* matching functions */ -static enum matcher_rv matcher_result_value; +static enum matcher_rv matcher_rv; enum matcher_rv match_command (struct graph_node *start, - const char *line, - struct list **argvv, + vector vline, + struct list **argv, struct cmd_element **el) { - matcher_result_value = MATCHER_NO_MATCH; - // parse command - vector vline = cmd_make_strvec (line); - - for (unsigned int i = 0; i < vector_active(start->children); i++) - { - // call recursive matcher on each starting child - *argvv = match_command_r(vector_slot(start->children, i), vline, 0); - if (*argvv) break; - } + matcher_rv = MATCHER_NO_MATCH; - if (*argvv) { - struct listnode *ln; - struct graph_node *gn; - for (ALL_LIST_ELEMENTS_RO(*argvv,ln,gn)) - if (gn->type == END_GN) { - *el = gn->element; - break; - } - assert(*el); - } + // call recursive matcher on each starting child + for (unsigned int i = 0; i < vector_active (start->children); i++) + { + *argv = match_command_r (vector_slot (start->children, i), vline, 0); + if (*argv) // successful match + { + struct graph_node *end = listgetdata (listtail (*argv)); + *el = end->element; + assert (*el); + break; + } + } - return matcher_result_value; + return matcher_rv; } /** @@ -134,80 +151,83 @@ match_command (struct graph_node *start, * the best match for the command, each with their `arg` fields pointing to the * matching token string. * - * @param[out] start the start node. + * @param[in] start the start node. * @param[in] vline the vectorized input line. - * @param[in] n the index of the first input token. Should be 0 for external - * callers. + * @param[in] n the index of the first input token. */ static struct list * match_command_r (struct graph_node *start, vector vline, unsigned int n) { // get the minimum match level that can count as a full match - enum match_type minmatch = min_match_level(start->type); + enum match_type minmatch = min_match_level (start->type); // get the current operating token - char *token = vector_slot(vline, n); + char *token = vector_slot (vline, n); // if we don't match this node, die - if (match_token(start, token) < minmatch) + if (match_token (start, token) < minmatch) return NULL; // pointers for iterating linklist + struct listnode *ln; struct graph_node *gn; - struct listnode *ln; // get all possible nexthops struct list *next = list_new(); - add_nexthops(next, start); + add_nexthops (next, start); // determine the best match int ambiguous = 0; struct list *currbest = NULL; - for (ALL_LIST_ELEMENTS_RO(next,ln,gn)) - { - // if we've matched all input we're looking for END_GN - if (n+1 == vector_active (vline)) { - if (gn->type == END_GN) { - currbest = list_new(); - listnode_add(currbest, copy_node(gn)); - currbest->del = &delete_nodelist; - break; - } - else continue; - } + for (ALL_LIST_ELEMENTS_RO (next,ln,gn)) + { + // if we've matched all input we're looking for END_GN + if (n+1 == vector_active (vline)) + { + if (gn->type == END_GN) + { + currbest = list_new(); + listnode_add (currbest, copy_node(gn)); + currbest->del = &delete_nodelist; + break; + } + else continue; + } - // else recurse on candidate child node - struct list *result = match_command_r (gn, vline, n+1); + // else recurse on candidate child node + struct list *result = match_command_r (gn, vline, n+1); - // save the best match, subtle logic at play here - if (result && currbest) { - struct list *newbest = disambiguate_nodelist(currbest, result, vline, n+1); - ambiguous = !newbest || (ambiguous && newbest == currbest); - list_delete ((newbest && newbest == result) ? currbest : result); - currbest = newbest ? newbest : currbest; + // save the best match + if (result && currbest) + { + struct list *newbest = disambiguate (currbest, result, vline, n+1); + ambiguous = !newbest || (ambiguous && newbest == currbest); + list_delete ((newbest && newbest == result) ? currbest : result); + currbest = newbest ? newbest : currbest; + } + else if (result) + currbest = result; } - else if (result) - currbest = result; - } - if (currbest) { - if (ambiguous) { - list_delete(currbest); - currbest = NULL; - matcher_result_value = MATCHER_AMBIGUOUS; - } - else { - // copy current node, set arg and prepend to currbest - struct graph_node *curr = copy_node(start); - curr->arg = XSTRDUP(MTYPE_CMD_TOKENS, token); - list_add_node_prev (currbest, currbest->head, curr); - matcher_result_value = MATCHER_OK; + if (currbest) + { + if (ambiguous) + { + list_delete (currbest); + currbest = NULL; + matcher_rv = MATCHER_AMBIGUOUS; + } + else + { + // copy current node, set arg and prepend to currbest + struct graph_node *curr = copy_node (start); + curr->arg = XSTRDUP(MTYPE_CMD_TOKENS, token); + list_add_node_prev (currbest, currbest->head, curr); + matcher_rv = MATCHER_OK; + } } - } - else { - if (n+1 == vector_active(vline) && matcher_result_value == MATCHER_NO_MATCH) - matcher_result_value = MATCHER_INCOMPLETE; - } + else if (n+1 == vector_active (vline) && matcher_rv == MATCHER_NO_MATCH) + matcher_rv = MATCHER_INCOMPLETE; // cleanup list_delete (next); @@ -215,12 +235,9 @@ match_command_r (struct graph_node *start, vector vline, unsigned int n) return currbest; } -struct list * -match_command_complete (struct graph_node *start, const char *line) +enum matcher_rv +match_command_complete (struct graph_node *start, vector vline, struct list **completions) { - // vectorize command line - vector vline = cmd_make_strvec (line); - // pointer to next input token to match char *token; @@ -232,33 +249,35 @@ match_command_complete (struct graph_node *start, const char *line) struct listnode *node; // add all children of start node to list - add_nexthops(next, start); + add_nexthops (next, start); unsigned int idx; - for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) - { - list_free (current); - current = next; - next = list_new(); + for (idx = 0; idx < vector_active (vline) && next->count > 0; idx++) + { + list_free (current); + current = next; + next = list_new(); - token = vector_slot(vline, idx); + token = vector_slot (vline, idx); - for (ALL_LIST_ELEMENTS_RO(current,node,gn)) - { - switch (match_token(gn, token)) { - case partly_match: - if (idx == vector_active(vline) - 1) { - listnode_add(next, gn); - break; - } - case exact_match: - add_nexthops(next, gn); - break; - default: - break; - } + for (ALL_LIST_ELEMENTS_RO (current,node,gn)) + { + switch (match_token (gn, token)) + { + case partly_match: + if (idx == vector_active (vline) - 1) + { + listnode_add (next, gn); + break; + } + case exact_match: + add_nexthops (next, gn); + break; + default: + break; + } + } } - } /* Variable summary * ----------------------------------------------------------------- @@ -268,101 +287,122 @@ match_command_complete (struct graph_node *start, const char *line) * next = set of all nodes reachable from all nodes in `matched` */ - matcher_result_value = + matcher_rv = idx + 1 == vector_active(vline) && next->count ? MATCHER_OK : MATCHER_NO_MATCH; list_free (current); - cmd_free_strvec(vline); + *completions = next; - return next; + return matcher_rv; } /** - * Adds all children that are reachable by one parser hop - * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN - * nodes are treated as transparent. + * Adds all children that are reachable by one parser hop to the given list. + * NUL_GN, SELECTOR_GN, and OPTION_GN nodes are treated as transparent. * - * @param[out] l the list to add the children to - * @param[in] node the node to get the children of + * @param[in] list to add the nexthops to + * @param[in] node to start calculating nexthops from * @return the number of children added to the list */ static int -add_nexthops(struct list *l, struct graph_node *node) +add_nexthops (struct list *list, struct graph_node *node) { int added = 0; struct graph_node *child; - for (unsigned int i = 0; i < vector_active(node->children); i++) - { - child = vector_slot(node->children, i); - switch (child->type) { - case OPTION_GN: - case SELECTOR_GN: - case NUL_GN: - added += add_nexthops(l, child); - break; - default: - listnode_add(l, child); - added++; + for (unsigned int i = 0; i < vector_active (node->children); i++) + { + child = vector_slot (node->children, i); + switch (child->type) + { + case OPTION_GN: + case SELECTOR_GN: + case NUL_GN: + added += add_nexthops (list, child); + break; + default: + listnode_add (list, child); + added++; + } } - } + return added; } /** * Determines the node types for which a partial match may count as a full * match. Enables command abbrevations. + * + * @param[in] type node type + * @return minimum match level needed to for a token to fully match */ static enum match_type -min_match_level(enum node_type type) +min_match_level (enum node_type type) { - switch (type) { - case WORD_GN: - return partly_match; - default: - return exact_match; - } + switch (type) + { + // allowing words to partly match enables command abbreviation + case WORD_GN: + return partly_match; + default: + return exact_match; + } } -/* Precedence score used to disambiguate matches. */ +/** + * Assigns precedence scores to node types. + * + * @param[in] type node type to score + * @return precedence score + */ static int score_precedence (enum graph_node_type type) { switch (type) - { - // some of these are mutually exclusive, so they share - // the same precedence value - case IPV4_GN: - case IPV4_PREFIX_GN: - case IPV6_GN: - case IPV6_PREFIX_GN: - case NUMBER_GN: - return 1; - case RANGE_GN: - return 2; - case WORD_GN: - return 3; - case VARIABLE_GN: - return 4; - default: - return 10; - } + { + // some of these are mutually exclusive, so they share + // the same precedence value + case IPV4_GN: + case IPV4_PREFIX_GN: + case IPV6_GN: + case IPV6_PREFIX_GN: + case NUMBER_GN: + return 1; + case RANGE_GN: + return 2; + case WORD_GN: + return 3; + case VARIABLE_GN: + return 4; + default: + return 10; + } } -/* Disambiguation logic to pick the best of two possible matches */ +/** + * Picks the better of two possible matches for a token. + * + * @param[in] first candidate node matching token + * @param[in] second candidate node matching token + * @param[in] token the token being matched + * @return the best-matching node, or NULL if the two are entirely ambiguous + */ static struct graph_node * -disambiguate (struct graph_node *first, struct graph_node *second, char *token) +disambiguate_nodes (struct graph_node *first, + struct graph_node *second, + char *token) { // if the types are different, simply go off of type precedence - if (first->type != second->type) { - int firstprec = score_precedence(first->type); - int secndprec = score_precedence(second->type); - if (firstprec != secndprec) - return firstprec < secndprec ? first : second; - else - return NULL; - } + if (first->type != second->type) + { + int firstprec = score_precedence (first->type); + int secndprec = score_precedence (second->type); + if (firstprec != secndprec) + return firstprec < secndprec ? first : second; + else + return NULL; + } // if they're the same, return the more exact match enum match_type fmtype = match_token (first, token); @@ -373,40 +413,60 @@ disambiguate (struct graph_node *first, struct graph_node *second, char *token) return NULL; } +/** + * Picks the better of two possible matches for an input line. + * + * @param[in] first candidate list of graph_node matching vline + * @param[in] second candidate list of graph_node matching vline + * @param[in] vline the input line being matched + * @param[in] n index into vline to start comparing at + * @return the best-matching list, or NULL if the two are entirely ambiguous + */ static struct list * -disambiguate_nodelist (struct list *first, struct list *second, vector vline, unsigned int n) +disambiguate (struct list *first, + struct list *second, + vector vline, + unsigned int n) { - fprintf(stderr, "%d --- %d\n", first->count, n); - // doesn't make sense for these to be inequal length - assert(first->count == second->count); - assert(first->count == vector_active(vline) - n+1); + assert (first->count == second->count); + assert (first->count == vector_active (vline) - n+1); - struct listnode *fnode = listhead(first), - *snode = listhead(second); - struct graph_node *fgn = listgetdata(fnode), - *sgn = listgetdata(snode), + struct listnode *fnode = listhead (first), + *snode = listhead (second); + struct graph_node *fgn = listgetdata (fnode), + *sgn = listgetdata (snode), *best = NULL; // compare each node, if one matches better use that one - for (unsigned int i = n; i < vector_active(vline); i++) { - if ((best = disambiguate (fgn, sgn, (char*) vector_slot(vline, i)))) - return best == fgn ? first : second; - fnode = listnextnode(fnode); - fgn = (struct graph_node *) listgetdata (fnode); - snode = listnextnode(snode); - sgn = (struct graph_node *) listgetdata (snode); - } + for (unsigned int i = n; i < vector_active (vline); i++) + { + char *token = vector_slot(vline, i); + if ((best = disambiguate_nodes (fgn, sgn, token))) + return best == fgn ? first : second; + fnode = listnextnode (fnode); + snode = listnextnode (snode); + fgn = (struct graph_node *) listgetdata (fnode); + sgn = (struct graph_node *) listgetdata (snode); + } return NULL; } +/** + * Performs a deep copy on a node. + * Used to build argv node lists that can be safely deleted or modified by + * endpoint functions. Everything is copied except the children vector, + * subgraph end pointer and reference count. + * + * @param[in] node to copy + * @return the copy + */ static struct graph_node * copy_node (struct graph_node *node) { struct graph_node *new = new_node(node->type); new->children = NULL; - new->is_start = node->is_start; new->end = NULL; new->text = node->text ? XSTRDUP(MTYPE_CMD_TOKENS, node->text) : NULL; new->value = node->value; @@ -418,11 +478,13 @@ copy_node (struct graph_node *node) return new; } -/* Linked list data deletion callback */ +/** + * List deletion callback for argv lists. + */ static void delete_nodelist (void *node) { - free_node ((struct graph_node *) node); + delete_node ((struct graph_node *) node); } @@ -632,7 +694,7 @@ match_ipv6_prefix (const char *str) return no_match; /* tokenize to address + mask */ - dupe = XMALLOC(MTYPE_TMP, strlen(str)+1); + dupe = XCALLOC(MTYPE_TMP, strlen(str)+1); strncpy(dupe, str, strlen(str)+1); prefix = strtok_r(dupe, delim, &context); mask = strtok_r(NULL, delim, &context); @@ -656,8 +718,10 @@ match_ipv6_prefix (const char *str) #endif static enum match_type -match_range (struct graph_node *rangenode, const char *str) +match_range (struct graph_node *node, const char *str) { + assert (node->type == RANGE_GN); + char *endptr = NULL; long long val; @@ -668,45 +732,54 @@ match_range (struct graph_node *rangenode, const char *str) if (*endptr != '\0') return 0; - if (val < rangenode->min || val > rangenode->max) + if (val < node->min || val > node->max) return no_match; else return exact_match; } static enum match_type -match_word(struct graph_node *wordnode, const char *word) +match_word (struct graph_node *node, const char *word) { + assert (node->type == WORD_GN); + // if the passed token is null or 0 length, partly match if (!word || !strlen(word)) return partly_match; // if the passed token is strictly a prefix of the full word, partly match - if (strlen(word) < strlen(wordnode->text)) - return !strncmp(wordnode->text, word, strlen(word)) ? partly_match : no_match; + if (strlen (word) < strlen (node->text)) + return !strncmp (node->text, word, strlen (word)) ? + partly_match : + no_match; // if they are the same length and exactly equal, exact match - else if (strlen(word) == strlen(wordnode->text)) - return !strncmp(wordnode->text, word, strlen(word)) ? exact_match : no_match; + else if (strlen (word) == strlen (node->text)) + return !strncmp (node->text, word, strlen (word)) ? exact_match : no_match; return no_match; } static enum match_type -match_number(struct graph_node *numnode, const char *word) +match_number (struct graph_node *node, const char *word) { - if (!strcmp("\0", word)) return no_match; + assert (node->type == NUMBER_GN); + + if (!strcmp ("\0", word)) return no_match; char *endptr; long long num = strtoll (word, &endptr, 10); if (endptr != '\0') return no_match; - return num == numnode->value ? exact_match : no_match; + return num == node->value ? exact_match : no_match; } -#define VARIABLE_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:" +#define VARIABLE_ALPHABET \ +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:" static enum match_type -match_variable(struct graph_node *varnode, const char *word) +match_variable (struct graph_node *node, const char *word) { - return strlen(word) == strspn(word, VARIABLE_ALPHABET) ? + assert (node->type == VARIABLE_GN); + + return strlen (word) == strspn(word, VARIABLE_ALPHABET) ? exact_match : no_match; } diff --git a/lib/command_match.h b/lib/command_match.h index 895a678dc..6804a0777 100644 --- a/lib/command_match.h +++ b/lib/command_match.h @@ -1,21 +1,45 @@ -#ifndef COMMAND_MATCH_H -#define COMMAND_MATCH_H +/* + * Input matching routines for CLI backend. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _ZEBRA_COMMAND_MATCH_H +#define _ZEBRA_COMMAND_MATCH_H #include "command.h" #include "command_graph.h" #include "linklist.h" -/** These definitions exist in command.c in - * the current engine but will be relocated - * here in the new engine*/ +/* These definitions exist in command.c in the current engine but should be + * relocated here in the new engine + */ enum filter_type { FILTER_RELAXED, FILTER_STRICT }; -/* matcher result value. */ +/* matcher result value */ enum matcher_rv { MATCHER_NO_MATCH, @@ -24,7 +48,7 @@ enum matcher_rv MATCHER_OK, }; -/* Completion match types. */ +/* completion match types */ enum match_type { no_match, @@ -32,9 +56,8 @@ enum match_type exact_match }; -/* Defines which matcher_rv values constitute - * an error. Should be used against matcher_rv - * return values to do basic error checking. +/* Defines which matcher_rv values constitute an error. Should be used with + * matcher_rv return values to do basic error checking. */ #define MATCHER_ERROR(matcher_rv) \ ( (matcher_rv) == MATCHER_INCOMPLETE \ @@ -45,37 +68,29 @@ enum match_type /** * Attempt to find an exact command match for a line of user input. * - * @param DFA to match against - * @param input string - * @param pointer which will be pointed at argv upon match - * @param pointer which will be pointed at matching cmd_element upon match - * @return result of matcher run + * @param[in] start start node of command graph to match against + * @param[in] vline vectorized input string + * @param[out] argv pointer to argument list if successful match + * @param[out] element pointer to matched cmd_element if successful match + * @return matcher status */ enum matcher_rv -match_command (struct graph_node *, const char *, struct list **, struct cmd_element **); +match_command (struct graph_node *start, + vector vline, + struct list **argv, + struct cmd_element **element); /** - * Compiles next-hops for a given line of user input. - * - * Given a string of input and a start node for a matching DFA, runs the input - * against the DFA until the input is exhausted or a mismatch is encountered. - * - * This function returns all valid next hops away from the current node. - * - If the input is a valid prefix to a longer command(s), the set of next - * hops determines what tokens are valid to follow the prefix. In other words, - * the returned list is a list of possible completions. - * - If the input matched a full command, exactly one of the next hops will be - * a node of type END_GN and its function pointer will be set. - * - If the input did not match any valid token sequence, the returned list - * will be empty (there are no transitions away from a nonexistent state). + * Compiles possible completions for a given line of user input. * * @param[in] start the start node of the DFA to match against - * @param[in] filter the filtering method - * @param[in] input the input string - * @return pointer to linked list with all possible next hops from the last - * matched token. If this is empty, the input did not match any command. + * @param[in] vline vectorized input string + * @param[in] completions pointer to possible completions + * @return matcher status */ -struct list * -match_command_complete (struct graph_node *, const char *); +enum matcher_rv +match_command_complete (struct graph_node *start, + vector vline, + struct list **completions); -#endif +#endif /* _ZEBRA_COMMAND_MATCH_H */ diff --git a/lib/command_parse.y b/lib/command_parse.y index 5a8bb6930..6b972afe9 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -1,15 +1,30 @@ /* - * Command format string parser. + * Command format string parser for CLI backend. * - * Turns a command definition into a DFA that together with the functions - * provided in command_match.c may be used to map command line input to a - * function. + * -- + * Copyright (C) 2015 Cumulus Networks, Inc. * - * @author Quentin Young + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. */ %{ -#define YYDEBUG 1 // compile with debugging facilities +// compile with debugging facilities +#define YYDEBUG 1 %} /* names for generated header and parser files */ @@ -23,16 +38,16 @@ #include "memory.h" extern int - yylex(void); + yylex (void); extern void - set_buffer_string(const char *); + set_buffer_string (const char *); } /* functionality this unit exports */ %code provides { struct graph_node * - parse_command_format(struct graph_node *, struct cmd_element *); + parse_command_format (struct graph_node *, struct cmd_element *); /* maximum length of a number, lexer will not match anything longer */ #define DECIMAL_STRLEN_MAX 20 @@ -71,7 +86,7 @@ %code { /* bison declarations */ void - yyerror(struct cmd_element *el, struct graph_node *sn, char const *msg); + yyerror (struct cmd_element *el, struct graph_node *sn, char const *msg); /* state variables for a single parser run */ struct graph_node *currnode, // current position in DFA @@ -90,13 +105,13 @@ doc_next(void); static struct graph_node * - node_exists(struct graph_node *, struct graph_node *); + node_exists (struct graph_node *, struct graph_node *); static struct graph_node * - node_replace(struct graph_node *, struct graph_node *); + node_replace (struct graph_node *, struct graph_node *); static int - cmp_node(struct graph_node *, struct graph_node *); + cmp_node (struct graph_node *, struct graph_node *); static void terminate_graph (struct graph_node *, @@ -104,7 +119,7 @@ struct cmd_element *); static void - cleanup(void); + cleanup (void); } /* yyparse parameters */ @@ -120,7 +135,7 @@ optnode_start = optnode_end = NULL; /* set string to parse */ - set_buffer_string(element->string); + set_buffer_string (element->string); /* copy docstring and keep a pointer to the copy */ docstr = element->doc ? XSTRDUP(MTYPE_TMP, element->doc) : NULL; @@ -138,13 +153,12 @@ start: } | sentence_root cmd_token_seq '.' placeholder_token { - if ((currnode = node_replace(currnode, $4)) != $4) - free_node ($4); + if ((currnode = node_replace (currnode, $4)) != $4) + delete_node ($4); - // since varargs may match any number of the last token, - // simply add this node as a child of itself and proceed - // wth normal command termination procedure - node_replace(currnode, currnode); + // adding a node as a child of itself accepts any number + // of the same token, which is what we want for varags + node_replace (currnode, currnode); // tack on the command element terminate_graph (startnode, currnode, element); @@ -153,11 +167,11 @@ start: sentence_root: WORD { - struct graph_node *root = new_node(WORD_GN); + struct graph_node *root = new_node (WORD_GN); root->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); root->doc = doc_next(); - if ((currnode = node_replace(startnode, root)) != root) + if ((currnode = node_replace (startnode, root)) != root) free (root); free ($1); @@ -167,24 +181,24 @@ sentence_root: WORD cmd_token: placeholder_token { - if ((currnode = node_replace(currnode, $1)) != $1) - free_node ($1); + if ((currnode = node_replace (currnode, $1)) != $1) + delete_node ($1); } | literal_token { - if ((currnode = node_replace(currnode, $1)) != $1) - free_node ($1); + if ((currnode = node_replace (currnode, $1)) != $1) + delete_node ($1); } /* selectors and options are subgraphs with start and end nodes */ | selector { - add_node(currnode, $1); + add_node (currnode, $1); currnode = selnode_end; selnode_start = selnode_end = NULL; } | option { - add_node(currnode, $1); + add_node (currnode, $1); currnode = optnode_end; optnode_start = optnode_end = NULL; } @@ -198,53 +212,53 @@ cmd_token_seq: placeholder_token: IPV4 { - $$ = new_node(IPV4_GN); + $$ = new_node (IPV4_GN); $$->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); $$->doc = doc_next(); free ($1); } | IPV4_PREFIX { - $$ = new_node(IPV4_PREFIX_GN); + $$ = new_node (IPV4_PREFIX_GN); $$->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); $$->doc = doc_next(); free ($1); } | IPV6 { - $$ = new_node(IPV6_GN); + $$ = new_node (IPV6_GN); $$->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); $$->doc = doc_next(); free ($1); } | IPV6_PREFIX { - $$ = new_node(IPV6_PREFIX_GN); + $$ = new_node (IPV6_PREFIX_GN); $$->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); $$->doc = doc_next(); free ($1); } | VARIABLE { - $$ = new_node(VARIABLE_GN); + $$ = new_node (VARIABLE_GN); $$->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); $$->doc = doc_next(); free ($1); } | RANGE { - $$ = new_node(RANGE_GN); + $$ = new_node (RANGE_GN); $$->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); $$->doc = doc_next(); // get the numbers out yylval.string++; - $$->min = strtoll( yylval.string, &yylval.string, 10 ); + $$->min = strtoll (yylval.string, &yylval.string, 10); strsep (&yylval.string, "-"); - $$->max = strtoll( yylval.string, &yylval.string, 10 ); + $$->max = strtoll (yylval.string, &yylval.string, 10); // validate range - if ($$->min >= $$->max) yyerror(element, startnode, "Invalid range."); + if ($$->min >= $$->max) yyerror (element, startnode, "Invalid range."); free ($1); } @@ -253,14 +267,14 @@ placeholder_token: literal_token: WORD { - $$ = new_node(WORD_GN); + $$ = new_node (WORD_GN); $$->text = XSTRDUP(MTYPE_CMD_TOKENS, $1); $$->doc = doc_next(); free ($1); } | NUMBER { - $$ = new_node(NUMBER_GN); + $$ = new_node (NUMBER_GN); $$->value = yylval.number; $$->text = XCALLOC(MTYPE_CMD_TOKENS, DECIMAL_STRLEN_MAX+1); snprintf($$->text, DECIMAL_STRLEN_MAX, "%lld", $$->value); @@ -286,26 +300,26 @@ selector_element: selector_element_root selector_token_seq // if the selector start and end do not exist, create them if (!selnode_start || !selnode_end) { // if one is null assert(!selnode_start && !selnode_end); // both should be null - selnode_start = new_node(SELECTOR_GN); // diverging node - selnode_end = new_node(NUL_GN); // converging node + selnode_start = new_node (SELECTOR_GN); // diverging node + selnode_end = new_node (NUL_GN); // converging node selnode_start->end = selnode_end; // duh } // add element head as a child of the selector - add_node(selnode_start, $1); + add_node (selnode_start, $1); if ($2->type != NUL_GN) { - add_node($1, seqhead); - add_node($2, selnode_end); + add_node ($1, seqhead); + add_node ($2, selnode_end); } else - add_node($1, selnode_end); + add_node ($1, selnode_end); seqhead = NULL; } selector_token_seq: - %empty { $$ = new_node(NUL_GN); } + %empty { $$ = new_node (NUL_GN); } | selector_token_seq selector_token { // if the sequence component is NUL_GN, this is a sequence start @@ -314,7 +328,7 @@ selector_token_seq: seqhead = $2; } else // chain on new node - add_node($1, $2); + add_node ($1, $2); $$ = $2; } @@ -334,7 +348,7 @@ selector_token: option: '[' option_part ']' { // add null path - add_node(optnode_start, optnode_end); + add_node (optnode_start, optnode_end); $$ = optnode_start; }; @@ -348,19 +362,19 @@ option_element: { if (!optnode_start || !optnode_end) { assert(!optnode_start && !optnode_end); - optnode_start = new_node(OPTION_GN); - optnode_end = new_node(NUL_GN); + optnode_start = new_node (OPTION_GN); + optnode_end = new_node (NUL_GN); } - add_node(optnode_start, seqhead); - add_node($1, optnode_end); + add_node (optnode_start, seqhead); + add_node ($1, optnode_end); } option_token_seq: option_token { $$ = seqhead = $1; } | option_token_seq option_token -{ $$ = add_node($1, $2); } +{ $$ = add_node ($1, $2); } ; option_token: @@ -373,6 +387,7 @@ option_token: struct graph_node * parse_command_format(struct graph_node *start, struct cmd_element *cmd) { + // set to 1 to enable parser traces yydebug = 0; // parse command into DFA @@ -384,9 +399,10 @@ parse_command_format(struct graph_node *start, struct cmd_element *cmd) /* parser helper functions */ void -yyerror(struct cmd_element *el, struct graph_node *sn, char const *msg) +yyerror (struct cmd_element *el, struct graph_node *sn, char const *msg) { - fprintf(stderr, "Grammar error: %s\n", msg); + zlog_err ("%s: FATAL parse error: %s", __func__, msg); + zlog_err ("while parsing this command definition: \n\t%s\n", el->string); exit(EXIT_FAILURE); } @@ -409,45 +425,45 @@ terminate_graph (struct graph_node *startnode, struct graph_node *finalnode, struct cmd_element *element) { - struct graph_node *end = new_node(END_GN); + struct graph_node *end = new_node (END_GN); end->element = element; - if (node_exists(finalnode, end)) - yyerror(element, startnode, "Duplicate command."); + if (node_exists (finalnode, end)) + yyerror (element, startnode, "Duplicate command."); else - add_node(finalnode, end); + add_node (finalnode, end); } static char * doc_next() { char *piece = NULL; - if (!docstr || !(piece = strsep(&docstr, "\n"))) + if (!docstr || !(piece = strsep (&docstr, "\n"))) return NULL; return XSTRDUP(MTYPE_CMD_TOKENS, piece); } static struct graph_node * -node_exists(struct graph_node *parent, struct graph_node *child) +node_exists (struct graph_node *parent, struct graph_node *child) { struct graph_node *p_child; - for (unsigned int i = 0; i < vector_active(parent->children); i++) - { - p_child = vector_slot(parent->children, i); - if (cmp_node(child, p_child)) - return p_child; - } + for (unsigned int i = 0; i < vector_active (parent->children); i++) + { + p_child = vector_slot (parent->children, i); + if (cmp_node (child, p_child)) + return p_child; + } return NULL; } static struct graph_node * -node_replace(struct graph_node *parent, struct graph_node *child) +node_replace (struct graph_node *parent, struct graph_node *child) { struct graph_node *existing = node_exists (parent, child); - return existing ? existing : add_node(parent, child); + return existing ? existing : add_node (parent, child); } static int -cmp_node(struct graph_node *first, struct graph_node *second) +cmp_node (struct graph_node *first, struct graph_node *second) { // compare types if (first->type != second->type) return 0; @@ -455,9 +471,8 @@ cmp_node(struct graph_node *first, struct graph_node *second) switch (first->type) { case WORD_GN: case VARIABLE_GN: - if (first->text && second->text) { - if (strcmp(first->text, second->text)) return 0; - } + if (first->text && second->text && strcmp (first->text, second->text)) + return 0; else if (first->text != second->text) return 0; break; case RANGE_GN: @@ -467,16 +482,16 @@ cmp_node(struct graph_node *first, struct graph_node *second) case NUMBER_GN: if (first->value != second->value) return 0; break; - /* selectors and options should be equal if all paths are equal, - * but the graph isomorphism problem is not solvable in polynomial - * time so we consider selectors and options inequal in all cases; - * ultimately this forks the graph + /* selectors and options should be equal if their subgraphs are equal, but + * the graph isomorphism problem is not known to be solvable in polynomial time + * so we consider selectors and options inequal in all cases; ultimately this + * forks the graph, but the matcher can handle this regardless */ case SELECTOR_GN: case OPTION_GN: return 0; /* end nodes are always considered equal, since each node may only - * have one at a time + * have one END_GN child at a time */ case START_GN: case END_GN: diff --git a/lib/grammar_sandbox.c b/lib/grammar_sandbox.c index d1779a9b6..39d4d6b7c 100644 --- a/lib/grammar_sandbox.c +++ b/lib/grammar_sandbox.c @@ -1,69 +1,86 @@ +/* + * Testing shim and API examples for the new CLI backend. + * + * This unit defines a number of commands in the old engine that can + * be used to test and interact with the new engine. + * + * This shim should be removed upon integration. It is currently hooked in + * vtysh/vtysh.c. It has no header, vtysh.c merely includes this entire unit + * since it clutters up the makefiles less and this is only a temporary shim. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + #include "command.h" #include "command_graph.h" #include "command_parse.h" #include "command_match.h" -#include "linklist.h" #define GRAMMAR_STR "CLI grammar sandbox\n" +void +grammar_sandbox_init(void); +void +pretty_print_graph (struct graph_node *start, int level); + +/* + * Start node for testing command graph. + * + * Each cmd_node will have one of these that replaces the `cmdvector` member. + * The examples below show how to install a command to the graph, calculate + * completions for a given input line, and match input against the graph. + */ struct graph_node * nodegraph; +/** + * Reference use of parsing / command installation API + */ DEFUN (grammar_test, grammar_test_cmd, "grammar parse .COMMAND", GRAMMAR_STR "command to pass to new parser\n") { - char* command = argv_concat(argv, argc, 0); - struct cmd_element *cmd = malloc(sizeof(struct cmd_element)); + char *command = argv_concat(argv, argc, 0); + + // initialize a pretend cmd_element + struct cmd_element *cmd = XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element)); cmd->string = command; cmd->doc = NULL; cmd->func = NULL; cmd->tokens = vector_init(VECTOR_MIN_SIZE); - parse_command_format(nodegraph, cmd); - return CMD_SUCCESS; -} -DEFUN (grammar_test_doc, - grammar_test_doc_cmd, - "grammar test docstring", - GRAMMAR_STR - "Test function for docstring\n" - "Command end\n") -{ - struct cmd_element *cmd = malloc(sizeof(struct cmd_element)); - cmd->string = "test docstring (1-255) end VARIABLE [OPTION|set lol] . VARARG"; - cmd->doc = "Test stuff\n" - "docstring thing\n" - "first example\n" - "second example\n" - "follow\n" - "random range\n" - "end thingy\n" - "variable\n" - "optional variable\n" - "optional set\n" - "optional lol\n" - "vararg!\n"; - cmd->func = NULL; - cmd->tokens = vector_init(VECTOR_MIN_SIZE); - parse_command_format(nodegraph, cmd); - return CMD_SUCCESS; -} + // parse the command and install it into the command graph + parse_command_format (nodegraph, cmd); + + // free resources + free (command); -DEFUN (grammar_test_show, - grammar_test_show_cmd, - "grammar tree", - GRAMMAR_STR - "print current accumulated DFA\n") -{ - if (!nodegraph) - fprintf(stderr, "!nodegraph\n"); - else - walk_graph(nodegraph, 0); return CMD_SUCCESS; } + +/** + * Reference use of completions API + */ DEFUN (grammar_test_complete, grammar_test_complete_cmd, "grammar complete .COMMAND", @@ -71,31 +88,42 @@ DEFUN (grammar_test_complete, "attempt to complete input on DFA\n" "command to complete") { - const char* command = argv_concat(argv, argc, 0); - struct list *result = match_command_complete (nodegraph, command); + char *cmdstr = argv_concat (argv, argc, 0); + vector command = cmd_make_strvec (cmdstr); + + struct list *completions; + enum matcher_rv result = match_command_complete (nodegraph, command, &completions); - if (result->count == 0) // invalid command - fprintf(stderr, "%% Unknown command\n"); + // print completions or relevant error message + if (completions) + { + struct listnode *ln; + struct graph_node *gn; + for (ALL_LIST_ELEMENTS_RO(completions,ln,gn)) + { + if (gn->type == END_GN) + zlog_info (" (%p)", gn->element->func); + else + zlog_info ("%-30s%s", gn->text, gn->doc); + } + list_delete (completions); + } else - { - fprintf(stderr, "%% Matched full input, possible completions:\n"); - char* desc = malloc(30); - struct listnode *node; - struct graph_node *cnode; - // print possible next hops, if any - for (ALL_LIST_ELEMENTS_RO(result,node,cnode)) { - if (cnode->type == END_GN) - fprintf(stderr, " %p\n", cnode->element->func); - else - fprintf(stderr, "%-30s%s\n", describe_node(cnode, desc, 30), cnode->doc); + { + assert(MATCHER_ERROR(result)); + zlog_info ("%% No match for \"%s\"", cmdstr); } - free(desc); - } - list_delete(result); + + // free resources + cmd_free_strvec (command); + free (cmdstr); return CMD_SUCCESS; } +/** + * Reference use of matching API + */ DEFUN (grammar_test_match, grammar_test_match_cmd, "grammar match .COMMAND", @@ -103,43 +131,105 @@ DEFUN (grammar_test_match, "attempt to match input on DFA\n" "command to match") { - const char *line = argv_concat(argv, argc, 0); + char *cmdstr = argv_concat(argv, argc, 0); + vector command = cmd_make_strvec (cmdstr); struct list *argvv = NULL; struct cmd_element *element = NULL; - enum matcher_rv result = match_command (nodegraph, line, &argvv, &element); - - if (element) { - fprintf(stderr, "Matched: %s\n", element->string); - struct listnode *ln; - struct graph_node *gn; - for (ALL_LIST_ELEMENTS_RO(argvv,ln,gn)) - fprintf(stderr, "%s -- %s\n", gn->text, gn->arg); - } + enum matcher_rv result = match_command (nodegraph, command, &argvv, &element); + + // print completions or relevant error message + if (element) + { + zlog_info ("Matched: %s", element->string); + struct listnode *ln; + struct graph_node *gn; + for (ALL_LIST_ELEMENTS_RO(argvv,ln,gn)) + if (gn->type != END_GN) + zlog_info ("func: %p", gn->element->func); + else + zlog_info ("%s -- %s", gn->text, gn->arg); + + list_delete (argvv); + } else { + assert(MATCHER_ERROR(result)); switch (result) { case MATCHER_NO_MATCH: - fprintf(stderr, "%% Unknown command\n"); + zlog_info ("%% Unknown command"); break; case MATCHER_INCOMPLETE: - fprintf(stderr, "%% Incomplete command\n"); + zlog_info ("%% Incomplete command"); break; case MATCHER_AMBIGUOUS: - fprintf(stderr, "%% Ambiguous command\n"); + zlog_info ("%% Ambiguous command"); break; default: - fprintf(stderr, "%% Unknown error\n"); + zlog_info ("%% Unknown error"); break; } } + // free resources + cmd_free_strvec(command); + free(cmdstr); + return CMD_SUCCESS; } +/** + * Testing shim to test docstrings + */ +DEFUN (grammar_test_doc, + grammar_test_doc_cmd, + "grammar test docstring", + GRAMMAR_STR + "Test function for docstring\n" + "Command end\n") +{ + // create cmd_element with docstring + struct cmd_element *cmd = XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element)); + cmd->string = "test docstring (1-255) end VARIABLE [OPTION|set lol] . VARARG"; + cmd->doc = "Test stuff\n" + "docstring thing\n" + "first example\n" + "second example\n" + "follow\n" + "random range\n" + "end thingy\n" + "variable\n" + "optional variable\n" + "optional set\n" + "optional lol\n" + "vararg!\n"; + cmd->func = NULL; + cmd->tokens = vector_init (VECTOR_MIN_SIZE); + + // parse element + parse_command_format (nodegraph, cmd); + + return CMD_SUCCESS; +} + +/** + * Debugging command to print command graph + */ +DEFUN (grammar_test_show, + grammar_test_show_cmd, + "grammar show graph", + GRAMMAR_STR + "print current accumulated DFA\n") +{ + if (!nodegraph) + zlog_info("nodegraph uninitialized"); + else + pretty_print_graph (nodegraph, 0); + return CMD_SUCCESS; +} -void grammar_sandbox_init(void); +/* this is called in vtysh.c to set up the testing shim */ void grammar_sandbox_init() { - fprintf(stderr, "reinitializing graph\n"); + zlog_info ("Initializing grammar testing shim"); nodegraph = new_node(START_GN); install_element (ENABLE_NODE, &grammar_test_cmd); install_element (ENABLE_NODE, &grammar_test_show_cmd); @@ -147,3 +237,30 @@ void grammar_sandbox_init() { install_element (ENABLE_NODE, &grammar_test_complete_cmd); install_element (ENABLE_NODE, &grammar_test_doc_cmd); } + +/* recursive pretty-print for command graph */ +void +pretty_print_graph (struct graph_node *start, int level) +{ + // print this node + fprintf (stdout, "%s[%d] ", start->text, vector_active (start->children)); + + if (vector_active (start->children)) + { + if (vector_active (start->children) == 1) + pretty_print_graph (vector_slot (start->children, 0), level); + else + { + fprintf(stdout, "\n"); + for (unsigned int i = 0; i < vector_active (start->children); i++) + { + struct graph_node *r = vector_slot (start->children, i); + for (int j = 0; j < level+1; j++) + fprintf (stdout, " "); + pretty_print_graph (r, level+1); + } + } + } + else + fprintf(stdout, "\n"); +}