MySQL Lists are EOL. Please join:

List:Commits« Previous MessageNext Message »
From:John David Duncan Date:June 11 2010 6:51pm
Subject:bzr commit into mysql-5.1-telco-7.0 branch (jdd:3538) Bug#53280
View as plain text  
#At file:///Users/jdd/Desktop/dev/70-completion/ based on revid:jorgen.austvik@stripped

 3538 John David Duncan	2010-06-11
      bug#53280
      Actual tab-completion for ndb_mgm.  
      This is context-sensitive completion, implemented by parsing EBNF help text into a syntax graph.
      It includes the debug (VM_TRACE) commands, and if you are already connected the first time you hit TAB, it also knows the node ids of the data nodes.
      Also includes some cleanup of readline handling in main.cpp.

    added:
      storage/ndb/src/mgmclient/BNFgrammar.cpp
      storage/ndb/src/mgmclient/BNFgrammar.hpp
      storage/ndb/src/mgmclient/test_grammar.cpp
    modified:
      storage/ndb/src/mgmclient/CMakeLists.txt
      storage/ndb/src/mgmclient/CommandInterpreter.cpp
      storage/ndb/src/mgmclient/Makefile.am
      storage/ndb/src/mgmclient/main.cpp
      storage/ndb/src/mgmclient/ndb_mgmclient.hpp
      storage/ndb/src/mgmsrv/Makefile.am
=== added file 'storage/ndb/src/mgmclient/BNFgrammar.cpp'
--- a/storage/ndb/src/mgmclient/BNFgrammar.cpp	1970-01-01 00:00:00 +0000
+++ b/storage/ndb/src/mgmclient/BNFgrammar.cpp	2010-06-11 18:45:41 +0000
@@ -0,0 +1,303 @@
+/*
+ Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ 
+ 
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+ 
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ 
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+*/
+
+#include "BNFgrammar.hpp"
+
+int print_gen = 1;
+
+/* ********** BNF::Node ************ */
+
+void BNF::Node::setTerminal(const char *s)
+{
+  terminal = true;
+  name = s;
+  len = strlen(s);
+}
+
+void BNF::Node::chain(BNF::Node *node) 
+{
+  BNF::Node *cur;
+  if(name)
+  {
+    for(cur = this ; cur->next ; cur = cur->next);  // follow to end of chain
+    cur->next = node;
+  }
+  else resolveTo(node);
+}
+
+/* "resolving" means: 
+    - for every nonterminal node,
+       - look it up in the symbol table
+       - take that subtree from the symbol table and splice it
+         into the tree in the place of the nonterminal
+*/
+const char * BNF::Node::resolve(BNF::Grammar &g) 
+{
+  for(BNF::Node *cur = this ; cur ; cur = cur->next) 
+  {                // walk the list at this level.
+    if(cur->child)    // depth-first recursion
+    {
+      const char *err = cur->child->resolve(g);
+      if(err) return err;
+    }
+    
+    while(cur->name && ! cur->terminal)   // resolve repeatedly, if needed.
+    {
+      BNF::Node *target;
+      if(g.m_symtab.search(cur->name, target)) 
+      {
+        g.m_resolved_symbols++;
+        cur->resolveTo(target);
+      }
+      else
+      {
+        BaseString * err = new BaseString;
+        err->assfmt("symbol \"%s\" not found.", cur->name);
+        return err->c_str(); 
+      }
+    }
+  } // for()
+  return 0;
+}
+
+/* resolveTo(): splice a subtree into the place of a nonterminal */
+void BNF::Node::resolveTo(BNF::Node *ref)  
+{ 
+  BNF::Node *n, *nsplice, *c, *csplice;
+  BNF::Node *prev = this;
+
+  nsplice = next;
+  csplice = child;
+
+  name = ref->name;
+  len = ref->len;
+  terminal = ref->terminal;
+  next = ref->next;
+  child = ref->child;
+  
+  for(n = this ; n ; prev = n, n = n->next) 
+  {
+    for(c = n ; c->child ; c = c->child);  // follow chain until child == 0
+    c->child = csplice;
+  } 
+  prev->next = nsplice;
+}
+
+void BNF::Node::print(int indent) 
+{
+  indent++;
+  BNF::Node *n = this; 
+  while (n) {
+    if(n->crumb == print_gen) /* already seen */
+    {
+      printf("[%s, ...], ", n->name);
+      break;
+    }
+    else 
+    {
+      n->crumb = print_gen;
+      printf("%s", n->name ? n->name : "[node]");
+      if(n->repeating || n->optional) 
+      {
+        printf("[%c%c]", n->repeating ? 'r':' ', n->optional ? 'o':' ');
+      }
+    }
+    n = n->next;
+    if(n) printf(",");
+  }
+  printf("\n");
+  for(n = this ; n ; n = n->next) {
+    if(n->child) {
+      for(int i = 0 ; i < indent ; i++) printf(" ");
+      printf("%s: ", n->name ? n->name : "[node]");
+      n->child->print(indent);
+    }
+  }
+  if(indent == 0) print_gen++; 
+}
+
+
+int BNF::Node::cmp(const char *str, size_t length) 
+{
+  if(length == 0) length = len;
+  /* special rule: a number matches "id" or any other number */
+  if(isdigit(*str) && (isdigit(*name) || !strcmp(name,"id")))
+  {
+    return 0;
+  }
+  return strncasecmp(str, name, length);
+}
+
+
+/* ********** BNF::Grammar ************ */
+bool BNF::Grammar::resolve()
+{
+  m_resolved_symbols = 0;
+  m_error = 0;
+  /* keep running resolve() until there are no more unresolved symbols */
+  while(m_resolved_symbols < m_symtab.entries()) 
+  {
+    unsigned int last_iteration = m_resolved_symbols;
+    m_error = root->resolve( *this);
+    if(m_error) return false;
+    if(! (m_resolved_symbols > last_iteration))
+    {  // we ran an iteration without resolving anything new: 
+      m_error = "grammar contains an unreachable symbol";
+      return false;
+    }
+  }
+  return true;
+}
+
+BNF::Node * BNF::Grammar::parse(const char *s1, const char *s2) 
+{
+  char *s = (char *) malloc(strlen(s1) + strlen(s2) + 1);
+  strcpy(s, s1);
+  strcat(s, s2);
+  return parse(&s, 255);
+}
+
+inline void BNF::Grammar::ADVANCE_CURSOR(bool &todo, BNF::Node *&cursor, bool opt)
+{
+  if(todo) 
+  {
+    cursor->child = new BNF::Node;
+    cursor = cursor->child;
+  }
+  cursor->optional = opt;
+  todo = true;
+}
+
+BNF::Node * BNF::Grammar::parse(char **s, char delim)
+{  
+  char *tok; 
+  BaseString tempname;
+  bool optional = false;
+  bool advance = false;  // implicit concatenation
+  BNF::Node *head = new BNF::Node;
+  BNF::Node *cur = head;
+  BNF::Node *temp = 0;
+  
+  m_error = 0;
+  tok = token(s, ' ');  
+  if(*tok == delim)  /* e.g. parse("}",'}')  */
+  {
+    return head; // ?? return 0 ??
+  }
+  
+  while(tok) { 
+    if(*tok) {
+      switch (*tok) {
+        case '|':     // alternation
+          cur->next = new BNF::Node;
+          cur = cur->next;
+          advance = false;
+          break;
+        case '[': 
+          optional = true;
+          break;
+        case ']':
+          optional = false;
+          break;
+        case '(':    // a grouping node
+          ADVANCE_CURSOR(advance, cur, optional);
+          tempname.assfmt("@(_%d_)", m_counter++);
+          cur->name = strdup(tempname.c_str());
+          m_symtab.insert(tempname, parse(s, ')'));
+          break;
+        case '{':  // a repeating group
+          ADVANCE_CURSOR(advance, cur, optional);
+          tempname.assfmt("@{_%d_}", m_counter++);
+          cur->name = strdup(tempname.c_str());
+          cur->repeating = true;
+          m_symtab.insert(tempname, parse(s, '}'));
+          break;        
+        case '<':     // unresolved nonterminal
+          ADVANCE_CURSOR(advance, cur, optional);
+          cur->name = token(& ++tok, '>');
+          break;
+        case '"':     // a quoted terminal
+          ADVANCE_CURSOR(advance, cur, optional);
+          cur->setTerminal(token(s, '"'));
+          break;
+        default:  // a literal terminal, space-delimited
+          ADVANCE_CURSOR(advance, cur, optional);
+          cur->setTerminal(tok);
+      }
+    }
+    tok = token(s, ' ');
+    if((!tok) || (*tok == delim)) 
+    {
+      return head;    
+    }
+  }
+  return head;
+}
+
+bool BNF::Grammar::addSymbol(const char *name, BNF::Node *node) 
+{
+  bool result = m_symtab.insert(name, node);
+  m_error = result ? 0 : "failed to add symbol.";
+  return result;
+}
+
+/** The API for token() is based on strsep().
+    The location of the next character after the delimiter character (or NULL, 
+    if the end of the string was reached) is stored in the pointer, *stringp.
+    
+    If the current token is an EBNF special character, then simply advance the
+    past any whitespace.  Otherwise, replace the next occurence of delim with 
+    0, and advance the pointer to the position following delim.  In this case,
+    the return value is a null-terminated literal (terminal or nonterminal). 
+*/
+char * BNF::Grammar::token(char **stringp, char delim) 
+{
+  char *tok = *stringp;    
+  if(tok == NULL) return NULL;
+  
+  switch(*tok)
+  {  
+    case '[':   case ']':
+    case '(':   case ')':
+    case '{':   case '}':
+    case '|':   case '"':
+      do {
+        (*stringp)++;
+      } while(isspace(**stringp));
+      break;
+    
+    default: 
+      while((**stringp != delim) && (**stringp != 0)) {
+        (*stringp)++;
+      }
+      /* two possibilities now: 0 or delim */
+      if(**stringp == 0) 
+      {
+        *stringp = NULL;  /* tokenizing is finished */
+      }
+      else  /* delim */ 
+      {
+        **stringp = 0;  /* end of this token */
+        (*stringp)++;   /* start of the next */
+      }
+  }
+  return tok;
+}
+
+
+template class HashMap<BaseString, BNF::Node *, BaseString_get_key>;

