lib/command.c: rewrite command matching/parsing

Add support for keyword commands.

Includes new documentation for DEFUN() in lib/command.h, for preexisting
features as well as new keyword specification.

Signed-off-by: Christian Franke <chris@opensourcerouting.org>
Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
diff --git a/babeld/babel_main.c b/babeld/babel_main.c
index 7b1d879..529c3f2 100644
--- a/babeld/babel_main.c
+++ b/babeld/babel_main.c
@@ -277,9 +277,6 @@
     /* this replace kernel_setup && kernel_setup_socket */
     babelz_zebra_init ();
 
-    /* Sort all installed commands. */
-    sort_node ();
-
     /* Get zebra configuration file. */
     zlog_set_level (NULL, ZLOG_DEST_STDOUT, ZLOG_DISABLED);
     vty_read_config (babel_config_file, babel_config_default);
diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c
index 1ff1ac9..1be6504 100644
--- a/bgpd/bgp_main.c
+++ b/bgpd/bgp_main.c
@@ -431,9 +431,6 @@
   /* BGP related initialization.  */
   bgp_init ();
 
-  /* Sort CLI commands. */
-  sort_node ();
-
   /* Parse config file. */
   vty_read_config (config_file, config_default);
 
diff --git a/isisd/isis_main.c b/isisd/isis_main.c
index 96816eb..283b7ea 100644
--- a/isisd/isis_main.c
+++ b/isisd/isis_main.c
@@ -339,8 +339,6 @@
 
   isis_zebra_init ();
 
-  sort_node ();
-
   /* parse config file */
   /* this is needed three times! because we have interfaces before the areas */
   vty_read_config (config_file, config_default);
diff --git a/lib/command.c b/lib/command.c
index 3b3fada..a237364 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -1,6 +1,8 @@
 /*
    Command interpreter routine for virtual terminal [aka TeletYpe]
    Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+   Copyright (C) 2013 by Open Source Routing.
+   Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC")
 
 This file is part of GNU Zebra.
  
@@ -35,9 +37,32 @@
    each daemon maintains each own cmdvec. */
 vector cmdvec = NULL;
 
-struct desc desc_cr;
+struct cmd_token token_cr;
 char *command_cr = NULL;
 
+enum filter_type
+{
+  FILTER_RELAXED,
+  FILTER_STRICT
+};
+
+enum matcher_rv
+{
+  MATCHER_OK,
+  MATCHER_COMPLETE,
+  MATCHER_INCOMPLETE,
+  MATCHER_NO_MATCH,
+  MATCHER_AMBIGUOUS,
+  MATCHER_EXCEED_ARGC_MAX
+};
+
+#define MATCHER_ERROR(matcher_rv) \
+  (   (matcher_rv) == MATCHER_INCOMPLETE \
+   || (matcher_rv) == MATCHER_NO_MATCH \
+   || (matcher_rv) == MATCHER_AMBIGUOUS \
+   || (matcher_rv) == MATCHER_EXCEED_ARGC_MAX \
+  )
+
 /* Host information structure. */
 struct host host;
 
@@ -196,53 +221,6 @@
   node->cmd_vector = vector_init (VECTOR_MIN_SIZE);
 }
 
-/* Compare two command's string.  Used in sort_node (). */
-static int
-cmp_node (const void *p, const void *q)
-{
-  const struct cmd_element *a = *(struct cmd_element * const *)p;
-  const struct cmd_element *b = *(struct cmd_element * const *)q;
-
-  return strcmp (a->string, b->string);
-}
-
-static int
-cmp_desc (const void *p, const void *q)
-{
-  const struct desc *a = *(struct desc * const *)p;
-  const struct desc *b = *(struct desc * const *)q;
-
-  return strcmp (a->cmd, b->cmd);
-}
-
-/* Sort each node's command element according to command string. */
-void
-sort_node ()
-{
-  unsigned int i, j;
-  struct cmd_node *cnode;
-  vector descvec;
-  struct cmd_element *cmd_element;
-
-  for (i = 0; i < vector_active (cmdvec); i++)
-    if ((cnode = vector_slot (cmdvec, i)) != NULL)
-      {	
-	vector cmd_vector = cnode->cmd_vector;
-	qsort (cmd_vector->index, vector_active (cmd_vector), 
-	       sizeof (void *), cmp_node);
-
-	for (j = 0; j < vector_active (cmd_vector); j++)
-	  if ((cmd_element = vector_slot (cmd_vector, j)) != NULL
-	      && vector_active (cmd_element->strvec))
-	    {
-	      descvec = vector_slot (cmd_element->strvec,
-				     vector_active (cmd_element->strvec) - 1);
-	      qsort (descvec->index, vector_active (descvec), 
-	             sizeof (void *), cmp_desc);
-	    }
-      }
-}
-
 /* 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. */
@@ -312,15 +290,46 @@
   vector_free (v);
 }
 
-/* Fetch next description.  Used in cmd_make_descvec(). */
+struct format_parser_state
+{
+  vector topvect; /* Top level vector */
+  vector intvect; /* Intermediate level vector, used when there's
+                   * a multiple in a keyword. */
+  vector curvect; /* current vector where read tokens should be
+                     appended. */
+
+  const char *string; /* pointer to command string, not modified */
+  const char *cp; /* pointer in command string, moved along while
+                     parsing */
+  const char *dp;  /* pointer in description string, moved along while
+                     parsing */
+
+  int in_keyword; /* flag to remember if we are in a keyword group */
+  int in_multiple; /* flag to remember if we are in a multiple group */
+  int just_read_word; /* flag to remember if the last thing we red was a
+                       * real word and not some abstract token */
+};
+
+static void
+format_parser_error(struct format_parser_state *state, const char *message)
+{
+  int offset = state->cp - state->string + 1;
+
+  fprintf(stderr, "\nError parsing command: \"%s\"\n", state->string);
+  fprintf(stderr, "                        %*c\n", offset, '^');
+  fprintf(stderr, "%s at offset %d.\n", message, offset);
+  fprintf(stderr, "This is a programming error. Check your DEFUNs etc.\n");
+  exit(1);
+}
+
 static char *
-cmd_desc_str (const char **string)
+format_parser_desc_str(struct format_parser_state *state)
 {
   const char *cp, *start;
   char *token;
   int strlen;
-  
-  cp = *string;
+
+  cp = state->dp;
 
   if (cp == NULL)
     return NULL;
@@ -339,132 +348,226 @@
     cp++;
 
   strlen = cp - start;
-  token = XMALLOC (MTYPE_STRVEC, strlen + 1);
+  token = XMALLOC (MTYPE_CMD_TOKENS, strlen + 1);
   memcpy (token, start, strlen);
   *(token + strlen) = '\0';
 
-  *string = cp;
+  state->dp = cp;
 
   return token;
 }
 
-/* New string vector. */
-static vector
-cmd_make_descvec (const char *string, const char *descstr)
+static void
+format_parser_begin_keyword(struct format_parser_state *state)
 {
-  int multiple = 0;
-  const char *sp;
-  char *token;
-  int len;
-  const char *cp;
-  const char *dp;
-  vector allvec;
-  vector strvec = NULL;
-  struct desc *desc;
+  struct cmd_token *token;
+  vector keyword_vect;
 
-  cp = string;
-  dp = descstr;
+  if (state->in_keyword
+      || state->in_multiple)
+    format_parser_error(state, "Unexpected '{'");
 
-  if (cp == NULL)
-    return NULL;
+  state->cp++;
+  state->in_keyword = 1;
 
-  allvec = vector_init (VECTOR_MIN_SIZE);
+  token = XCALLOC(MTYPE_CMD_TOKENS, sizeof(*token));
+  token->type = TOKEN_KEYWORD;
+  token->keyword = vector_init(VECTOR_MIN_SIZE);
 
-  while (1)
+  keyword_vect = vector_init(VECTOR_MIN_SIZE);
+  vector_set(token->keyword, keyword_vect);
+
+  vector_set(state->curvect, token);
+  state->curvect = keyword_vect;
+}
+
+static void
+format_parser_begin_multiple(struct format_parser_state *state)
+{
+  struct cmd_token *token;
+
+  if (state->in_keyword == 1)
+    format_parser_error(state, "Keyword starting with '('");
+
+  if (state->in_multiple)
+    format_parser_error(state, "Nested group");
+
+  state->cp++;
+  state->in_multiple = 1;
+  state->just_read_word = 0;
+
+  token = XCALLOC(MTYPE_CMD_TOKENS, sizeof(*token));
+  token->type = TOKEN_MULTIPLE;
+  token->multiple = vector_init(VECTOR_MIN_SIZE);
+
+  vector_set(state->curvect, token);
+  if (state->curvect != state->topvect)
+    state->intvect = state->curvect;
+  state->curvect = token->multiple;
+}
+
+static void
+format_parser_end_keyword(struct format_parser_state *state)
+{
+  if (state->in_multiple
+      || !state->in_keyword)
+    format_parser_error(state, "Unexpected '}'");
+
+  if (state->in_keyword == 1)
+    format_parser_error(state, "Empty keyword group");
+
+  state->cp++;
+  state->in_keyword = 0;
+  state->curvect = state->topvect;
+}
+
+static void
+format_parser_end_multiple(struct format_parser_state *state)
+{
+  char *dummy;
+
+  if (!state->in_multiple)
+    format_parser_error(state, "Unepexted ')'");
+
+  if (vector_active(state->curvect) == 0)
+    format_parser_error(state, "Empty multiple section");
+
+  if (!state->just_read_word)
     {
-      while (isspace ((int) *cp) && *cp != '\0')
-	cp++;
+      /* There are constructions like
+       * 'show ip ospf database ... (self-originate|)'
+       * in use.
+       * The old parser reads a description string for the
+       * word '' between |) which will never match.
+       * Simulate this behvaior by dropping the next desc
+       * string in such a case. */
 
-      if (*cp == '(')
-	{
-	  multiple = 1;
-	  cp++;
-	}
-      if (*cp == ')')
-	{
-	  multiple = 0;
-	  cp++;
-	}
-      if (*cp == '|')
-	{
-	  if (! multiple)
-	    {
-	      fprintf (stderr, "Command parse error!: %s\n", string);
-	      exit (1);
-	    }
-	  cp++;
-	}
-      
-      while (isspace ((int) *cp) && *cp != '\0')
-	cp++;
+      dummy = format_parser_desc_str(state);
+      XFREE(MTYPE_CMD_TOKENS, dummy);
+    }
 
-      if (*cp == '(')
-	{
-	  multiple = 1;
-	  cp++;
-	}
+  state->cp++;
+  state->in_multiple = 0;
 
-      if (*cp == '\0') 
-	return allvec;
+  if (state->intvect)
+    state->curvect = state->intvect;
+  else
+    state->curvect = state->topvect;
+}
 
-      sp = cp;
+static void
+format_parser_handle_pipe(struct format_parser_state *state)
+{
+  struct cmd_token *keyword_token;
+  vector keyword_vect;
 
-      while (! (isspace ((int) *cp) || *cp == '\r' || *cp == '\n' || *cp == ')' || *cp == '|') && *cp != '\0')
-	cp++;
+  if (state->in_multiple)
+    {
+      state->just_read_word = 0;
+      state->cp++;
+    }
+  else if (state->in_keyword)
+    {
+      state->in_keyword = 1;
+      state->cp++;
 
-      len = cp - sp;
-
-      token = XMALLOC (MTYPE_STRVEC, len + 1);
-      memcpy (token, sp, len);
-      *(token + len) = '\0';
-
-      desc = XCALLOC (MTYPE_DESC, sizeof (struct desc));
-      desc->cmd = token;
-      desc->str = cmd_desc_str (&dp);
-
-      if (multiple)
-	{
-	  if (multiple == 1)
-	    {
-	      strvec = vector_init (VECTOR_MIN_SIZE);
-	      vector_set (allvec, strvec);
-	    }
-	  multiple++;
-	}
-      else
-	{
-	  strvec = vector_init (VECTOR_MIN_SIZE);
-	  vector_set (allvec, strvec);
-	}
-      vector_set (strvec, desc);
+      keyword_token = vector_slot(state->topvect,
+                                  vector_active(state->topvect) - 1);
+      keyword_vect = vector_init(VECTOR_MIN_SIZE);
+      vector_set(keyword_token->keyword, keyword_vect);
+      state->curvect = keyword_vect;
+    }
+  else
+    {
+      format_parser_error(state, "Unexpected '|'");
     }
 }
 
