forked from Mirror/frr
lib: Mostly complete matcher
Input matching and completions works. Still some rough edges. Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
This commit is contained in:
parent
9d0662e009
commit
18be0e599d
|
@ -78,11 +78,19 @@ new_node(enum graph_node_type type)
|
|||
return node;
|
||||
}
|
||||
|
||||
void
|
||||
walk_graph(struct graph_node *start, int level)
|
||||
const char *
|
||||
describe_node(struct graph_node *node)
|
||||
{
|
||||
const char *desc = NULL;
|
||||
char num[21];
|
||||
|
||||
if (node == NULL) {
|
||||
desc = "(null node)";
|
||||
return desc;
|
||||
}
|
||||
|
||||
// print this node
|
||||
switch (start->type) {
|
||||
switch (node->type) {
|
||||
case WORD_GN:
|
||||
case IPV4_GN:
|
||||
case IPV4_PREFIX_GN:
|
||||
|
@ -90,44 +98,46 @@ walk_graph(struct graph_node *start, int level)
|
|||
case IPV6_PREFIX_GN:
|
||||
case VARIABLE_GN:
|
||||
case RANGE_GN:
|
||||
fprintf(stderr, "%s", start->text);
|
||||
desc = node->text;
|
||||
break;
|
||||
case NUMBER_GN:
|
||||
fprintf(stderr, "%d", start->value);
|
||||
sprintf(num, "%d", node->value);
|
||||
break;
|
||||
case SELECTOR_GN:
|
||||
fprintf(stderr, "<>");
|
||||
desc = "<>";
|
||||
break;
|
||||
case OPTION_GN:
|
||||
fprintf(stderr, "[]");
|
||||
desc = "[]";
|
||||
break;
|
||||
case NUL_GN:
|
||||
fprintf(stderr, "NUL");
|
||||
desc = "NUL";
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "ERROR");
|
||||
desc = "ERROR";
|
||||
}
|
||||
fprintf(stderr, "[%d] ", vector_active(start->children));
|
||||
return desc;
|
||||
}
|
||||
|
||||
if (vector_active(start->children))
|
||||
for (unsigned int i = 0; i < vector_active(start->children); i++)
|
||||
{
|
||||
struct graph_node *r = vector_slot(start->children, i);
|
||||
if (!r) {
|
||||
fprintf(stderr, "Child seems null?\n");
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (vector_active(start->children) > 1) {
|
||||
fprintf(stderr, "\n");
|
||||
for (int i = 0; i < level+1; i++)
|
||||
fprintf(stderr, " ");
|
||||
walk_graph(r, level+1);
|
||||
}
|
||||
else
|
||||
walk_graph(r, level);
|
||||
|
||||
void
|
||||
walk_graph(struct graph_node *start, int level)
|
||||
{
|
||||
// print this node
|
||||
fprintf(stderr, "%s[%d] ", describe_node(start), vector_active(start->children));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (level == 0)
|
||||
}
|
||||
else
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
|
|
@ -82,4 +82,11 @@ new_node(enum graph_node_type);
|
|||
extern void
|
||||
walk_graph(struct graph_node *, int);
|
||||
|
||||
/**
|
||||
* Returns a string representation of the given node.
|
||||
* @param[in] the node to describe
|
||||
* @return pointer to description string
|
||||
*/
|
||||
extern const char *
|
||||
describe_node(struct graph_node *);
|
||||
#endif
|
||||
|
|
|
@ -1,12 +1,187 @@
|
|||
#include <zebra.h>
|
||||
#include "memory.h"
|
||||
#include "vector.h"
|
||||
#include "command_match.h"
|
||||
|
||||
enum match_type
|
||||
static enum match_type
|
||||
match_token (struct graph_node *node, char *token, enum filter_type filter)
|
||||
{
|
||||
switch (node->type) {
|
||||
case WORD_GN:
|
||||
return cmd_word_match (node, filter, token);
|
||||
case IPV4_GN:
|
||||
return cmd_ipv4_match (token);
|
||||
case IPV4_PREFIX_GN:
|
||||
return cmd_ipv4_prefix_match (token);
|
||||
case IPV6_GN:
|
||||
return cmd_ipv6_match (token);
|
||||
case IPV6_PREFIX_GN:
|
||||
return cmd_ipv6_prefix_match (token);
|
||||
case RANGE_GN:
|
||||
return cmd_range_match (node, token);
|
||||
case NUMBER_GN:
|
||||
return node->value == atoi(token);
|
||||
case VARIABLE_GN:
|
||||
default:
|
||||
return no_match;
|
||||
}
|
||||
}
|
||||
|
||||
/* Breaking up string into each command piece. I assume given
|
||||
character is separated by a space character. Return value is a
|
||||
vector which includes char ** data element. */
|
||||
static vector
|
||||
cmd_make_strvec (const char *string)
|
||||
{
|
||||
const char *cp, *start;
|
||||
char *token;
|
||||
int strlen;
|
||||
vector strvec;
|
||||
|
||||
if (string == NULL)
|
||||
return NULL;
|
||||
|
||||
cp = string;
|
||||
|
||||
/* Skip white spaces. */
|
||||
while (isspace ((int) *cp) && *cp != '\0')
|
||||
cp++;
|
||||
|
||||
/* Return if there is only white spaces */
|
||||
if (*cp == '\0')
|
||||
return NULL;
|
||||
|
||||
if (*cp == '!' || *cp == '#')
|
||||
return NULL;
|
||||
|
||||
/* Prepare return vector. */
|
||||
strvec = vector_init (VECTOR_MIN_SIZE);
|
||||
|
||||
/* Copy each command piece and set into vector. */
|
||||
while (1)
|
||||
{
|
||||
start = cp;
|
||||
while (!(isspace ((int) *cp) || *cp == '\r' || *cp == '\n') &&
|
||||
*cp != '\0')
|
||||
cp++;
|
||||
strlen = cp - start;
|
||||
token = XMALLOC (MTYPE_STRVEC, strlen + 1);
|
||||
memcpy (token, start, strlen);
|
||||
*(token + strlen) = '\0';
|
||||
vector_set (strvec, token);
|
||||
|
||||
while ((isspace ((int) *cp) || *cp == '\n' || *cp == '\r') &&
|
||||
*cp != '\0')
|
||||
cp++;
|
||||
|
||||
if (*cp == '\0')
|
||||
return strvec;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 though their children are attached
|
||||
* to their parent.
|
||||
*
|
||||
* @param[out] l the list to add the children to
|
||||
* @param[in] node the node to get the children of
|
||||
* @return the number of children added to the list
|
||||
*/
|
||||
static int
|
||||
add_nexthops(struct list *l, 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);
|
||||
if (child->type == OPTION_GN || child->type == SELECTOR_GN || child->type == NUL_GN)
|
||||
added += add_nexthops(l, child);
|
||||
else {
|
||||
listnode_add(l, child);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles matches or 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, there are no possible
|
||||
* transitions, or both.
|
||||
* If there are no further state transitions available, one of two scenarios is possible:
|
||||
* - The end of input has been reached. This indicates a valid command.
|
||||
* - The end of input has not yet been reached. The input does not match any command.
|
||||
* If there are further transitions available, one of two scenarios is possible:
|
||||
* - The current input is a valid prefix to a longer command
|
||||
* - The current input matches a command
|
||||
* - The current input matches a command, and is also a valid prefix to a longer command
|
||||
*
|
||||
* Any other states indicate a programming error.
|
||||
*
|
||||
* @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 an array with two lists. The first list is
|
||||
*/
|
||||
struct list**
|
||||
match_command (struct graph_node *start, enum filter_type filter, const char *input)
|
||||
{
|
||||
// match input on DFA
|
||||
return exact_match;
|
||||
// break command
|
||||
vector command = cmd_make_strvec (input);
|
||||
|
||||
// pointer to next input token to match
|
||||
char *token;
|
||||
|
||||
struct list *current = list_new(), // current nodes to match input token against
|
||||
*matched = list_new(), // current nodes that match the input token
|
||||
*next = list_new(); // possible next hops to current input token
|
||||
|
||||
// pointers used for iterating lists
|
||||
struct graph_node *cnode;
|
||||
struct listnode *node;
|
||||
|
||||
// add all children of start node to list
|
||||
add_nexthops(next, start);
|
||||
|
||||
unsigned int idx;
|
||||
for (idx = 0; idx < vector_active(command) && next->count > 0; idx++)
|
||||
{
|
||||
list_free (current);
|
||||
current = next;
|
||||
next = list_new();
|
||||
|
||||
token = vector_slot(command, idx);
|
||||
|
||||
list_delete_all_node(matched);
|
||||
|
||||
for (ALL_LIST_ELEMENTS_RO(current,node,cnode))
|
||||
{
|
||||
if (match_token(cnode, token, filter) == exact_match) {
|
||||
listnode_add(matched, cnode);
|
||||
add_nexthops(next, cnode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Variable summary
|
||||
* -----------------------------------------------------------------
|
||||
* token = last input token processed
|
||||
* idx = index in `command` of last token processed
|
||||
* current = set of all transitions from the previous input token
|
||||
* matched = set of all nodes reachable with current input
|
||||
* next = set of all nodes reachable from all nodes in `matched`
|
||||
*/
|
||||
|
||||
struct list **result = malloc( 2 * sizeof(struct list *) );
|
||||
result[0] = matched;
|
||||
result[1] = next;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define COMMAND_MATCH_H
|
||||
|
||||
#include "command_graph.h"
|
||||
#include "linklist.h"
|
||||
|
||||
/**
|
||||
* Filter types. These tell the parser whether to allow
|
||||
|
@ -27,11 +28,11 @@ enum matcher_rv
|
|||
};
|
||||
|
||||
/* Completion match types. */
|
||||
enum match_type
|
||||
enum match_type
|
||||
{
|
||||
no_match,
|
||||
partly_match,
|
||||
exact_match
|
||||
exact_match
|
||||
};
|
||||
/**
|
||||
* Defines which matcher_rv values constitute
|
||||
|
@ -63,7 +64,7 @@ cmd_range_match (struct graph_node *, const char *str);
|
|||
enum match_type
|
||||
cmd_word_match (struct graph_node *, enum filter_type, const char *);
|
||||
|
||||
enum match_type
|
||||
struct list**
|
||||
match_command (struct graph_node *, enum filter_type, const char *);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,9 +19,7 @@ extern void yyerror(const char *);
|
|||
%}
|
||||
%code provides {
|
||||
extern struct
|
||||
graph_node *cmd_parse_format(const char *,
|
||||
const char *,
|
||||
struct graph_node *);
|
||||
graph_node *cmd_parse_format(const char *, const char *, struct graph_node *);
|
||||
extern void
|
||||
set_buffer_string(const char *);
|
||||
}
|
||||
|
|
|
@ -2,29 +2,12 @@
|
|||
#include "command_graph.h"
|
||||
#include "command_parse.h"
|
||||
#include "command_match.h"
|
||||
#include "linklist.h"
|
||||
|
||||
#define GRAMMAR_STR "CLI grammar sandbox\n"
|
||||
|
||||
struct graph_node * nodegraph;
|
||||
|
||||
/*
|
||||
char* combine_vararg(char* argv, int argc) {
|
||||
size_t linesize = 0;
|
||||
for (int i = 0; i < argc; i++)
|
||||
linesize += strlen(argv[i]) + 1;
|
||||
|
||||
char* cat = malloc(linesize);
|
||||
cat[0] = '\0';
|
||||
for (int i = 0; i < argc; i++) {
|
||||
strcat(cat, argv[i]);
|
||||
if (i != argc)
|
||||
strcat(cat, " ");
|
||||
}
|
||||
|
||||
return cat;
|
||||
}
|
||||
*/
|
||||
|
||||
DEFUN (grammar_test,
|
||||
grammar_test_cmd,
|
||||
"grammar parse .COMMAND",
|
||||
|
@ -57,7 +40,26 @@ DEFUN (grammar_test_match,
|
|||
"command to match")
|
||||
{
|
||||
const char* command = argv_concat(argv, argc, 0);
|
||||
match_command(nodegraph, FILTER_STRICT, command);
|
||||
struct list **result = match_command(nodegraph, FILTER_STRICT, command);
|
||||
struct list *matched = result[0];
|
||||
struct list *next = result[1];
|
||||
|
||||
if (matched->count == 0) // the last token tried yielded no matches
|
||||
fprintf(stderr, "%% Unknown command\n");
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%% Matched full input, possible completions:\n");
|
||||
struct listnode *node;
|
||||
struct graph_node *cnode;
|
||||
// iterate through currently matched nodes to see if any are leaves
|
||||
for (ALL_LIST_ELEMENTS_RO(matched,node,cnode))
|
||||
if (cnode->is_leaf)
|
||||
fprintf(stderr, "<cr>\n");
|
||||
// print possible next hops, if any
|
||||
for (ALL_LIST_ELEMENTS_RO(next,node,cnode))
|
||||
fprintf(stderr, "%s\n",describe_node(cnode));
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue