The CommandProcessor is the interface to install a run-time menu into an embedded system.

Dependents:   A_CANAdapter USB2I2C

Revision:
7:0f058d664b21
Parent:
6:1a0512faa75d
Child:
8:25581f24f7f9
--- a/CommandProcessor.c	Sat Apr 02 17:12:39 2011 +0000
+++ b/CommandProcessor.c	Sun Apr 03 21:36:22 2011 +0000
@@ -1,12 +1,12 @@
-/// @file CommandProcessor.c is a simple interface to an interactive 
-///            command set of user defined commands.
+/// @file CommandProcessor.c is a simple interface to an interactive
+///         command set of user defined commands.
 ///
-///    With this, you can create functions that are exposed to a console
-///    interface. Each command may have 0 or more parameters.
-///    Typing the command, or at least the set of characters that make
-///    it unique from all other commands is enough to activate the command.
-///    
-///    Even though it is a c interface, it is somewhat object oriented.
+/// With this, you can create functions that are exposed to a console
+/// interface. Each command may have 0 or more parameters.
+/// Typing the command, or at least the set of characters that make
+/// it unique from all other commands is enough to activate the command.
+///
+/// Even though it is a c interface, it is somewhat object oriented.
 ///
 /// @version 1.0
 ///
@@ -26,9 +26,17 @@
 #include "CommandProcessor.h"
 
 /// This holds the single linked list of commands
+/// +-- Head->next
+/// v
+/// +-- p       +-- n
+/// v           v
+/// |menu|-------------------------------->|"Command"  |
+/// |next|->0   |menu|---->|"Help"         |"Help"     |
+///             |next|->0  |"..."          |*(callback)|
+///                        |*(callback)    |visible    |
+///                        |visible
 ///