-/* Count mandantory string vector size.  This is to determine inputed
-   command has enough command length. */
-static int
-cmd_cmdsize (vector strvec)
+static void
+format_parser_read_word(struct format_parser_state *state)
 {
-  unsigned int i;
-  int size = 0;
-  vector descvec;
-  struct desc *desc;
+  const char *start;
+  int len;
+  char *cmd;
+  struct cmd_token *token;
 
-  for (i = 0; i < vector_active (strvec); i++)
-    if ((descvec = vector_slot (strvec, i)) != NULL)
+  start = state->cp;
+
+  while (state->cp[0] != '\0'
+         && !strchr("\r\n(){}|", state->cp[0])
+         && !isspace((int)state->cp[0]))
+    state->cp++;
+
+  len = state->cp - start;
+  cmd = XMALLOC(MTYPE_CMD_TOKENS, len + 1);
+  memcpy(cmd, start, len);
+  cmd[len] = '\0';
+
+  token = XCALLOC(MTYPE_CMD_TOKENS, sizeof(*token));
+  token->type = TOKEN_TERMINAL;
+  token->cmd = cmd;
+  token->desc = format_parser_desc_str(state);
+  vector_set(state->curvect, token);
+
+  if (state->in_keyword == 1)
+    state->in_keyword = 2;
+
+  state->just_read_word = 1;
+}
+
+/**
+ * Parse a given command format string and build a tree of tokens from
+ * it that is suitable to be used by the command subsystem.
+ *
+ * @param string Command format string.
+ * @param descstr Description string.
+ * @return A vector of struct cmd_token representing the given command,
+ *         or NULL on error.
+ */
+static vector
+cmd_parse_format(const char *string, const char *descstr)
+{
+  struct format_parser_state state;
+
+  if (string == NULL)
+    return NULL;
+
+  memset(&state, 0, sizeof(state));
+  state.topvect = state.curvect = vector_init(VECTOR_MIN_SIZE);
+  state.cp = state.string = string;
+  state.dp = descstr;
+
+  while (1)
     {
-      if ((vector_active (descvec)) == 1
-        && (desc = vector_slot (descvec, 0)) != NULL)
-	{
-	  if (desc->cmd == NULL || CMD_OPTION (desc->cmd))
-	    return size;
-	  else
-	    size++;
-	}
-      else
-	size++;
+      while (isspace((int)state.cp[0]) && state.cp[0] != '\0')
+        state.cp++;
+
+      switch (state.cp[0])
+        {
+        case '\0':
+          if (state.in_keyword
+              || state.in_multiple)
+            format_parser_error(&state, "Unclosed group/keyword");
+          return state.topvect;
+        case '{':
+          format_parser_begin_keyword(&state);
+          break;
+        case '(':
+          format_parser_begin_multiple(&state);
+          break;
+        case '}':
+          format_parser_end_keyword(&state);
+          break;
+        case ')':
+          format_parser_end_multiple(&state);
+          break;
+        case '|':
+          format_parser_handle_pipe(&state);
+          break;
+        default:
+          format_parser_read_word(&state);
+        }
     }
-  return size;
 }
 
 /* Return prompt character of specified node. */
@@ -497,11 +600,8 @@
     }
 
   vector_set (cnode->cmd_vector, cmd);
-
-  if (cmd->strvec == NULL)
-    cmd->strvec = cmd_make_descvec (cmd->string, cmd->doc);
-
-  cmd->cmdsize = cmd_cmdsize (cmd->strvec);
+  if (cmd->tokens == NULL)
+    cmd->tokens = cmd_parse_format(cmd->string, cmd->doc);
 }
 
 static const unsigned char itoa64[] =
@@ -847,9 +947,6 @@
 static enum match_type
 cmd_ipv6_match (const char *str)
 {
-  int state = STATE_START;
-  int colons = 0, nums = 0, double_colon = 0;
-  const char *sp = NULL;
   struct sockaddr_in6 sin6_dummy;
   int ret;
 
@@ -1056,254 +1153,700 @@
   return 1;
 }
 
-/* Make completion match and return match type flag. */
 static enum match_type
-cmd_filter_by_completion (char *command, vector v, unsigned int index)
+cmd_word_match(struct cmd_token *token,
+               enum filter_type filter,
+               const char *word)
 {
-  unsigned int i;
   const char *str;
-  struct cmd_element *cmd_element;
   enum match_type match_type;
-  vector descvec;
-  struct desc *desc;
 
-  match_type = no_match;
+  str = token->cmd;
 
-  /* If command and cmd_element string does not match set NULL to vector */
-  for (i = 0; i < vector_active (v); i++)
-    if ((cmd_element = vector_slot (v, i)) != NULL)
-      {
-	if (index >= vector_active (cmd_element->strvec))
-	  vector_slot (v, i) = NULL;
-	else
-	  {
-	    unsigned int j;
-	    int matched = 0;
+  if (filter == FILTER_RELAXED)
+    if (!word || !strlen(word))
+      return partly_match;
 
-	    descvec = vector_slot (cmd_element->strvec, index);
+  if (!word)
+    return no_match;
 
-	    for (j = 0; j < vector_active (descvec); j++)
-	      if ((desc = vector_slot (descvec, j)))
-		{
-		  str = desc->cmd;
-		  
-		  if (CMD_VARARG (str))
-		    {
-		      if (match_type < vararg_match)
-			match_type = vararg_match;
-		      matched++;
-		    }
-		  else if (CMD_RANGE (str))
-		    {
-		      if (cmd_range_match (str, command))
-			{
-			  if (match_type < range_match)
-			    match_type = range_match;
-
-			  matched++;
-			}
-		    }
+  if (CMD_VARARG(str))
+    {
+      return vararg_match;
+    }
+  else if (CMD_RANGE(str))
+    {
+      if (cmd_range_match(str, word))
+        return range_match;
+    }
 #ifdef HAVE_IPV6
-		  else if (CMD_IPV6 (str))
-		    {
-		      if (cmd_ipv6_match (command))
-			{
-			  if (match_type < ipv6_match)
-			    match_type = ipv6_match;
+  else if (CMD_IPV6(str))
+    {
+      match_type = cmd_ipv6_match(word);
+      if ((filter == FILTER_RELAXED && match_type != no_match)
+          || (filter == FILTER_STRICT && match_type == exact_match))
+        return ipv6_match;
+    }
+  else if (CMD_IPV6_PREFIX(str))
+    {
+      match_type = cmd_ipv6_prefix_match(word);
+      if ((filter == FILTER_RELAXED && match_type != no_match)
+          || (filter == FILTER_STRICT && match_type == exact_match))
+        return ipv6_prefix_match;
+    }
+#endif /* HAVE_IPV6 */
+  else if (CMD_IPV4(str))
+    {
+      match_type = cmd_ipv4_match(word);
+      if ((filter == FILTER_RELAXED && match_type != no_match)
+          || (filter == FILTER_STRICT && match_type == exact_match))
+        return ipv4_match;
+    }
+  else if (CMD_IPV4_PREFIX(str))
+    {
+      match_type = cmd_ipv4_prefix_match(word);
+      if ((filter == FILTER_RELAXED && match_type != no_match)
+          || (filter == FILTER_STRICT && match_type == exact_match))
+        return ipv4_prefix_match;
+    }
+  else if (CMD_OPTION(str) || CMD_VARIABLE(str))
+    {
+      return extend_match;
+    }
+  else
+    {
+      if (filter == FILTER_RELAXED && !strncmp(str, word, strlen(word)))
+        {
+          if (!strcmp(str, word))
+            return exact_match;
+          return partly_match;
+        }
+      if (filter == FILTER_STRICT && !strcmp(str, word))
+        return exact_match;
+    }
 
-			  matched++;
-			}
-		    }
-		  else if (CMD_IPV6_PREFIX (str))
-		    {
-		      if (cmd_ipv6_prefix_match (command))
-			{
-			  if (match_type < ipv6_prefix_match)
-			    match_type = ipv6_prefix_match;
-
-			  matched++;
-			}
-		    }
-#endif /* HAVE_IPV6  */
-		  else if (CMD_IPV4 (str))
-		    {
-		      if (cmd_ipv4_match (command))
-			{
-			  if (match_type < ipv4_match)
-			    match_type = ipv4_match;
-
-			  matched++;
-			}
-		    }
-		  else if (CMD_IPV4_PREFIX (str))
-		    {
-		      if (cmd_ipv4_prefix_match (command))
-			{
-			  if (match_type < ipv4_prefix_match)
-			    match_type = ipv4_prefix_match;
-			  matched++;
-			}
-		    }
-		  else
-		    /* Check is this point's argument optional ? */
-		  if (CMD_OPTION (str) || CMD_VARIABLE (str))
-		    {
-		      if (match_type < extend_match)
-			match_type = extend_match;
-		      matched++;
-		    }
-		  else if (strncmp (command, str, strlen (command)) == 0)
-		    {
-		      if (strcmp (command, str) == 0)
-			match_type = exact_match;
-		      else
-			{
-			  if (match_type < partly_match)
-			    match_type = partly_match;
-			}
-		      matched++;
-		    }
-		}
-	    if (!matched)
-	      vector_slot (v, i) = NULL;
-	  }
-      }
-  return match_type;
+  return no_match;
 }
 
