/*
- * 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 <qlyoung@cumulusnetworks.com>
+ * --
+ * 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 <zebra.h>
+#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);
}
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);
}
-#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;
* @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 */
+/*
+ * 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"
+/*
+ * 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 <zebra.h>
#include "command_match.h"
#include "command_parse.h"
-#include <zebra.h>
#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);
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 *);
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
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;
}
/**
* 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);
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;
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
* -----------------------------------------------------------------
* 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);
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;
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);
}
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);
#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;
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;
}
-#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,
MATCHER_OK,
};
-/* Completion match types. */
+/* completion match types */
enum match_type
{
no_match,
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 \
/**
* 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 */
/*
- * 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 <qlyoung@cumulusnetworks.com>
+ * 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 */
#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
%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
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 *,
struct cmd_element *);
static void
- cleanup(void);
+ cleanup (void);
}
/* yyparse parameters */
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;
}
| 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);
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);
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;
}
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);
}
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);
// 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
seqhead = $2;
}
else // chain on new node
- add_node($1, $2);
+ add_node ($1, $2);
$$ = $2;
}
option: '[' option_part ']'
{
// add null path
- add_node(optnode_start, optnode_end);
+ add_node (optnode_start, optnode_end);
$$ = optnode_start;
};
{
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:
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
/* 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);
}
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;
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:
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:
+/*
+ * 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 <example|selector follow> (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",
"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 ("<cr> (%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, "<cr> %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",
"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 <example|selector follow> (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);
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");
+}