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