-/* Filter vector by command character with index. */
-static enum match_type
-cmd_filter_by_string (char *command, vector v, unsigned int index)
+struct cmd_matcher
+{
+  struct cmd_element *cmd; /* The command element the matcher is using */
+  enum filter_type filter; /* Whether to use strict or relaxed matching */
+  vector vline; /* The tokenized commandline which is to be matched */
+  unsigned int index; /* The index up to which matching should be done */
+
+  /* If set, construct a list of matches at the position given by index */
+  enum match_type *match_type;
+  vector *match;
+
+  unsigned int word_index; /* iterating over vline */
+};
+
+static int
+push_argument(int *argc, const char **argv, const char *arg)
+{
+  if (!arg || !strlen(arg))
+    arg = NULL;
+
+  if (!argc || !argv)
+    return 0;
+
+  if (*argc >= CMD_ARGC_MAX)
+    return -1;
+
+  argv[(*argc)++] = arg;
+  return 0;
+}
+
+static void
+cmd_matcher_record_match(struct cmd_matcher *matcher,
+                         enum match_type match_type,
+                         struct cmd_token *token)
+{
+  if (matcher->word_index != matcher->index)
+    return;
+
+  if (matcher->match)
+    {
+      if (!*matcher->match)
+        *matcher->match = vector_init(VECTOR_MIN_SIZE);
+      vector_set(*matcher->match, token);
+    }
+
+  if (matcher->match_type)
+    {
+      if (match_type > *matcher->match_type)
+        *matcher->match_type = match_type;
+    }
+}
+
+static int
+cmd_matcher_words_left(struct cmd_matcher *matcher)
+{
+  return matcher->word_index < vector_active(matcher->vline);
+}
+
+static const char*
+cmd_matcher_get_word(struct cmd_matcher *matcher)
+{
+  assert(cmd_matcher_words_left(matcher));
+
+  return vector_slot(matcher->vline, matcher->word_index);
+}
+
+static enum matcher_rv
+cmd_matcher_match_terminal(struct cmd_matcher *matcher,
+                           struct cmd_token *token,
+                           int *argc, const char **argv)
+{
+  const char *word;
+  enum match_type word_match;
+
+  assert(token->type == TOKEN_TERMINAL);
+
+  if (!cmd_matcher_words_left(matcher))
+    {
+      if (CMD_OPTION(token->cmd))
+        return MATCHER_OK; /* missing optional args are NOT pushed as NULL */
+      else
+        return MATCHER_INCOMPLETE;
+    }
+
+  word = cmd_matcher_get_word(matcher);
+  word_match = cmd_word_match(token, matcher->filter, word);
+  if (word_match == no_match)
+    return MATCHER_NO_MATCH;
+
+  /* We have to record the input word as argument if it matched
+   * against a variable. */
+  if (CMD_VARARG(token->cmd)
+      || CMD_VARIABLE(token->cmd)
+      || CMD_OPTION(token->cmd))
+    {
+      if (push_argument(argc, argv, word))
+        return MATCHER_EXCEED_ARGC_MAX;
+    }
+
+  cmd_matcher_record_match(matcher, word_match, token);
+
+  matcher->word_index++;
+
+  /* A vararg token should consume all left over words as arguments */
+  if (CMD_VARARG(token->cmd))
+    while (cmd_matcher_words_left(matcher))
+      {
+        word = cmd_matcher_get_word(matcher);
+        if (word && strlen(word))
+          push_argument(argc, argv, word);
+        matcher->word_index++;
+      }
+
+  return MATCHER_OK;
+}
+
+static enum matcher_rv
+cmd_matcher_match_multiple(struct cmd_matcher *matcher,
+                           struct cmd_token *token,
+                           int *argc, const char **argv)
+{
+  enum match_type multiple_match;
+  unsigned int multiple_index;
+  const char *word;
+  const char *arg;
+  struct cmd_token *word_token;
+  enum match_type word_match;
+
+  assert(token->type == TOKEN_MULTIPLE);
+
+  multiple_match = no_match;
+
+  if (!cmd_matcher_words_left(matcher))
+    return MATCHER_INCOMPLETE;
+
+  word = cmd_matcher_get_word(matcher);
+  for (multiple_index = 0;
+       multiple_index < vector_active(token->multiple);
+       multiple_index++)
+    {
+      word_token = vector_slot(token->multiple, multiple_index);
+
+      word_match = cmd_word_match(word_token, matcher->filter, word);
+      if (word_match == no_match)
+        continue;
+
+      cmd_matcher_record_match(matcher, word_match, word_token);
+
+      if (word_match > multiple_match)
+        {
+          multiple_match = word_match;
+          arg = word;
+        }
+      /* To mimic the behavior of the old command implementation, we
+       * tolerate any ambiguities here :/ */
+    }
+
+  matcher->word_index++;
+
+  if (multiple_match == no_match)
+    return MATCHER_NO_MATCH;
+
+  if (push_argument(argc, argv, arg))
+    return MATCHER_EXCEED_ARGC_MAX;
+
+  return MATCHER_OK;
+}
+
+static enum matcher_rv
+cmd_matcher_read_keywords(struct cmd_matcher *matcher,
+                          struct cmd_token *token,
+                          vector args_vector)
 {
   unsigned int i;
-  const char *str;
+  unsigned long keyword_mask;
+  unsigned int keyword_found;
+  enum match_type keyword_match;
+  enum match_type word_match;
+  vector keyword_vector;
+  struct cmd_token *word_token;
+  const char *word;
+  int keyword_argc;
+  const char **keyword_argv;
+  enum matcher_rv rv;
+
+  keyword_mask = 0;
+  while (1)
+    {
+      if (!cmd_matcher_words_left(matcher))
+        return MATCHER_OK;
+
+      word = cmd_matcher_get_word(matcher);
+
+      keyword_found = -1;
+      keyword_match = no_match;
+      for (i = 0; i < vector_active(token->keyword); i++)
+        {
+          if (keyword_mask & (1 << i))
+            continue;
+
+          keyword_vector = vector_slot(token->keyword, i);
+          word_token = vector_slot(keyword_vector, 0);
+
+          word_match = cmd_word_match(word_token, matcher->filter, word);
+          if (word_match == no_match)
+            continue;
+
+          cmd_matcher_record_match(matcher, word_match, word_token);
+
+          if (word_match > keyword_match)
+            {
+              keyword_match = word_match;
+              keyword_found = i;
+            }
+          else if (word_match == keyword_match)
+            {
+              if (matcher->word_index != matcher->index || args_vector)
+                return MATCHER_AMBIGUOUS;
+            }
+        }
+
+      if (keyword_found == (unsigned int)-1)
+        return MATCHER_NO_MATCH;
+
+      matcher->word_index++;
+
+      if (matcher->word_index > matcher->index)
+        return MATCHER_OK;
+
+      keyword_mask |= (1 << keyword_found);
+
+      if (args_vector)
+        {
+          keyword_argc = 0;
+          keyword_argv = XMALLOC(MTYPE_TMP, (CMD_ARGC_MAX + 1) * sizeof(char*));
+          /* We use -1 as a marker for unused fields as NULL might be a valid value */
+          for (i = 0; i < CMD_ARGC_MAX + 1; i++)
+            keyword_argv[i] = (void*)-1;
+          vector_set_index(args_vector, keyword_found, keyword_argv);
+        }
+      else
+        {
+          keyword_argv = NULL;
+        }
+
+      keyword_vector = vector_slot(token->keyword, keyword_found);
+      /* the keyword itself is at 0. We are only interested in the arguments,
+       * so start counting at 1. */
+      for (i = 1; i < vector_active(keyword_vector); i++)
+        {
+          word_token = vector_slot(keyword_vector, i);
+
+          switch (word_token->type)
+            {
+            case TOKEN_TERMINAL:
+              rv = cmd_matcher_match_terminal(matcher, word_token,
+                                              &keyword_argc, keyword_argv);
+              break;
+            case TOKEN_MULTIPLE:
+              rv = cmd_matcher_match_multiple(matcher, word_token,
+                                              &keyword_argc, keyword_argv);
+              break;
+            case TOKEN_KEYWORD:
+              assert(!"Keywords should never be nested.");
+              break;
+            }
+
+          if (MATCHER_ERROR(rv))
+            return rv;
+
+          if (matcher->word_index > matcher->index)
+            return MATCHER_OK;
+        }
+    }
+  /* not reached */
+}
+
+static enum matcher_rv
+cmd_matcher_build_keyword_args(struct cmd_matcher *matcher,
+                               struct cmd_token *token,
+                               int *argc, const char **argv,
+                               vector keyword_args_vector)
+{
+  unsigned int i, j;
+  const char **keyword_args;
+  vector keyword_vector;
+  struct cmd_token *word_token;
+  const char *arg;
+  enum matcher_rv rv;
+
+  rv = MATCHER_OK;
+
+  if (keyword_args_vector == NULL)
+    return rv;
+
+  for (i = 0; i < vector_active(token->keyword); i++)
+    {
+      keyword_vector = vector_slot(token->keyword, i);
+      keyword_args = vector_lookup(keyword_args_vector, i);
+
+      if (vector_active(keyword_vector) == 1)
+        {
+          /* this is a keyword without arguments */
+          if (keyword_args)
+            {
+              word_token = vector_slot(keyword_vector, 0);
+              arg = word_token->cmd;
+            }
+          else
+            {
+              arg = NULL;
+            }
+
+          if (push_argument(argc, argv, arg))
+            rv = MATCHER_EXCEED_ARGC_MAX;
+        }
+      else
+        {
+          /* this is a keyword with arguments */
+          if (keyword_args)
+            {
+              /* the keyword was present, so just fill in the arguments */
+              for (j = 0; keyword_args[j] != (void*)-1; j++)
+                if (push_argument(argc, argv, keyword_args[j]))
+                  rv = MATCHER_EXCEED_ARGC_MAX;
+              XFREE(MTYPE_TMP, keyword_args);
+            }
+          else
+            {
+              /* the keyword was not present, insert NULL for the arguments
+               * the keyword would have taken. */
+              for (j = 1; j < vector_active(keyword_vector); j++)
+                {
+                  word_token = vector_slot(keyword_vector, j);
+                  if ((word_token->type == TOKEN_TERMINAL
+                       && (CMD_VARARG(word_token->cmd)
+                           || CMD_VARIABLE(word_token->cmd)
+                           || CMD_OPTION(word_token->cmd)))
+                      || word_token->type == TOKEN_MULTIPLE)
+                    {
+                      if (push_argument(argc, argv, NULL))
+                        rv = MATCHER_EXCEED_ARGC_MAX;
+                    }
+                }
+            }
+        }
+    }
+  vector_free(keyword_args_vector);
+  return rv;
+}
+
+static enum matcher_rv
+cmd_matcher_match_keyword(struct cmd_matcher *matcher,
+                          struct cmd_token *token,
+                          int *argc, const char **argv)
+{
+  vector keyword_args_vector;
+  enum matcher_rv reader_rv;
+  enum matcher_rv builder_rv;
+
+  assert(token->type == TOKEN_KEYWORD);
+
+  if (argc && argv)
+    keyword_args_vector = vector_init(VECTOR_MIN_SIZE);
+  else
+    keyword_args_vector = NULL;
+
+  reader_rv = cmd_matcher_read_keywords(matcher, token, keyword_args_vector);
+  builder_rv = cmd_matcher_build_keyword_args(matcher, token, argc,
+                                              argv, keyword_args_vector);
+  /* keyword_args_vector is consumed by cmd_matcher_build_keyword_args */
+
+  if (!MATCHER_ERROR(reader_rv) && MATCHER_ERROR(builder_rv))
+    return builder_rv;
+
+  return reader_rv;
+}
+
+static void
+cmd_matcher_init(struct cmd_matcher *matcher,
+                 struct cmd_element *cmd,
+                 enum filter_type filter,
+                 vector vline,
+                 unsigned int index,
+                 enum match_type *match_type,
+                 vector *match)
+{
+  memset(matcher, 0, sizeof(*matcher));
+
+  matcher->cmd = cmd;
+  matcher->filter = filter;
+  matcher->vline = vline;
+  matcher->index = index;
+
+  matcher->match_type = match_type;
+  if (matcher->match_type)
+    *matcher->match_type = no_match;
+  matcher->match = match;
+
+  matcher->word_index = 0;
+}
+
+static enum matcher_rv
+cmd_element_match(struct cmd_element *cmd_element,
+                  enum filter_type filter,
+                  vector vline,
+                  unsigned int index,
+                  enum match_type *match_type,
+                  vector *match,
+                  int *argc,
+                  const char **argv)
+{
+  struct cmd_matcher matcher;
+  unsigned int token_index;
+  enum matcher_rv rv;
+
+  cmd_matcher_init(&matcher, cmd_element, filter,
+                   vline, index, match_type, match);
+
+  if (argc != NULL)
+    *argc = 0;
+
+  for (token_index = 0;
+       token_index < vector_active(cmd_element->tokens);
+       token_index++)
+    {
+      struct cmd_token *token = vector_slot(cmd_element->tokens, token_index);
+
+      switch (token->type)
+        {
+        case TOKEN_TERMINAL:
+          rv = cmd_matcher_match_terminal(&matcher, token, argc, argv);
+          break;
+        case TOKEN_MULTIPLE:
+          rv = cmd_matcher_match_multiple(&matcher, token, argc, argv);
+          break;
+        case TOKEN_KEYWORD:
+          rv = cmd_matcher_match_keyword(&matcher, token, argc, argv);
+        }
+
+      if (MATCHER_ERROR(rv))
+        return rv;
+
+      if (matcher.word_index > index)
+        return MATCHER_OK;
+    }
+
+  /* return MATCHER_COMPLETE if all words were consumed */
+  if (matcher.word_index >= vector_active(vline))
+    return MATCHER_COMPLETE;
+
+  /* return MATCHER_COMPLETE also if only an empty word is left. */
+  if (matcher.word_index == vector_active(vline) - 1
+      && (!vector_slot(vline, matcher.word_index)
+          || !strlen((char*)vector_slot(vline, matcher.word_index))))
+    return MATCHER_COMPLETE;
+
+  return MATCHER_NO_MATCH; /* command is too long to match */
+}
+
+/**
+ * Filter a given vector of commands against a given commandline and
+ * calculate possible completions.
+ *
+ * @param commands A vector of struct cmd_element*. Commands that don't
+ *                 match against the given command line will be overwritten
+ *                 with NULL in that vector.
+ * @param filter Either FILTER_RELAXED or FILTER_STRICT. This basically
+ *               determines how incomplete commands are handled, compare with
+ *               cmd_word_match for details.
+ * @param vline A vector of char* containing the tokenized commandline.
+ * @param index Only match up to the given token of the commandline.
+ * @param match_type Record the type of the best match here.
+ * @param matches Record the matches here. For each cmd_element in the commands
+ *                vector, a match vector will be created in the matches vector.
+ *                That vector will contain all struct command_token* of the
+ *                cmd_element which matched against the given vline at the given
+ *                index.
+ * @return A code specifying if an error occured. If all went right, it's
+ *         CMD_SUCCESS.
+ */
+static int
+cmd_vector_filter(vector commands,
+                  enum filter_type filter,
+                  vector vline,
+                  unsigned int index,
+                  enum match_type *match_type,
+                  vector *matches)
+{
+  unsigned int i;
   struct cmd_element *cmd_element;
-  enum match_type match_type;
-  vector descvec;
-  struct desc *desc;
+  enum match_type best_match;
+  enum match_type element_match;
+  enum matcher_rv matcher_rv;
 
-  match_type = no_match;
+  best_match = no_match;
+  *matches = vector_init(VECTOR_MIN_SIZE);
 
-  /* If command and cmd_element string does not match set NULL to vector */
-  for (i = 0; i < vector_active (v); i++)
-    if ((cmd_element = vector_slot (v, i)) != NULL)
+  for (i = 0; i < vector_active (commands); i++)
+    if ((cmd_element = vector_slot (commands, i)) != NULL)
       {
-	/* If given index is bigger than max string vector of command,
-	   set NULL */
-	if (index >= vector_active (cmd_element->strvec))
-	  vector_slot (v, i) = NULL;
-	else
-	  {
-	    unsigned int j;
-	    int matched = 0;
-
-	    descvec = vector_slot (cmd_element->strvec, index);
-
-	    for (j = 0; j < vector_active (descvec); j++)
-	      if ((desc = vector_slot (descvec, j)))
-		{
-		  str = desc->cmd;
-
-		  if (CMD_VARARG (str))
-		    {
-		      if (match_type < vararg_match)
-			match_type = vararg_match;
-		      matched++;
-		    }
-		  else if (CMD_RANGE (str))
-		    {
-		      if (cmd_range_match (str, command))
-			{
-			  if (match_type < range_match)
-			    match_type = range_match;
-			  matched++;
-			}
-		    }
-#ifdef HAVE_IPV6
-		  else if (CMD_IPV6 (str))
-		    {
-		      if (cmd_ipv6_match (command) == exact_match)
-			{
-			  if (match_type < ipv6_match)
-			    match_type = ipv6_match;
-			  matched++;
-			}
-		    }
-		  else if (CMD_IPV6_PREFIX (str))
-		    {
-		      if (cmd_ipv6_prefix_match (command) == exact_match)
-			{
-			  if (match_type < ipv6_prefix_match)
-			    match_type = ipv6_prefix_match;
-			  matched++;
-			}
-		    }
-#endif /* HAVE_IPV6  */
-		  else if (CMD_IPV4 (str))
-		    {
-		      if (cmd_ipv4_match (command) == exact_match)
-			{
-			  if (match_type < ipv4_match)
-			    match_type = ipv4_match;
-			  matched++;
-			}
-		    }
-		  else if (CMD_IPV4_PREFIX (str))
-		    {
-		      if (cmd_ipv4_prefix_match (command) == exact_match)
-			{
-			  if (match_type < ipv4_prefix_match)
-			    match_type = ipv4_prefix_match;
-			  matched++;
-			}
-		    }
-		  else if (CMD_OPTION (str) || CMD_VARIABLE (str))
-		    {
-		      if (match_type < extend_match)
-			match_type = extend_match;
-		      matched++;
-		    }
-		  else
-		    {
-		      if (strcmp (command, str) == 0)
-			{
-			  match_type = exact_match;
-			  matched++;
-			}
-		    }
-		}
-	    if (!matched)
-	      vector_slot (v, i) = NULL;
-	  }
+        vector_set_index(*matches, i, NULL);
+        matcher_rv = cmd_element_match(cmd_element, filter,
+                                       vline, index,
+                                       &element_match,
+                                       (vector*)&vector_slot(*matches, i),
+                                       NULL, NULL);
+        if (MATCHER_ERROR(matcher_rv))
+          {
+            vector_slot(commands, i) = NULL;
+            if (matcher_rv == MATCHER_AMBIGUOUS)
+              return CMD_ERR_AMBIGUOUS;
+            if (matcher_rv == MATCHER_EXCEED_ARGC_MAX)
+              return CMD_ERR_EXEED_ARGC_MAX;
+          }
+        else if (element_match > best_match)
+          {
+            best_match = element_match;
+          }
       }
-  return match_type;
+  *match_type = best_match;
+  return CMD_SUCCESS;
+}
+
+/**
+ * Check whether a given commandline is complete if used for a specific
+ * cmd_element.
+ *
+ * @param cmd_element A cmd_element against which the commandline should be
+ *                    checked.
+ * @param vline The tokenized commandline.
+ * @return 1 if the given commandline is complete, 0 otherwise.
+ */
+static int
+cmd_is_complete(struct cmd_element *cmd_element,
+                vector vline)
+{
+  enum matcher_rv rv;
+
+  rv = cmd_element_match(cmd_element,
+                         FILTER_RELAXED,
+                         vline, -1,
+                         NULL, NULL,
+                         NULL, NULL);
+  return (rv == MATCHER_COMPLETE);
+}
+
+/**
+ * Parse a given commandline and construct a list of arguments for the
+ * given command_element.
+ *
+ * @param cmd_element The cmd_element for which we want to construct arguments.
+ * @param vline The tokenized commandline.
+ * @param argc Where to store the argument count.
+ * @param argv Where to store the argument list. Should be at least
+ *             CMD_ARGC_MAX elements long.
+ * @return CMD_SUCCESS if everything went alright, an error otherwise.
+ */
+static int
+cmd_parse(struct cmd_element *cmd_element,
+          vector vline,
+          int *argc, const char **argv)
+{
+  enum matcher_rv rv = cmd_element_match(cmd_element,
+                                         FILTER_RELAXED,
+                                         vline, -1,
+                                         NULL, NULL,
+                                         argc, argv);
+  switch (rv)
+    {
+    case MATCHER_COMPLETE:
+      return CMD_SUCCESS;
+
+    case MATCHER_NO_MATCH:
+      return CMD_ERR_NO_MATCH;
+
+    case MATCHER_AMBIGUOUS:
+      return CMD_ERR_AMBIGUOUS;
+
+    case MATCHER_EXCEED_ARGC_MAX:
+      return CMD_ERR_EXEED_ARGC_MAX;
+
+    default:
+      return CMD_ERR_INCOMPLETE;
+    }
 }
 
 /* Check ambiguous match */
 static int