=== added file 'storage/ndb/src/mgmclient/BNFgrammar.hpp'
--- a/storage/ndb/src/mgmclient/BNFgrammar.hpp	1970-01-01 00:00:00 +0000
+++ b/storage/ndb/src/mgmclient/BNFgrammar.hpp	2010-06-11 18:45:41 +0000
@@ -0,0 +1,143 @@
+/*
+ Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ 
+ 
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+ 
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ 
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+*/
+
+#include "HashMap.hpp"
+#include "util/BaseString.hpp"
+
+/**** 
+   Simple EBNF Parser.
+
+   Special characters are:  
+     grouping: ( ) 
+     repeating group: { } 
+     optional item: [ ] 
+     alternatives: | 
+     nonterminal: <symbol>
+   Anything else is a terminal 
+   Concatenation is implicit.  (i.e. "START BACKUP", not "START , BACKUP")
+   
+   Terminal tokens must always be followed by a space character.
+   GOOD: "(A | B ){ C }"
+   BAD:  "(A|B) { C}"
+
+   There is no precedence hierarchy.  Do not try to parse anything like an 
+   ambiguous "A B | C", but always use explicit groupings, e.g. "A (B | C )".
+
+   This is a top-down parser good enough to take the grammar in the ndb_mgm HELP 
+   text and create a graph (with cycles) to use for context-sensitive command
+   completion with readline.
+  
+  Example:
+  
+  BNF::Grammar g;
+  g.root = g.parse("( A | B ) <nodeid>");         // parse a BNF right-hand-side
+  g.addSymbol("nodeid", g.parse("1 | 2 | 3")) ;   // define a nonterminal symbol 
+  g.resolve();                                    // resolve all nonterminals
+
+ BUGS:
+   Terminals must have a trailing space wherever they appear in the grammar.
+   The grammar must be LL(0) (I think).
+   Diagnostics are very poor.  
+   Many possible errors in the grammar are not checked for.  
+   Parsing could fail for many reasons without producing an error.  
+   Infinite recursion is a real possibility. 
+*****/
+
+
+namespace BNF {
+  class Node; 
+  class Grammar;
+
+  typedef HashMap<BaseString, Node *, BaseString_get_key> SymbolTable;
+  
+  class Node  {
+    friend class Grammar;
+
+    /* instance variables */
+    public:
+      const char *name;  /* terminal token or non-terminal name */
+      size_t len;        /* length of token, for terminal nodes */
+      Node *next;        /* next alternative in chain */
+      Node *child;       /* syntactic path from this alternative */
+      bool terminal;     /* is this token terminal or nonterminal? */
+      bool optional;     /* is this an [ optional ] element? */
+      bool repeating;    /* is this a { repeating } element? */
+
+    private:
+      int crumb;         /* used to note loops when traversing a graph */
+
+    /* methods */
+    public:
+      Node() : name(0), len(0), next(0), child(0) , 
+        repeating(false) , optional(false) , terminal(false) , crumb(0)     {};
+      void chain(BNF::Node *);
+      void print(int indent = 0);
+      int cmp(const char *str, size_t length = 0);
+
+    protected:
+      void setTerminal(const char *);
+      const char * resolve(Grammar &);
+      void resolveTo(Node *);
+  };
+
+
+  class Grammar {    
+    /* instance variables */
+    public:
+      Node *root;
+      const char *m_error;
+      SymbolTable m_symtab;
+      int m_resolved_symbols;      
+
+    private:
+      int m_counter;
+
+    /* methods */
+    public:
+      Grammar() : m_symtab() , m_error(0) , m_counter(0) {};
+
+      Grammar(const char *r) : m_symtab() , m_error(0) , m_counter(0) 
+      {
+        root = parse(r);
+      }
+
+      /** parse a BNF right-hand-side.  The string may be in two parts,
+          for convenience.
+          @arg s1 the BNF string
+          @arg s2 the (optional) second part of the string
+          @return a parse node
+      */
+      Node *parse(const char *s1, const char *s2 = "");
+      
+      /** Add the name of a nonterminal to the symbol table 
+      */
+      bool addSymbol(const char *s, Node *n);
+
+      /** After the grammar has been completely parsed and all nonterminals
+          defined, the resolve() method must be called to resolve the 
+          nonterminals from the symbol table into the parse tree.
+          @return true on success.
+      */
+      bool resolve();
+
+    private:
+      char * token(char **, char);
+      Node *parse(char **, char);
+      void ADVANCE_CURSOR(bool &, BNF::Node *&, bool);
+  };
+}; // namespace