-typedef struct CMDLINK_T
-{
+typedef struct CMDLINK_T {
     CMD_T * menu;                // handle to the menu item
     struct CMDLINK_T * next;    // handle to the next link
 } CMDLINK_T;
@@ -37,8 +45,7 @@
 
 static char *buffer;        // buffer space must be allocated based on the longest command
 
-static struct
-{
+static struct {
     int caseinsensitive;    // FALSE=casesensitive, TRUE=insensitive
     int echo;               // TRUE=echo on, FALSE=echo off
     int bufferSize;         // size of the buffer
@@ -52,19 +59,24 @@
     int defaultMenu,
     int caseinsensitive,
     int echo,
-    int maxCmdLen, 
+    int maxCmdLen,
     int (*kbhit)(void),
     int (*getch)(void),
     int (*putch)(int ch),
     int (*puts)(const char * s)
-    );
+);
 static ADDRESULT_T CommandProcessor_Add(CMD_T *m);
 static RUNRESULT_T CommandProcessor_Run(void);
 static RUNRESULT_T CommandProcessor_End(void);
 static RUNRESULT_T CommandProcessor_Echo(int echo);
 
-static CMDP_T CommandProcessor = 
-{
+// helper functions
+static int myisprint(int c);
+static void mystrcat(char *dst, char *src);
+static char mytolower(char a);
+int mystrnicmp(const char *l, const char *r, size_t n);
+
+static CMDP_T CommandProcessor = {
     CommandProcessor_Init,
     CommandProcessor_Add,
     CommandProcessor_Run,
@@ -85,27 +97,43 @@
 
 /// Gets a handle to the CommandProcessor
 ///
-///    This returns a handle to the CommandProcessor, which then permits
-///    access to the CommandProcessor functions.
+/// This returns a handle to the CommandProcessor, which then permits
+/// access to the CommandProcessor functions.
 ///
-///    @returns handle to the CommandProcessor
+/// @returns handle to the CommandProcessor
 ///
-CMDP_T * GetCommandProcessor(void)
-{
+CMDP_T * GetCommandProcessor(void) {
     return &CommandProcessor;
 }
 
 
-static RUNRESULT_T About(char *p)
-{
+/// Information about this command processor
+///
+/// @param p is a pointer to command line arguments, of which there is
+///        none for this function.
+/// @returns runok
+///
+static RUNRESULT_T About(char *p) {
     cfg.puts("\r\n About this CommandProcessor:\r\n"
-        "    This CommandProcessor provides easy facility for creating a\r\n"
-        "      runtime CommandProcessor in an embedded systems.\r\n"
-        "    author: David Smart, Smartware Computing\r\n"
-        );
+             "    This CommandProcessor provides an easy facility for creating a\r\n"
+             "      runtime interactive interpreter in an embedded system.\r\n"
+             "    Copyright (c) 2011 by Smartware Computing, all rights reserved.\r\n"
+             "    Author: David Smart, Smartware Computing\r\n"
+            );
     return runok;
 }
 
+/// Turns command prompt echo on and off
+///
+/// This command is used to turn the command prompt on and off. When
+/// running in an interactive mode, it is best to have this one.
+/// When driven by another program, off may be the best choice.
+///
+/// This command also displays the current state of the echo mode.
+///
+/// @param p is a pointer to a string "on" | "1" | "off" | "0"
+/// @returns runok
+///
 static RUNRESULT_T Echo(char *p) {
     if (*p) {
         if (*p == '1' || mystrnicmp(p, "on", 2) == 0)
@@ -120,28 +148,21 @@
     return runok;
 }
 
-static RUNRESULT_T Exit(char *p)
-{
+static RUNRESULT_T Exit(char *p) {
     (void)p;
-
     cfg.puts("\r\nbye.");
     return runexit;
 }
 
-static RUNRESULT_T Help(char *p)
-{
+static RUNRESULT_T Help(char *p) {
     CMDLINK_T *link = head;
     char buffer[100];
-
     cfg.puts("\r\n");
     sprintf(buffer, " %-10s: %s", "Command", "Description");
     cfg.puts(buffer);
-    while (link && link->menu)
-    {
-        if (link->menu->visible)
-        {
-            if (strlen(link->menu->command) + strlen(link->menu->helptext) + 5 < sizeof(buffer))
-            {
+    while (link && link->menu) {
+        if (link->menu->visible) {
+            if (strlen(link->menu->command) + strlen(link->menu->helptext) + 5 < sizeof(buffer)) {
                 sprintf(buffer, " %-10s: %s", link->menu->command, link->menu->helptext);
                 cfg.puts(buffer);
             }
@@ -149,34 +170,326 @@
         link = link->next;
     }
     cfg.puts("");
-    if (*p == '?')
-    {
+    if (*p == '?') {
         cfg.puts("\r\n Extended Help\r\n"
-            "    The general form for entering commands is:\r\n"
-            "      >command option1 option2 ...\r\n"
-            "      [note that note all commands support optional parameters]\r\n"
-            "    * Abbreviations of commands may be entered so long as there\r\n"
-            "      is exactly one match in the list of commands. For example,\r\n"
-            "      'he' is the same as 'help', if there is no other command \r\n"
-            "       that starts with 'he'.\r\n"
-            "    * <bs> can be used to erase previously entered information.\r\n"
-            "    * <esc> can be used to cancel a command.\r\n"
-            "    * <tab> can be used to complete the entry of a partial command.\r\n"
-            "");
+                 "    The general form for entering commands is:\r\n"
+                 "      >command option1 option2 ...\r\n"
+                 "      [note that note all commands support optional parameters]\r\n"
+                 "    * Abbreviations of commands may be entered so long as there\r\n"
+                 "      is exactly one match in the list of commands. For example,\r\n"
+                 "      'he' is the same as 'help', if there is no other command \r\n"
+                 "       that starts with 'he'.\r\n"
+                 "    * <bs> can be used to erase previously entered information.\r\n"
+                 "    * <esc> can be used to cancel a command.\r\n"
+                 "    * <tab> can be used to complete the entry of a partial command.\r\n"
+                 "");
     }
     return runok;
 }
 
+/// CommandMatches is the function that determines if the user is entering a valid
+/// command
+///
+/// This function gets called whenever the user types a printable character or when
+/// they press \<enter\>.
+/// The buffer of user input is evaluated to determine if the command is legitimate.
+/// It also determines if they want to execute the command, or simply evaluate it.
+/// Finally, it identifies writes to user provided pointers, the menu item that
+/// matches and a pointer to any parameters that the user entered.
+///
+/// @param buffer is the buffer that the user has entered commands into
+/// @param exec indicates the intention to execute the command, if found
+/// @param menu is a pointer to a pointer to a menu structure, which is updated based on the command
+/// @param params is a pointer to a pointer to the user entered parameters
+/// @returns the number of menu picks that match the user entered command
+///
+static int CommandMatches(char * buffer, int exec, CMD_T **menu, char **params) {
+    char *space;
+    int compareLength;
+    int foundCount = 0;
+    CMDLINK_T *link = head;
+
+    if (strlen(buffer)) {  // simple sanity check
+        // Try to process the buffer. A command could be "Help", or it could be "Test1 123 abc"
+        space = strchr(buffer, ' ');        // if the command has parameters, find the delimiter
+        if (space) {
+            compareLength = space - buffer;
+            space++;        // char after the space
+        } else {
+            compareLength = strlen(buffer);
+            space = buffer + compareLength;
+        }
+        while (link && link->menu) {
+            int cmpResult;
+
+            if (cfg.caseinsensitive) {
+                cmpResult = mystrnicmp(buffer, link->menu->command, compareLength);
+            } else
+                cmpResult = strncmp(buffer, link->menu->command, compareLength);
+            if (0 == cmpResult) {  // A match to what they typed
+                if (menu) {  // yes, we have a callback
+                    *menu = link->menu;    // accessor to the command they want to execute
+                    *params = space;
+                }
+                foundCount++;    // how many command match what they typed so far
+            }
+            link = link->next;    // follow the link to the next command
+        }
+        if (foundCount == 1) {
+            // If we found exactly one and they expressed an intent to execute that command
+            // then we'll rewrite the command to be fully qualified
+            if (exec) {  // command wants to execute it, not just validate the command syntax
+                // If they type "He 1234 5678", we backup and rewrite as "Help 1234 5678"
+                int diff = strlen((*menu)->command) - compareLength;    // e.g. 5 - 3
+
+                if (diff > 0) {
+                    int back = 0;
+                    char *p;
+                    if (strlen(space))
+                        back = strlen(space) + 1;
+
+                    while (back--)
+                        cfg.putch(0x08);
+                    p = (*menu)->command + compareLength;
+                    while (*p)
+                        cfg.putch(*p++);
+                    cfg.putch(' ');
+                    p = space;
+                    while (*p)
+                        cfg.putch(*p++);
+                    //cfg.printf("%s %s", link->menu->command + compareLength, space);
+                }
+            }
+        }
+    }
+    return foundCount;
+}
+
+/// Initialize the CommandProcessor
+///
+/// This initializes the CommandProcessor by adding the built-in menus
+///
+/// @param addDefaultMenu configures it to add the Help, About, and Exit menus
+/// @param bufSize configures the size of the longest command, which must be
+///         greater than 6 (the size of "About\0").
+/// @returns initok if it successfully initialized the CommandProcessor
+/// @returns initfailed if it could not allocate memory
+///
+INITRESULT_T CommandProcessor_Init(
+    int addDefaultMenu,
+    int caseinsensitive,
+    int echo,
+    int maxCmdLen,
+    int (*kbhit)(void),
+    int (*getch)(void),
+    int (*putch)(int ch),
+    int (*puts)(const char * s)
+) {
+    if (maxCmdLen < 6)
+        maxCmdLen = 6;
+    buffer = (char *)malloc(maxCmdLen+1);
+    cfg.bufferSize = maxCmdLen;
+    if (buffer) {
+        if (addDefaultMenu & 0x0008)
+            CommandProcessor.Add(&QuestionMenu);
+        if (addDefaultMenu & 0x0008)
+            CommandProcessor.Add(&HelpMenu);
+        if (addDefaultMenu & 0x0004)
+            CommandProcessor.Add(&EchoMenu);
+        if (addDefaultMenu & 0x0002)
+            CommandProcessor.Add(&AboutMenu);
+        if (addDefaultMenu & 0x0001)
+            CommandProcessor.Add(&ExitMenu);
+        cfg.caseinsensitive = caseinsensitive;
+        cfg.echo = echo;
+        cfg.kbhit = kbhit;
+        cfg.getch = getch;
+        cfg.putch = putch;
+        cfg.puts = puts;
+        return initok;
+    } else
+        return initfailed;
+}
+
+/// Add a command to the CommandProcessor
+///
+/// This adds a command to the CommandProcessor. A command has several components
+/// to it, including the command name, a brief description, the function to
+/// activate when the command is entered, and a flag indicating if the command
+/// should show up in the built-in help.
+///
+/// @param menu is the menu to add to the CommandProcessor
+/// @returns addok if the command was added
+/// @returns addfail if the command could not be added (failure to allocate memory for the linked list)
+///
+ADDRESULT_T CommandProcessor_Add(CMD_T * menu) {
+    CMDLINK_T *ptr;
+    CMDLINK_T *prev;
+    CMDLINK_T *temp;
+
+    // Allocate the storage for this menu item
+    temp = (CMDLINK_T *)malloc(sizeof(CMDLINK_T));
+    if (!temp)
+        return addfailed;			// something went really bad
+    temp->menu = menu;
+    temp->next = NULL;
+
+    prev = ptr = head;
+    if (!ptr) {
+        head = temp;			// This installs the very first item
+        return addok;
+    }
+    // Search alphabetically for the insertion point
+    while (ptr && mystrnicmp(ptr->menu->command, menu->command, strlen(menu->command)) < 0) {
+        prev = ptr;
+        ptr = ptr->next;
+    }
+    prev->next = temp;
+    prev = temp;
+    prev->next = ptr;
+    return addok;
+}
+
+/// Run the CommandProcessor
+///
+/// This will peek to see if there is a keystroke ready. It will pull that into a
+/// buffer if it is part of a valid command in the command set. You may then enter
+/// arguments to the command to be run.
+///
+/// Primitive editing is permitted with <bs>.
+///
+/// When you press <enter> it will evaluate the command and execute the command
+/// passing it the parameter string.
+///
+/// @returns runok if the command that was run allows continuation of the CommandProcessor
+/// @returns runfail if the command that was run is asking the CommandProcessor to exit
+///
+RUNRESULT_T CommandProcessor_Run(void) {
+    static int showPrompt = TRUE;
+    static int keycount = 0;    // how full?
+    RUNRESULT_T val = runok;            // return true when happy, false to exit the prog
+    CMD_T *cbk = NULL;
+    char * params = NULL;
+
+    if (showPrompt && cfg.echo) {
+        cfg.putch('>');
+        showPrompt = FALSE;
+    }
+    if (cfg.kbhit()) {
+        int c = cfg.getch();
+
+        switch (c) {
+            case 0x09:    // <TAB> to request command completion
+                if (1 == CommandMatches(buffer, FALSE, &cbk, &params)) {
+                    size_t n;
+                    char *p = strchr(buffer, ' ');
+                    if (p)
+                        n = p - buffer;
+                    else
+                        n = strlen(buffer);
+                    if (n < strlen(cbk->command)) {
+                        p = cbk->command + strlen(buffer);
+                        mystrcat(buffer, p);
+                        keycount = strlen(buffer);
+                        while (*p)
+                            cfg.putch(*p++);
+                        //cfg.printf("%s", p);
+                    }
+                }
+                break;
+            case 0x1b:    // <ESC> to empty the command buffer
+                while (keycount--) {
+                    cfg.putch(0x08);    // <bs>
+                    cfg.putch(' ');
+                    cfg.putch(0x08);
+                }
+                keycount = 0;
+                buffer[keycount] = '\0';
+                break;
+            case '\x08':    // <bs>
+                if (keycount) {
+                    buffer[--keycount] = '\0';
+                    cfg.putch(0x08);
+                    cfg.putch(' ');
+                    cfg.putch(0x08);
+                } else
+                    cfg.putch(0x07);    // bell
+                break;
+            case '\r':
+            case '\n': {
+                int foundCount = 0;
+
+                if (strlen(buffer)) {
+                    foundCount = CommandMatches(buffer, TRUE, &cbk, &params);
+                    if (foundCount == 1) {
+                        val = (*cbk->callback)(params);        // Execute the command
+                    } else if (foundCount > 1)
+                        cfg.puts(" *** non-unique command ignored      try 'Help' ***");
+                    else if (foundCount == 0)
+                        cfg.puts(" *** huh?                            try 'Help' ***");
+                } else
+                    cfg.puts("");
+                keycount = 0;
+                buffer[keycount] = '\0';
+                showPrompt = TRUE;        // forces the prompt
+            }
+            break;
+            default:
+                if (myisprint(c) && keycount < cfg.bufferSize) {
+                    buffer[keycount++] = c;
+                    buffer[keycount] = '\0';
+                    if (CommandMatches(buffer, FALSE, &cbk, &params))
+                        cfg.putch(c);
+                    else {
+                        buffer[--keycount] = '\0';
+                        cfg.putch(0x07);    // bell
+                    }
+                } else
+                    cfg.putch(0x07);    // bell
+                break;
+        }
+    }
+    return val;
+}
+
+
+static RUNRESULT_T CommandProcessor_Echo(int echo) {
+    cfg.echo = echo;
+    return runok;
+}
+
+/// End the CommandProcessor by freeing all the memory that was allocated
+///
+/// @returns runok
+///
+RUNRESULT_T CommandProcessor_End(void) {
+    CMDLINK_T *p = head;
+    CMDLINK_T *n;
+
+    do {
+        n = p->next;
+        free(p);            // free each of the allocated links to menu items.
+        p = n;
+    } while (n);
+    free(buffer);            // finally, free the command buffer
+    buffer = NULL;            // flag it as deallocated
+    return runok;
+}
+
+
+// Helper functions follow. These functions may exist in some environments and
+// not in other combinations of libraries and compilers, so private versions
+// are here to ensure consistent behavior.
+
 /// mytolower exists because not all compiler libraries have this function
 ///
 /// This takes a character and if it is upper-case, it converts it to
 /// lower-case and returns it.
 ///
-///    @param a is the character to convert
+/// @param a is the character to convert
 /// @returns the lower case equivalent to a
 ///
-char mytolower(char a)
-{
+static char mytolower(char a) {
     if (a >= 'A' && a <= 'Z')
         return (a - 'A' + 'a');
     else
@@ -188,19 +501,17 @@
 /// Some have strnicmp, others _strnicmp, and others have C++ methods, which
 /// is outside the scope of this C-portable set of functions.
 ///
-///    @param l is a pointer to the string on the left
+/// @param l is a pointer to the string on the left
 /// @param r is a pointer to the string on the right
 /// @param n is the number of characters to compare
 /// @returns -1 if l < r
 /// @returns 0 if l == r
 /// @returns +1 if l > r
 ///
-int mystrnicmp(const char *l, const char *r, size_t n)
-{
+int mystrnicmp(const char *l, const char *r, size_t n) {
     int result = 0;
 
-    if (n != 0)
-    {
+    if (n != 0) {
         do {
             result = mytolower(*l++) - mytolower(*r++);
         } while ((result == 0) && (*l != '\0') && (--n > 0));
@@ -214,21 +525,20 @@
 
 /// mystrcat exists because not all compiler libraries have this function
 ///
-/// This function concatinates one string onto another. It is generally 
+/// This function concatinates one string onto another. It is generally
 /// considered unsafe, because of the potential for buffer overflow.
 /// Some libraries offer a strcat_s as the safe version, and others may have
 /// _strcat. Because this is needed only internal to the CommandProcessor,
 /// this version was created.
 ///
-///    @param dst is a pointer to the destination string
+/// @param dst is a pointer to the destination string
 /// @param src is a pointer to the source string
 /// @returns nothing
 ///
-void mystrcat(char *dst, char *src)
-{
+static void mystrcat(char *dst, char *src) {
     while (*dst)
         dst++;
-    do 
+    do
         *dst++ = *src;
     while (*src++);
 }
@@ -242,337 +552,10 @@
 /// @returns TRUE if the character is printable
 /// @returns FALSE if the character is not printable
 ///
-static int myisprint(int c)
-{
+static int myisprint(int c) {
     if (c >= ' ' && c <= '~')
         return TRUE;
     else
         return FALSE;
 }
 
-
-/// CommandMatches is the function that determines if the user is entering a valid
-/// command
-///
-/// This function gets called whenever the user types a printable character or when
-/// they press \<enter\>.
-/// The buffer of user input is evaluated to determine if the command is legitimate.
-/// It also determines if they want to execute the command, or simply evaluate it.
-/// Finally, it identifies writes to user provided pointers, the menu item that 
-/// matches and a pointer to any parameters that the user entered.
-///
-///    @param buffer is the buffer that the user has entered commands into
-///    @param exec indicates the intention to execute the command, if found
-/// @param menu is a pointer to a pointer to a menu structure, which is updated based on the command
-/// @param params is a pointer to a pointer to the user entered parameters
-/// @returns the number of menu picks that match the user entered command
-///
-static int CommandMatches(char * buffer, int exec, CMD_T **menu, char ** params)
-{
-    char *space;
-    int compareLength;
-    int foundCount = 0;
-    CMDLINK_T *link = head;
-
-    if (strlen(buffer))    // simple sanity check
-    {
-        // Try to process the buffer. A command could be "Help", or it could be "Test1 123 abc"
-        space = strchr(buffer, ' ');        // if the command has parameters, find the delimiter
-        if (space)
-        {
-            compareLength = space - buffer;
-            space++;        // char after the space
-        }
-        else
-        {
-            compareLength = strlen(buffer);
-            space = buffer + compareLength;
-        }
-        while (link && link->menu)
-        {
-            int cmpResult;
-
-            if (cfg.caseinsensitive)
-            {
-                cmpResult = mystrnicmp(buffer, link->menu->command, compareLength);
-            }
-            else
-                cmpResult = strncmp(buffer, link->menu->command, compareLength);
-            if (0 == cmpResult)    // A match to what they typed
-            {
-                if (menu)    // yes, we have a callback
-                {
-                    if (exec)    // command wants to execute it, not just validate the command syntax
-                    {
-                        // If they type "He 1234 5678", we backup and rewrite as "Help 1234 5678"
-                        int diff = strlen(link->menu->command) - compareLength;    // e.g. 5 - 3
-                        
-                        if (diff > 0)
-                        {
-                            int back = 0;
-                            char *p;
-                            if (strlen(space))
-                                back = strlen(space) + 1;
-
-                            while (back--)
-                                cfg.putch(0x08);
-                            p = link->menu->command + compareLength;
-                            while (*p)
-                                cfg.putch(*p++);
-                            cfg.putch(' ');
-                            p = space;
-                            while (*p)
-                                cfg.putch(*p++);
-                            //cfg.printf("%s %s", link->menu->command + compareLength, space);
-                        }
-                    }
-                    *menu = link->menu;    // accessor to the command they want to execute
-                    *params = space;
-                }
-                foundCount++;    // how many command match what they typed so far
-            }
-            link = link->next;    // follow the link to the next command
-        }
-    }
-    return foundCount;
-}
-
-/// Initialize the CommandProcessor
-///
-///    This initializes the CommandProcessor by adding the built-in menus
-///
-///    @param addDefaultMenu configures it to add the Help, About, and Exit menus
-///    @param bufSize configures the size of the longest command, which must be 
-///            greater than 6 (the size of "About\0").
-///    @returns initok if it successfully initialized the CommandProcessor
-///    @returns initfailed if it could not allocate memory
-///
-INITRESULT_T CommandProcessor_Init(
-    int addDefaultMenu, 
-    int caseinsensitive,
-    int echo,
-    int maxCmdLen, 
-    int (*kbhit)(void),
-    int (*getch)(void),
-    int (*putch)(int ch),
-    int (*puts)(const char * s)
-    )
-{
-    if (maxCmdLen < 6)
-        maxCmdLen = 6;
-    buffer = (char *)malloc(maxCmdLen+1);
-    cfg.bufferSize = maxCmdLen;
-    if (buffer)
-    {
-        if (addDefaultMenu & 0x0008)
-            CommandProcessor.Add(&QuestionMenu);
-        if (addDefaultMenu & 0x0008)
-            CommandProcessor.Add(&HelpMenu);
-        if (addDefaultMenu & 0x0004)
-            CommandProcessor.Add(&EchoMenu);            
-        if (addDefaultMenu & 0x0002)
-            CommandProcessor.Add(&AboutMenu);
-        if (addDefaultMenu & 0x0001)
-            CommandProcessor.Add(&ExitMenu);
-        cfg.caseinsensitive = caseinsensitive;
-        cfg.echo = echo;
-        cfg.kbhit = kbhit;
-        cfg.getch = getch;
-        cfg.putch = putch;
-        cfg.puts = puts;
-        return initok;
-    }
-    else
-        return initfailed;
-}
-
-/// Add a command to the CommandProcessor
-///
-///    This adds a command to the CommandProcessor. A command has several components
-///    to it, including the command name, a brief description, the function to 
-///    activate when the command is entered, and a flag indicating if the command
-///    should show up in the built-in help.
-///
-///    @todo sort them when adding a menu item
-///
-///    @param m is the menu to add to the CommandProcessor
-///    @returns addok if the command was added
-///    @returns addfail if the command could not be added (failure to allocate memory for the linked list)
-///
-ADDRESULT_T CommandProcessor_Add(CMD_T * m)
-{
-    CMDLINK_T *p;
-
-    if (head == NULL)
-    {
-        head = (CMDLINK_T *)malloc(sizeof(CMDLINK_T));
-        if (!head)
-            return addfailed;
-        head->menu = NULL;
-        head->next = NULL;
-    }
-    p = head;
-    while (p->next)
-        p = p->next;
-    if (p->menu == NULL)
-    {
-        p->menu = m;
-        return addok;
-    }
-    else if (p->next == NULL)
-    {
-        p->next = (CMDLINK_T *)malloc(sizeof(CMDLINK_T));
-        if (!p->next)
-            return addfailed;
-        p = p->next;
-        p->menu = m;
-        p->next = NULL;
-        return addok;
-    }
-    return addfailed;
-}
-
-/// Run the CommandProcessor
-///
-///    This will peek to see if there is a keystroke ready. It will pull that into a
-///    buffer if it is part of a valid command in the command set. You may then enter
-/// arguments to the command to be run.
-///
-/// Primitive editing is permitted with <bs>.
-///    
-///    When you press <enter> it will evaluate the command and execute the command
-///    passing it the parameter string.
-///
-///    @returns runok if the command that was run allows continuation of the CommandProcessor
-///    @returns runfail if the command that was run is asking the CommandProcessor to exit
-///
-RUNRESULT_T CommandProcessor_Run(void)
-{
-    static int showPrompt = TRUE;
-    static int keycount = 0;    // how full?
-    RUNRESULT_T val = runok;            // return true when happy, false to exit the prog
-    CMD_T *cbk = NULL;
-    char * params = NULL;
-
-    if (showPrompt && cfg.echo)
-    {
-        cfg.putch('>');
-        showPrompt = FALSE;
-    }
-    if (cfg.kbhit())
-    {
-        int c = cfg.getch();
-
-        switch (c)
-        {
-        case 0x09:    // <TAB> to request command completion
-            if (1 == CommandMatches(buffer, FALSE, &cbk, &params))
-            {
-                size_t n;
-                char *p = strchr(buffer, ' ');
-                if (p)
-                    n = p - buffer;
-                else
-                    n = strlen(buffer);
-                if (n < strlen(cbk->command))
-                {
-                    p = cbk->command + strlen(buffer);
-                    mystrcat(buffer, p);
-                    keycount = strlen(buffer);
-                    while (*p)
-                        cfg.putch(*p++);
-                    //cfg.printf("%s", p);
-                }
-            }
-            break;
-        case 0x1b:    // <ESC> to empty the command buffer
-            while (keycount--)
-            {
-                cfg.putch(0x08);    // <bs>
-                cfg.putch(' ');
-                cfg.putch(0x08);
-            }
-            keycount = 0;
-            buffer[keycount] = '\0';
-            break;
-        case '\x08':    // <bs>
-            if (keycount)
-            {
-                buffer[--keycount] = '\0';
-                cfg.putch(0x08);
-                cfg.putch(' ');
-                cfg.putch(0x08);
-            }
-            else
-                cfg.putch(0x07);    // bell
-            break;
-        case '\r':
-        case '\n':
-            {
-                int foundCount = 0;
-
-                if (strlen(buffer))
-                {
-                    foundCount = CommandMatches(buffer, TRUE, &cbk, &params);
-                    if (foundCount == 1)
-                    {
-                        val = (*cbk->callback)(params);        // Execute the command
-                    }
-                    else if (foundCount > 1)
-                        cfg.puts(" *** non-unique command ignored      try 'Help' ***");
-                    else if (foundCount == 0)
-                        cfg.puts(" *** huh?                            try 'Help' ***");
-                }
-                else
-                    cfg.puts("");
-                keycount = 0;
-                buffer[keycount] = '\0';
-                showPrompt = TRUE;        // forces the prompt
-            }
-            break;
-        default:
-            if (myisprint(c) && keycount < cfg.bufferSize)
-            {
-                buffer[keycount++] = c;
-                buffer[keycount] = '\0';
-                if (CommandMatches(buffer, FALSE, &cbk, &params))
-                    cfg.putch(c);
-                else
-                {
-                    buffer[--keycount] = '\0';
-                    cfg.putch(0x07);    // bell
-                }
-            }
-            else
-                cfg.putch(0x07);    // bell
-            break;
-        }
-    }
-    return val;
-}
-
-
-static RUNRESULT_T CommandProcessor_Echo(int echo) {
-    cfg.echo = echo;
-    return runok;
-}
-
-/// End the CommandProcessor by freeing all the memory that was allocated
-///
-///    returns runok
-///
-RUNRESULT_T CommandProcessor_End(void)
-{
-    CMDLINK_T *p = head;
-    CMDLINK_T *n;
-
-    do
-    {
-        n = p->next;
-        free(p);            // free each of the allocated links to menu items.
-        p = n;
-    } while (n);
-    free(buffer);            // finally, free the command buffer
-    buffer = NULL;            // flag it as deallocated
-    return runok;
-}