]> git.proxmox.com Git - mirror_frr.git/commitdiff
lib: Mostly complete matcher
authorQuentin Young <qlyoung@cumulusnetworks.com>
Thu, 21 Jul 2016 21:38:03 +0000 (21:38 +0000)
committerQuentin Young <qlyoung@cumulusnetworks.com>
Thu, 21 Jul 2016 21:38:03 +0000 (21:38 +0000)
Input matching and completions works. Still some
rough edges.

Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
lib/command_graph.c
lib/command_graph.h
lib/command_match.c
lib/command_match.h
lib/command_parse.y
lib/grammar_sandbox.c

index 9b91eaf2423144133b19df630a3a8153a07b919d..4e99884dc84f7208195b5ce3ea57ea5e0d24c40a 100644 (file)
@@ -78,11 +78,19 @@ new_node(enum graph_node_type type)
   return node;
 }
 
-void
-walk_graph(struct graph_node *start, int level)
+const char *
+describe_node(struct graph_node *node)
 {
+  const char *desc = NULL;
+  char num[21];
+
+  if (node == NULL) {
+    desc = "(null node)";
+    return desc;
+  }
+
   // print this node
-  switch (start->type) {
+  switch (node->type) {
     case WORD_GN:
     case IPV4_GN:
     case IPV4_PREFIX_GN:
@@ -90,44 +98,46 @@ walk_graph(struct graph_node *start, int level)
     case IPV6_PREFIX_GN:
     case VARIABLE_GN:
     case RANGE_GN:
-      fprintf(stderr, "%s", start->text);
+      desc = node->text;
       break;
     case NUMBER_GN:
-      fprintf(stderr, "%d", start->value);
+      sprintf(num, "%d", node->value);
       break;
     case SELECTOR_GN:
-      fprintf(stderr, "<>");
+      desc = "<>";
       break;
     case OPTION_GN:
-      fprintf(stderr, "[]");
+      desc = "[]";
       break;
     case NUL_GN:
-      fprintf(stderr, "NUL");
+      desc = "NUL";
       break;
     default:
-      fprintf(stderr, "ERROR");
+      desc = "ERROR";
   }
-  fprintf(stderr, "[%d] ", vector_active(start->children));
+  return desc;
+}
 
-  if (vector_active(start->children))
-    for (unsigned int i = 0; i < vector_active(start->children); i++)
-    {
-      struct graph_node *r = vector_slot(start->children, i);
-      if (!r) {
-        fprintf(stderr, "Child seems null?\n");
-        break;
-      }
-      else {
-        if (vector_active(start->children) > 1) {
-          fprintf(stderr, "\n");
-          for (int i = 0; i < level+1; i++)
-            fprintf(stderr, "    ");
-          walk_graph(r, level+1);
-        }
-        else
-          walk_graph(r, level);
+
+void
+walk_graph(struct graph_node *start, int level)
+{
+  // print this node
+  fprintf(stderr, "%s[%d] ", describe_node(start), vector_active(start->children));
+
+  if (vector_active(start->children)) {
+    if (vector_active(start->children) == 1)
+      walk_graph(vector_slot(start->children, 0), level);
+    else {
+      fprintf(stderr, "\n");
+      for (unsigned int i = 0; i < vector_active(start->children); i++) {
+        struct graph_node *r = vector_slot(start->children, i);
+        for (int j = 0; j < level+1; j++)
+          fprintf(stderr, "    ");
+        walk_graph(r, level+1);
       }
     }
-  if (level == 0)
+  }
+  else
     fprintf(stderr, "\n");
 }
index 8d23577d378dcfe359a03e66e3605869bad10c73..081ecaac4c55550c311d3906db9da34a1c18cd7c 100644 (file)
@@ -82,4 +82,11 @@ new_node(enum graph_node_type);
 extern void
 walk_graph(struct graph_node *, int);
 
+/**
+ * Returns a string representation of the given node.
+ * @param[in] the node to describe
+ * @return pointer to description string
+ */
+extern const char *
+describe_node(struct graph_node *);
 #endif
index bcc28bdc5b6111d275791643414c38219c6e563f..f7e2789253d132a3fd3dc37e68c0f84231607129 100644 (file)
 #include <zebra.h>
 #include "memory.h"
+#include "vector.h"
 #include "command_match.h"
 
-enum match_type
+static enum match_type
+match_token (struct graph_node *node, char *token, enum filter_type filter)
+{
+  switch (node->type) {
+    case WORD_GN:
+      return cmd_word_match (node, filter, token);
+    case IPV4_GN:
+      return cmd_ipv4_match (token);
+    case IPV4_PREFIX_GN:
+      return cmd_ipv4_prefix_match (token);
+    case IPV6_GN:
+      return cmd_ipv6_match (token);
+    case IPV6_PREFIX_GN:
+      return cmd_ipv6_prefix_match (token);
+    case RANGE_GN:
+      return cmd_range_match (node, token);
+    case NUMBER_GN:
+      return node->value == atoi(token);
+    case VARIABLE_GN:
+    default:
+      return no_match;
+  }
+}
+
+/* Breaking up string into each command piece. I assume given
+   character is separated by a space character. Return value is a
+   vector which includes char ** data element. */
+static vector
+cmd_make_strvec (const char *string)
+{
+  const char *cp, *start;
+  char *token;
+  int strlen;
+  vector strvec;
+
+  if (string == NULL)
+    return NULL;
+
+  cp = string;
+
+  /* Skip white spaces. */
+  while (isspace ((int) *cp) && *cp != '\0')
+    cp++;
+
+  /* Return if there is only white spaces */
+  if (*cp == '\0')
+    return NULL;
+
+  if (*cp == '!' || *cp == '#')
+    return NULL;
+
+  /* Prepare return vector. */
+  strvec = vector_init (VECTOR_MIN_SIZE);
+
+  /* Copy each command piece and set into vector. */
+  while (1)
+    {
+      start = cp;
+      while (!(isspace ((int) *cp) || *cp == '\r' || *cp == '\n') &&
+            *cp != '\0')
+         cp++;
+      strlen = cp - start;
+      token = XMALLOC (MTYPE_STRVEC, strlen + 1);
+      memcpy (token, start, strlen);
+      *(token + strlen) = '\0';
+      vector_set (strvec, token);
+
+      while ((isspace ((int) *cp) || *cp == '\n' || *cp == '\r') &&
+            *cp != '\0')
+         cp++;
+
+      if (*cp == '\0')
+        return strvec;
+    }
+}
+
+/**
+ * Adds all children that are reachable by one parser hop
+ * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
+ * nodes are treated as though their children are attached
+ * to their parent.
+ *
+ * @param[out] l the list to add the children to
+ * @param[in] node the node to get the children of
+ * @return the number of children added to the list
+ */
+static int
+add_nexthops(struct list *l, struct graph_node *node)
+{
+  int added = 0;
+  struct graph_node *child;
+  for (unsigned int i = 0; i < vector_active(node->children); i++)
+  {
+    child = vector_slot(node->children, i);
+    if (child->type == OPTION_GN || child->type == SELECTOR_GN || child->type == NUL_GN)
+      added += add_nexthops(l, child);
+    else {
+      listnode_add(l, child);
+      added++;
+    }
+  }
+  return added;
+}
+
+/**
+ * Compiles matches or next-hops for a given line of user input.
+ *
+ * Given a string of input and a start node for a matching DFA, runs the input
+ * against the DFA until the input is exhausted, there are no possible
+ * transitions, or both.
+ * If there are no further state transitions available, one of two scenarios is possible:
+ *  - The end of input has been reached. This indicates a valid command.
+ *  - The end of input has not yet been reached. The input does not match any command.
+ * If there are further transitions available, one of two scenarios is possible:
+ *  - The current input is a valid prefix to a longer command
+ *  - The current input matches a command
+ *  - The current input matches a command, and is also a valid prefix to a longer command
+ *
+ * Any other states indicate a programming error.
+ *
+ * @param[in] start the start node of the DFA to match against
+ * @param[in] filter the filtering method
+ * @param[in] input the input string
+ * @return an array with two lists. The first list is
+ */
+struct list**
 match_command (struct graph_node *start, enum filter_type filter, const char *input)
 {
-  // match input on DFA
-  return exact_match;
+  // break command
+  vector command = cmd_make_strvec (input);
+
+  // pointer to next input token to match
+  char *token;
+
+  struct list *current  = list_new(), // current nodes to match input token against
+              *matched  = list_new(), // current nodes that match the input token
+              *next     = list_new(); // possible next hops to current input token
+
+  // pointers used for iterating lists
+  struct graph_node *cnode;
+  struct listnode *node;
+
+  // add all children of start node to list
+  add_nexthops(next, start);
+
+  unsigned int idx;
+  for (idx = 0; idx < vector_active(command) && next->count > 0; idx++)
+  {
+    list_free (current);
+    current = next;
+    next = list_new();
+
+    token = vector_slot(command, idx);
+
+    list_delete_all_node(matched);
+
+    for (ALL_LIST_ELEMENTS_RO(current,node,cnode))
+    {
+      if (match_token(cnode, token, filter) == exact_match) {
+        listnode_add(matched, cnode);
+        add_nexthops(next, cnode);
+      }
+    }
+  }
+
+  /* Variable summary
+   * -----------------------------------------------------------------
+   * token    = last input token processed
+   * idx      = index in `command` of last token processed
+   * current  = set of all transitions from the previous input token
+   * matched  = set of all nodes reachable with current input
+   * next     = set of all nodes reachable from all nodes in `matched`
+   */
+
+  struct list **result = malloc( 2 * sizeof(struct list *) );
+  result[0] = matched;
+  result[1] = next;
+
+  return result;
 }
 
 
index cddeb08af7b0c407413067fd637cacff3cb16f45..4fc099594079248899cbd72230a9f229e53fc2c7 100644 (file)
@@ -2,6 +2,7 @@
 #define COMMAND_MATCH_H
 
 #include "command_graph.h"
+#include "linklist.h"
 
 /**
  * Filter types. These tell the parser whether to allow
@@ -27,11 +28,11 @@ enum matcher_rv
 };
 
 /* Completion match types. */
-enum match_type 
+enum match_type
 {
   no_match,
   partly_match,
-  exact_match 
+  exact_match
 };
 /**
  * Defines which matcher_rv values constitute
@@ -63,7 +64,7 @@ cmd_range_match (struct graph_node *, const char *str);
 enum match_type
 cmd_word_match (struct graph_node *, enum filter_type, const char *);
 
-enum match_type
+struct list**
 match_command (struct graph_node *, enum filter_type, const char *);
 
 #endif
index 80487af7cde4eb7684ce6677dbdb39972153e272..9c1b3fff24b89ffbb947982d20eed1f1c4c158c4 100644 (file)
@@ -19,9 +19,7 @@ extern void yyerror(const char *);
 %}
 %code provides {
 extern struct
-graph_node *cmd_parse_format(const char *,
-                             const char *,
-                             struct graph_node *);
+graph_node *cmd_parse_format(const char *, const char *, struct graph_node *);
 extern void
 set_buffer_string(const char *);
 }
index 1e6a76c69cec709680dbc16d9584351866832925..fab78f3e2119b3fe3c832ba951a2264f6dd50fc4 100644 (file)
@@ -2,29 +2,12 @@
 #include "command_graph.h"
 #include "command_parse.h"
 #include "command_match.h"
+#include "linklist.h"
 
 #define GRAMMAR_STR "CLI grammar sandbox\n"
 
 struct graph_node * nodegraph;
 
-/*
-char* combine_vararg(char* argv, int argc) {
-  size_t linesize = 0;
-  for (int i = 0; i < argc; i++)
-    linesize += strlen(argv[i]) + 1;
-
-  char* cat = malloc(linesize);
-  cat[0] = '\0';
-  for (int i = 0; i < argc; i++) {
-    strcat(cat, argv[i]);
-    if (i != argc)
-      strcat(cat, " ");
-  }
-
-  return cat;
-}
-*/
-
 DEFUN (grammar_test,
        grammar_test_cmd,
        "grammar parse .COMMAND",
@@ -57,7 +40,26 @@ DEFUN (grammar_test_match,
        "command to match")
 {
   const char* command = argv_concat(argv, argc, 0);
-  match_command(nodegraph, FILTER_STRICT, command);
+  struct list **result = match_command(nodegraph, FILTER_STRICT, command);
+  struct list *matched = result[0];
+  struct list *next    = result[1];
+
+  if (matched->count == 0) // the last token tried yielded no matches
+    fprintf(stderr, "%% Unknown command\n");
+  else
+  {
+    fprintf(stderr, "%% Matched full input, possible completions:\n");
+    struct listnode *node;
+    struct graph_node *cnode;
+    // iterate through currently matched nodes to see if any are leaves
+    for (ALL_LIST_ELEMENTS_RO(matched,node,cnode))
+      if (cnode->is_leaf)
+        fprintf(stderr, "<cr>\n");
+    // print possible next hops, if any
+    for (ALL_LIST_ELEMENTS_RO(next,node,cnode))
+      fprintf(stderr, "%s\n",describe_node(cnode));
+  }
+
   return CMD_SUCCESS;
 }