-is_cmd_ambiguous (char *command, vector v, int index, enum match_type type)
+is_cmd_ambiguous (vector cmd_vector,
+                  const char *command,
+                  vector matches,
+                  enum match_type type)
 {
   unsigned int i;
   unsigned int j;
   const char *str = NULL;
-  struct cmd_element *cmd_element;
   const char *matched = NULL;
-  vector descvec;
-  struct desc *desc;
+  vector match_vector;
+  struct cmd_token *cmd_token;
 
-  for (i = 0; i < vector_active (v); i++)
-    if ((cmd_element = vector_slot (v, i)) != NULL)
+  if (command == NULL)
+    command = "";
+
+  for (i = 0; i < vector_active (matches); i++)
+    if ((match_vector = vector_slot (matches, i)) != NULL)
       {
 	int match = 0;
 
-	descvec = vector_slot (cmd_element->strvec, index);
-
-	for (j = 0; j < vector_active (descvec); j++)
-	  if ((desc = vector_slot (descvec, j)))
+	for (j = 0; j < vector_active (match_vector); j++)
+	  if ((cmd_token = vector_slot (match_vector, j)) != NULL)
 	    {
 	      enum match_type ret;
-	      
-	      str = desc->cmd;
+
+	      assert(cmd_token->type == TOKEN_TERMINAL);
+	      if (cmd_token->type != TOKEN_TERMINAL)
+		continue;
+
+	      str = cmd_token->cmd;
 
 	      switch (type)
 		{
@@ -1371,7 +1914,7 @@
 		}
 	    }
 	if (!match)
-	  vector_slot (v, i) = NULL;
+	  vector_slot (cmd_vector, i) = NULL;
       }
   return 0;
 }
@@ -1461,8 +2004,12 @@
     return NULL;
 }
 
-/* Check same string element existence.  If it isn't there return
-    1. */
+/**
+ * Check whether a string is already present in a vector of strings.
+ * @param v A vector of char*.
+ * @param str A char*.
+ * @return 0 if str is already present in the vector, 1 otherwise.
+ */
 static int
 cmd_unique_string (vector v, const char *str)
 {
@@ -1476,19 +2023,25 @@
   return 1;
 }
 
-/* Compare string to description vector.  If there is same string
-   return 1 else return 0. */
+/**
+ * Check whether a struct cmd_token matching a given string is already
+ * present in a vector of struct cmd_token.
+ * @param v A vector of struct cmd_token*.
+ * @param str A char* which should be searched for.
+ * @return 0 if there is a struct cmd_token* with its cmd matching str,
+ *         1 otherwise.
+ */
 static int
 desc_unique_string (vector v, const char *str)
 {
   unsigned int i;
-  struct desc *desc;
+  struct cmd_token *token;
 
   for (i = 0; i < vector_active (v); i++)
-    if ((desc = vector_slot (v, i)) != NULL)
-      if (strcmp (desc->cmd, str) == 0)
-	return 1;
-  return 0;
+    if ((token = vector_slot (v, i)) != NULL)
+      if (strcmp (token->cmd, str) == 0)
+	return 0;
+  return 1;
 }
 
 static int 
@@ -1504,6 +2057,35 @@
   return 0;
 }
 
+static void
+cmd_matches_free(vector *matches)
+{
+  unsigned int i;
+  vector cmd_matches;
+
+  for (i = 0; i < vector_active(*matches); i++)
+    if ((cmd_matches = vector_slot(*matches, i)) != NULL)
+      vector_free(cmd_matches);
+  vector_free(*matches);
+  *matches = NULL;
+}
+
+static int
+cmd_describe_cmp(const void *a, const void *b)
+{
+  const struct cmd_token *first = *(struct cmd_token * const *)a;
+  const struct cmd_token *second = *(struct cmd_token * const *)b;
+
+  return strcmp(first->cmd, second->cmd);
+}
+
+static void
+cmd_describe_sort(vector matchvec)
+{
+  qsort(matchvec->index, vector_active(matchvec),
+        sizeof(void*), cmd_describe_cmp);
+}
+
 /* '?' describe command support. */
 static vector
 cmd_describe_command_real (vector vline, struct vty *vty, int *status)
