From fa133e00c2103ffcacbf3a20d0c1a7a1a86d63ab Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Wed, 23 Nov 2016 11:58:13 +0100 Subject: [PATCH 01/13] lib: parser: refresh grammar_sandbox This makes grammar_sandbox a workable tool again, updating it for the recent changes. Signed-off-by: David Lamparter --- lib/Makefile.am | 5 + lib/grammar_sandbox.c | 219 ++++++++++++++++++++++++++---------------- lib/grammar_sandbox.h | 9 -- 3 files changed, 140 insertions(+), 93 deletions(-) diff --git a/lib/Makefile.am b/lib/Makefile.am index 29584f82b5d8..2906b9513a5c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -51,6 +51,11 @@ pkginclude_HEADERS = \ noinst_HEADERS = \ plist_int.h +noinst_PROGRAMS = grammar_sandbox + +grammar_sandbox_SOURCES = grammar_sandbox.c +grammar_sandbox_LDADD = libzebra.la + EXTRA_DIST = \ queue.h \ command_lex.h \ diff --git a/lib/grammar_sandbox.c b/lib/grammar_sandbox.c index c4ae2d7d7704..0239ca44ac9c 100644 --- a/lib/grammar_sandbox.c +++ b/lib/grammar_sandbox.c @@ -30,19 +30,23 @@ */ #include "command.h" +#include "memory_vty.h" #include "graph.h" -#include "command_parse.h" #include "command_match.h" #define GRAMMAR_STR "CLI grammar sandbox\n" +DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc") + +#define MAXDEPTH 64 + /** headers **/ void grammar_sandbox_init (void); void -pretty_print_graph (struct graph_node *, int); +pretty_print_graph (struct vty *vty, struct graph_node *, int, int, struct graph_node **, size_t); void -init_cmdgraph (struct graph **); +init_cmdgraph (struct vty *, struct graph **); vector completions_to_vec (struct list *); int @@ -53,8 +57,9 @@ struct graph *nodegraph; DEFUN (grammar_test, grammar_test_cmd, - "grammar parse .COMMAND", + "grammar parse LINE...", GRAMMAR_STR + "parse a command\n" "command to pass to new parser\n") { int idx_command = 2; @@ -64,9 +69,8 @@ DEFUN (grammar_test, // create cmd_element for parser struct cmd_element *cmd = XCALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_element)); cmd->string = command; - cmd->doc = NULL; + cmd->doc = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n"; cmd->func = NULL; - cmd->tokens = vector_init (VECTOR_MIN_SIZE); // parse the command and install it into the command graph command_parse_format (nodegraph, cmd); @@ -76,13 +80,16 @@ DEFUN (grammar_test, DEFUN (grammar_test_complete, grammar_test_complete_cmd, - "grammar complete .COMMAND", + "grammar complete COMMAND...", GRAMMAR_STR "attempt to complete input on DFA\n" - "command to complete") + "command to complete\n") { int idx_command = 2; char *cmdstr = argv_concat (argv, argc, idx_command); + if (!cmdstr) + return CMD_SUCCESS; + vector command = cmd_make_strvec (cmdstr); // generate completions of user input @@ -93,7 +100,7 @@ DEFUN (grammar_test_complete, if (!MATCHER_ERROR(result)) { vector comps = completions_to_vec (completions); - struct cmd_token_t *tkn; + struct cmd_token *tkn; // calculate length of longest tkn->text in completions unsigned int width = 0, i = 0; @@ -106,15 +113,15 @@ DEFUN (grammar_test_complete, // print completions for (i = 0; i < vector_active (comps); i++) { tkn = vector_slot (comps, i); - fprintf (stdout, " %-*s %s%s", width, tkn->text, tkn->desc, "\n"); + vty_out (vty, " %-*s %s%s", width, tkn->text, tkn->desc, VTY_NEWLINE); } for (i = 0; i < vector_active (comps); i++) - del_cmd_token ((struct cmd_token_t *) vector_slot (comps, i)); + del_cmd_token ((struct cmd_token *) vector_slot (comps, i)); vector_free (comps); } else - fprintf (stdout, "%% No match%s", "\n"); + vty_out (vty, "%% No match%s", VTY_NEWLINE); // free resources list_delete (completions); @@ -126,50 +133,49 @@ DEFUN (grammar_test_complete, DEFUN (grammar_test_match, grammar_test_match_cmd, - "grammar match .COMMAND", + "grammar match COMMAND...", GRAMMAR_STR "attempt to match input on DFA\n" - "command to match") + "command to match\n") { int idx_command = 2; - if (argv[0][0] == '#') + if (argv[2]->arg[0] == '#') return CMD_SUCCESS; char *cmdstr = argv_concat(argv, argc, idx_command); vector command = cmd_make_strvec (cmdstr); struct list *argvv = NULL; - struct cmd_element *element = NULL; + const struct cmd_element *element = NULL; enum matcher_rv result = command_match (nodegraph, command, &argvv, &element); // print completions or relevant error message if (element) { - fprintf (stdout, "Matched: %s%s", element->string, "\n"); + vty_out (vty, "Matched: %s%s", element->string, VTY_NEWLINE); struct listnode *ln; - struct cmd_token_t *token; + struct cmd_token *token; for (ALL_LIST_ELEMENTS_RO(argvv,ln,token)) - fprintf (stdout, "%s -- %s%s", token->text, token->arg, "\n"); + vty_out (vty, "%s -- %s%s", token->text, token->arg, VTY_NEWLINE); - fprintf (stdout, "func: %p%s", element->func, "\n"); + vty_out (vty, "func: %p%s", element->func, VTY_NEWLINE); list_delete (argvv); - del_cmd_element (element); } else { assert(MATCHER_ERROR(result)); switch (result) { case MATCHER_NO_MATCH: - fprintf (stdout, "%% Unknown command%s", "\n"); + vty_out (vty, "%% Unknown command%s", VTY_NEWLINE); break; case MATCHER_INCOMPLETE: - fprintf (stdout, "%% Incomplete command%s", "\n"); + vty_out (vty, "%% Incomplete command%s", VTY_NEWLINE); break; case MATCHER_AMBIGUOUS: - fprintf (stdout, "%% Ambiguous command%s", "\n"); + vty_out (vty, "%% Ambiguous command%s", VTY_NEWLINE); break; default: - fprintf (stdout, "%% Unknown error%s", "\n"); + vty_out (vty, "%% Unknown error%s", VTY_NEWLINE); break; } } @@ -208,7 +214,6 @@ DEFUN (grammar_test_doc, "optional lol\n" "vararg!\n"); cmd->func = NULL; - cmd->tokens = vector_init (VECTOR_MIN_SIZE); // parse element command_parse_format (nodegraph, cmd); @@ -221,31 +226,34 @@ DEFUN (grammar_test_doc, */ DEFUN (grammar_test_show, grammar_test_show_cmd, - "grammar show graph", + "grammar show [doc]", GRAMMAR_STR - "print current accumulated DFA\n") + "print current accumulated DFA\n" + "include docstrings\n") { + struct graph_node *stack[MAXDEPTH]; + if (!nodegraph) - zlog_info("nodegraph uninitialized"); + vty_out(vty, "nodegraph uninitialized\r\n"); else - pretty_print_graph (vector_slot (nodegraph->nodes, 0), 0); + pretty_print_graph (vty, vector_slot (nodegraph->nodes, 0), 0, argc >= 3, stack, 0); return CMD_SUCCESS; } DEFUN (grammar_init_graph, grammar_init_graph_cmd, - "grammar init graph", + "grammar init", GRAMMAR_STR "(re)initialize graph\n") { graph_delete_graph (nodegraph); - init_cmdgraph (&nodegraph); + init_cmdgraph (vty, &nodegraph); return CMD_SUCCESS; } /* this is called in vtysh.c to set up the testing shim */ -void grammar_sandbox_init() { - init_cmdgraph (&nodegraph); +void grammar_sandbox_init(void) { + init_cmdgraph (NULL, &nodegraph); // install all enable elements install_element (ENABLE_NODE, &grammar_test_cmd); @@ -256,6 +264,25 @@ void grammar_sandbox_init() { install_element (ENABLE_NODE, &grammar_init_graph_cmd); } +#define item(x) { x, #x } +struct message tokennames[] = { + item(WORD_TKN), // words + item(VARIABLE_TKN), // almost anything + item(RANGE_TKN), // integer range + item(IPV4_TKN), // IPV4 addresses + item(IPV4_PREFIX_TKN), // IPV4 network prefixes + item(IPV6_TKN), // IPV6 prefixes + item(IPV6_PREFIX_TKN), // IPV6 network prefixes + + /* plumbing types */ + item(SELECTOR_TKN), // marks beginning of selector + item(OPTION_TKN), // marks beginning of option + item(NUL_TKN), // dummy token + item(START_TKN), // first token in line + item(END_TKN), // last token in line + { 0, NULL } +}; +size_t tokennames_max = array_size(tokennames); /** * Pretty-prints a graph, assuming it is a tree. @@ -264,51 +291,78 @@ void grammar_sandbox_init() { * @param level indent level for recursive calls, always pass 0 */ void -pretty_print_graph (struct graph_node *start, int level) +pretty_print_graph (struct vty *vty, struct graph_node *start, int level, + int desc, struct graph_node **stack, size_t stackpos) { // print this node - struct cmd_token_t *tok = start->data; - fprintf (stdout, "%s[%d] ", tok->text, tok->type); + char tokennum[32]; + struct cmd_token *tok = start->data; + + snprintf(tokennum, sizeof(tokennum), "%d?", tok->type); + vty_out(vty, "%s", LOOKUP_DEF(tokennames, tok->type, tokennum)); + if (tok->text) + vty_out(vty, ":\"%s\"", tok->text); + if (desc) + vty_out(vty, " ?'%s'", tok->desc); + vty_out(vty, " "); + + if (stackpos == MAXDEPTH) + { + vty_out(vty, " -aborting! (depth limit)%s", VTY_NEWLINE); + return; + } + stack[stackpos++] = start; - int numto = vector_active (start->to); + int numto = desc ? 2 : vector_active (start->to); if (numto) { if (numto > 1) - fprintf (stdout, "\n"); + vty_out(vty, "%s", VTY_NEWLINE); for (unsigned int i = 0; i < vector_active (start->to); i++) { struct graph_node *adj = vector_slot (start->to, i); // if we're listing multiple children, indent! if (numto > 1) for (int j = 0; j < level+1; j++) - fprintf (stdout, " "); + vty_out(vty, " "); // if this node is a vararg, just print * if (adj == start) - fprintf (stdout, "*"); - else - pretty_print_graph (adj, numto > 1 ? level+1 : level); - } + vty_out(vty, "*"); + else if (((struct cmd_token *)adj->data)->type == END_TKN) + vty_out(vty, "--END%s", VTY_NEWLINE); + else { + size_t k; + for (k = 0; k < stackpos; k++) + if (stack[k] == adj) { + vty_out(vty, "< 1 ? level+1 : level, desc, stack, stackpos); + } + } } else - fprintf(stdout, "\n"); + vty_out(vty, "%s", VTY_NEWLINE); } /** stuff that should go in command.c + command.h */ void -init_cmdgraph (struct graph **graph) +init_cmdgraph (struct vty *vty, struct graph **graph) { // initialize graph, add start noe *graph = graph_new (); - struct cmd_token_t *token = new_cmd_token (START_TKN, NULL, NULL); + struct cmd_token *token = new_cmd_token (START_TKN, 0, NULL, NULL); graph_new_node (*graph, token, (void (*)(void *)) &del_cmd_token); - fprintf (stdout, "initialized graph\n"); + if (vty) + vty_out (vty, "initialized graph%s", VTY_NEWLINE); } int compare_completions (const void *fst, const void *snd) { - struct cmd_token_t *first = *(struct cmd_token_t **) fst, - *secnd = *(struct cmd_token_t **) snd; + struct cmd_token *first = *(struct cmd_token **) fst, + *secnd = *(struct cmd_token **) snd; return strcmp (first->text, secnd->text); } @@ -318,7 +372,7 @@ completions_to_vec (struct list *completions) vector comps = vector_init (VECTOR_MIN_SIZE); struct listnode *ln; - struct cmd_token_t *token; + struct cmd_token *token; unsigned int i, exists; for (ALL_LIST_ELEMENTS_RO(completions,ln,token)) { @@ -326,7 +380,7 @@ completions_to_vec (struct list *completions) exists = 0; for (i = 0; i < vector_active (comps) && !exists; i++) { - struct cmd_token_t *curr = vector_slot (comps, i); + struct cmd_token *curr = vector_slot (comps, i); exists = !strcmp (curr->text, token->text) && !strcmp (curr->desc, token->desc); } @@ -344,43 +398,40 @@ completions_to_vec (struct list *completions) return comps; } -struct cmd_token_t * -new_cmd_token (enum cmd_token_type_t type, char *text, char *desc) +static void vty_do_exit(void) { - struct cmd_token_t *token = XMALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_token_t)); - token->type = type; - token->text = text; - token->desc = desc; - token->arg = NULL; - - return token; + printf ("\nend.\n"); + exit (0); } -void -del_cmd_token (struct cmd_token_t *token) +struct thread_master *master; + +int main(int argc, char **argv) { - if (!token) return; + struct thread thread; - if (token->text) - XFREE (MTYPE_CMD_TOKENS, token->text); - if (token->desc) - XFREE (MTYPE_CMD_TOKENS, token->desc); - if (token->arg) - XFREE (MTYPE_CMD_TOKENS, token->arg); + master = thread_master_create (); - XFREE (MTYPE_CMD_TOKENS, token); -} + zlog_default = openzlog ("grammar_sandbox", ZLOG_NONE, 0, + LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON); + zlog_set_level (NULL, ZLOG_DEST_SYSLOG, ZLOG_DISABLED); + zlog_set_level (NULL, ZLOG_DEST_STDOUT, LOG_DEBUG); + zlog_set_level (NULL, ZLOG_DEST_MONITOR, ZLOG_DISABLED); -struct cmd_token_t * -copy_cmd_token (struct cmd_token_t *token) -{ - struct cmd_token_t *copy = new_cmd_token (token->type, NULL, NULL); - copy->value = token->value; - copy->max = token->max; - copy->min = token->min; - copy->text = token->text ? XSTRDUP (MTYPE_CMD_TOKENS, token->text) : NULL; - copy->desc = token->desc ? XSTRDUP (MTYPE_CMD_TOKENS, token->desc) : NULL; - copy->arg = token->arg ? XSTRDUP (MTYPE_CMD_TOKENS, token->arg) : NULL; - - return copy; + /* Library inits. */ + cmd_init (1); + host.name = strdup ("test"); + + vty_init (master); + memory_init (); + grammar_sandbox_init(); + + vty_stdio (vty_do_exit); + + /* Fetch next active thread. */ + while (thread_fetch (master, &thread)) + thread_call (&thread); + + /* Not reached. */ + exit (0); } diff --git a/lib/grammar_sandbox.h b/lib/grammar_sandbox.h index 6e61ce1b466d..5da0b05d09f5 100644 --- a/lib/grammar_sandbox.h +++ b/lib/grammar_sandbox.h @@ -53,13 +53,4 @@ struct cmd_token_t char *arg; // user input that matches this token }; -struct cmd_token_t * -new_cmd_token (enum cmd_token_type_t, char *, char *); - -void -del_cmd_token (struct cmd_token_t *); - -struct cmd_token_t * -copy_cmd_token (struct cmd_token_t *); - #endif /* _GRAMMAR_SANDBOX_H */ From 7d5718c140611ae125dfab56b94f6a96e19b5922 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 18 Nov 2016 15:56:18 +0100 Subject: [PATCH 02/13] lib: parser: support keyword arguments This re-adds "{foo WORD|bar WORD}" keyword-argument support to the CLI parser. Note that token graphs may now contain loops for this purpose; therefore the matching functions retain a history of already-matched tokens. Each token can thus only be consumed once. And then LINE... gets its special treatment with allowrepeat. Signed-off-by: David Lamparter --- lib/command_match.c | 104 +++++++++++++++++++++++++++++++++++--------- lib/command_parse.y | 22 ++++++++++ lib/graph.h | 1 + 3 files changed, 107 insertions(+), 20 deletions(-) diff --git a/lib/command_match.c b/lib/command_match.c index 944ade969d77..b6897109afc1 100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@ -28,6 +28,9 @@ #include "memory.h" DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens") +DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack") + +#define MAXDEPTH 64 #ifdef TRACE_MATCHER #define TM 1 @@ -40,10 +43,12 @@ DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens") /* matcher helper prototypes */ static int -add_nexthops (struct list *, struct graph_node *); +add_nexthops (struct list *, struct graph_node *, + struct graph_node **, size_t); static struct list * -command_match_r (struct graph_node *, vector, unsigned int); +command_match_r (struct graph_node *, vector, unsigned int, + struct graph_node **); static int score_precedence (enum cmd_token_type); @@ -97,6 +102,7 @@ command_match (struct graph *cmdgraph, struct list **argv, const struct cmd_element **el) { + struct graph_node *stack[MAXDEPTH]; matcher_rv = MATCHER_NO_MATCH; // prepend a dummy token to match that pesky start node @@ -106,7 +112,7 @@ command_match (struct graph *cmdgraph, vvline->active = vline->active + 1; struct graph_node *start = vector_slot (cmdgraph->nodes, 0); - if ((*argv = command_match_r (start, vvline, 0))) // successful match + if ((*argv = command_match_r (start, vvline, 0, stack))) // successful match { struct listnode *head = listhead (*argv); struct listnode *tail = listtail (*argv); @@ -191,10 +197,21 @@ command_match (struct graph *cmdgraph, * If no match was found, the return value is NULL. */ static struct list * -command_match_r (struct graph_node *start, vector vline, unsigned int n) +command_match_r (struct graph_node *start, vector vline, unsigned int n, + struct graph_node **stack) { assert (n < vector_active (vline)); + /* check history/stack of tokens + * this disallows matching the same one more than once if there is a + * circle in the graph (used for keyword arguments) */ + if (n == MAXDEPTH) + return NULL; + if (!start->allowrepeat) + for (size_t s = 0; s < n; s++) + if (stack[s] == start) + return NULL; + // get the minimum match level that can count as a full match struct cmd_token *token = start->data; enum match_type minmatch = min_match_level (token->type); @@ -229,13 +246,15 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n) if (match_token (token, input_token) < minmatch) return NULL; + stack[n] = start; + // pointers for iterating linklist struct listnode *ln; struct graph_node *gn; // get all possible nexthops struct list *next = list_new(); - add_nexthops (next, start); + add_nexthops (next, start, NULL, 0); // determine the best match int ambiguous = 0; @@ -271,7 +290,7 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n) } // else recurse on candidate child node - struct list *result = command_match_r (gn, vline, n+1); + struct list *result = command_match_r (gn, vline, n+1, stack); // save the best match if (result && currbest) @@ -317,6 +336,12 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n) return currbest; } +static void +stack_del (void *val) +{ + XFREE (MTYPE_CMD_MATCHSTACK, val); +} + enum matcher_rv command_complete (struct graph *graph, vector vline, @@ -327,14 +352,15 @@ command_complete (struct graph *graph, struct list *current = list_new(), // current nodes to match input token against *next = list_new(); // possible next hops after current input token + current->del = next->del = stack_del; // pointers used for iterating lists - struct graph_node *gn; + struct graph_node **gstack, **newstack; struct listnode *node; // add all children of start node to list struct graph_node *start = vector_slot (graph->nodes, 0); - add_nexthops (next, start); + add_nexthops (next, start, &start, 0); unsigned int idx; for (idx = 0; idx < vector_active (vline) && next->count > 0; idx++) @@ -342,19 +368,20 @@ command_complete (struct graph *graph, list_delete (current); current = next; next = list_new(); + next->del = stack_del; input_token = vector_slot (vline, idx); int exact_match_exists = 0; - for (ALL_LIST_ELEMENTS_RO (current,node,gn)) + for (ALL_LIST_ELEMENTS_RO (current,node,gstack)) if (!exact_match_exists) - exact_match_exists = (match_token (gn->data, input_token) == exact_match); + exact_match_exists = (match_token (gstack[0]->data, input_token) == exact_match); else break; - for (ALL_LIST_ELEMENTS_RO (current,node,gn)) + for (ALL_LIST_ELEMENTS_RO (current,node,gstack)) { - struct cmd_token *token = gn->data; + struct cmd_token *token = gstack[0]->data; if (token->attr == CMD_ATTR_HIDDEN || token->attr == CMD_ATTR_DEPRECATED) continue; @@ -371,7 +398,11 @@ command_complete (struct graph *graph, case trivial_match: trace_matcher ("trivial_match\n"); assert(last_token); - listnode_add (next, gn); + newstack = XMALLOC (MTYPE_CMD_MATCHSTACK, + sizeof(struct graph_node *)); + /* we're not recursing here, just the first element is OK */ + newstack[0] = gstack[0]; + listnode_add (next, newstack); break; case partly_match: trace_matcher ("trivial_match\n"); @@ -380,9 +411,15 @@ command_complete (struct graph *graph, case exact_match: trace_matcher ("exact_match\n"); if (last_token) - listnode_add (next, gn); + { + newstack = XMALLOC (MTYPE_CMD_MATCHSTACK, + sizeof(struct graph_node *)); + /* same as above, not recursing on this */ + newstack[0] = gstack[0]; + listnode_add (next, newstack); + } else if (matchtype >= minmatch) - add_nexthops (next, gn); + add_nexthops (next, gstack[0], gstack, idx + 1); break; default: trace_matcher ("no_match\n"); @@ -409,8 +446,9 @@ command_complete (struct graph *graph, { // extract cmd_token into list *completions = list_new (); - for (ALL_LIST_ELEMENTS_RO (next,node,gn)) - listnode_add (*completions, gn->data); + for (ALL_LIST_ELEMENTS_RO (next,node,gstack)) { + listnode_add (*completions, gstack[0]->data); + } } list_delete (current); @@ -425,26 +463,52 @@ command_complete (struct graph *graph, * * @param[in] list to add the nexthops to * @param[in] node to start calculating nexthops from + * @param[in] stack listing previously visited nodes, if non-NULL. + * @param[in] stackpos how many valid entries are in stack * @return the number of children added to the list + * + * NB: non-null "stack" means that new stacks will be added to "list" as + * output, instead of direct node pointers! */ static int -add_nexthops (struct list *list, struct graph_node *node) +add_nexthops (struct list *list, struct graph_node *node, + struct graph_node **stack, size_t stackpos) { int added = 0; struct graph_node *child; + struct graph_node **nextstack; for (unsigned int i = 0; i < vector_active (node->to); i++) { child = vector_slot (node->to, i); + size_t j; + if (!child->allowrepeat) + { + for (j = 0; j < stackpos; j++) + if (child == stack[j]) + break; + if (j != stackpos) + continue; + } struct cmd_token *token = child->data; switch (token->type) { case OPTION_TKN: case SELECTOR_TKN: case NUL_TKN: - added += add_nexthops (list, child); + added += add_nexthops (list, child, stack, stackpos); break; default: - listnode_add (list, child); + if (stack) + { + nextstack = XMALLOC (MTYPE_CMD_MATCHSTACK, + (stackpos + 1) * sizeof(struct graph_node *)); + nextstack[0] = child; + memcpy(nextstack + 1, stack, stackpos * sizeof(struct graph_node *)); + + listnode_add (list, nextstack); + } + else + listnode_add (list, child); added++; } } diff --git a/lib/command_parse.y b/lib/command_parse.y index 339e6be8f981..3dba8f0e89a5 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -180,6 +180,8 @@ start: if ((ctx->currnode = add_edge_dedup (ctx->currnode, $3)) != $3) graph_delete_node (ctx->graph, $3); + ctx->currnode->allowrepeat = 1; + // adding a node as a child of itself accepts any number // of the same token, which is what we want for variadics add_edge_dedup (ctx->currnode, ctx->currnode); @@ -336,6 +338,26 @@ selector_seq_seq: } ; +/* {keyword} productions */ +selector: '{' selector_seq_seq '}' +{ + $$ = malloc (sizeof (struct subgraph)); + $$->start = new_token_node (ctx, SELECTOR_TKN, NULL, NULL); + $$->end = new_token_node (ctx, NUL_TKN, NULL, NULL); + graph_add_edge ($$->start, $$->end); + for (unsigned int i = 0; i < vector_active ($2->start->to); i++) + { + struct graph_node *sn = vector_slot ($2->start->to, i), + *en = vector_slot ($2->end->from, i); + graph_add_edge ($$->start, sn); + graph_add_edge (en, $$->start); + } + graph_delete_node (ctx->graph, $2->start); + graph_delete_node (ctx->graph, $2->end); + free ($2); +}; + + selector_token_seq: simple_token { diff --git a/lib/graph.h b/lib/graph.h index 8d8aa3823ba5..ee8e37c93255 100644 --- a/lib/graph.h +++ b/lib/graph.h @@ -36,6 +36,7 @@ struct graph_node { vector from; // nodes which have edges to this node vector to; // nodes which this node has edges to + bool allowrepeat; void *data; // node data void (*del) (void *data); // deletion callback From 53d5ec367882becf01cf3c484f3570f10a11618d Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Thu, 15 Dec 2016 23:53:02 +0100 Subject: [PATCH 03/13] lib: parser: fix SEGV when Tab hits non-WORD_TKN If processing finds that there is only 1 candidate, but that candidate is not a WORD_TKN that we can tab-complete on, the status would remain at CMD_COMPLETE_FULL_MATCH, but the resulting list of possible completions is empty. This then SEGVs in lib/vty.c where it tries to access the first element of the list, assuming FULL_MATCH always has 1 element there... Signed-off-by: David Lamparter Cc: Quentin Young --- lib/command.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/command.c b/lib/command.c index 8d5493ca56ac..a93f3a55e802 100644 --- a/lib/command.c +++ b/lib/command.c @@ -686,6 +686,19 @@ cmd_complete_command (vector vline, struct vty *vty, int *status) } vector_free (initial_comps); + // since we filtered results, we need to re-set status code + switch (vector_active (comps)) + { + case 0: + *status = CMD_ERR_NO_MATCH; + break; + case 1: + *status = CMD_COMPLETE_FULL_MATCH; + break; + default: + *status = CMD_COMPLETE_LIST_MATCH; + } + // copy completions text into an array of char* ret = XMALLOC (MTYPE_TMP, (vector_active (comps)+1) * sizeof (char *)); unsigned int i; From 7ef290efa335d238e26a06b8946f3f8291e95b3f Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Thu, 15 Dec 2016 23:20:04 +0100 Subject: [PATCH 04/13] tests: adjust testcli ref output to new argv rules Now that we have keyword argument support in the matcher again, this needs to be updated because argv[] will be 1:1 user input without mangling or reordering. Signed-off-by: David Lamparter --- tests/testcli.refout | 50 ++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/tests/testcli.refout b/tests/testcli.refout index 922a620ce60f..088cbdfec423 100644 --- a/tests/testcli.refout +++ b/tests/testcli.refout @@ -198,37 +198,44 @@ cmd8 with 2 args. [00]: pat [01]: d test# pat d foo 1.2.3.4 -cmd8 with 3 args. -[00]: 1.2.3.4 -[01]: (null) -[02]: (null) +cmd8 with 4 args. +[00]: pat +[01]: d +[02]: foo +[03]: 1.2.3.4 test# pat d foo % Command incomplete. test# pat d noooo % [NONE] Unknown command: pat d noooo test# pat d bar 1::2 -cmd8 with 3 args. -[00]: (null) -[01]: 1::2 -[02]: (null) +cmd8 with 4 args. +[00]: pat +[01]: d +[02]: bar +[03]: 1::2 test# pat d bar 1::2 foo 3.4.5.6 -cmd8 with 3 args. -[00]: 3.4.5.6 -[01]: 1::2 -[02]: (null) +cmd8 with 6 args. +[00]: pat +[01]: d +[02]: bar +[03]: 1::2 +[04]: foo +[05]: 3.4.5.6 test# pat d ba bar 04 baz 06 test# pat d baz cmd8 with 3 args. -[00]: (null) -[01]: (null) +[00]: pat +[01]: d [02]: baz test# pat d foo 3.4.5.6 baz -cmd8 with 3 args. -[00]: 3.4.5.6 -[01]: (null) -[02]: baz +cmd8 with 5 args. +[00]: pat +[01]: d +[02]: foo +[03]: 3.4.5.6 +[04]: baz test# test# pat e cmd9 with 2 args. @@ -260,7 +267,6 @@ cmd10 with 3 args. [02]: key test# test# alt a - test# alt a a WORD 02 test# alt a ab @@ -269,7 +275,6 @@ cmd11 with 3 args. [01]: a [02]: ab test# alt a 1 - test# alt a 1.2 A.B.C.D 02 WORD 02 @@ -279,7 +284,6 @@ cmd12 with 3 args. [01]: a [02]: 1.2.3.4 test# alt a 1 - test# alt a 1:2 WORD 02 test# alt a 1:2 @@ -295,8 +299,8 @@ test# test# conf t test(config)# do pat d baz cmd8 with 3 args. -[00]: (null) -[01]: (null) +[00]: pat +[01]: d [02]: baz test(config)# exit test# From 46715ff034d41b8432cc05a6a1caa61d3f5551b1 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 00:01:20 +0100 Subject: [PATCH 05/13] tests: add missing qobj_init() call This was SEGV'ing the test in bgp_master_init() since QOBJ_REG was used without qobj_init() being called first. Signed-off-by: David Lamparter --- tests/aspath_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/aspath_test.c b/tests/aspath_test.c index 9d595807fee4..f3999cbcfffe 100644 --- a/tests/aspath_test.c +++ b/tests/aspath_test.c @@ -1331,6 +1331,7 @@ int main (void) { int i = 0; + qobj_init (); bgp_master_init (); master = bm->master; bgp_option_set (BGP_OPT_NO_LISTEN); From e988dfd35b0d9df437ac043a613472437c8842e6 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 00:09:46 +0100 Subject: [PATCH 06/13] tests: fix exit <> return mix-up in dejagnu exit 0 exits the entire testrunner... Oops. Also, "unresolved" breaks too many things, so make this a pass. Signed-off-by: David Lamparter --- tests/libzebra.tests/testcommands.exp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/libzebra.tests/testcommands.exp b/tests/libzebra.tests/testcommands.exp index d4bfc8231f38..c5d5a007302b 100644 --- a/tests/libzebra.tests/testcommands.exp +++ b/tests/libzebra.tests/testcommands.exp @@ -5,8 +5,8 @@ if {![info exists env(QUAGGA_TEST_COMMANDS)]} { # sadly, the test randomly fails when configure parameters differ from # what was used to create testcommands.refout. this can be fixed by # shipping a matching vtysh_cmd.c, which we'll add after 0.99.23 - unresolved "$test_name" - exit 0 + pass "$test_name" + return 0 } spawn sh -c "./testcommands -e 0 < $env(srcdir)/testcommands.in | diff -au - $env(srcdir)/testcommands.refout" From eed831e065694b3746896b126f6ad47548ea9dc0 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 00:14:55 +0100 Subject: [PATCH 07/13] tests: fix mis-fixed format string PRIu64 is "u", we need "x", so PRIx64... Signed-off-by: David Lamparter Cc: Donald Sharp --- tests/test-stream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-stream.c b/tests/test-stream.c index 7ef6374756a9..3ac45eb203ee 100644 --- a/tests/test-stream.c +++ b/tests/test-stream.c @@ -70,7 +70,7 @@ main (void) printf ("c: 0x%hhx\n", stream_getc (s)); printf ("w: 0x%hx\n", stream_getw (s)); printf ("l: 0x%x\n", stream_getl (s)); - printf ("q: 0x%" PRIu64 "\n", stream_getq (s)); + printf ("q: 0x%" PRIx64 "\n", stream_getq (s)); return 0; } From 0d37f9f325217a6dc3e396c9227d2a164775f4d9 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 00:37:52 +0100 Subject: [PATCH 08/13] build: fix bison < 3.0 compatibility bison-2.7.x really wants "foo" for api.prefix while bison-3.0.x really wants {foo} ... great. Signed-off-by: David Lamparter --- configure.ac | 39 +++++++++++++++++++++++++++++++++++++++ lib/Makefile.am | 2 +- lib/command_parse.y | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 128300cc6cf1..c46575d83f0b 100755 --- a/configure.ac +++ b/configure.ac @@ -1419,7 +1419,46 @@ if test "x$LEX" != xflex; then AC_SUBST([LEX_OUTPUT_ROOT], [lex.yy]) AC_SUBST([LEXLIB], ['']) fi + AC_PROG_YACC +dnl thanks GNU bison for this b*llshit... +AC_MSG_CHECKING(version of bison) +quagga_ac_bison_version="$(eval $YACC -V | grep bison | head -n 1)" +quagga_ac_bison_version="${quagga_ac_bison_version##* }" +quagga_ac_bison_missing="false" +case "x${quagga_ac_bison_version}" in + x2.7*) + BISON_OPENBRACE='"' + BISON_CLOSEBRACE='"' + AC_MSG_RESULT([$quagga_ac_bison_version - 2.7 or older]) + ;; + x2.*|x1.*) + AC_MSG_RESULT([$quagga_ac_bison_version]) + AC_MSG_WARN([installed bison is too old. Please install GNU bison 2.7.x or newer.]) + quagga_ac_bison_missing="true" + ;; + x) + AC_MSG_RESULT([none]) + AC_MSG_WARN([could not determine bison version. Please install GNU bison 2.7.x or newer.]) + quagga_ac_bison_missing="true" + ;; + *) + BISON_OPENBRACE='{' + BISON_CLOSEBRACE='}' + AC_MSG_RESULT([$quagga_ac_bison_version - 3.0 or newer]) + ;; +esac +AC_SUBST(BISON_OPENBRACE) +AC_SUBST(BISON_CLOSEBRACE) + +if $quagga_ac_bison_missing; then + YACC="$SHELL $missing_dir/missing bison -y" + if test -f "${srcdir}/lib/command_parse.c" -a -f "${srcdir}/lib/command_parse.h"; then + AC_MSG_WARN([using pregenerated bison output files]) + else + AC_MSG_ERROR([bison failure and pregenerated files not included (probably a git build)]) + fi +fi dnl ------------------- dnl capabilities checks diff --git a/lib/Makefile.am b/lib/Makefile.am index 2906b9513a5c..1bb2d395c870 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -3,7 +3,7 @@ AM_CPPFLAGS = -I.. -I$(top_srcdir) -I$(top_srcdir)/lib -I$(top_builddir)/lib AM_CFLAGS = $(WERROR) DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\" -AM_YFLAGS = -d +AM_YFLAGS = -d -Dapi.prefix=@BISON_OPENBRACE@cmd_yy@BISON_CLOSEBRACE@ command_lex.h: command_lex.c @if test ! -f $@; then rm -f command_lex.c; else :; fi diff --git a/lib/command_parse.y b/lib/command_parse.y index 3dba8f0e89a5..d2dd5aa5cc3a 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -33,7 +33,7 @@ typedef union CMD_YYSTYPE CMD_YYSTYPE; %} %define api.pure full -%define api.prefix {cmd_yy} +/* define api.prefix {cmd_yy} */ /* names for generated header and parser files */ %defines "command_parse.h" From 4a06690fcac47d732e9cc3ae4e86cac55e801ea5 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 01:24:53 +0100 Subject: [PATCH 09/13] build: check flex >= 2.5.20 is available Signed-off-by: David Lamparter --- .gitignore | 1 + configure.ac | 20 +++-- m4/ax_compare_version.m4 | 177 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 m4/ax_compare_version.m4 diff --git a/.gitignore b/.gitignore index 4c90eb00d6e6..af345e5603a1 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ build *.loT m4/*.m4 !m4/ax_sys_weak_alias.m4 +!m4/ax_compare_version.m4 debian/autoreconf.after debian/autoreconf.before debian/files diff --git a/configure.ac b/configure.ac index c46575d83f0b..caf3836f4858 100755 --- a/configure.ac +++ b/configure.ac @@ -1413,12 +1413,22 @@ AC_CHECK_DECL(CLOCK_MONOTONIC, dnl -------------------------------------- dnl checking for flex and bison dnl -------------------------------------- + AM_PROG_LEX -if test "x$LEX" != xflex; then - LEX="$SHELL $missing_dir/missing flex" - AC_SUBST([LEX_OUTPUT_ROOT], [lex.yy]) - AC_SUBST([LEXLIB], ['']) -fi +AC_MSG_CHECKING(version of flex) +quagga_ac_flex_version="$(eval $LEX -V | grep flex | head -n 1)" +quagga_ac_flex_version="${quagga_ac_flex_version##* }" +AC_MSG_RESULT([$quagga_ac_flex_version]) +AX_COMPARE_VERSION([$quagga_ac_flex_version], [lt], [2.5.20], [ + LEX="$SHELL $missing_dir/missing flex" + if test -f "${srcdir}/lib/command_lex.c" -a -f "${srcdir}/lib/command_lex.h"; then + AC_MSG_WARN([using pregenerated flex output files]) + else + AC_MSG_ERROR([flex failure and pregenerated files not included (probably a git build)]) + fi + AC_SUBST([LEX_OUTPUT_ROOT], [lex.yy]) + AC_SUBST([LEXLIB], ['']) +]) AC_PROG_YACC dnl thanks GNU bison for this b*llshit... diff --git a/m4/ax_compare_version.m4 b/m4/ax_compare_version.m4 new file mode 100644 index 000000000000..74dc0fdd9a40 --- /dev/null +++ b/m4/ax_compare_version.m4 @@ -0,0 +1,177 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_compare_version.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# +# DESCRIPTION +# +# This macro compares two version strings. Due to the various number of +# minor-version numbers that can exist, and the fact that string +# comparisons are not compatible with numeric comparisons, this is not +# necessarily trivial to do in a autoconf script. This macro makes doing +# these comparisons easy. +# +# The six basic comparisons are available, as well as checking equality +# limited to a certain number of minor-version levels. +# +# The operator OP determines what type of comparison to do, and can be one +# of: +# +# eq - equal (test A == B) +# ne - not equal (test A != B) +# le - less than or equal (test A <= B) +# ge - greater than or equal (test A >= B) +# lt - less than (test A < B) +# gt - greater than (test A > B) +# +# Additionally, the eq and ne operator can have a number after it to limit +# the test to that number of minor versions. +# +# eq0 - equal up to the length of the shorter version +# ne0 - not equal up to the length of the shorter version +# eqN - equal up to N sub-version levels +# neN - not equal up to N sub-version levels +# +# When the condition is true, shell commands ACTION-IF-TRUE are run, +# otherwise shell commands ACTION-IF-FALSE are run. The environment +# variable 'ax_compare_version' is always set to either 'true' or 'false' +# as well. +# +# Examples: +# +# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) +# +# would both be true. +# +# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) +# +# would both be false. +# +# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) +# +# would be true because it is only comparing two minor versions. +# +# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) +# +# would be true because it is only comparing the lesser number of minor +# versions of the two values. +# +# Note: The characters that separate the version numbers do not matter. An +# empty string is the same as version 0. OP is evaluated by autoconf, not +# configure, so must be a string, not a variable. +# +# The author would like to acknowledge Guido Draheim whose advice about +# the m4_case and m4_ifvaln functions make this macro only include the +# portions necessary to perform the specific comparison specified by the +# OP argument in the final configure script. +# +# LICENSE +# +# Copyright (c) 2008 Tim Toolan +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +dnl ######################################################################### +AC_DEFUN([AX_COMPARE_VERSION], [ + AC_REQUIRE([AC_PROG_AWK]) + + # Used to indicate true or false condition + ax_compare_version=false + + # Convert the two version strings to be compared into a format that + # allows a simple string comparison. The end result is that a version + # string of the form 1.12.5-r617 will be converted to the form + # 0001001200050617. In other words, each number is zero padded to four + # digits, and non digits are removed. + AS_VAR_PUSHDEF([A],[ax_compare_version_A]) + A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + AS_VAR_PUSHDEF([B],[ax_compare_version_B]) + B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary + dnl # then the first line is used to determine if the condition is true. + dnl # The sed right after the echo is to remove any indented white space. + m4_case(m4_tolower($2), + [lt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [gt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [le],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ], + [ge],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ],[ + dnl Split the operator from the subversion count if present. + m4_bmatch(m4_substr($2,2), + [0],[ + # A count of zero means use the length of the shorter version. + # Determine the number of characters in A and B. + ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` + ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` + + # Set A to no more than B's length and B to no more than A's length. + A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` + B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` + ], + [[0-9]+],[ + # A count greater than zero means use only that many subversions + A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + ], + [.+],[ + AC_WARNING( + [illegal OP numeric parameter: $2]) + ],[]) + + # Pad zeros at end of numbers to make same length. + ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" + B="$B`echo $A | sed 's/./0/g'`" + A="$ax_compare_version_tmp_A" + + # Check for equality or inequality as necessary. + m4_case(m4_tolower(m4_substr($2,0,2)), + [eq],[ + test "x$A" = "x$B" && ax_compare_version=true + ], + [ne],[ + test "x$A" != "x$B" && ax_compare_version=true + ],[ + AC_WARNING([illegal OP parameter: $2]) + ]) + ]) + + AS_VAR_POPDEF([A])dnl + AS_VAR_POPDEF([B])dnl + + dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. + if test "$ax_compare_version" = "true" ; then + m4_ifvaln([$4],[$4],[:])dnl + m4_ifvaln([$5],[else $5])dnl + fi +]) dnl AX_COMPARE_VERSION From afc3a6ceb640abda6a549fa3e789252ae78e930c Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 02:10:48 +0100 Subject: [PATCH 10/13] lib: parser: reorder bison incarnations This shuffles the code blocks in command_parser.y to match file output order, then adjusts things to make the include handling less messy. (also dropped unused DECIMAL_STRLEN_MAX define.) This should hopefully fix the build on NetBSD 6. Signed-off-by: David Lamparter --- lib/command_lex.l | 1 - lib/command_parse.y | 49 ++++++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/command_lex.l b/lib/command_lex.l index e978803b2213..0cb306b6828d 100644 --- a/lib/command_lex.l +++ b/lib/command_lex.l @@ -24,7 +24,6 @@ %{ #include "command_parse.h" -#define YYSTYPE CMD_YYSTYPE %} WORD (\-|\+)?[a-z\*][-+_a-zA-Z0-9\*]* diff --git a/lib/command_parse.y b/lib/command_parse.y index d2dd5aa5cc3a..81aa8a04e77c 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -23,11 +23,6 @@ */ %{ - -typedef union CMD_YYSTYPE CMD_YYSTYPE; -#define YYSTYPE CMD_YYSTYPE -#include "command_lex.h" - // compile with debugging facilities #define YYDEBUG 1 %} @@ -39,7 +34,13 @@ typedef union CMD_YYSTYPE CMD_YYSTYPE; %defines "command_parse.h" %output "command_parse.c" -/* required external units */ +/* note: code blocks are output in order, to both .c and .h: + * 1. %code requires + * 2. %union + bison forward decls + * 3. %code provides + * command_lex.h needs to be included at 3.; it needs the union and YYSTYPE. + * struct parser_ctx is needed for the bison forward decls. + */ %code requires { #include "stdlib.h" #include "string.h" @@ -47,6 +48,25 @@ typedef union CMD_YYSTYPE CMD_YYSTYPE; #include "log.h" #include "graph.h" + #define YYSTYPE CMD_YYSTYPE + struct parser_ctx; +} + +%union { + long long number; + char *string; + struct graph_node *node; + struct subgraph *subgraph; +} + +%code provides { + #ifndef FLEX_SCANNER + #include "command_lex.h" + #endif + + extern void set_lexer_string (yyscan_t *scn, const char *string); + extern void cleanup_lexer (yyscan_t *scn); + struct parser_ctx { yyscan_t scanner; @@ -58,23 +78,6 @@ typedef union CMD_YYSTYPE CMD_YYSTYPE; /* pointers to copy of command docstring */ char *docstr_start, *docstr; }; - - extern void set_lexer_string (yyscan_t *scn, const char *string); - extern void cleanup_lexer (yyscan_t *scn); -} - -/* functionality this unit exports */ -%code provides { - /* maximum length of a number, lexer will not match anything longer */ - #define DECIMAL_STRLEN_MAX 20 -} - -/* valid semantic types for tokens and rules */ -%union { - long long number; - char *string; - struct graph_node *node; - struct subgraph *subgraph; } /* union types for lexed tokens */ From 0e64d123fd50cb8c45ad32a209f10173bdb65c4d Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 08:12:54 +0100 Subject: [PATCH 11/13] bgpd: shuffle qobj_init() bgp_master_init is called first thing in main(), so we need to wedge a qobj_init() call in there... this needs some improvement... Signed-off-by: David Lamparter --- bgpd/bgpd.c | 2 ++ lib/qobj.c | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 5ed630b49d9e..30f6e0d859a5 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -7477,6 +7477,8 @@ bgp_config_write (struct vty *vty) void bgp_master_init (void) { + qobj_init (); + memset (&bgp_master, 0, sizeof (struct bgp_master)); bm = &bgp_master; diff --git a/lib/qobj.c b/lib/qobj.c index aeae52e02974..8a386d24862e 100644 --- a/lib/qobj.c +++ b/lib/qobj.c @@ -73,7 +73,8 @@ void *qobj_get_typed(uint64_t id, struct qobj_nodetype *type) void qobj_init (void) { - nodes = hash_create (qobj_key, qobj_cmp); + if (!nodes) + nodes = hash_create (qobj_key, qobj_cmp); } void qobj_finish (void) From 4d94b2929671e9251e95e6d2f88da8797a1d6e1e Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 17:19:37 +0100 Subject: [PATCH 12/13] lib: parser: move allowrepeat to cmd_token struct graph_node isn't quite the right place to control matcher behaviour. Signed-off-by: David Lamparter --- lib/command.h | 2 ++ lib/command_match.c | 14 +++++++------- lib/command_parse.y | 2 +- lib/graph.h | 1 - 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/command.h b/lib/command.h index ba6fd9b7b038..1e1698fc7d12 100644 --- a/lib/command.h +++ b/lib/command.h @@ -196,6 +196,8 @@ struct cmd_token { enum cmd_token_type type; // token type u_char attr; // token attributes + bool allowrepeat; // matcher allowed to match token repetively? + char *text; // token text char *desc; // token description long long min, max; // for ranges diff --git a/lib/command_match.c b/lib/command_match.c index b6897109afc1..62905a4f7f87 100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@ -202,20 +202,20 @@ command_match_r (struct graph_node *start, vector vline, unsigned int n, { assert (n < vector_active (vline)); + // get the minimum match level that can count as a full match + struct cmd_token *token = start->data; + enum match_type minmatch = min_match_level (token->type); + /* check history/stack of tokens * this disallows matching the same one more than once if there is a * circle in the graph (used for keyword arguments) */ if (n == MAXDEPTH) return NULL; - if (!start->allowrepeat) + if (!token->allowrepeat) for (size_t s = 0; s < n; s++) if (stack[s] == start) return NULL; - // get the minimum match level that can count as a full match - struct cmd_token *token = start->data; - enum match_type minmatch = min_match_level (token->type); - // get the current operating input token char *input_token = vector_slot (vline, n); @@ -481,7 +481,8 @@ add_nexthops (struct list *list, struct graph_node *node, { child = vector_slot (node->to, i); size_t j; - if (!child->allowrepeat) + struct cmd_token *token = child->data; + if (!token->allowrepeat) { for (j = 0; j < stackpos; j++) if (child == stack[j]) @@ -489,7 +490,6 @@ add_nexthops (struct list *list, struct graph_node *node, if (j != stackpos) continue; } - struct cmd_token *token = child->data; switch (token->type) { case OPTION_TKN: diff --git a/lib/command_parse.y b/lib/command_parse.y index 81aa8a04e77c..43062eb5da97 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -183,7 +183,7 @@ start: if ((ctx->currnode = add_edge_dedup (ctx->currnode, $3)) != $3) graph_delete_node (ctx->graph, $3); - ctx->currnode->allowrepeat = 1; + ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1; // adding a node as a child of itself accepts any number // of the same token, which is what we want for variadics diff --git a/lib/graph.h b/lib/graph.h index ee8e37c93255..8d8aa3823ba5 100644 --- a/lib/graph.h +++ b/lib/graph.h @@ -36,7 +36,6 @@ struct graph_node { vector from; // nodes which have edges to this node vector to; // nodes which this node has edges to - bool allowrepeat; void *data; // node data void (*del) (void *data); // deletion callback From a6cf5da4fd6253b5f92ff4bee84d9e02c2439326 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 16 Dec 2016 17:33:31 +0100 Subject: [PATCH 13/13] build: automake 1.12 to deal with flex & bison bison conditionally writes its output to different files based on the filenames specified in the source code. This could be disabled, however... flex changes its output filenames when "prefix" is specified. And ylwrap from <1.11 doesn't understand how to handle the header file... ...so this requires automake 1.12 which can deal with this properly. Signed-off-by: David Lamparter --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index caf3836f4858..40eab0a9a172 100755 --- a/configure.ac +++ b/configure.ac @@ -22,7 +22,7 @@ AC_CANONICAL_TARGET() # Disable portability warnings -- our automake code (in particular # common.am) uses some constructs specific to gmake. -AM_INIT_AUTOMAKE([1.6 -Wno-portability]) +AM_INIT_AUTOMAKE([1.12 -Wno-portability]) m4_ifndef([AM_SILENT_RULES], [m4_define([AM_SILENT_RULES],[])]) AM_SILENT_RULES([yes]) AC_CONFIG_HEADERS(config.h)