lib: Continue matching system refactor

Most things back to working, all CLI units refactored
to use improved graph implementation.

Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
This commit is contained in:
Quentin Young 2016-09-07 04:05:07 +00:00
parent 7a6ded4096
commit 1eb5e8dcd1
9 changed files with 396 additions and 290 deletions

View File

@ -4191,7 +4191,7 @@ cmd_terminate_element(struct cmd_element *cmd)
}
void
free_cmd_element(struct cmd_element *cmd)
del_cmd_element(struct cmd_element *cmd)
{
if (!cmd) return;
free ((char*) cmd->string);

View File

@ -573,7 +573,7 @@ extern void cmd_terminate (void);
/* memory management for cmd_element */
void
free_cmd_element(struct cmd_element *);
del_cmd_element(struct cmd_element *);
struct cmd_element *
copy_cmd_element(struct cmd_element *cmd);

View File

@ -25,6 +25,7 @@
#include <zebra.h>
#include "command_match.h"
#include "command_parse.h"
#include "grammar_sandbox.h"
#include "memory.h"
/* matcher helper prototypes */
@ -35,19 +36,16 @@ static struct list *
command_match_r (struct graph_node *, vector, unsigned int);
static int
score_precedence (enum graph_node_type);
score_precedence (enum cmd_token_type_t);
static enum match_type
min_match_level (enum node_type);
static struct graph_node *
copy_node (struct graph_node *);
min_match_level (enum cmd_token_type_t);
static void
delete_nodelist (void *);
del_arglist (struct list *);
static struct graph_node *
disambiguate_nodes (struct graph_node *, struct graph_node *, char *);
static struct cmd_token_t *
disambiguate_tokens (struct cmd_token_t *, struct cmd_token_t *, char *);
static struct list *
disambiguate (struct list *, struct list *, vector, unsigned int);
@ -57,7 +55,7 @@ compare_completions (const void *, const void *);
/* token matcher prototypes */
static enum match_type
match_token (struct graph_node *, char *);
match_token (struct cmd_token_t *, char *);
static enum match_type
match_ipv4 (const char *);
@ -72,22 +70,22 @@ static enum match_type
match_ipv6_prefix (const char *);
static enum match_type
match_range (struct graph_node *, const char *);
match_range (struct cmd_token_t *, const char *);
static enum match_type
match_word (struct graph_node *, const char *);
match_word (struct cmd_token_t *, const char *);
static enum match_type
match_number (struct graph_node *, const char *);
match_number (struct cmd_token_t *, const char *);
static enum match_type
match_variable (struct graph_node *node, const char *word);
match_variable (struct cmd_token_t *, const char *);
/* matching functions */
static enum matcher_rv matcher_rv;
enum matcher_rv
command_match (struct graph_node *start,
command_match (struct graph *cmdgraph,
vector vline,
struct list **argv,
struct cmd_element **el)
@ -100,11 +98,19 @@ command_match (struct graph_node *start,
memcpy (vvline->index + 1, vline->index, sizeof (void *) * vline->alloced);
vvline->active = vline->active + 1;
struct graph_node *start = vector_slot (cmdgraph->nodes, 0);
if ((*argv = command_match_r (start, vvline, 0))) // successful match
{
// delete dummy start node
list_delete_node (*argv, listhead (*argv));
struct graph_node *end = listgetdata (listtail (*argv));
*el = end->element;
// get cmd_element out of list tail
struct listnode *tail = listtail (*argv);
*el = listgetdata (tail);
// delete list tail
tail->data = NULL;
list_delete_node (*argv, tail);
// now argv is an ordered list of cmd_token matching the user
// input, with each cmd_token->arg holding the corresponding input
assert (*el);
}
@ -120,7 +126,7 @@ command_match (struct graph_node *start,
*
* The next step is to see if there is further input in the input line. If
* there is not, the current node's children are searched to see if any of them
* are leaves (type END_GN). If this is the case, then the bottom of the
* are leaves (type END_TKN). If this is the case, then the bottom of the
* recursion stack has been reached, the leaf is pushed onto the argument list,
* the current node is pushed, and the resulting argument list is
* returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
@ -165,13 +171,14 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n)
assert (n < vector_active (vline));
// get the minimum match level that can count as a full match
enum match_type minmatch = min_match_level (start->type);
struct cmd_token_t *token = start->data;
enum match_type minmatch = min_match_level (token->type);
// get the current operating token
char *token = vector_slot (vline, n);
// get the current operating input token
char *input_token = vector_slot (vline, n);
// if we don't match this node, die
if (match_token (start, token) < minmatch)
if (match_token (token, input_token) < minmatch)
return NULL;
// pointers for iterating linklist
@ -187,14 +194,22 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n)
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 we've matched all input we're looking for END_TKN
if (n+1 == vector_active (vline))
{
if (gn->type == END_GN)
struct cmd_token_t *tok = gn->data;
if (tok->type == END_TKN)
{
currbest = list_new();
listnode_add (currbest, copy_node(gn));
currbest->del = &delete_nodelist;
// node should have one child node with the element
struct graph_node *leaf = vector_slot (gn->to, 0);
// last node in the list will hold the cmd_element;
// this is important because list_delete() expects
// that all nodes have the same data type, so when
// deleting this list the last node must be
// manually deleted
listnode_add (currbest, leaf->data);
currbest->del = (void (*)(void *)) &del_cmd_token;
break;
}
else continue;
@ -206,9 +221,17 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n)
// save the best match
if (result && currbest)
{
// pick the best of two matches
struct list *newbest = disambiguate (currbest, result, vline, n+1);
// set ambiguity flag
ambiguous = !newbest || (ambiguous && newbest == currbest);
list_delete ((newbest && newbest == result) ? currbest : result);
// choose the list to be deleted
struct list *todelete = ((newbest && newbest == result) ? currbest : result);
// manually delete the last node, which has a cmd_element
del_cmd_element (listgetdata (listtail (todelete)));
// use the list->del callback to delete the rest of the list
list_delete (todelete);
currbest = newbest ? newbest : currbest;
}
else if (result)
@ -219,16 +242,16 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n)
{
if (ambiguous)
{
list_delete (currbest);
currbest = NULL;
del_arglist (currbest);
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);
// copy token, set arg and prepend to currbest
struct cmd_token_t *token = start->data;
struct cmd_token_t *copy = copy_cmd_token (token);
copy->arg = XSTRDUP(MTYPE_CMD_TOKENS, input_token);
list_add_node_prev (currbest, currbest->head, copy);
matcher_rv = MATCHER_OK;
}
}
@ -242,12 +265,12 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n)
}
enum matcher_rv
command_complete (struct graph_node *start,
command_complete (struct graph *graph,
vector vline,
struct list **completions)
{
// pointer to next input token to match
char *token;
char *input_token;
struct list *current = list_new(), // current nodes to match input token against
*next = list_new(); // possible next hops after current input token
@ -257,6 +280,7 @@ command_complete (struct graph_node *start,
struct listnode *node;
// add all children of start node to list
struct graph_node *start = vector_slot (graph->nodes, 0);
add_nexthops (next, start);
unsigned int idx;
@ -266,11 +290,12 @@ command_complete (struct graph_node *start,
current = next;
next = list_new();
token = vector_slot (vline, idx);
input_token = vector_slot (vline, idx);
for (ALL_LIST_ELEMENTS_RO (current,node,gn))
{
switch (match_token (gn, token))
struct cmd_token_t *token = gn->data;
switch (match_token (token, input_token))
{
case partly_match:
if (idx == vector_active (vline) - 1)
@ -300,19 +325,24 @@ command_complete (struct graph_node *start,
MATCHER_OK :
MATCHER_NO_MATCH;
// extract cmd_token into list
*completions = list_new ();
for (ALL_LIST_ELEMENTS_RO (next,node,gn))
listnode_add (*completions, gn->data);
list_free (current);
*completions = next;
list_free (next);
return matcher_rv;
}
/**
* TODO: move this logic to command.c
* Compare two completions. Tightly coupled to vector.
*
* @param[in] fst pointer to first item pointer in vector->index
* @param[in] snd pointer to second item poitner in vector->index
* @return integer compare code as determined by strcmp
*/
int
compare_completions (const void *fst, const void *snd)
{
@ -353,10 +383,11 @@ command_complete_str (struct graph_node *start,
list_delete (comps);
return 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.
* NUL_TKN, SELECTOR_TKN, and OPTION_TKN nodes are treated as transparent.
*
* @param[in] list to add the nexthops to
* @param[in] node to start calculating nexthops from
@ -367,14 +398,15 @@ 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++)
for (unsigned int i = 0; i < vector_active (node->to); i++)
{
child = vector_slot (node->children, i);
switch (child->type)
child = vector_slot (node->to, i);
struct cmd_token_t *token = child->data;
switch (token->type)
{
case OPTION_GN:
case SELECTOR_GN:
case NUL_GN:
case OPTION_TKN:
case SELECTOR_TKN:
case NUL_TKN:
added += add_nexthops (list, child);
break;
default:
@ -394,15 +426,15 @@ add_nexthops (struct list *list, struct graph_node *node)
* @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 cmd_token_type_t type)
{
switch (type)
{
// anything matches a start node, for the sake of recursion
case START_GN:
case START_TKN:
return no_match;
// allowing words to partly match enables command abbreviation
case WORD_GN:
case WORD_TKN:
return partly_match;
default:
return exact_match;
@ -416,23 +448,23 @@ min_match_level (enum node_type type)
* @return precedence score
*/
static int
score_precedence (enum graph_node_type type)
score_precedence (enum cmd_token_type_t 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:
case IPV4_TKN:
case IPV4_PREFIX_TKN:
case IPV6_TKN:
case IPV6_PREFIX_TKN:
case NUMBER_TKN:
return 1;
case RANGE_GN:
case RANGE_TKN:
return 2;
case WORD_GN:
case WORD_TKN:
return 3;
case VARIABLE_GN:
case VARIABLE_TKN:
return 4;
default:
return 10;
@ -447,10 +479,10 @@ score_precedence (enum graph_node_type type)
* @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_nodes (struct graph_node *first,
struct graph_node *second,
char *token)
static struct cmd_token_t *
disambiguate_tokens (struct cmd_token_t *first,
struct cmd_token_t *second,
char *input_token)
{
// if the types are different, simply go off of type precedence
if (first->type != second->type)
@ -464,8 +496,8 @@ disambiguate_nodes (struct graph_node *first,
}
// if they're the same, return the more exact match
enum match_type fmtype = match_token (first, token);
enum match_type smtype = match_token (second, token);
enum match_type fmtype = match_token (first, input_token);
enum match_type smtype = match_token (second, input_token);
if (fmtype != smtype)
return fmtype > smtype ? first : second;
@ -475,8 +507,8 @@ disambiguate_nodes (struct graph_node *first,
/**
* 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] first candidate list of cmd_token_t matching vline
* @param[in] second candidate list of cmd_token_t 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
@ -493,82 +525,68 @@ disambiguate (struct list *first,
struct listnode *fnode = listhead (first),
*snode = listhead (second);
struct graph_node *fgn = listgetdata (fnode),
*sgn = listgetdata (snode),
*best = NULL;
struct cmd_token_t *ftok = listgetdata (fnode),
*stok = listgetdata (snode),
*best = NULL;
// compare each node, if one matches better use that one
// compare each token, if one matches better use that one
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);
if ((best = disambiguate_tokens (ftok, stok, token)))
return best == ftok ? first : second;
ftok = listgetdata (listnextnode (fnode));
stok = listgetdata (listnextnode (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.
/*
* Deletion function for arglist.
*
* @param[in] node to copy
* @return the copy
*/
static struct graph_node *
copy_node (struct graph_node *node)
{
struct graph_node *new = graphnode_new(node->type);
new->children = NULL;
new->text = node->text ? XSTRDUP(MTYPE_CMD_TOKENS, node->text) : NULL;
new->value = node->value;
new->min = node->min;
new->max = node->max;
new->element = node->element ? copy_cmd_element(node->element) : NULL;
new->arg = node->arg ? XSTRDUP(MTYPE_CMD_TOKENS, node->arg) : NULL;
new->refs = 0;
return new;
}
/**
* List deletion callback for argv lists.
* Since list->del for arglists expects all listnode->data to hold cmd_token,
* but arglists have cmd_element as the data for the tail, this function
* manually deletes the tail before deleting the rest of the list as usual.
*
* @param list the arglist to delete
*/
static void
delete_nodelist (void *node)
del_arglist (struct list *list)
{
graphnode_delete ((struct graph_node *) node);
// manually delete last node
struct listnode *tail = listtail (list);
del_cmd_element (tail->data);
tail->data = NULL;
list_delete_node (list, tail);
// delete the rest of the list as usual
list_delete (list);
}
/* token level matching functions */
/*---------- token level matching functions ----------*/
static enum match_type
match_token (struct graph_node *node, char *token)
match_token (struct cmd_token_t *token, char *input_token)
{
switch (node->type) {
case WORD_GN:
return match_word (node, token);
case IPV4_GN:
return match_ipv4 (token);
case IPV4_PREFIX_GN:
return match_ipv4_prefix (token);
case IPV6_GN:
return match_ipv6 (token);
case IPV6_PREFIX_GN:
return match_ipv6_prefix (token);
case RANGE_GN:
return match_range (node, token);
case NUMBER_GN:
return match_number (node, token);
case VARIABLE_GN:
return match_variable (node, token);
case END_GN:
switch (token->type) {
case WORD_TKN:
return match_word (token, input_token);
case IPV4_TKN:
return match_ipv4 (input_token);
case IPV4_PREFIX_TKN:
return match_ipv4_prefix (input_token);
case IPV6_TKN:
return match_ipv6 (input_token);
case IPV6_PREFIX_TKN:
return match_ipv6_prefix (input_token);
case RANGE_TKN:
return match_range (token, input_token);
case NUMBER_TKN:
return match_number (token, input_token);
case VARIABLE_TKN:
return match_variable (token, input_token);
case END_TKN:
default:
return no_match;
}
@ -776,9 +794,9 @@ match_ipv6_prefix (const char *str)
#endif
static enum match_type
match_range (struct graph_node *node, const char *str)
match_range (struct cmd_token_t *token, const char *str)
{
assert (node->type == RANGE_GN);
assert (token->type == RANGE_TKN);
char *endptr = NULL;
long long val;
@ -790,53 +808,53 @@ match_range (struct graph_node *node, const char *str)
if (*endptr != '\0')
return 0;
if (val < node->min || val > node->max)
if (val < token->min || val > token->max)
return no_match;
else
return exact_match;
}
static enum match_type
match_word (struct graph_node *node, const char *word)
match_word (struct cmd_token_t *token, const char *word)
{
assert (node->type == WORD_GN);
assert (token->type == WORD_TKN);
// 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 (node->text))
return !strncmp (node->text, word, strlen (word)) ?
if (strlen (word) < strlen (token->text))
return !strncmp (token->text, word, strlen (word)) ?
partly_match :
no_match;
// if they are the same length and exactly equal, exact match
else if (strlen (word) == strlen (node->text))
return !strncmp (node->text, word, strlen (word)) ? exact_match : no_match;
else if (strlen (word) == strlen (token->text))
return !strncmp (token->text, word, strlen (word)) ? exact_match : no_match;
return no_match;
}
static enum match_type
match_number (struct graph_node *node, const char *word)
match_number (struct cmd_token_t *token, const char *word)
{
assert (node->type == NUMBER_GN);
assert (token->type == NUMBER_TKN);
if (!strcmp ("\0", word)) return no_match;
char *endptr;
long long num = strtoll (word, &endptr, 10);
if (endptr != '\0') return no_match;
return num == node->value ? exact_match : no_match;
return num == token->value ? exact_match : no_match;
}
#define VARIABLE_ALPHABET \
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:"
static enum match_type
match_variable (struct graph_node *node, const char *word)
match_variable (struct cmd_token_t *token, const char *word)
{
assert (node->type == VARIABLE_GN);
assert (token->type == VARIABLE_TKN);
return strlen (word) == strspn(word, VARIABLE_ALPHABET) ?
exact_match : no_match;

View File

@ -25,10 +25,9 @@
#ifndef _ZEBRA_COMMAND_MATCH_H
#define _ZEBRA_COMMAND_MATCH_H
#include "command.h"
#include "command_graph.h"
#include "graph.h"
#include "linklist.h"
#include "command.h"
/* These definitions exist in command.c in the current engine but should be
* relocated here in the new engine
@ -68,14 +67,14 @@ enum match_type
/**
* Attempt to find an exact command match for a line of user input.
*
* @param[in] start start node of command graph to match against
* @param[in] cmdgraph 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
command_match (struct graph_node *start,
command_match (struct graph *cmdgraph,
vector vline,
struct list **argv,
struct cmd_element **element);
@ -85,11 +84,11 @@ command_match (struct graph_node *start,
*
* @param[in] start the start node of the DFA to match against
* @param[in] vline vectorized input string
* @param[in] completions pointer to list of possible next nodes
* @return matcher status
* @param[in] completions pointer to list of cmd_token representing
* acceptable next inputs
*/
enum matcher_rv
command_complete (struct graph_node *start,
command_complete (struct graph *cmdgraph,
vector vline,
struct list **completions);
@ -101,10 +100,10 @@ command_complete (struct graph_node *start,
* @param[in] vline vectorized input string
* @param[in] completions vector to fill with string completions
* @return matcher status
*/
enum matcher_rv
command_complete_str (struct graph_node *start,
command_complete_str (struct graph *cmdgraph,
vector vline,
vector completions);
*/
#endif /* _ZEBRA_COMMAND_MATCH_H */

View File

@ -36,6 +36,7 @@
#include "command.h"
#include "graph.h"
#include "memory.h"
#include "grammar_sandbox.h"
extern int
yylex (void);
@ -49,7 +50,7 @@
/* functionality this unit exports */
%code provides {
struct graph *
void
command_parse_format (struct graph *, struct cmd_element *);
/* maximum length of a number, lexer will not match anything longer */
@ -89,9 +90,11 @@
%code {
/* bison declarations */
void
yyerror (struct cmd_element *el, struct graph_node *sn, char const *msg);
yyerror (struct graph *, struct cmd_element *el, char const *msg);
/* state variables for a single parser run */
struct graph_node *startnode; // start node of DFA
struct graph_node *currnode, // current position in DFA
*seqhead; // sequence head
@ -108,16 +111,21 @@
doc_next (void);
static struct graph_node *
node_exists (struct graph_node *, struct graph_node *);
node_adjacent (struct graph_node *, struct graph_node *);
static struct graph_node *
node_replace (struct graph_node *, struct graph_node *);
add_edge_dedup (struct graph_node *, struct graph_node *);
static int
cmp_node (struct graph_node *, struct graph_node *);
cmp_token (struct cmd_token_t *, struct cmd_token_t *);
static struct graph_node *
new_token_node (struct graph *,
enum cmd_token_type_t type,
char *text, char *doc);
static void
terminate_graph (struct graph_node *,
terminate_graph (struct graph *,
struct graph_node *,
struct cmd_element *);
@ -126,11 +134,13 @@
}
/* yyparse parameters */
%parse-param { struct cmd_element *element }
%parse-param { struct graph *graph }
%parse-param { struct cmd_element *element }
/* called automatically before yyparse */
%initial-action {
startnode = vector_slot (graph->nodes, 0);
/* clear state pointers */
seqhead = NULL;
currnode = NULL;
@ -151,28 +161,28 @@ start:
sentence_root cmd_token_seq
{
// tack on the command element
terminate_graph (startnode, currnode, element);
terminate_graph (graph, currnode, element);
}
| sentence_root cmd_token_seq '.' placeholder_token
{
if ((currnode = node_replace (currnode, $4)) != $4)
graph_delete_node ($4);
if ((currnode = add_edge_dedup (currnode, $4)) != $4)
graph_delete_node (graph, $4);
// 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);
add_edge_dedup (currnode, currnode);
// tack on the command element
terminate_graph (startnode, currnode, element);
terminate_graph (graph, currnode, element);
}
sentence_root: WORD
{
struct graph_node *root =
new_token_node (WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
new_token_node (graph, WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
if ((currnode = node_replace (startnode, root)) != root)
free (root);
if ((currnode = add_edge_dedup (startnode, root)) != root)
graph_delete_node (graph, root);
free ($1);
$$ = currnode;
@ -181,13 +191,13 @@ sentence_root: WORD
cmd_token:
placeholder_token
{
if ((currnode = node_replace (currnode, $1)) != $1)
graph_delete_node ($1);
if ((currnode = add_edge_dedup (currnode, $1)) != $1)
graph_delete_node (graph, $1);
}
| literal_token
{
if ((currnode = node_replace (currnode, $1)) != $1)
graph_delete_node ($1);
if ((currnode = add_edge_dedup (currnode, $1)) != $1)
graph_delete_node (graph, $1);
}
/* selectors and options are subgraphs with start and end nodes */
| selector
@ -212,33 +222,33 @@ cmd_token_seq:
placeholder_token:
IPV4
{
$$ = new_token_node (IPV4_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
$$ = new_token_node (graph, IPV4_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
free ($1);
}
| IPV4_PREFIX
{
$$ = new_token_node (IPV4_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
$$ = new_token_node (graph, IPV4_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
free ($1);
}
| IPV6
{
$$ = new_token_node (IPV6_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
$$ = new_token_node (graph, IPV6_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
free ($1);
}
| IPV6_PREFIX
{
$$ = new_token_node (IPV6_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
$$ = new_token_node (graph, IPV6_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
free ($1);
}
| VARIABLE
{
$$ = new_token_node (VARIABLE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
$$ = new_token_node (graph, VARIABLE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
free ($1);
}
| RANGE
{
$$ = new_token_node (RANGE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
cmd_token_t token = (cmd_token_t *) $$->data;
$$ = new_token_node (graph, RANGE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
struct cmd_token_t *token = $$->data;
// get the numbers out
yylval.string++;
@ -247,7 +257,7 @@ placeholder_token:
token->max = strtoll (yylval.string, &yylval.string, 10);
// validate range
if (token->min >= token->max) yyerror (element, startnode, "Invalid range.");
if (token->min >= token->max) yyerror (graph, element, "Invalid range.");
free ($1);
}
@ -256,13 +266,13 @@ placeholder_token:
literal_token:
WORD
{
$$ = new_token_node (WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
$$ = new_token_node (graph, WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
free ($1);
}
| NUMBER
{
$$ = new_token_node (NUMBER_TKN, NULL, doc_next());
cmd_token_t token = (cmd_token_t *) $$->data;
$$ = new_token_node (graph, NUMBER_TKN, NULL, doc_next());
struct cmd_token_t *token = $$->data;
token->value = yylval.number;
token->text = XCALLOC(MTYPE_CMD_TOKENS, DECIMAL_STRLEN_MAX+1);
@ -288,14 +298,14 @@ selector_element: selector_element_root selector_token_seq
// if the selector start and end do not exist, create them
if (!selnode_start || !selnode_end) {
assert(!selnode_start && !selnode_end);
selnode_start = new_token_node (SELECTOR_TKN, NULL, NULL);
selnode_end = new_token_node (NUL_TKN, NULL, NULL);
selnode_start = new_token_node (graph, SELECTOR_TKN, NULL, NULL);
selnode_end = new_token_node (graph, NUL_TKN, NULL, NULL);
}
// add element head as a child of the selector
graph_add_edge (selnode_start, $1);
if ($2->type != NUL_GN) {
if (((struct cmd_token_t *) $2->data)->type != NUL_TKN) {
graph_add_edge ($1, seqhead);
graph_add_edge ($2, selnode_end);
}
@ -306,12 +316,12 @@ selector_element: selector_element_root selector_token_seq
}
selector_token_seq:
%empty { $$ = new_token_node (NUL_TKN, NULL, NULL); }
%empty { $$ = new_token_node (graph, NUL_TKN, NULL, NULL); }
| selector_token_seq selector_token
{
// if the sequence component is NUL_GN, this is a sequence start
if ($1->type == NUL_GN) {
assert(!seqhead); // sequence head should always be null here
// if the sequence component is NUL_TKN, this is a sequence start
if (((struct cmd_token_t *) $1->data)->type != NUL_TKN) {
assert(!seqhead);
seqhead = $2;
}
else // chain on new node
@ -348,8 +358,8 @@ option_element:
{
if (!optnode_start || !optnode_end) {
assert(!optnode_start && !optnode_end);
optnode_start = new_token_node (OPTION_TKN, NULL, NULL);
optnode_end = new_token_node (NUL_TKN, NULL, NULL);
optnode_start = new_token_node (graph, OPTION_TKN, NULL, NULL);
optnode_end = new_token_node (graph, NUL_TKN, NULL, NULL);
}
graph_add_edge (optnode_start, seqhead);
@ -371,25 +381,23 @@ option_token:
%%
struct graph_node *
command_parse_format (struct graph_node *start, struct cmd_element *cmd)
void
command_parse_format (struct graph *graph, struct cmd_element *cmd)
{
// set to 1 to enable parser traces
yydebug = 0;
// parse command into DFA
yyparse (cmd, start);
yyparse (graph, cmd);
// cleanup
cleanup ();
return start;
}
/* parser helper functions */
void
yyerror (struct cmd_element *el, struct graph_node *sn, char const *msg)
yyerror (struct graph *graph, struct cmd_element *el, char const *msg)
{
zlog_err ("%s: FATAL parse error: %s", __func__, msg);
zlog_err ("while parsing this command definition: \n\t%s\n", el->string);
@ -414,16 +422,19 @@ cleanup()
}
static void
terminate_graph (struct graph_node *startnode,
struct graph_node *finalnode,
struct cmd_element *element)
terminate_graph (struct graph *graph, struct graph_node *finalnode, struct cmd_element *element)
{
struct graph_node *end = graph_new_node (graph, element, &cmd_delete_element);
// end of graph should look like this
// * -> finalnode -> END_TKN -> cmd_element
struct graph_node *end_token_node = new_token_node (graph, END_TKN, NULL, NULL);
struct graph_node *end_element_node =
graph_new_node (graph, element, (void (*)(void *)) &del_cmd_element);
if (node_adjacent (finalnode, end))
yyerror (element, startnode, "Duplicate command.");
else
graph_add_edge (finalnode, end);
if (node_adjacent (finalnode, end_token_node))
yyerror (graph, element, "Duplicate command.");
graph_add_edge (finalnode, end_token_node);
graph_add_edge (end_token_node, end_element_node);
}
static char *
@ -432,57 +443,74 @@ doc_next()
char *piece = NULL;
if (!docstr || !(piece = strsep (&docstr, "\n")))
return NULL;
return XSTRDUP(MTYPE_CMD_TOKENS, piece);
return XSTRDUP (MTYPE_CMD_TOKENS, piece);
}
static struct graph_node *
new_token_node (cmd_token_type_t type, char *text, char *doc)
new_token_node (struct graph *graph, enum cmd_token_type_t type, char *text, char *doc)
{
struct cmd_token_t *token =
XMALLOC(MTYPE_CMD_TOKENS, sizeof (struct cmd_token_t));
XMALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_token_t));
token->type = type;
token->text = text;
token->desc = doc;
return graph_new_node (graph, token, &cmd_delete_token);
return graph_new_node (graph, token, (void (*)(void *)) &del_cmd_token);
}
/**
* Determines if a node is adjacent to another node
* Determines if there is an out edge from the first node to the second
*/
static struct graph_node *
node_adjacent (struct graph_node *node, struct graph_node *neighbor)
node_adjacent (struct graph_node *first, struct graph_node *second)
{
struct graph_node *adj;
for (unsigned int i = 0; i < vector_active (node->to); i++)
for (unsigned int i = 0; i < vector_active (first->to); i++)
{
adj = vector_slot (node->to, i);
if (cmp_node (neighbor, adj))
adj = vector_slot (first->to, i);
struct cmd_token_t *ftok = first->data,
*stok = second->data;
if (cmp_token (ftok, stok))
return adj;
}
return NULL;
}
/**
* Creates an edge betwen two nodes, unless there is already an edge to an
* equivalent node.
*
* The first node's out edges are searched to see if any of them point to a
* node that is equivalent to the second node. If such a node exists, it is
* returned. Otherwise an edge is created from the first node to the second.
*
* @param from start node for edge
* @param to end node for edge
* @return the node which the new edge points to
*/
static struct graph_node *
node_replace (struct graph_node *parent, struct graph_node *child)
add_edge_dedup (struct graph_node *from, struct graph_node *to)
{
struct graph_node *existing = node_adjacent (parent, child);
return existing ? existing : graph_add_edge (parent, child);
struct graph_node *existing = node_adjacent (from, to);
return existing ? existing : graph_add_edge (from, to);
}
/**
* Compares two cmd_token's for equality,
*
* As such, this function is the working definition of token equality
* for parsing purposes and determines overall graph structure.
*/
static int
cmp_token (struct cmd_token_t *first, struct cmd_token_t *second)
{
struct cmd_token_t *first = (cmd_token *) firstgn->data;
struct cmd_token_t *second = (cmd_token *) secondgn->data;
// compare types
if (first->type != second->type) return 0;
switch (first->type) {
case WORD_GN:
case VARIABLE_GN:
case WORD_TKN:
case VARIABLE_TKN:
if (first->text && second->text)
{
if (strcmp (first->text, second->text))
@ -490,28 +518,30 @@ cmp_token (struct cmd_token_t *first, struct cmd_token_t *second)
}
else if (first->text != second->text) return 0;
break;
case RANGE_GN:
case RANGE_TKN:
if (first->min != second->min || first->max != second->max)
return 0;
break;
case NUMBER_GN:
case NUMBER_TKN:
if (first->value != second->value) return 0;
break;
/* 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:
case SELECTOR_TKN:
case OPTION_TKN:
return 0;
/* end nodes are always considered equal, since each node may only
* have one END_GN child at a time
* have one END_TKN child at a time
*/
case START_GN:
case END_GN:
case NUL_GN:
case START_TKN:
case END_TKN:
case NUL_TKN:
default:
break;
}

View File

@ -30,16 +30,16 @@
*/
#include "command.h"
#include "command_graph.h"
#include "graph.h"
#include "command_parse.h"
#include "command_match.h"
#define GRAMMAR_STR "CLI grammar sandbox\n"
void
grammar_sandbox_init(void);
grammar_sandbox_init (void);
void
pretty_print_graph (struct graph_node *start, int level);
pretty_print_graph (struct graph *start, int level);
/*
* Start node for testing command graph.
@ -48,7 +48,7 @@ pretty_print_graph (struct graph_node *start, int level);
* 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;
struct graph *nodegraph;
/**
* Reference use of parsing / command installation API
@ -62,11 +62,11 @@ DEFUN (grammar_test,
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 = XSTRDUP(MTYPE_TMP, command);
struct cmd_element *cmd = XCALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_element));
cmd->string = XSTRDUP (MTYPE_TMP, command);
cmd->doc = NULL;
cmd->func = NULL;
cmd->tokens = vector_init(VECTOR_MIN_SIZE);
cmd->tokens = vector_init (VECTOR_MIN_SIZE);
// parse the command and install it into the command graph
command_parse_format (nodegraph, cmd);
@ -91,22 +91,24 @@ DEFUN (grammar_test_complete,
char *cmdstr = argv_concat (argv, argc, 0);
vector command = cmd_make_strvec (cmdstr);
vector completions = vector_init (VECTOR_MIN_SIZE);
struct list *completions = list_new ();
enum matcher_rv result =
command_complete_str (nodegraph, command, completions);
command_complete (nodegraph, command, &completions);
// print completions or relevant error message
if (!MATCHER_ERROR(result))
{
for (unsigned int i = 0; i < vector_active (completions); i++)
zlog_info ((char *) vector_slot (completions, i));
struct listnode *ln;
struct cmd_token_t *tkn;
for (ALL_LIST_ELEMENTS_RO(completions,ln,tkn))
zlog_info (tkn->text);
}
else
zlog_info ("%% No match for \"%s\"", cmdstr);
// free resources
cmd_free_strvec (command);
cmd_free_strvec (completions);
list_delete (completions);
free (cmdstr);
return CMD_SUCCESS;
@ -138,11 +140,12 @@ DEFUN (grammar_test_match,
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);
for (ALL_LIST_ELEMENTS_RO(argvv,ln,gn)) {
struct cmd_token_t *token = gn->data;
zlog_info ("%s -- %s", token->text, token->arg);
}
zlog_info ("func: %p", element->func);
list_delete (argvv);
}
@ -182,7 +185,7 @@ DEFUN (grammar_test_doc,
"Command end\n")
{
// create cmd_element with docstring
struct cmd_element *cmd = XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
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"
@ -224,7 +227,13 @@ DEFUN (grammar_test_show,
/* this is called in vtysh.c to set up the testing shim */
void grammar_sandbox_init() {
zlog_info ("Initializing grammar testing shim");
nodegraph = graphnode_new (START_GN);
// initialize graph, add start noe
nodegraph = graph_new ();
struct cmd_token_t *token = new_cmd_token (START_TKN, NULL, NULL);
graph_new_node (nodegraph, token, (void (*)(void *)) &del_cmd_token);
// install all enable elements
install_element (ENABLE_NODE, &grammar_test_cmd);
install_element (ENABLE_NODE, &grammar_test_show_cmd);
install_element (ENABLE_NODE, &grammar_test_match_cmd);
@ -234,8 +243,9 @@ void grammar_sandbox_init() {
/* recursive pretty-print for command graph */
void
pretty_print_graph (struct graph_node *start, int level)
pretty_print_graph (struct graph *graph, int level)
{
/*
// print this node
fprintf (stdout, "%s[%d] ", start->text, vector_active (start->children));
@ -266,4 +276,38 @@ pretty_print_graph (struct graph_node *start, int level)
}
else
fprintf(stdout, "\n");
*/
}
/** stuff that should go in command.c + command.h */
struct cmd_token_t *
new_cmd_token (enum cmd_token_type_t type, char *text, char *desc)
{
struct cmd_token_t *token = XMALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_token_t));
token->type = type;
token->text = text;
token->desc = desc;
token->arg = NULL;
return token;
}
void
del_cmd_token (struct cmd_token_t *token)
{
XFREE (MTYPE_CMD_TOKENS, token->text);
XFREE (MTYPE_CMD_TOKENS, token->desc);
XFREE (MTYPE_CMD_TOKENS, token->arg);
XFREE (MTYPE_CMD_TOKENS, token);
}
struct cmd_token_t *
copy_cmd_token (struct cmd_token_t *token)
{
struct cmd_token_t *copy = new_cmd_token (token->type, NULL, NULL);
copy->text = XSTRDUP (MTYPE_CMD_TOKENS, token->text);
copy->desc = XSTRDUP (MTYPE_CMD_TOKENS, token->desc);
copy->arg = copy->arg ? XSTRDUP (MTYPE_CMD_TOKENS, token->arg) : NULL;
return copy;
}

View File

@ -1,7 +1,14 @@
#ifndef _GRAMMAR_SANDBOX_H
#define _GRAMMAR_SANDBOX_H
/**
* Houses functionality for testing shim as well as code that should go into
* command.h and command.c during integration.
*/
#include "memory.h"
void
grammar_sandbox_init(void);
grammar_sandbox_init (void);
/**
* Types for tokens.
@ -11,9 +18,8 @@ grammar_sandbox_init(void);
*/
enum cmd_token_type_t
{
_TOKEN_BUG = 0,
LITERAL_TKN, // words
OPTION_TKN, // integer ranges
WORD_TKN, // words
NUMBER_TKN, // integral numbers
VARIABLE_TKN, // almost anything
RANGE_TKN, // integer range
IPV4_TKN, // IPV4 addresses
@ -22,13 +28,13 @@ enum cmd_token_type_t
IPV6_PREFIX_TKN, // IPV6 network prefixes
/* plumbing types */
SELECTOR, // marks beginning of selector
OPTION, // marks beginning of option
NUL, // dummy token
START, // first token in line
END; // last token in line
};
SELECTOR_TKN, // marks beginning of selector
OPTION_TKN, // marks beginning of option
NUL_TKN, // dummy token
START_TKN, // first token in line
END_TKN, // last token in line
};
/**
* Token struct.
*/
@ -41,13 +47,17 @@ struct cmd_token_t
long long value; // for numeric types
long long min, max; // for ranges
char *arg; // user input that matches this token
};
inline struct cmd_token_t *
cmd_new_token (cmd_token_type_t type, char *text, char *desc)
{
struct cmd_token_t *token = XMALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_token_t));
token->type = type;
token->text = text;
token->desc = desc;
}
struct cmd_token_t *
new_cmd_token (enum cmd_token_type_t, char *, char *);
void
del_cmd_token (struct cmd_token_t *);
struct cmd_token_t *
copy_cmd_token (struct cmd_token_t *);
#endif /* _GRAMMAR_SANDBOX_H */

View File

@ -22,18 +22,26 @@
* 02111-1307, USA.
*/
#include <zebra.h>
#include "command_graph.h"
#include "graph.h"
#include "memory.h"
struct graph *
graph_new ()
{
struct graph *graph = XCALLOC (MTYPE_GRAPH, sizeof(struct graph));
graph->nodes = vector_init (VECTOR_MIN_SIZE);
return graph;
}
struct graph_node *
graph_new_node (struct graph *graph, void *data, void (*del) (void*))
{
struct graph_node *node =
XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct graph_node));
XCALLOC(MTYPE_GRAPH_NODE, sizeof(struct graph_node));
node->from = vector_init(VECTOR_MIN_SIZE);
node->to = vector_init(VECTOR_MIN_SIZE);
node->from = vector_init (VECTOR_MIN_SIZE);
node->to = vector_init (VECTOR_MIN_SIZE);
node->data = data;
node->del = del;
@ -51,10 +59,10 @@ graph_delete_node (struct graph *graph, struct graph_node *node)
struct graph_node *adj;
// for all nodes that have an edge to us, remove us from their ->to
for (int i = 0; i < vector_active (node->from); i++)
for (unsigned int i = 0; i < vector_active (node->from); i++)
{
adj = vector_slot (node->from, i);
for (int j = 0; j < vector_active (adj->to); j++)
for (unsigned int j = 0; j < vector_active (adj->to); j++)
if (vector_slot (adj->to, j) == node)
vector_unset (adj->to, j);
@ -62,14 +70,14 @@ graph_delete_node (struct graph *graph, struct graph_node *node)
if (vector_active (adj->to) == 0 &&
vector_active (adj->from) == 0 &&
adj != node)
graph_delete_node (adj);
graph_delete_node (graph, adj);
}
// for all nodes that we have an edge to, remove us from their ->from
for (int i = 0; i < vector_active (node->to); i++)
for (unsigned int i = 0; i < vector_active (node->to); i++)
{
adj = vector_slot (node->to, i);
for (int j = 0; j < vector_active (adj->from); j++)
for (unsigned int j = 0; j < vector_active (adj->from); j++)
if (vector_slot (adj->from, j) == node)
vector_unset (adj->from, j);
@ -77,7 +85,7 @@ graph_delete_node (struct graph *graph, struct graph_node *node)
if (vector_active (adj->to) == 0 &&
vector_active (adj->from) == 0 &&
adj != node)
graph_delete_node (adj);
graph_delete_node (graph, adj);
}
// if there is a deletion callback, call it!
@ -89,12 +97,12 @@ graph_delete_node (struct graph *graph, struct graph_node *node)
vector_free (node->from);
// remove node from graph->nodes
for (int i = 0; i < vector_active (graph->nodes); i++)
for (unsigned int i = 0; i < vector_active (graph->nodes); i++)
if (vector_slot (graph->nodes, i) == node)
vector_unset (graph->nodes, i);
// free the node itself
free (node);
XFREE (MTYPE_GRAPH_NODE, node);
}
struct graph_node *
@ -109,9 +117,9 @@ void
graph_delete_graph (struct graph *graph)
{
// delete each node in the graph
for (int i = 0; i < vector_active (graph->nodes); i++)
graph_delete_node (vector_slot (graph->nodes, i));
for (unsigned int i = 0; i < vector_active (graph->nodes); i++)
graph_delete_node (graph, vector_slot (graph->nodes, i));
vector_free (graph->nodes);
free (graph);
XFREE (MTYPE_GRAPH, graph);
}

View File

@ -27,26 +27,23 @@
#include "vector.h"
/**
* Graph structure.
*/
struct graph
{
vector nodes; // all nodes in the graph
}
vector nodes;
};
/**
* Graph node / vertex.
*/
struct graph_node
{
vector from; // nodes which have edges to this node
vector to; // nodes which this node has edges to
void *data; // node data
void (*del) (void *data) // deletion callback
void (*del) (void *data); // deletion callback
};
struct graph *
graph_new (void);
/**
* Creates a new node.
*