@@ -1517,6 +2099,8 @@
   int ret;
   enum match_type match;
   char *command;
+  vector matches = NULL;
+  vector match_vector;
 
   /* Set index. */
   if (vector_active (vline) == 0)
@@ -1524,111 +2108,121 @@
       *status = CMD_ERR_NO_MATCH;
       return NULL;
     }
-  else
-    index = vector_active (vline) - 1;
-  
+
+  index = vector_active (vline) - 1;
+
   /* Make copy vector of current node's command vector. */
   cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node));
 
   /* Prepare match vector */
   matchvec = vector_init (INIT_MATCHVEC_SIZE);
 
-  /* Filter commands. */
-  /* Only words precedes current word will be checked in this loop. */
-  for (i = 0; i < index; i++)
-    if ((command = vector_slot (vline, i)))
-      {
-	match = cmd_filter_by_completion (command, cmd_vector, i);
-	
-	if (match == vararg_match)
-	  {
-	    struct cmd_element *cmd_element;
-	    vector descvec;
-	    unsigned int j, k;
+  /* Filter commands and build a list how they could possibly continue. */
+  for (i = 0; i <= index; i++)
+    {
+      command = vector_slot (vline, i);
 
-	    for (j = 0; j < vector_active (cmd_vector); j++)
-	      if ((cmd_element = vector_slot (cmd_vector, j)) != NULL
-		  && (vector_active (cmd_element->strvec)))
-		{
-		  descvec = vector_slot (cmd_element->strvec,
-					 vector_active (cmd_element->strvec) - 1);
-		  for (k = 0; k < vector_active (descvec); k++)
-		    {
-		      struct desc *desc = vector_slot (descvec, k);
-		      vector_set (matchvec, desc);
-		    }
-		}
-            
-	    vector_set (matchvec, &desc_cr);
-	    vector_free (cmd_vector);
+      if (matches)
+	cmd_matches_free(&matches);
 
-	    return matchvec;
-	  }
+      ret = cmd_vector_filter(cmd_vector,
+	                      FILTER_RELAXED,
+	                      vline, i,
+	                      &match,
+	                      &matches);
 
-	if ((ret = is_cmd_ambiguous (command, cmd_vector, i, match)) == 1)
-	  {
-	    vector_free (cmd_vector);
-	    vector_free (matchvec);
-	    *status = CMD_ERR_AMBIGUOUS;
-	    return NULL;
-	  }
-	else if (ret == 2)
-	  {
-	    vector_free (cmd_vector);
-	    vector_free (matchvec);
-	    *status = CMD_ERR_NO_MATCH;
-	    return NULL;
-	  }
-      }
+      if (ret != CMD_SUCCESS)
+	{
+	  vector_free (cmd_vector);
+	  vector_free (matchvec);
+	  cmd_matches_free(&matches);
+	  *status = ret;
+	  return NULL;
+	}
 
-  /* Prepare match vector */
-  /*  matchvec = vector_init (INIT_MATCHVEC_SIZE); */
+      /* The last match may well be ambigious, so break here */
+      if (i == index)
+	break;
 
-  /* Make sure that cmd_vector is filtered based on current word */
-  command = vector_slot (vline, index);
-  if (command)
-    match = cmd_filter_by_completion (command, cmd_vector, index);
+      if (match == vararg_match)
+	{
+	  /* We found a vararg match - so we can throw out the current matches here
+	   * and don't need to continue checking the command input */
+	  unsigned int j, k;
+
+	  for (j = 0; j < vector_active (matches); j++)
+	    if ((match_vector = vector_slot (matches, j)) != NULL)
+	      for (k = 0; k < vector_active (match_vector); k++)
+	        {
+	          struct cmd_token *token = vector_slot (match_vector, k);
+	          vector_set (matchvec, token);
+	        }
+
+	  *status = CMD_SUCCESS;
+	  vector_set(matchvec, &token_cr);
+	  vector_free (cmd_vector);
+	  cmd_matches_free(&matches);
+	  cmd_describe_sort(matchvec);
+	  return matchvec;
+	}
+
+      ret = is_cmd_ambiguous(cmd_vector, command, matches, match);
+      if (ret == 1)
+	{
+	  vector_free (cmd_vector);
+	  vector_free (matchvec);
+	  cmd_matches_free(&matches);
+	  *status = CMD_ERR_AMBIGUOUS;
+	  return NULL;
+	}
+      else if (ret == 2)
+	{
+	  vector_free (cmd_vector);
+	  vector_free (matchvec);
+	  cmd_matches_free(&matches);
+	  *status = CMD_ERR_NO_MATCH;
+	  return NULL;
+	}
+    }
 
   /* Make description vector. */
-  for (i = 0; i < vector_active (cmd_vector); i++)
+  for (i = 0; i < vector_active (matches); i++)
     if ((cmd_element = vector_slot (cmd_vector, i)) != NULL)
       {
-	vector strvec = cmd_element->strvec;
+        unsigned int j;
+        const char *last_word;
+        vector vline_trimmed;
 
-	/* if command is NULL, index may be equal to vector_active */
-	if (command && index >= vector_active (strvec))
-	  vector_slot (cmd_vector, i) = NULL;
-	else
-	  {
-	    /* Check if command is completed. */
-	    if (command == NULL && index == vector_active (strvec))
-	      {
-		if (!desc_unique_string (matchvec, command_cr))
-		  vector_set (matchvec, &desc_cr);
-	      }
-	    else
-	      {
-		unsigned int j;
-		vector descvec = vector_slot (strvec, index);
-		struct desc *desc;
+        last_word = vector_slot(vline, vector_active(vline) - 1);
+        if (last_word == NULL || !strlen(last_word))
+          {
+            vline_trimmed = vector_copy(vline);
+            vector_unset(vline_trimmed, vector_active(vline_trimmed) - 1);
 
-		for (j = 0; j < vector_active (descvec); j++)
-		  if ((desc = vector_slot (descvec, j)))
-		    {
-		      const char *string;
+            if (cmd_is_complete(cmd_element, vline_trimmed)
+                && desc_unique_string(matchvec, command_cr))
+              {
+                if (match != vararg_match)
+                  vector_set(matchvec, &token_cr);
+              }
 
-		      string = cmd_entry_function_desc (command, desc->cmd);
-		      if (string)
-			{
-			  /* Uniqueness check */
-			  if (!desc_unique_string (matchvec, string))
-			    vector_set (matchvec, desc);
-			}
-		    }
-	      }
-	  }
+            vector_free(vline_trimmed);
+          }
+
+        match_vector = vector_slot (matches, i);
+        if (match_vector)
+          for (j = 0; j < vector_active(match_vector); j++)
+            {
+              struct cmd_token *token = vector_slot(match_vector, j);
+              const char *string;
+
+              string = cmd_entry_function_desc(command, token->cmd);
+              if (string && desc_unique_string(matchvec, string))
+                vector_set(matchvec, token);
+            }
       }
   vector_free (cmd_vector);
+  cmd_matches_free(&matches);
 
   if (vector_slot (matchvec, 0) == NULL)
     {
@@ -1638,6 +2232,7 @@
     }
 
   *status = CMD_SUCCESS;
+  cmd_describe_sort(matchvec);
   return matchvec;
 }
 
@@ -1708,6 +2303,31 @@
   return lcd;
 }
 
+static int
+cmd_complete_cmp(const void *a, const void *b)
+{
+  const char *first = *(char * const *)a;
+  const char *second = *(char * const *)b;
+
+  if (!first)
+    {
+      if (!second)
+        return 0;
+      return 1;
+    }
+  if (!second)
+    return -1;
+
+  return strcmp(first, second);
+}
+
+static void
+cmd_complete_sort(vector matchvec)
+{
+  qsort(matchvec->index, vector_active(matchvec),
+        sizeof(void*), cmd_complete_cmp);
+}
+
 /* Command line completion support. */
 static char **
 cmd_complete_command_real (vector vline, struct vty *vty, int *status)
@@ -1716,13 +2336,13 @@
   vector cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node));
 #define INIT_MATCHVEC_SIZE 10
   vector matchvec;
-  struct cmd_element *cmd_element;
   unsigned int index;
   char **match_str;
-  struct desc *desc;
-  vector descvec;
+  struct cmd_token *token;
   char *command;
   int lcd;
+  vector matches = NULL;
+  vector match_vector;
 
   if (vector_active (vline) == 0)
     {
@@ -1733,66 +2353,80 @@
   else
     index = vector_active (vline) - 1;
 
-  /* First, filter by preceeding command string */
-  for (i = 0; i < index; i++)
-    if ((command = vector_slot (vline, i)))
-      {
-	enum match_type match;
-	int ret;
+  /* First, filter by command string */
+  for (i = 0; i <= index; i++)
+    {
+      command = vector_slot (vline, i);
+      enum match_type match;
+      int ret;
 
-	/* First try completion match, if there is exactly match return 1 */
-	match = cmd_filter_by_completion (command, cmd_vector, i);
+      if (matches)
+        cmd_matches_free(&matches);
 
-	/* If there is exact match then filter ambiguous match else check
-	   ambiguousness. */
-	if ((ret = is_cmd_ambiguous (command, cmd_vector, i, match)) == 1)
-	  {
-	    vector_free (cmd_vector);
-	    *status = CMD_ERR_AMBIGUOUS;
-	    return NULL;
-	  }
-	/*
+      /* First try completion match, if there is exactly match return 1 */
+      ret = cmd_vector_filter(cmd_vector,
+	                      FILTER_RELAXED,
+	                      vline, i,
+	                      &match,
+	                      &matches);
+
+      if (ret != CMD_SUCCESS)
+	{
+	  vector_free(cmd_vector);
+	  cmd_matches_free(&matches);
+	  *status = ret;
+	  return NULL;
+	}
+
+      /* Break here - the completion mustn't be checked to be non-ambiguous */
+      if (i == index)
+	break;
+
+      /* If there is exact match then filter ambiguous match else check
+	 ambiguousness. */
+      ret = is_cmd_ambiguous (cmd_vector, command, matches, match);
+      if (ret == 1)
+	{
+	  vector_free (cmd_vector);
+	  cmd_matches_free(&matches);
+	  *status = CMD_ERR_AMBIGUOUS;
+	  return NULL;
+	}
+      /*
 	   else if (ret == 2)
 	   {
 	   vector_free (cmd_vector);
+	   cmd_matches_free(&matches);
 	   *status = CMD_ERR_NO_MATCH;
 	   return NULL;
 	   }
 	 */
-      }
+    }
   
   /* Prepare match vector. */
   matchvec = vector_init (INIT_MATCHVEC_SIZE);
 
-  /* Now we got into completion */
-  for (i = 0; i < vector_active (cmd_vector); i++)
-    if ((cmd_element = vector_slot (cmd_vector, i)))
+  /* Build the possible list of continuations into a list of completions */
+  for (i = 0; i < vector_active (matches); i++)
+    if ((match_vector = vector_slot (matches, i)))
       {
 	const char *string;
-	vector strvec = cmd_element->strvec;
+	unsigned int j;
 
-	/* Check field length */
-	if (index >= vector_active (strvec))
-	  vector_slot (cmd_vector, i) = NULL;
-	else
-	  {
-	    unsigned int j;
-
-	    descvec = vector_slot (strvec, index);
-	    for (j = 0; j < vector_active (descvec); j++)
-	      if ((desc = vector_slot (descvec, j)))
+	for (j = 0; j < vector_active (match_vector); j++)
+	  if ((token = vector_slot (match_vector, j)))
 		{
 		  if ((string = 
 		       cmd_entry_function (vector_slot (vline, index),
-					   desc->cmd)))
+					   token->cmd)))
 		    if (cmd_unique_string (matchvec, string))
 		      vector_set (matchvec, XSTRDUP (MTYPE_TMP, string));
 		}