=== modified file 'storage/ndb/src/mgmclient/CMakeLists.txt'
--- a/storage/ndb/src/mgmclient/CMakeLists.txt	2010-03-05 19:19:56 +0000
+++ b/storage/ndb/src/mgmclient/CMakeLists.txt	2010-06-11 18:45:41 +0000
@@ -1,5 +1,5 @@
-# Copyright (C) 2007 MySQL AB
-#
+# Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation; version 2 of the License.
@@ -18,8 +18,8 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/
                     ${CMAKE_SOURCE_DIR}/storage/ndb/src/common/mgmcommon)
 
 ADD_LIBRARY(ndbmgmclient STATIC
-            CommandInterpreter.cpp)
-
+            CommandInterpreter.cpp
+            BNFgrammar.cpp)
 
 ADD_EXECUTABLE(ndb_mgm
                main.cpp)

=== modified file 'storage/ndb/src/mgmclient/CommandInterpreter.cpp'
--- a/storage/ndb/src/mgmclient/CommandInterpreter.cpp	2010-03-04 12:32:16 +0000
+++ b/storage/ndb/src/mgmclient/CommandInterpreter.cpp	2010-06-11 18:45:41 +0000
@@ -1,6 +1,6 @@
 /*
-   Copyright (C) 2003 MySQL AB, 2010 Sun Microsystems, Inc.
-    All rights reserved. Use is subject to license terms.
+   Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ 
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -16,14 +16,21 @@
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */
 
-#include <ndb_global.h>
+#include <ndb_global.h> 
 
 #include <mgmapi.h>
 #include <ndbd_exit_codes.h>
 
-#include <util/BaseString.hpp>
+#include <util/BaseString.hpp> 
 #include <util/Vector.hpp>
 #include <kernel/BlockNumbers.h>
+#include "BNFgrammar.hpp"
+
+#ifdef VM_TRACE
+#define TRACE_CMD(x) x
+#else
+#define TRACE_CMD(x) ""
+#endif
 
 class MgmtSrvr;
 
@@ -56,7 +63,23 @@ public:
   bool execute(const char *line, int try_reconnect = -1,
                bool interactive = true, int *error = NULL);
 
+  /**
+   * @param line:   A buffer containing a partial command
+   * @param rlstate:  An int used for coordinating state with readline 
+   * @return  possible completions for the partial command in the buffer
+   */
+  char * next_completion(const char * line, int rlstate);
+
+  /** parser used internally to set the completion state
+   *  @param line the input so far
+   *  @param start index into line for start of completion (unused)
+   *  @param end index into line for end of completion
+   */
+  void completion_context(const char *line, int start, int end);
+  
 private:
+  
+  void build_completion_tree();
   void printError();
   bool execute_impl(const char *line, bool interactive);
 
@@ -103,7 +126,7 @@ private:
   int  executeConnect(char* parameters, bool interactive);
   int  executeShutdown(char* parameters);
   void executeClusterLog(char* parameters);
-
+  
 public:
   int  executeStop(int processId, const char* parameters, bool all);
   int  executeEnterSingleUser(char* parameters);
@@ -165,6 +188,9 @@ private:
   int m_verbose;
   int m_try_reconnect;
   int m_error;
+  BNF::Grammar *m_completion_syntax;
+  BNF::Node *m_completions;
+  size_t m_compl_len;
   struct NdbThread* m_event_thread;
   NdbMutex *m_print_mutex;
 };
@@ -191,6 +217,16 @@ bool Ndb_mgmclient::execute(const char *
   return m_cmd->execute(line, try_reconnect, interactive, error);
 }
 
+char * Ndb_mgmclient::next_completion(const char * text, int state)
+{
+  return m_cmd->next_completion(text, state);
+}
+
+void Ndb_mgmclient::completion_context(const char * text, int start, int end)
+{
+  m_cmd->completion_context(text, start, end);
+}
+
 /*
  * The CommandInterpreter
  */
@@ -501,9 +537,7 @@ static const char* helpTextClusterlog =
 "                   CONNECTION, ERROR, INFO, CONGESTION, DEBUG, or BACKUP. \n\n"
 "                   <level> is represented by one of the numbers \n"
 "                   from 1 to 15 inclusive, where 1 indicates 'most important' \n"
