Merge pull request #1046 from qlyoung/fix-cli-ambiguous

fix handling of ambiguous cli
This commit is contained in:
David Lamparter 2017-08-27 19:20:14 +02:00 committed by GitHub
commit 272e15e723

View File

@ -46,8 +46,9 @@ DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack")
static int add_nexthops(struct list *, struct graph_node *, static int add_nexthops(struct list *, struct graph_node *,
struct graph_node **, size_t); struct graph_node **, size_t);
static struct list *command_match_r(struct graph_node *, vector, unsigned int, static enum matcher_rv command_match_r(struct graph_node *, vector,
struct graph_node **); unsigned int, struct graph_node **,
struct list **);
static int score_precedence(enum cmd_token_type); static int score_precedence(enum cmd_token_type);
@ -80,14 +81,12 @@ static enum match_type match_variable(struct cmd_token *, const char *);
static enum match_type match_mac(const char *, bool); static enum match_type match_mac(const char *, bool);
/* matching functions */
static enum matcher_rv matcher_rv;
enum matcher_rv command_match(struct graph *cmdgraph, vector vline, enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
struct list **argv, const struct cmd_element **el) struct list **argv, const struct cmd_element **el)
{ {
struct graph_node *stack[MAXDEPTH]; struct graph_node *stack[MAXDEPTH];
matcher_rv = MATCHER_NO_MATCH; enum matcher_rv status;
*argv = NULL;
// prepend a dummy token to match that pesky start node // prepend a dummy token to match that pesky start node
vector vvline = vector_init(vline->alloced + 1); vector vvline = vector_init(vline->alloced + 1);
@ -97,9 +96,8 @@ enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
vvline->active = vline->active + 1; vvline->active = vline->active + 1;
struct graph_node *start = vector_slot(cmdgraph->nodes, 0); struct graph_node *start = vector_slot(cmdgraph->nodes, 0);
if ((*argv = command_match_r(start, vvline, 0, status = command_match_r(start, vvline, 0, stack, argv);
stack))) // successful match if (status == MATCHER_OK) { // successful match
{
struct listnode *head = listhead(*argv); struct listnode *head = listhead(*argv);
struct listnode *tail = listtail(*argv); struct listnode *tail = listtail(*argv);
@ -115,6 +113,9 @@ enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
// input, with each cmd_token->arg holding the corresponding // input, with each cmd_token->arg holding the corresponding
// input // input
assert(*el); assert(*el);
} else if (*argv) {
del_arglist(*argv);
*argv = NULL;
} }
if (!*el) { if (!*el) {
@ -129,7 +130,7 @@ enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
// free vector // free vector
vector_free(vvline); vector_free(vvline);
return matcher_rv; return status;
} }
/** /**
@ -183,11 +184,15 @@ enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
* *
* If no match was found, the return value is NULL. * If no match was found, the return value is NULL.
*/ */
static struct list *command_match_r(struct graph_node *start, vector vline, static enum matcher_rv command_match_r(struct graph_node *start, vector vline,
unsigned int n, struct graph_node **stack) unsigned int n,
struct graph_node **stack,
struct list **currbest)
{ {
assert(n < vector_active(vline)); assert(n < vector_active(vline));
enum matcher_rv status = MATCHER_NO_MATCH;
// get the minimum match level that can count as a full match // get the minimum match level that can count as a full match
struct cmd_token *token = start->data; struct cmd_token *token = start->data;
enum match_type minmatch = min_match_level(token->type); enum match_type minmatch = min_match_level(token->type);
@ -196,11 +201,11 @@ static struct list *command_match_r(struct graph_node *start, vector vline,
* this disallows matching the same one more than once if there is a * this disallows matching the same one more than once if there is a
* circle in the graph (used for keyword arguments) */ * circle in the graph (used for keyword arguments) */
if (n == MAXDEPTH) if (n == MAXDEPTH)
return NULL; return MATCHER_NO_MATCH;
if (!token->allowrepeat) if (!token->allowrepeat)
for (size_t s = 0; s < n; s++) for (size_t s = 0; s < n; s++)
if (stack[s] == start) if (stack[s] == start)
return NULL; return MATCHER_NO_MATCH;
// get the current operating input token // get the current operating input token
char *input_token = vector_slot(vline, n); char *input_token = vector_slot(vline, n);
@ -231,7 +236,7 @@ static struct list *command_match_r(struct graph_node *start, vector vline,
// if we don't match this node, die // if we don't match this node, die
if (match_token(token, input_token) < minmatch) if (match_token(token, input_token) < minmatch)
return NULL; return MATCHER_NO_MATCH;
stack[n] = start; stack[n] = start;
@ -244,86 +249,92 @@ static struct list *command_match_r(struct graph_node *start, vector vline,
add_nexthops(next, start, NULL, 0); add_nexthops(next, start, NULL, 0);
// determine the best match // determine the best match
int ambiguous = 0;
struct list *currbest = NULL;
for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) { for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) {
// if we've matched all input we're looking for END_TKN // if we've matched all input we're looking for END_TKN
if (n + 1 == vector_active(vline)) { if (n + 1 == vector_active(vline)) {
struct cmd_token *tok = gn->data; struct cmd_token *tok = gn->data;
if (tok->type == END_TKN) { if (tok->type == END_TKN) {
if (currbest) // there is more than one END_TKN // if more than one END_TKN in the follow set
// in the follow set if (*currbest) {
{ status = MATCHER_AMBIGUOUS;
ambiguous = 1;
break; break;
} else {
status = MATCHER_OK;
} }
currbest = list_new(); *currbest = list_new();
// node should have one child node with the // node should have one child node with the
// element // element
struct graph_node *leaf = struct graph_node *leaf =
vector_slot(gn->to, 0); vector_slot(gn->to, 0);
// last node in the list will hold the // last node in the list will hold the
// cmd_element; // cmd_element; this is important because
// this is important because list_delete() // list_delete() expects that all nodes have
// expects // the same data type, so when deleting this
// that all nodes have the same data type, so // list the last node must be manually deleted
// when
// deleting this list the last node must be
// manually deleted
struct cmd_element *el = leaf->data; struct cmd_element *el = leaf->data;
listnode_add(currbest, el); listnode_add(*currbest, el);
currbest->del = (*currbest)->del =
(void (*)(void *)) & cmd_token_del; (void (*)(void *)) & cmd_token_del;
// do not break immediately; continue walking // do not break immediately; continue walking
// through the follow set // through the follow set to ensure that there
// to ensure that there is exactly one END_TKN // is exactly one END_TKN
} }
continue; continue;
} }
// else recurse on candidate child node // else recurse on candidate child node
struct list *result = command_match_r(gn, vline, n + 1, stack); struct list *result = NULL;
enum matcher_rv rstat =
command_match_r(gn, vline, n + 1, stack, &result);
// save the best match // save the best match
if (result && currbest) { if (result && *currbest) {
// pick the best of two matches // pick the best of two matches
struct list *newbest = struct list *newbest =
disambiguate(currbest, result, vline, n + 1); disambiguate(*currbest, result, vline, n + 1);
// set ambiguity flag
ambiguous = // current best and result are ambiguous
!newbest || (ambiguous && newbest == currbest); if (!newbest)
status = MATCHER_AMBIGUOUS;
// current best is still the best, but ambiguous
else if (newbest == *currbest
&& status == MATCHER_AMBIGUOUS)
status = MATCHER_AMBIGUOUS;
// result is better, but also ambiguous
else if (newbest == result
&& rstat == MATCHER_AMBIGUOUS)
status = MATCHER_AMBIGUOUS;
// one or the other is superior and not ambiguous
else
status = MATCHER_OK;
// delete the unnecessary result // delete the unnecessary result
struct list *todelete = struct list *todelete =
((newbest && newbest == result) ? currbest ((newbest && newbest == result) ? *currbest
: result); : result);
del_arglist(todelete); del_arglist(todelete);
currbest = newbest ? newbest : currbest; *currbest = newbest ? newbest : *currbest;
} else if (result) } else if (result) {
currbest = result; status = rstat;
} *currbest = result;
} else if (!*currbest) {
if (currbest) { status = MAX(rstat, status);
if (ambiguous) {
del_arglist(currbest);
currbest = NULL;
matcher_rv = MATCHER_AMBIGUOUS;
} else {
// copy token, set arg and prepend to currbest
struct cmd_token *token = start->data;
struct cmd_token *copy = cmd_token_dup(token);
copy->arg = XSTRDUP(MTYPE_CMD_ARG, input_token);
listnode_add_before(currbest, currbest->head, copy);
matcher_rv = MATCHER_OK;
} }
} else if (n + 1 == vector_active(vline) }
&& matcher_rv == MATCHER_NO_MATCH) if (*currbest) {
matcher_rv = MATCHER_INCOMPLETE; // copy token, set arg and prepend to currbest
struct cmd_token *token = start->data;
struct cmd_token *copy = cmd_token_dup(token);
copy->arg = XSTRDUP(MTYPE_CMD_ARG, input_token);
listnode_add_before(*currbest, (*currbest)->head, copy);
} else if (n + 1 == vector_active(vline) && status == MATCHER_NO_MATCH)
status = MATCHER_INCOMPLETE;
// cleanup // cleanup
list_delete(next); list_delete(next);
return currbest; return status;
} }
static void stack_del(void *val) static void stack_del(void *val)
@ -432,12 +443,12 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,
* next = set of all nodes reachable from all nodes in `matched` * next = set of all nodes reachable from all nodes in `matched`
*/ */
matcher_rv = idx == vector_active(vline) && next->count enum matcher_rv mrv = idx == vector_active(vline) && next->count
? MATCHER_OK ? MATCHER_OK
: MATCHER_NO_MATCH; : MATCHER_NO_MATCH;
*completions = NULL; *completions = NULL;
if (!MATCHER_ERROR(matcher_rv)) { if (!MATCHER_ERROR(mrv)) {
// extract cmd_token into list // extract cmd_token into list
*completions = list_new(); *completions = list_new();
for (ALL_LIST_ELEMENTS_RO(next, node, gstack)) { for (ALL_LIST_ELEMENTS_RO(next, node, gstack)) {
@ -448,7 +459,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,
list_delete(current); list_delete(current);
list_delete(next); list_delete(next);
return matcher_rv; return mrv;
} }
/** /**