-	  }
       }
 
   /* We don't need cmd_vector any more. */
   vector_free (cmd_vector);
+  cmd_matches_free(&matches);
 
   /* No matched command */
   if (vector_slot (matchvec, 0) == NULL)
@@ -1832,7 +2466,7 @@
 	    {
 	      char *lcdstr;
 
-	      lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1);
+	      lcdstr = XMALLOC (MTYPE_TMP, lcd + 1);
 	      memcpy (lcdstr, matchvec->index[0], lcd);
 	      lcdstr[lcd] = '\0';
 
@@ -1842,7 +2476,7 @@
 	      for (i = 0; i < vector_active (matchvec); i++)
 		{
 		  if (vector_slot (matchvec, i))
-		    XFREE (MTYPE_STRVEC, vector_slot (matchvec, i));
+		    XFREE (MTYPE_TMP, vector_slot (matchvec, i));
 		}
 	      vector_free (matchvec);
 
@@ -1859,6 +2493,7 @@
     }
 
   match_str = (char **) matchvec->index;
+  cmd_complete_sort(matchvec);
   vector_only_wrapper_free (matchvec);
   *status = CMD_COMPLETE_LIST_MATCH;
   return match_str;
@@ -1927,7 +2562,9 @@
 
 /* Execute command by argument vline vector. */
 static int
-cmd_execute_command_real (vector vline, struct vty *vty,
+cmd_execute_command_real (vector vline,
+			  enum filter_type filter,
+			  struct vty *vty,
 			  struct cmd_element **cmd)
 {
   unsigned int i;
@@ -1939,35 +2576,48 @@
   int argc;
   const char *argv[CMD_ARGC_MAX];
   enum match_type match = 0;
-  int varflag;
   char *command;
+  int ret;
+  vector matches;
 
   /* Make copy of command elements. */
   cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node));
 
   for (index = 0; index < vector_active (vline); index++)
-    if ((command = vector_slot (vline, index)))
-      {
-	int ret;
+    {
+      command = vector_slot (vline, index);
+      ret = cmd_vector_filter(cmd_vector,
+			      filter,
+			      vline, index,
+			      &match,
+			      &matches);
 
-	match = cmd_filter_by_completion (command, cmd_vector, index);
+      if (ret != CMD_SUCCESS)
+	{
+	  cmd_matches_free(&matches);
+	  return ret;
+	}
 
-	if (match == vararg_match)
+      if (match == vararg_match)
+	{
+	  cmd_matches_free(&matches);
 	  break;
-        
-	ret = is_cmd_ambiguous (command, cmd_vector, index, match);
+	}
 
-	if (ret == 1)
-	  {
-	    vector_free (cmd_vector);
-	    return CMD_ERR_AMBIGUOUS;
-	  }
-	else if (ret == 2)
-	  {
-	    vector_free (cmd_vector);
-	    return CMD_ERR_NO_MATCH;
-	  }
-      }
+      ret = is_cmd_ambiguous (cmd_vector, command, matches, match);
+      cmd_matches_free(&matches);
+
+      if (ret == 1)
+	{
+	  vector_free(cmd_vector);
+	  return CMD_ERR_AMBIGUOUS;
+	}
+      else if (ret == 2)
+	{
+	  vector_free(cmd_vector);
+	  return CMD_ERR_NO_MATCH;
+	}
+    }
 
   /* Check matched count. */
   matched_element = NULL;
@@ -1977,12 +2627,9 @@
   for (i = 0; i < vector_active (cmd_vector); i++)
     if ((cmd_element = vector_slot (cmd_vector, i)))
       {
-	if (match == vararg_match || index >= cmd_element->cmdsize)
+	if (cmd_is_complete(cmd_element, vline))
 	  {
 	    matched_element = cmd_element;
-#if 0
-	    printf ("DEBUG: %s\n", cmd_element->string);
-#endif
 	    matched_count++;
 	  }
 	else
@@ -2006,35 +2653,9 @@
   if (matched_count > 1)
     return CMD_ERR_AMBIGUOUS;
 
-  /* Argument treatment */
-  varflag = 0;
-  argc = 0;
-
-  for (i = 0; i < vector_active (vline); i++)
-    {
-      if (varflag)
-	argv[argc++] = vector_slot (vline, i);
-      else
-	{
-	  vector descvec = vector_slot (matched_element->strvec, i);
-
-	  if (vector_active (descvec) == 1)
-	    {
-	      struct desc *desc = vector_slot (descvec, 0);
-
-	      if (CMD_VARARG (desc->cmd))
-		varflag = 1;
-
-	      if (varflag || CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd))
-		argv[argc++] = vector_slot (vline, i);
-	    }
-	  else
-	    argv[argc++] = vector_slot (vline, i);
-	}
-
-      if (argc >= CMD_ARGC_MAX)
-	return CMD_ERR_EXEED_ARGC_MAX;
-    }
+  ret = cmd_parse(matched_element, vline, &argc, argv);
+  if (ret != CMD_SUCCESS)
+    return ret;
 
   /* For vtysh execution. */
   if (cmd)
@@ -2047,6 +2668,21 @@
   return (*matched_element->func) (matched_element, vty, argc, argv);
 }
 
+/**
+ * Execute a given command, handling things like "do ..." and checking
+ * whether the given command might apply at a parent node if doesn't
+ * apply for the current node.
+ *
+ * @param vline Command line input, vector of char* where each element is
+ *              one input token.
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element of the matched command
+ *            will be stored, if any. May be set to NULL if this info is
+ *            not needed.
+ * @param vtysh If set != 0, don't lookup the command at parent nodes.
+ * @return The status of the command that has been executed or an error code
+ *         as to why no command could be executed.
+ */
 int
 cmd_execute_command (vector vline, struct vty *vty, struct cmd_element **cmd,
 		     int vtysh) {
@@ -2070,7 +2706,7 @@
 	  vector_set_index (shifted_vline, index-1, vector_lookup(vline, index));
 	}
 
-      ret = cmd_execute_command_real (shifted_vline, vty, cmd);
+      ret = cmd_execute_command_real (shifted_vline, FILTER_RELAXED, vty, cmd);
 
       vector_free(shifted_vline);
       vty->node = onode;
@@ -2078,7 +2714,7 @@
   }
 
 
-  saved_ret = ret = cmd_execute_command_real (vline, vty, cmd);
+  saved_ret = ret = cmd_execute_command_real (vline, FILTER_RELAXED, vty, cmd);
 
   if (vtysh)
     return saved_ret;
@@ -2089,7 +2725,7 @@
     {
       try_node = node_parent(try_node);
       vty->node = try_node;
-      ret = cmd_execute_command_real (vline, vty, cmd);
+      ret = cmd_execute_command_real (vline, FILTER_RELAXED, vty, cmd);
       tried = 1;
       if (ret == CMD_SUCCESS || ret == CMD_WARNING)
 	{
@@ -2104,123 +2740,24 @@
   return saved_ret;
 }
 
-/* Execute command by argument readline. */
+/**
+ * Execute a given command, matching it strictly against the current node.
+ * This mode is used when reading config files.
+ *
+ * @param vline Command line input, vector of char* where each element is
+ *              one input token.
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element* of the matched command
+ *            will be stored, if any. May be set to NULL if this info is
+ *            not needed.
+ * @return The status of the command that has been executed or an error code
+ *         as to why no command could be executed.
+ */
 int
 cmd_execute_command_strict (vector vline, struct vty *vty,
 			    struct cmd_element **cmd)
 {
-  unsigned int i;
-  unsigned int index;
-  vector cmd_vector;
-  struct cmd_element *cmd_element;
-  struct cmd_element *matched_element;
-  unsigned int matched_count, incomplete_count;
-  int argc;
-  const char *argv[CMD_ARGC_MAX];
-  int varflag;
-  enum match_type match = 0;
-  char *command;
-
-  /* Make copy of command element */
-  cmd_vector = vector_copy (cmd_node_vector (cmdvec, vty->node));
-
-  for (index = 0; index < vector_active (vline); index++)
-    if ((command = vector_slot (vline, index)))
-      {
-	int ret;
-	
-	match = cmd_filter_by_string (vector_slot (vline, index),
-				      cmd_vector, index);
-
-	/* If command meets '.VARARG' then finish matching. */
-	if (match == vararg_match)
-	  break;
-        
-	ret = is_cmd_ambiguous (command, cmd_vector, index, match);
-	if (ret == 1)
-	  {
-	    vector_free (cmd_vector);
-	    return CMD_ERR_AMBIGUOUS;
-	  }
-	if (ret == 2)
-	  {
-	    vector_free (cmd_vector);
-	    return CMD_ERR_NO_MATCH;
-	  }
-      }
-
-  /* Check matched count. */
-  matched_element = NULL;
-  matched_count = 0;
-  incomplete_count = 0;
-  for (i = 0; i < vector_active (cmd_vector); i++)
-    if (vector_slot (cmd_vector, i) != NULL)
-      {
-	cmd_element = vector_slot (cmd_vector, i);
-
-	if (match == vararg_match || index >= cmd_element->cmdsize)
-	  {
-	    matched_element = cmd_element;
-	    matched_count++;
-	  }
-	else
-	  incomplete_count++;
-      }
-
-  /* Finish of using cmd_vector. */
-  vector_free (cmd_vector);
-
-  /* To execute command, matched_count must be 1. */
-  if (matched_count == 0)
-    {
-      if (incomplete_count)
-	return CMD_ERR_INCOMPLETE;
-      else
-	return CMD_ERR_NO_MATCH;
-    }
-
-  if (matched_count > 1)
-    return CMD_ERR_AMBIGUOUS;
-
-  /* Argument treatment */
-  varflag = 0;
-  argc = 0;
-
-  for (i = 0; i < vector_active (vline); i++)
-    {
-      if (varflag)
-	argv[argc++] = vector_slot (vline, i);
-      else
-	{
-	  vector descvec = vector_slot (matched_element->strvec, i);
-
-	  if (vector_active (descvec) == 1)
-	    {
-	      struct desc *desc = vector_slot (descvec, 0);
-
-	      if (CMD_VARARG (desc->cmd))
-		varflag = 1;
-
-	      if (varflag || CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd))
-		argv[argc++] = vector_slot (vline, i);
-	    }
-	  else
-	    argv[argc++] = vector_slot (vline, i);
-	}
-
-      if (argc >= CMD_ARGC_MAX)
-	return CMD_ERR_EXEED_ARGC_MAX;
-    }
-
-  /* For vtysh execution. */
-  if (cmd)
-    *cmd = matched_element;
-
-  if (matched_element->daemon)
-    return CMD_SUCCESS_DAEMON;
-
-  /* Now execute matched command */
-  return (*matched_element->func) (matched_element, vty, argc, argv);
+  return cmd_execute_command_real(vline, FILTER_STRICT, vty, cmd);
 }
 
 /* Configration make from file. */
@@ -3461,9 +3998,10 @@
 void
 cmd_init (int terminal)
 {
-  command_cr = XSTRDUP(MTYPE_STRVEC, "<cr>");
-  desc_cr.cmd = command_cr;
-  desc_cr.str = XSTRDUP(MTYPE_STRVEC, "");
+  command_cr = XSTRDUP(MTYPE_CMD_TOKENS, "<cr>");
+  token_cr.type = TOKEN_TERMINAL;
+  token_cr.cmd = command_cr;
+  token_cr.desc = XSTRDUP(MTYPE_CMD_TOKENS, "");
 
   /* Allocate initial top vector of commands. */
   cmdvec = vector_init (VECTOR_MIN_SIZE);
@@ -3584,14 +4122,61 @@
   srand(time(NULL));
 }
 