-"                   and 15 'least important'.\n\n"
-"                   <severity> can be any one of the following values:\n"
-"                   ALERT, CRITICAL, ERROR, WARNING, INFO, DEBUG.\n"
+"                   and 15 'least important'.\n"
 ;
 
 
@@ -586,39 +620,45 @@ static const char* helpTextDebug =
 #endif
 
 struct st_cmd_help {
+  const char *symbol;  // non-terminal symbol in the tab-completion grammar
   const char *cmd;
+  const char *syntax; // must have whitespace after literal tokens!
   const char * help;
-  void (* help_fn)();
+  void (* help_fn)();  
 }help_items[]={
-  {"SHOW", helpTextShow, NULL},
-  {"HELP", helpTextHelp, NULL},
-  {"BACKUP", helpTextBackup, NULL},
-  {"START BACKUP", helpTextStartBackup, NULL},
-  {"START BACKUP NOWAIT", helpTextStartBackup, NULL},
-  {"START BACKUP WAIT STARTED", helpTextStartBackup, NULL},
-  {"START BACKUP WAIT", helpTextStartBackup, NULL},
-  {"START BACKUP WAIT COMPLETED", helpTextStartBackup, NULL},
-  {"ABORT BACKUP", helpTextAbortBackup, NULL},
-  {"SHUTDOWN", helpTextShutdown, NULL},
-  {"CLUSTERLOG ON", helpTextClusterlogOn, NULL},
-  {"CLUSTERLOG OFF", helpTextClusterlogOff, NULL},
-  {"CLUSTERLOG TOGGLE", helpTextClusterlogToggle, NULL},
-  {"CLUSTERLOG INFO", helpTextClusterlogInfo, NULL},
-  {"START", helpTextStart, NULL},
-  {"RESTART", helpTextRestart, NULL},
-  {"STOP", helpTextStop, NULL},
-  {"ENTER SINGLE USER MODE", helpTextEnterSingleUserMode, NULL},
-  {"EXIT SINGLE USER MODE", helpTextExitSingleUserMode, NULL},
-  {"STATUS", helpTextStatus, NULL},
-  {"CLUSTERLOG", helpTextClusterlog, NULL},
-  {"PURGE STALE SESSIONS", helpTextPurgeStaleSessions, NULL},
-  {"CONNECT", helpTextConnect, NULL},
-  {"REPORT", helpTextReport, helpTextReportFn},
-  {"QUIT", helpTextQuit, NULL},
+  {NULL   , "SHOW", "", helpTextShow, NULL},
+  {"help" , "HELP", "", helpTextHelp, NULL},
+  {NULL   , "BACKUP", "", helpTextBackup, NULL},
+  {"stbak", "START BACKUP", " [ 1 ] [( SNAPSHOTSTART | SNAPSHOTEND )]"
+            " [( NOWAIT | WAIT ( STARTED | COMPLETED ))]", 
+            helpTextStartBackup, NULL},
+  {NULL   , "START BACKUP NOWAIT", "", helpTextStartBackup, NULL},
+  {NULL   , "START BACKUP WAIT STARTED", "", helpTextStartBackup, NULL},
+  {NULL   , "START BACKUP WAIT", "", helpTextStartBackup, NULL},
+  {NULL   , "START BACKUP WAIT COMPLETED", "", helpTextStartBackup, NULL},
+  {"abbak", "ABORT BACKUP", " id", helpTextAbortBackup, NULL},
+  {NULL   , "SHUTDOWN", "", helpTextShutdown, NULL},
+  {"cllog", "CLUSTERLOG", " (INFO | (( ON | OFF | TOGGLE ) { <severity> }))", 
+             NULL, NULL},  
+  {NULL   , "CLUSTERLOG ON", "", helpTextClusterlogOn, NULL},
+  {NULL   , "CLUSTERLOG OFF","", helpTextClusterlogOff, NULL},
+  {NULL   , "CLUSTERLOG TOGGLE", "", helpTextClusterlogToggle, NULL},
+  {NULL   , "CLUSTERLOG INFO", "", helpTextClusterlogInfo, NULL},
+  {"nstar", "START", "", helpTextStart, NULL},
+  {"nre",   "RESTART", " [ -n | -i | -a ]", helpTextRestart, NULL},
+  {"nstop", "STOP", "" , helpTextStop, NULL},
+  {"enter", "ENTER SINGLE USER MODE", "", helpTextEnterSingleUserMode, NULL},
+  {"exits", "EXIT SINGLE USER MODE", "", helpTextExitSingleUserMode, NULL},
+  {"nstat", "STATUS", "", helpTextStatus, NULL},
+  {"nlog" , "CLUSTERLOG", " { <category_level> }", helpTextClusterlog, NULL},
+  {"purge", "PURGE STALE SESSIONS", "", helpTextPurgeStaleSessions, NULL},
+  {NULL   , "CONNECT", "", helpTextConnect, NULL},
+  {"nrep" , "REPORT", " <reporttype>", helpTextReport, helpTextReportFn},
+  {NULL   , "QUIT", "", helpTextQuit, NULL},
 #ifdef VM_TRACE // DEBUG ONLY
-  {"DEBUG", helpTextDebug, NULL},
+  {NULL   , "DEBUG", "", helpTextDebug, NULL},
 #endif //VM_TRACE
-  {NULL, NULL, NULL}
+  {NULL, NULL, NULL, NULL, NULL }
 };
 
 static bool
@@ -652,7 +692,9 @@ CommandInterpreter::CommandInterpreter(c
   m_verbose(verbose),
   m_try_reconnect(0),
   m_error(-1),
-  m_event_thread(NULL)
+  m_event_thread(NULL),
+  m_completion_syntax(0),
+  m_completions(0)
 {
   m_print_mutex= NdbMutex_Create();
 }
@@ -3260,4 +3302,147 @@ err:
   return -1;
 }
 
