diff --git a/lib/command_graph.c b/lib/command_graph.c index 9b91eaf242..4e99884dc8 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -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"); } diff --git a/lib/command_graph.h b/lib/command_graph.h index 8d23577d37..081ecaac4c 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -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 diff --git a/lib/command_match.c b/lib/command_match.c index bcc28bdc5b..f7e2789253 100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@ -1,12 +1,187 @@ #include #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; } diff --git a/lib/command_match.h b/lib/command_match.h index cddeb08af7..4fc0995940 100644 --- a/lib/command_match.h +++ b/lib/command_match.h @@ -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 diff --git a/lib/command_parse.y b/lib/command_parse.y index 80487af7cd..9c1b3fff24 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -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 *); } diff --git a/lib/grammar_sandbox.c b/lib/grammar_sandbox.c index 1e6a76c69c..fab78f3e21 100644 --- a/lib/grammar_sandbox.c +++ b/lib/grammar_sandbox.c @@ -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, "\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; }