+static void
+cmd_terminate_token(struct cmd_token *token)
+{
+  unsigned int i, j;
+  vector keyword_vect;
+
+  if (token->multiple)
+    {
+      for (i = 0; i < vector_active(token->multiple); i++)
+        cmd_terminate_token(vector_slot(token->multiple, i));
+      vector_free(token->multiple);
+      token->multiple = NULL;
+    }
+
+  if (token->keyword)
+    {
+      for (i = 0; i < vector_active(token->keyword); i++)
+        {
+          keyword_vect = vector_slot(token->keyword, i);
+          for (j = 0; j < vector_active(keyword_vect); j++)
+            cmd_terminate_token(vector_slot(keyword_vect, j));
+          vector_free(keyword_vect);
+        }
+      vector_free(token->keyword);
+      token->keyword = NULL;
+    }
+
+  XFREE(MTYPE_CMD_TOKENS, token->cmd);
+  XFREE(MTYPE_CMD_TOKENS, token->desc);
+
+  XFREE(MTYPE_CMD_TOKENS, token);
+}
+
+static void
+cmd_terminate_element(struct cmd_element *cmd)
+{
+  unsigned int i;
+
+  if (cmd->tokens == NULL)
+    return;
+
+  for (i = 0; i < vector_active(cmd->tokens); i++)
+    cmd_terminate_token(vector_slot(cmd->tokens, i));
+
+  vector_free(cmd->tokens);
+  cmd->tokens = NULL;
+}
+
 void
 cmd_terminate ()
 {
-  unsigned int i, j, k, l;
+  unsigned int i, j;
   struct cmd_node *cmd_node;
   struct cmd_element *cmd_element;
-  struct desc *desc;
-  vector cmd_node_v, cmd_element_v, desc_v;
+  vector cmd_node_v;
 
   if (cmdvec)
     {
@@ -3601,30 +4186,8 @@
             cmd_node_v = cmd_node->cmd_vector;
 
             for (j = 0; j < vector_active (cmd_node_v); j++)
-              if ((cmd_element = vector_slot (cmd_node_v, j)) != NULL &&
-                  cmd_element->strvec != NULL)
-                {
-                  cmd_element_v = cmd_element->strvec;
-
-                  for (k = 0; k < vector_active (cmd_element_v); k++)
-                    if ((desc_v = vector_slot (cmd_element_v, k)) != NULL)
-                      {
-                        for (l = 0; l < vector_active (desc_v); l++)
-                          if ((desc = vector_slot (desc_v, l)) != NULL)
-                            {
-                              if (desc->cmd)
-                                XFREE (MTYPE_STRVEC, desc->cmd);
-                              if (desc->str)
-                                XFREE (MTYPE_STRVEC, desc->str);
-
-                              XFREE (MTYPE_DESC, desc);
-                            }
-                        vector_free (desc_v);
-                      }
-
-                  cmd_element->strvec = NULL;
-                  vector_free (cmd_element_v);
-                }
+              if ((cmd_element = vector_slot (cmd_node_v, j)) != NULL)
+                cmd_terminate_element(cmd_element);
 
             vector_free (cmd_node_v);
           }
@@ -3634,9 +4197,9 @@
     }
 
   if (command_cr)
-    XFREE(MTYPE_STRVEC, command_cr);
-  if (desc_cr.str)
-    XFREE(MTYPE_STRVEC, desc_cr.str);
+    XFREE(MTYPE_CMD_TOKENS, command_cr);
+  if (token_cr.desc)
+    XFREE(MTYPE_CMD_TOKENS, token_cr.desc);
   if (host.name)
     XFREE (MTYPE_HOST, host.name);
   if (host.password)
diff --git a/lib/command.h b/lib/command.h
index 2d708d8..e47c425 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -138,18 +138,32 @@
   int (*func) (struct cmd_element *, struct vty *, int, const char *[]);
   const char *doc;			/* Documentation of this command. */
   int daemon;                   /* Daemon to which this command belong. */
-  vector strvec;		/* Pointing out each description vector. */
-  unsigned int cmdsize;		/* Command index count. */
-  char *config;			/* Configuration string */
-  vector subconfig;		/* Sub configuration string */
+  vector tokens;		/* Vector of cmd_tokens */
   u_char attr;			/* Command attributes */
 };
 
-/* Command description structure. */
-struct desc
+
+enum cmd_token_type
 {
+  TOKEN_TERMINAL = 0,
+  TOKEN_MULTIPLE,
+  TOKEN_KEYWORD,
+};
+
+/* Command description structure. */
+struct cmd_token
+{
+  enum cmd_token_type type;
+
+  /* Used for type == MULTIPLE */
+  vector multiple; /* vector of cmd_token, type == FINAL */
+
+  /* Used for type == KEYWORD */
+  vector keyword; /* vector of vector of cmd_tokens */
+
+  /* Used for type == TERMINAL */
   char *cmd;                    /* Command string. */
-  char *str;                    /* Command's description. */
+  char *desc;                    /* Command's description. */
 };
 
 /* Return value of the commands. */
@@ -192,7 +206,170 @@
      int argc __attribute__ ((unused)), \
      const char *argv[] __attribute__ ((unused)) )
 
-/* DEFUN for vty command interafce. Little bit hacky ;-). */
+/* DEFUN for vty command interafce. Little bit hacky ;-).
+ *
+ * DEFUN(funcname, cmdname, cmdstr, helpstr)
+ *
+ * funcname
+ * ========
+ *
+ * Name of the function that will be defined.
+ *
+ * cmdname
+ * =======
+ *
+ * Name of the struct that will be defined for the command.
+ *
+ * cmdstr
+ * ======
+ *
+ * The cmdstr defines the command syntax. It is used by the vty subsystem
+ * and vtysh to perform matching and completion in the cli. So you have to take
+ * care to construct it adhering to the following grammar. The names used
+ * for the production rules losely represent the names used in lib/command.c
+ *
+ * cmdstr = cmd_token , { " " , cmd_token } ;
+ *
+ * cmd_token = cmd_terminal
+ *           | cmd_multiple
+ *           | cmd_keyword ;
+ *
+ * cmd_terminal_fixed = fixed_string
+ *                    | variable
+ *                    | range
+ *                    | ipv4
+ *                    | ipv4_prefix
+ *                    | ipv6
+ *                    | ipv6_prefix ;
+ *
+ * cmd_terminal = cmd_terminal_fixed
+ *              | option
+ *              | vararg ;
+ *
+ * multiple_part = cmd_terminal_fixed ;
+ * cmd_multiple = "(" , multiple_part , ( "|" | { "|" , multiple_part } ) , ")" ;
+ *
+ * keyword_part = fixed_string , { " " , ( cmd_terminal_fixed | cmd_multiple ) } ;
+ * cmd_keyword = "{" , keyword_part , { "|" , keyword_part } , "}" ;
+ *
+ * lowercase = "a" | ... | "z" ;
+ * uppercase = "A" | ... | "Z" ;
+ * digit = "0" | ... | "9" ;
+ * number = digit , { digit } ;
+ *
+ * fixed_string = (lowercase | digit) , { lowercase | digit | uppercase | "-" | "_" } ;
+ * variable = uppercase , { uppercase | "_" } ;
+ * range = "<" , number , "-" , number , ">" ;
+ * ipv4 = "A.B.C.D" ;
+ * ipv4_prefix = "A.B.C.D/M" ;
+ * ipv6 = "X:X::X:X" ;
+ * ipv6_prefix = "X:X::X:X/M" ;
+ * option = "[" , variable , "]" ;
+ * vararg = "." , variable ;
+ *
+ * To put that all in a textual description: A cmdstr is a sequence of tokens,
+ * separated by spaces.
+ *
+ * Terminal Tokens:
+ *
+ * A very simple cmdstring would be something like: "show ip bgp". It consists
+ * of three Terminal Tokens, each containing a fixed string. When this command
+ * is called, no arguments will be passed down to the function implementing it,
+ * as it only consists of fixed strings.
+ *
+ * Apart from fixed strings, Terminal Tokens can also contain variables:
+ * An example would be "show ip bgp A.B.C.D". This command expects an IPv4
+ * as argument. As this is a variable, the IP address entered by the user will
+ * be passed down as an argument. Apart from two exceptions, the other options
+ * for Terminal Tokens behave exactly as we just discussed and only make a
+ * difference for the CLI. The two exceptions will be discussed in the next
+ * paragraphs.
+ *
+ * A Terminal Token can contain a so called option match. This is a simple
+ * string variable that the user may omit. An example would be:
+ * "show interface [IFNAME]". If the user calls this without an interface as
+ * argument, no arguments will be passed down to the function implementing
+ * this command. Otherwise, the interface name will be provided to the function
+ * as a regular argument.
+
+ * Also, a Terminal Token can contain a so called vararg. This is used e.g. in
+ * "show ip bgp regexp .LINE". The last token is a vararg match and will
+ * consume all the arguments the user inputs on the command line and append
+ * those to the list of arguments passed down to the function implementing this
+ * command. (Therefore, it doesn't make much sense to have any tokens after a
+ * vararg because the vararg will already consume all the words the user entered
+ * in the CLI)
+ *
+ * Multiple Tokens:
+ *
+ * The Multiple Token type can be used if there are multiple possibilities what
+ * arguments may be used for a command, but it should map to the same function
+ * nonetheless. An example would be "ip route A.B.C.D/M (reject|blackhole)"
+ * In that case both "reject" and "blackhole" would be acceptable as last
+ * arguments. The words matched by Multiple Tokens are always added to the
+ * argument list, even if they are matched by fixed strings. Such a Multiple
+ * Token can contain almost any type of token that would also be acceptable
+ * for a Terminal Token, the exception are optional variables and varag.
+ *
+ * There is one special case that is used in some places of Quagga that should be
+ * pointed out here shortly. An example would be "password (8|) WORD". This
+ * construct is used to have fixed strings communicated as arguments. (The "8"
+ * will be passed down as an argument in this case) It does not mean that
+ * the "8" is optional. Another historic and possibly surprising property of
+ * this construct is that it consumes two parts of helpstr. (Help
+ * strings will be explained later)
+ *
+ * Keyword Tokens:
+ *
+ * There are commands that take a lot of different and possibly optional arguments.
+ * An example from ospf would be the "default-information originate" command. This
+ * command takes a lot of optional arguments that may be provided in any order.
+ * To accomodate such commands, the Keyword Token has been implemented.
+ * Using the keyword token, the "default-information originate" command and all
+ * its possible options can be represented using this single cmdstr:
+ * "default-information originate \
+ *  {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}"
+ *
+ * Keywords always start with a fixed string and may be followed by arguments.
+ * Except optional variables and vararg, everything is permitted here.
+ *
+ * For the special case of a keyword without arguments, either NULL or the
+ * keyword itself will be pushed as an argument, depending on whether the
+ * keyword is present.
+ * For the other keywords, arguments will be only pushed for
+ * variables/Multiple Tokens. If the keyword is not present, the arguments that
+ * would have been pushed will be substituted by NULL.
+ *
+ * A few examples:
+ *   "default information originate metric-type 1 metric 1000"
+ * would yield the following arguments:
+ *   { NULL, "1000", "1", NULL }
+ *
+ *   "default information originate always route-map RMAP-DEFAULT"
+ * would yield the following arguments:
+ *   { "always", NULL, NULL, "RMAP-DEFAULT" }
+ *
+ * helpstr
+ * =======
+ *
+ * The helpstr is used to show a short explantion for the commands that
+ * are available when the user presses '?' on the CLI. It is the concatenation
+ * of the helpstrings for all the tokens that make up the command.
+ *
+ * There should be one helpstring for each token in the cmdstr except those
+ * containing other tokens, like Multiple or Keyword Tokens. For those, there
+ * will only be the helpstrings of the contained tokens.
+ *
+ * The individual helpstrings are expected to be in the same order as their
+ * respective Tokens appear in the cmdstr. They should each be terminated with
+ * a linefeed. The last helpstring should be terminated with a linefeed as well.
+ *
+ * Care should also be taken to avoid having similar tokens with different
+ * helpstrings. Imagine e.g. the commands "show ip ospf" and "show ip bgp".
+ * they both contain a helpstring for "show", but only one will be displayed
+ * when the user enters "sh?". If those two helpstrings differ, it is not
+ * defined which one will be shown and the behavior is therefore unpredictable.
+ */
 #define DEFUN(funcname, cmdname, cmdstr, helpstr) \
   DEFUN_CMD_FUNC_DECL(funcname) \
   DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