+void CommandInterpreter::build_completion_tree() 
+{  
+  BNF::Grammar *g = new BNF::Grammar(
+      "<node_cmd> | SHOW | SHUTDOWN | QUIT | <help> | <stbak> | "
+      "<abbak> | <cllog> | <enter> | <exits> | <purge> | CONNECT"
+  );
+  g->addSymbol("node_cmd", g->parse("( <datanode> | ALL ) "
+      "<nstar> | <nre> | <nstop> | <nstat> | <nlog> | <nrep>", 
+      TRACE_CMD(" |(ERROR id )| <logblock> | TESTON | TESTOFF | SET | DUMP "
+                " | GETSTAT | <loglevel>")));
+  g->addSymbol("severity", 
+    g->parse("ALERT | CRITICAL | WARNING | INFO | DEBUG | ALL"));
+  g->addSymbol("help", g->parse("HELP [<helptopic>]"));
+  g->addSymbol("helptopic", 
+    g->parse("SHOW | HELP | BACKUP | (START [BACKUP ])|"
+             " (ABORT BACKUP )| SHUTDOWN | START | RESTART | STATUS | "
+             "( CLUSTERLOG ( ON | OFF | INFO | TOGGLE )) | "
+             "( ENTER SINGLE USER MODE ) | ( EXIT SINGLE USER MODE ) | "
+             "( PURGE STALE SESSIONS ) | CONNECT | REPORT | QUIT",
+             TRACE_CMD(" | DEBUG"))); 
+#ifdef VM_TRACE
+  g->addSymbol("logblock", g->parse("LOG BLOCK = <blocks>"));
+  g->addSymbol("loglevel", g->parse("LOGLEVEL { <category_level> }"));
+#endif
+
+  /*
+     Dynamically generated stuff: 
+  */
+  
+  /* Data node ids: autofill if connected */
+  if(m_connected)
+  {
+    struct ndb_mgm_cluster_state *cl = ndb_mgm_get_status(m_mgmsrv);
+    if(cl)
+    {
+      BaseString idstring;
+      int next_nodeid = 0;
+      while(get_next_nodeid(cl, &next_nodeid, NDB_MGM_NODE_TYPE_NDB))
+      {
+        if(idstring.length() > 0) idstring.append(" | ");
+        idstring.appfmt("%d", next_nodeid);
+      }
+      g->addSymbol("datanode", g->parse(idstring.c_str()));
+    }
+    else g->addSymbol("datanode", g->parse("id"));
+  }
+  else g->addSymbol("datanode", g->parse("id"));
+  
+  /* Log events */ 
+  BNF::Node * nd_events = new BNF::Node;
+  g->addSymbol("category_level", nd_events);
+  for(int i = CFG_MIN_LOGLEVEL; i <= CFG_MAX_LOGLEVEL; i++) {
+    const char *cat = ndb_mgm_get_event_category_string((ndb_mgm_event_category) i);
+    if(cat) nd_events->chain(g->parse(cat, "=n"));
+  }
+  
+  /* Report types */
+  BNF::Node *nd_reports = new BNF::Node; 
+  g->addSymbol("reporttype", nd_reports);
+  for(int i = 0; report_cmds[i].name ; i++)
+    nd_reports->chain(g->parse(report_cmds[i].name));
+
+#ifdef VM_TRACE
+  /* Block names */
+  BaseString blocklist;
+  for(int i = 0 ; i < NO_OF_BLOCK_NAMES ; i++) {
+    if(i > 0) blocklist.append(" | ");
+    blocklist.append(BlockNames[i].name);
+  }
+  g->addSymbol("blocks", g->parse(blocklist.c_str()));
+#endif 
+  
+  // the rest of the grammar comes from the HELP table */
+  for(int i = 0 ; help_items[i].cmd ; i++) {
+    st_cmd_help &item = help_items[i];
+    if(item.symbol) 
+    {
+      g->addSymbol(item.symbol, g->parse(item.cmd, item.syntax));
+    }
+  }
+  
+  require(g->resolve());
+  m_completion_syntax = g;
+}
+
+
+
+/* This is called at the start of a readline completion.  It receives the 
+   entire line (rl_line_buffer), whereas next_completion() will receive only 
+   a fragment of the line.   Parse the line as far as possible, and set 
+   m_completions, which next_completion() will use to provide context-sensitive
+   completions.
+*/
+void CommandInterpreter::completion_context(const char *line, int st, int end) 
+{
+  const char *s = line;
+  if(m_completion_syntax == 0) build_completion_tree();
+  m_completions = m_completion_syntax->root;
+      
+  /* Match command against the completion tree */
+  BNF::Node *item = m_completions;
+  while(*s && item) 
+  {
+    if(! item->cmp(s))
+    {
+      while(isalnum(*s)) s++;
+      if(s - line < end) 
+      {
+        m_completions = item->child;
+        item = item->child;
+      }
+      while(*s == ' ') s++; 
+    }
+    else item = item->next;
+  }
+}
+
+
+/* next_completion(): Provide possible completions back to readline.
+   Readline will eventually free() every string that is returned.
+   Return 0 to indicate no more possibilities.
+*/
+char * CommandInterpreter::next_completion(const char *line, int rlstate)
+{  
+  if(rlstate == 0) 
+  {
+    m_compl_len = strlen(line);
+  }
+
+  while(m_completions) {
+    if((m_compl_len == 0) || (! strncasecmp(m_completions->name, line, m_compl_len)))
+    {
+      char * cmd = strdup(m_completions->name);
+      m_completions = m_completions->next;
+      return cmd;
+    }
+    m_completions = m_completions->next;
+  }
+
+  return 0;
+}
+
+
 template class Vector<char const*>;

=== modified file 'storage/ndb/src/mgmclient/Makefile.am'
--- a/storage/ndb/src/mgmclient/Makefile.am	2009-05-27 15:21:45 +0000
+++ b/storage/ndb/src/mgmclient/Makefile.am	2010-06-11 18:45:41 +0000
@@ -1,6 +1,5 @@
-# Copyright (C) 2004 MySQL AB
-#  All rights reserved. Use is subject to license terms.
-#
+# Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation; version 2 of the License.
@@ -19,7 +18,7 @@ EXTRA_DIST = CMakeLists.txt
 noinst_LTLIBRARIES = libndbmgmclient.la
 ndbtools_PROGRAMS = ndb_mgm
 
-libndbmgmclient_la_SOURCES = CommandInterpreter.cpp
+libndbmgmclient_la_SOURCES = CommandInterpreter.cpp BNFGrammar.cpp
 libndbmgmclient_la_LIBADD = ../mgmapi/libmgmapi.la \
 			    ../common/logger/liblogger.la \
 			    ../common/portlib/libportlib.la \
@@ -47,5 +46,16 @@ LDADD_LOC = $(noinst_LTLIBRARIES) \
 
 ndb_mgm_LDFLAGS = @ndb_bin_am_ldflags@
 
+noinst_PROGRAMS = bnf-t
+
+bnf_t_SOURCES = test_grammar.cpp
+
+bnf_t_LDADD = \
+    libndbmgmclient.la \
+    ../common/util/libgeneral.la \
+    $(top_builddir)/strings/libmystringslt.la \
+    $(top_builddir)/mysys/libmysyslt.la
+
+
 # Don't update the files from bitkeeper
 %::SCCS/s.%

=== modified file 'storage/ndb/src/mgmclient/main.cpp'
--- a/storage/ndb/src/mgmclient/main.cpp	2010-05-04 14:34:54 +0000
+++ b/storage/ndb/src/mgmclient/main.cpp	2010-06-11 18:45:41 +0000
@@ -1,6 +1,5 @@
 /*
-   Copyright (C) 2003 MySQL AB
-    All rights reserved. Use is subject to license terms.
+   Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -18,30 +17,28 @@
 
 #include <ndb_global.h>
 #include <ndb_opts.h>
-
-// copied from mysql.cc to get readline
-extern "C" {
-#if defined( __WIN__)
-#include <conio.h>
-#elif !defined(__NETWARE__)
-#include <readline/readline.h>
-extern "C" int add_history(const char *command); /* From readline directory */
-extern "C" int read_history(const char *command);
-extern "C" int write_history(const char *command);
-#define HAVE_READLINE
-#endif
-}
-
 #include <NdbMain.h>
 #include <BaseString.hpp>
 #include <NdbOut.hpp>
 #include <mgmapi.h>
 #include <ndb_version.h>
-
 #include "ndb_mgmclient.hpp"
 
-const char *load_default_groups[]= { "mysql_cluster","ndb_mgm",0 };
+#ifdef __WIN__
+#define do_readline no_readline
+#define completion_matches(x, y) 0
+#define write_history(x) 1
+#define add_history
+#else
+#include <readline/readline.h>
+#define HAVE_READLINE
+#define do_readline readline
+#endif
 
+extern "C" char ** mgmclient_completion(const char *, int, int);
+extern "C" char * mgmcmd_generator(const char *text, int state);
+
+const char *load_default_groups[]= { "mysql_cluster","ndb_mgm",0 };
 
 static Ndb_mgmclient* com;
 
@@ -79,6 +76,22 @@ static void usage()
   ndb_usage(short_usage_sub, load_default_groups, my_long_options);
 }
 
+char *no_readline(const char *prompt)
+{
+  char * line_read = 0;
+  static char linebuffer[254];
+  fputs(prompt, stdout);
+  linebuffer[sizeof(linebuffer)-1]=0;
+  line_read = fgets(linebuffer, sizeof(linebuffer)-1, stdin);
+  if (line_read == linebuffer) {
+    char *q=linebuffer;
+    while (*q > 31) q++;
+    *q=0;
+    return strdup(linebuffer);
+  }
+  return "";
+}
+
 static bool
 read_and_execute(int try_reconnect)
 {
@@ -91,24 +104,11 @@ read_and_execute(int try_reconnect)
     free (line_read);
     line_read = (char *)NULL;
   }
-#ifdef HAVE_READLINE
   /* Get a line from the user. */
-  line_read = readline (prompt);    
+  line_read = do_readline (prompt); 
   /* If the line has any text in it, save it on the history. */
   if (line_read && *line_read)
     add_history (line_read);
-#else
-  static char linebuffer[254];
-  fputs(prompt, stdout);
-  linebuffer[sizeof(linebuffer)-1]=0;
-  line_read = fgets(linebuffer, sizeof(linebuffer)-1, stdin);
-  if (line_read == linebuffer) {
-    char *q=linebuffer;
-    while (*q > 31) q++;
-    *q=0;
-    line_read= strdup(linebuffer);
-  }
-#endif
   return com->execute(line_read, try_reconnect, 1);
 }
 