@@ -330,7 +507,6 @@
 extern void install_node (struct cmd_node *, int (*) (struct vty *));
 extern void install_default (enum node_type);
 extern void install_element (enum node_type, struct cmd_element *);
-extern void sort_node (void);
 
 /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated
    string with a space between each element (allocated using
@@ -346,7 +522,6 @@
 extern enum node_type node_parent (enum node_type);
 extern int cmd_execute_command (vector, struct vty *, struct cmd_element **, int);
 extern int cmd_execute_command_strict (vector, struct vty *, struct cmd_element **);
-extern void config_replace_string (struct cmd_element *, char *, ...);
 extern void cmd_init (int);
 extern void cmd_terminate (void);
 
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 50b6fa4..47a3438 100644
--- a/lib/memtypes.c
+++ b/lib/memtypes.c
@@ -54,7 +54,7 @@
   { MTYPE_ROUTE_MAP_RULE,	"Route map rule"		},
   { MTYPE_ROUTE_MAP_RULE_STR,	"Route map rule str"		},
   { MTYPE_ROUTE_MAP_COMPILED,	"Route map compiled"		},
-  { MTYPE_DESC,			"Command desc"			},
+  { MTYPE_CMD_TOKENS,		"Command desc"			},
   { MTYPE_KEY,			"Key"				},
   { MTYPE_KEYCHAIN,		"Key chain"			},
   { MTYPE_IF_RMAP,		"Interface route map"		},
diff --git a/lib/vty.c b/lib/vty.c
index 96cb1e4..9908b02 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -931,23 +931,23 @@
 
 static void
 vty_describe_fold (struct vty *vty, int cmd_width,
-		   unsigned int desc_width, struct desc *desc)
+		   unsigned int desc_width, struct cmd_token *token)
 {
   char *buf;
   const char *cmd, *p;
   int pos;
 
-  cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd;
+  cmd = token->cmd[0] == '.' ? token->cmd + 1 : token->cmd;
 
   if (desc_width <= 0)
     {
-      vty_out (vty, "  %-*s  %s%s", cmd_width, cmd, desc->str, VTY_NEWLINE);
+      vty_out (vty, "  %-*s  %s%s", cmd_width, cmd, token->desc, VTY_NEWLINE);
       return;
     }
 
-  buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1);
+  buf = XCALLOC (MTYPE_TMP, strlen (token->desc) + 1);
 
-  for (p = desc->str; strlen (p) > desc_width; p += pos + 1)
+  for (p = token->desc; strlen (p) > desc_width; p += pos + 1)
     {
       for (pos = desc_width; pos > 0; pos--)
       if (*(p + pos) == ' ')
@@ -976,7 +976,7 @@
   vector vline;
   vector describe;
   unsigned int i, width, desc_width;
-  struct desc *desc, *desc_cr = NULL;
+  struct cmd_token *token, *token_cr = NULL;
 
   vline = cmd_make_strvec (vty->buf);
 
@@ -1010,15 +1010,15 @@
   /* Get width of command string. */
   width = 0;
   for (i = 0; i < vector_active (describe); i++)
-    if ((desc = vector_slot (describe, i)) != NULL)
+    if ((token = vector_slot (describe, i)) != NULL)
       {
 	unsigned int len;
 
-	if (desc->cmd[0] == '\0')
+	if (token->cmd[0] == '\0')
 	  continue;
 
-	len = strlen (desc->cmd);
-	if (desc->cmd[0] == '.')
+	len = strlen (token->cmd);
+	if (token->cmd[0] == '.')
 	  len--;
 
 	if (width < len)
@@ -1030,27 +1030,27 @@
 
   /* Print out description. */
   for (i = 0; i < vector_active (describe); i++)
-    if ((desc = vector_slot (describe, i)) != NULL)
+    if ((token = vector_slot (describe, i)) != NULL)
       {
-	if (desc->cmd[0] == '\0')
+	if (token->cmd[0] == '\0')
 	  continue;
 	
-	if (strcmp (desc->cmd, command_cr) == 0)
+	if (strcmp (token->cmd, command_cr) == 0)
 	  {
-	    desc_cr = desc;
+	    token_cr = token;
 	    continue;
 	  }
 
-	if (!desc->str)
+	if (!token->desc)
 	  vty_out (vty, "  %-s%s",
-		   desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+		   token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
 		   VTY_NEWLINE);
-	else if (desc_width >= strlen (desc->str))
+	else if (desc_width >= strlen (token->desc))
 	  vty_out (vty, "  %-*s  %s%s", width,
-		   desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
-		   desc->str, VTY_NEWLINE);
+		   token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
+		   token->desc, VTY_NEWLINE);
 	else
-	  vty_describe_fold (vty, width, desc_width, desc);
+	  vty_describe_fold (vty, width, desc_width, token);
 
 #if 0
 	vty_out (vty, "  %-*s %s%s", width
@@ -1059,18 +1059,18 @@
 #endif /* 0 */
       }
 
-  if ((desc = desc_cr))
+  if ((token = token_cr))
     {
-      if (!desc->str)
+      if (!token->desc)
 	vty_out (vty, "  %-s%s",
-		 desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+		 token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
 		 VTY_NEWLINE);
-      else if (desc_width >= strlen (desc->str))
+      else if (desc_width >= strlen (token->desc))
 	vty_out (vty, "  %-*s  %s%s", width,
-		 desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
-		 desc->str, VTY_NEWLINE);
+		 token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
+		 token->desc, VTY_NEWLINE);
       else
-	vty_describe_fold (vty, width, desc_width, desc);
+	vty_describe_fold (vty, width, desc_width, token);
     }
 
 out:
diff --git a/ospf6d/ospf6_main.c b/ospf6d/ospf6_main.c
index e991971..4f6d9e5 100644
--- a/ospf6d/ospf6_main.c
+++ b/ospf6d/ospf6_main.c
@@ -325,9 +325,6 @@
   /* initialize ospf6 */
   ospf6_init ();
 
-  /* sort command vector */
-  sort_node ();
-
   /* parse config file */
   vty_read_config (config_file, config_default);
 
diff --git a/ospfd/ospf_main.c b/ospfd/ospf_main.c
index 6d58b4e..c68aa4d 100644
--- a/ospfd/ospf_main.c
+++ b/ospfd/ospf_main.c
@@ -310,8 +310,6 @@
   ospf_opaque_init ();
 #endif /* HAVE_OPAQUE_LSA */
   
-  sort_node ();
-
   /* Get configuration file. */
   vty_read_config (config_file, config_default);
 
diff --git a/ripd/rip_main.c b/ripd/rip_main.c
index 6a9fa71..a512fbc 100644
--- a/ripd/rip_main.c
+++ b/ripd/rip_main.c
@@ -287,9 +287,6 @@
   rip_zclient_init ();
   rip_peer_init ();
 
-  /* Sort all installed commands. */
-  sort_node ();
-
   /* Get configuration file. */
   vty_read_config (config_file, config_default);
 
diff --git a/ripngd/ripng_main.c b/ripngd/ripng_main.c
index 20225b7..7525a26 100644
--- a/ripngd/ripng_main.c
+++ b/ripngd/ripng_main.c
@@ -282,9 +282,6 @@
   zebra_init ();
   ripng_peer_init ();
 
-  /* Sort all installed commands. */
-  sort_node ();
-
   /* Get configuration file. */
   vty_read_config (config_file, config_default);
 
diff --git a/tests/main.c b/tests/main.c
index e0fbb4d..2d8cb0c 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -171,8 +171,6 @@
   /* OSPF vty inits. */
   test_vty_init ();
 
-  sort_node ();
-
   /* Change to the daemon program. */
   if (daemon_mode && daemon (0, 0) < 0)
     {
diff --git a/tests/test-commands.c b/tests/test-commands.c
index e2f40c6..18b3b50 100644
--- a/tests/test-commands.c
+++ b/tests/test-commands.c
@@ -233,8 +233,6 @@
             cmd->daemon = 0;
             cmd->func = test_callback;
           }
-  sort_node();
-
   test_load();
   vty_init_vtysh();
 }
@@ -340,8 +338,8 @@
           {
             for (j = 0; j < vector_active(descriptions); j++)
               {
-                struct desc *cmd = vector_slot(descriptions, j);
-                printf("  '%s' '%s'\n", cmd->cmd, cmd->str);
+                struct cmd_token *cmd = vector_slot(descriptions, j);
+                printf("  '%s' '%s'\n", cmd->cmd, cmd->desc);
               }
             vector_free(descriptions);
           }
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index c575902..34c3bd6 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -554,7 +554,7 @@
   vector vline;
   vector describe;
   int width;
-  struct desc *desc;
+  struct cmd_token *token;
 
   vline = cmd_make_strvec (rl_line_buffer);
 
@@ -592,15 +592,15 @@
   /* Get width of command string. */
   width = 0;
   for (i = 0; i < vector_active (describe); i++)
-    if ((desc = vector_slot (describe, i)) != NULL)
+    if ((token = vector_slot (describe, i)) != NULL)
       {
 	int len;
 
-	if (desc->cmd[0] == '\0')
+	if (token->cmd[0] == '\0')
 	  continue;
 
-	len = strlen (desc->cmd);
-	if (desc->cmd[0] == '.')
+	len = strlen (token->cmd);
+	if (token->cmd[0] == '.')
 	  len--;
 
 	if (width < len)
@@ -608,19 +608,19 @@
       }
 
   for (i = 0; i < vector_active (describe); i++)
-    if ((desc = vector_slot (describe, i)) != NULL)
+    if ((token = vector_slot (describe, i)) != NULL)
       {
-	if (desc->cmd[0] == '\0')
+	if (token->cmd[0] == '\0')
 	  continue;
 
-	if (! desc->str)
+	if (! token->desc)
 	  fprintf (stdout,"  %-s\n",
-		   desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd);
+		   token->cmd[0] == '.' ? token->cmd + 1 : token->cmd);
 	else
 	  fprintf (stdout,"  %-*s  %s\n",
 		   width,
-		   desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
-		   desc->str);
+		   token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
+		   token->desc);
       }
 
   cmd_free_strvec (vline);
diff --git a/vtysh/vtysh_main.c b/vtysh/vtysh_main.c
index 4a315a5..48958f0 100644
--- a/vtysh/vtysh_main.c
+++ b/vtysh/vtysh_main.c
@@ -299,8 +299,6 @@
 
   vty_init_vtysh ();
 
-  sort_node ();
-
   /* Read vtysh configuration file before connecting to daemons. */
   vtysh_read_config (config_default);
 
diff --git a/zebra/main.c b/zebra/main.c
index 742e029..523b191 100644
--- a/zebra/main.c
+++ b/zebra/main.c
@@ -343,9 +343,6 @@
   interface_list ();
   route_read ();
 
-  /* Sort VTY commands. */
-  sort_node ();
-
 #ifdef HAVE_SNMP
   zebra_snmp_init ();
 #endif /* HAVE_SNMP */
diff --git a/zebra/test_main.c b/zebra/test_main.c
index a951863..c695172 100644
--- a/zebra/test_main.c
+++ b/zebra/test_main.c
@@ -298,9 +298,6 @@
   route_read ();
   zebra_vty_init();
 
-  /* Sort VTY commands. */
-  sort_node ();
-
   /* Configuration file read*/
   vty_read_config (config_file, config_default);