@@ -142,7 +142,6 @@ int main(int argc, char** argv){
   BaseString histfile;
   if (!opt_execute_str)
   {
-#ifdef HAVE_READLINE
     char *histfile_env= getenv("NDB_MGM_HISTFILE");
     if (histfile_env)
       histfile.assign(histfile_env,strlen(histfile_env));
@@ -151,15 +150,16 @@ int main(int argc, char** argv){
       histfile.assign(getenv("HOME"),strlen(getenv("HOME")));
       histfile.append("/.ndb_mgm_history");
     }
+#ifdef HAVE_READLINE
     if (histfile.length())
       read_history(histfile.c_str());
+    rl_attempted_completion_function = (CPPFunction *) mgmclient_completion;
 #endif
 
     ndbout << "-- NDB Cluster -- Management Client --" << endl;
     while(read_and_execute(opt_try_reconnect))
-      ;
+      ;                                                     // main loop
 
-#ifdef HAVE_READLINE
     if (histfile.length())
     {
       BaseString histfile_tmp;
@@ -168,7 +168,6 @@ int main(int argc, char** argv){
       if(!write_history(histfile_tmp.c_str()))
         my_rename(histfile_tmp.c_str(), histfile.c_str(), MYF(MY_WME));
     }
-#endif
   }
   else
   {
@@ -184,3 +183,20 @@ int main(int argc, char** argv){
   return ret;
 }
 
+
+#ifdef HAVE_READLINE
+char * mgmcmd_generator(const char *text, int state) 
+{
+  return com->next_completion(text, state);
+}
+
+char ** mgmclient_completion(const char *text, int start, int end)
+{
+  // "start" and "end" delimit "text" inside of "rl_line_buffer"
+  com->completion_context(rl_line_buffer, start, end);
+  char **c = completion_matches(text, mgmcmd_generator); 
+  if(c == NULL) 
+    rl_attempted_completion_over = 1; /* disable standard filename completion */
+  return c;
+}
+#endif

=== modified file 'storage/ndb/src/mgmclient/ndb_mgmclient.hpp'
--- a/storage/ndb/src/mgmclient/ndb_mgmclient.hpp	2010-03-04 12:20:12 +0000
+++ b/storage/ndb/src/mgmclient/ndb_mgmclient.hpp	2010-06-11 18:45:41 +0000
@@ -1,7 +1,6 @@
 /*
-   Copyright (C) 2003 MySQL AB
-    All rights reserved. Use is subject to license terms.
-
+   Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; version 2 of the License.
@@ -27,6 +26,8 @@ public:
   ~Ndb_mgmclient();
   bool execute(const char *line, int try_reconnect = -1,
                bool interactive = true, int *error = NULL);
+  char * next_completion(const char *text, int state);
+  void completion_context(const char *text, int start, int end);
 private:
   CommandInterpreter *m_cmd;
 };

=== added file 'storage/ndb/src/mgmclient/test_grammar.cpp'
--- a/storage/ndb/src/mgmclient/test_grammar.cpp	1970-01-01 00:00:00 +0000
+++ b/storage/ndb/src/mgmclient/test_grammar.cpp	2010-06-11 18:45:41 +0000
@@ -0,0 +1,352 @@
+
+/*
+ Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ 
+ 
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+ 
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ 
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include "BNFgrammar.hpp"
+#include "NdbTap.hpp"
+
+#define REQUIRE(x) if(!(x)) return fail(__LINE__)
+#define STRING(x,y) if(strcmp(x, y)) return fail(__LINE__)
+
+int verbose;
+
+int fail(int line)
+{
+  fprintf(stderr, "Failing at line %d \n", line);
+  if(verbose) exit(1);
+  return 0;
+}
+
+int printtree(BNF::Node *n) 
+{
+  if(verbose) n->print();
+}
+
+
+int testA()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar("( A | B ) C");
+  g->resolve();
+  printtree(g->root);
+  REQUIRE(g->root->child);
+  REQUIRE(g->root->next);
+  REQUIRE(g->root->next->child == g->root->child);
+  delete g;
+  return 1;
+}
+
+int testB()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar("<ab> C");
+  STRING(g->root->child->name, "C");
+  g->addSymbol("ab", g->parse("A | B"));
+  g->resolve();
+  printtree(g->root);
+  REQUIRE(g->root->child);
+  REQUIRE(g->root->next);
+  REQUIRE(g->root->next->child == g->root->child);
+  delete g;
+  return 1;
+}
+
+int testC()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar("A { B }");
+  g->resolve();
+  printtree(g->root);
+  REQUIRE(g->root->child);
+  REQUIRE(g->root->child->repeating);
+  delete g;
+  return 1;
+}
+
+
+int testD()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar("( A | B ) { C }");
+  g->resolve();
+  printtree(g->root);
+  REQUIRE(g->root->child);
+  REQUIRE(g->root->child->repeating);
+  REQUIRE(g->root->next->child->repeating);
+  delete g;
+  return 1;
+}
+
+int testE()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g("{ <nonterm> }");
+  g.addSymbol("nonterm", g.parse("1 | 2"));
+  g.resolve();
+  printtree(g.root);
+  return 1;
+}
+
+int testF()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g("<nt1> | <nt2>");
+  BNF::Node *nt1 = g.parse("START BACKUP [ <backupid> ] ");
+  g.addSymbol("nt1", nt1);
+  g.addSymbol("backupid", g.parse("1 | 2"));
+  g.addSymbol("nt2", g.parse("CLUSTERLOG ( ON | OFF )"));
+  REQUIRE(g.m_symtab.entries() == 4); // nt1,nt2,backupid,@(_0_)
+  REQUIRE(g.resolve());
+  STRING(g.root->name, "START");
+  STRING(g.root->child->name, "BACKUP");
+  REQUIRE(! (g.root->child->optional));
+  STRING(g.root->child->child->name, "1");
+  REQUIRE(g.root->child->child->optional);
+  printtree(g.root);
+  printtree(g.root);
+  return 1;
+}
+
+int testG()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar;
+  g->root = g->parse("(A |B ){ C }");
+  g->resolve();
+  printtree(g->root);
+  REQUIRE(g->root->child);
+  REQUIRE(g->root->child->repeating);
+  REQUIRE(g->root->next->child->repeating);
+  delete g;
+  return 1;
+}
+
+int testH()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar;
+  g->root = g->parse("(\"A\"|\"B\")C");  /* ("A"|"B")C */
+  g->resolve();
+  printtree(g->root);
+  REQUIRE(g->root->child);
+  REQUIRE(g->root->next);
+  REQUIRE(g->root->next->child == g->root->child);
+  STRING(g->root->name, "A");
+  STRING(g->root->next->name, "B");
+  STRING(g->root->child->name, "C");  
+  delete g;
+  return 1;
+}
+
+int testI()
+{ // precedence? 
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g;
+  g.root = g.parse("A B | C");
+  g.resolve();
+  g.root->print();
+  return 1;
+}
+
+int testJ()
+{ // precedence? 
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g;
+  g.root = g.parse("A | B C");
+  g.resolve();
+  g.root->print();
+  return 1;
+}
+
+int test1() 
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g;
+  g.root = g.parse("FRED | GEORGE");
+  printtree(g.root);
+  REQUIRE(g.root->terminal == true);
+  STRING(g.root->name, "FRED");
+  STRING(g.root->next->name, "GEORGE");
+  return 1;
+}
+
+int test2()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g;
+  g.root = g.parse("HI ( FRED | GEORGE )");
+  REQUIRE(g.m_symtab.entries() == 1);
+  g.resolve();
+  printtree(g.root);
+  REQUIRE(g.root->terminal ==  true);
+  STRING(g.root->child->name, "FRED");
+  STRING(g.root->child->next->name, "GEORGE");
+  return 1;
+}
+
+int test3()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g;
+  BNF::Node *n1, *n2;
+  
+  n1 = new BNF::Node;
+  n1->name = "Potter";
+  
+  REQUIRE(g.m_symtab.entries() == 0);
+  REQUIRE(g.m_symtab.insert("harry", n1));
+  REQUIRE(g.m_symtab.entries() == 1);
+  REQUIRE(g.m_symtab.insert("ron", n2));
+  REQUIRE(g.m_symtab.entries() == 2);
+    
+  BNF::Node *n3, *n4 = 0;
+  REQUIRE(g.m_symtab.search("harry", n3));
+  REQUIRE(g.m_symtab.search(strdup("harry"), n4));
+  STRING(n3->name, "Potter");
+  STRING(n4->name, "Potter");
+
+  g.root = g.parse("<harry> | <ron>");
+  g.resolve();  // ok if this fails
+  STRING(g.root->name, "Potter");
+  return 1;
+}
+
+int test4()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar g;
+  g.root = g.parse("HI ( FRED | <nodeid> )");
+  BNF::Node * nid = g.parse("1 | 2");
+  REQUIRE(!strcmp(nid->name, "1"));
+  REQUIRE(!strcmp(nid->next->name, "2"));
+  g.addSymbol("nodeid", nid);
+  REQUIRE(g.resolve());
+  printtree(g.root);
+  STRING(g.root->name, "HI");
+  STRING(g.root->child->name, "FRED");
+  STRING(g.root->child->next->name, "1");
+  STRING(g.root->child->next->next->name, "2");
+  return 1;
+}
+
+int test5()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar;
+  g->root = g->parse("<node_cmd> | SHOW | QUIT | [ { HI } ]");
+  REQUIRE(g->m_symtab.entries() == 1);
+  printtree(g->root);
+  STRING(g->root->name, "node_cmd");
+  BNF::Node *n1 = g->parse(" ALL | ", "1 | 2");
+  BNF::Node *n2 = g->parse("3 | 4 | 5");
+  REQUIRE(! strcmp(n2->name, "3"));
+  REQUIRE(n2->next);
+  REQUIRE(! n2->child);
+  STRING(n1->name, "ALL");
+  REQUIRE( n1->next);
+  REQUIRE(! n1->child);
+  REQUIRE(g->addSymbol("node_cmd", n1));
+  REQUIRE(g->resolve());
+
+  printtree(g->root);
+  n2 = g->root->next->next->next;
+  STRING(n2->name, "SHOW");
+  REQUIRE(n2->next->next->terminal);  
+  REQUIRE(n2->next->next->optional);
+  REQUIRE(n2->next->next->repeating);
+  delete g;
+  return 1;
+}
+
+int test6() 
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar;
+  g->root = g->parse("CLUSTERLOG ( INFO | ( ( ON | OFF | TOGGLE ) { <severity> } ) )");
+  REQUIRE(g->m_symtab.entries() == 4);
+  g->addSymbol("severity", g->parse("1 | 2 | 3 | 4 | 5"));
+  g->resolve();
+  printtree(g->root);
+  /* require that <severity> is a child of OFF */
+  REQUIRE(g->root->child->next->next);
+  REQUIRE(g->root->child->next->next->next);
+  REQUIRE(g->root->child->next->next->next->child);
+  REQUIRE(g->root->child->next->next->next->child->repeating);
+  delete g;
+  return 1;
+}
+
+int test7()
+{
+  if(verbose) printf("------------------------------------\n");
+  BNF::Grammar *g = new BNF::Grammar;
+  g->root = g->parse("<stbak> | <cllog> | <abbak> | SHUTDOWN");
+  g->addSymbol("severity", g->parse("1 | 2 | 3 | 4 | 5"));
+  g->addSymbol("backupid", g->parse("1 | 2 | 3"));
+  g->addSymbol("stbak", g->parse("START BACKUP", 
+               " [ <backupid> ] [ ( SNAPSHOTSTART | SNAPSHOTEND ) ] "
+              "[ ( NOWAIT | WAIT ( STARTED | COMPLETED ) ) ]"));
+  g->addSymbol("abbak", g->parse("ABORT BACKUP", " <backupid>"));
+  g->addSymbol("cllog", g->parse("CLUSTERLOG", 
+                                 " ( INFO | ( ( ON | OFF | TOGGLE ) { <severity> } ) )"));  
+  REQUIRE(g->resolve());
+  printtree(g->root);
+  delete g;
+  return 1;
+}
+
+
+int main(int argc, char *argv[])
+{
+  verbose = 0;
+  for (int i = argc - 1 ; i > 0 ; i--)
+  {
+    if(!strcmp(argv[i], "-d")) // print pid and wait for debugger
+    {
+      char s[8];
+      printf("Pid is: %d. Hit RETURN to start.\n", getpid());
+      fgets(s, 2, stdin);
+    }
+    else if(!strcmp(argv[i], "-v")) verbose = 1;
+  }
+
+  my_init();
+
+  plan(15);
+
+  ok(testA(), "( A | B ) C");
+  ok(testB(), "<ab> C");
+  ok(testC(), "A { B }");
+  ok(testD(), "( A | B ) { C }");
+  ok(testE(), "{ <nonterm> }");
+  ok(testF(), "<nt1> | <nt2>");
+  ok(testG(), "(A |B ){ C }");
+//  ok(testI(), "A B | C");
+//  ok(testJ(), "A | B C");
+  ok(test1(), "test1");
+  ok(test2(), "test2");
+  ok(test3(), "test3");
+  ok(test4(), "test4");
+  ok(test5(), "test5");
+  ok(test6(), "test6");
+  ok(test7(), "test7");
+  ok(testH(), "(\"A\"|\"B\")C");
+  todo_end();
+}

=== modified file 'storage/ndb/src/mgmsrv/Makefile.am'
--- a/storage/ndb/src/mgmsrv/Makefile.am	2009-11-08 12:52:27 +0000
+++ b/storage/ndb/src/mgmsrv/Makefile.am	2010-06-11 18:45:41 +0000
@@ -46,7 +46,7 @@ INCLUDES_LOC = -I$(top_srcdir)/storage/n
                -I$(top_srcdir)/storage/ndb/src/common/mgmcommon \
                -I$(top_srcdir)/storage/ndb/src/mgmclient
 
-LDADD_LOC = $(top_builddir)/storage/ndb/src/mgmclient/CommandInterpreter.lo \
+LDADD_LOC = $(top_builddir)/storage/ndb/src/mgmclient/libndbmgmclient.la \
             $(top_builddir)/storage/ndb/src/libndbclient.la \
             $(top_builddir)/dbug/libdbuglt.la \
             $(top_builddir)/mysys/libmysyslt.la \


Attachment: [text/bzr-bundle] bzr/jdd@mysql.com-20100611184541-va2cayarrfmontaf.bundle
Thread
bzr commit into mysql-5.1-telco-7.0 branch (jdd:3538) Bug#53280John David Duncan11 Jun