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

Dependents:   A_CANAdapter USB2I2C

Files at this revision

API Documentation at this revision

Comitter:
WiredHome
Date:
Sun Oct 30 19:57:39 2011 +0000
Parent:
14:7971c8bd3f11
Child:
16:4ce4f55213ac
Commit message:
v1.05 Adapted for ANSI (VT100) cursor keys to access the history.

Changed in this revision

CommandProcessor.c Show annotated file Show diff for this revision Revisions of this file
CommandProcessor.h Show annotated file Show diff for this revision Revisions of this file
--- a/CommandProcessor.c	Sat Oct 01 20:01:44 2011 +0000
+++ b/CommandProcessor.c	Sun Oct 30 19:57:39 2011 +0000
@@ -8,8 +8,6 @@
 ///
 /// Even though it is a c interface, it is somewhat object oriented.
 ///
-/// @version 1.04
-///
 /// @note Copyright &copr; 2011 by Smartware Computing, all rights reserved.
 ///     Individuals may use this application for evaluation or non-commercial
 ///     purposes. Within this restriction, changes may be made to this application
@@ -53,14 +51,14 @@
 static CMDLINK_T * head = NULL;
 
 static char *buffer;        // buffer space must be allocated based on the longest command
-static char *historyBuffer;		// keeps the history of commands for recall
-static int historyCount = 0;	// and the count of
+static char *historyBuffer;        // keeps the history of commands for recall
+static int historyCount = 0;    // and the count of
 static int historyDepth = 0;
 static size_t longestCommand = 0;
 
 static struct {
-	CMD_T *SignOnBanner;
-	int showSignOnBanner;		// Shows the sign-on banner at startup
+    CMD_T *SignOnBanner;
+    int showSignOnBanner;        // Shows the sign-on banner at startup
     int caseinsensitive;    // FALSE=casesensitive, TRUE=insensitive
     int echo;               // TRUE=echo on, FALSE=echo off
     int bufferSize;         // size of the command buffer
@@ -72,14 +70,22 @@
 
 static INITRESULT_T CommandProcessor_Init(
     CMD_T *SignOnBanner,
-	CONFIG_T config,
+    CONFIG_T config,
     int maxCmdLen,
-	int historyCount,
+    int historyCount,
     int (*kbhit)(void),
     int (*getch)(void),
     int (*putch)(int ch),
     int (*puts)(const char * s)
 );
+
+// Used when processing characters
+static int keycount = 0;    // how full?
+static int leadinChar = 0;
+static int whereInHistory = 0;        // navigates history
+static int showPrompt = TRUE;
+
+
 static ADDRESULT_T CommandProcessor_Add(CMD_T *m);
 static RUNRESULT_T CommandProcessor_Run(void);
 static RUNRESULT_T CommandProcessor_End(void);
@@ -131,17 +137,17 @@
 /// @returns runok
 ///
 static RUNRESULT_T History(char *p) {
-	int whereInHistory = 0;
-	char buf[100];
+    int whereInHistory = 0;
+    char buf[100];
 
-	cfg.puts("");
-	for (whereInHistory = 0; whereInHistory < historyCount; whereInHistory++) {
-		sprintf(buf, "  %2i: %s", whereInHistory - historyCount, &historyBuffer[whereInHistory * cfg.bufferSize]);
-		cfg.puts(buf);
-	}
-	sprintf(buf, "  %2i: %s", 0, buffer);
-	cfg.puts(buf);
-	return runok;
+    cfg.puts("");
+    for (whereInHistory = 0; whereInHistory < historyCount; whereInHistory++) {
+        sprintf(buf, "  %2i: %s", whereInHistory - historyCount, &historyBuffer[whereInHistory * cfg.bufferSize]);
+        cfg.puts(buf);
+    }
+    sprintf(buf, "  %2i: %s", 0, buffer);
+    cfg.puts(buf);
+    return runok;
 }
 
 /// Turns command prompt echo on and off
@@ -204,11 +210,11 @@
                  "    * <esc> can be used to cancel a command.\r\n"
                  "    * <tab> can be used to complete the entry of a partial command.\r\n"
                  "");
-		cfg.puts("\r\n About this CommandProcessor:\r\n"
-				 "    This CommandProcessor provides an easy facility for creating an\r\n"
-				 "      interactive runtime 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");
+        cfg.puts("\r\n About this CommandProcessor:                               (v" VERSION ")\r\n"
+                 "    This CommandProcessor provides an easy facility for creating an\r\n"
+                 "      interactive runtime 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;
 }
@@ -235,7 +241,7 @@
     int compareLength;
     int foundCount = 0;
     CMDLINK_T *link = head;
-	char * alternateBuffer;
+    char * alternateBuffer;
 
     if (strlen(buffer)) {  // simple sanity check
         // Try to process the buffer. A command could be "Help", or it could be "Test1 123 abc"
@@ -270,17 +276,17 @@
                 // If they type "He 1234 5678", we backup and rewrite as "Help 1234 5678"
                 int diff = strlen((*menu)->command) - compareLength;    // e.g. 5 - 3
 
-				// or if they entered it in a case that doesn't match the command exactly
+                // or if they entered it in a case that doesn't match the command exactly
                 if (diff > 0 || 0 != strncmp(buffer, (*menu)->command, compareLength)) {
                     char *p = buffer;
-					alternateBuffer = (char *)malloc(cfg.bufferSize);
-					strcpy(alternateBuffer, (*menu)->command);
-					strcat(alternateBuffer, " ");
-					strcat(alternateBuffer, space);
-					EraseChars(strlen(buffer));
-					strcpy(buffer, alternateBuffer);
-					free(alternateBuffer);
-					EchoString(p);
+                    alternateBuffer = (char *)malloc(cfg.bufferSize);
+                    strcpy(alternateBuffer, (*menu)->command);
+                    strcat(alternateBuffer, " ");
+                    strcat(alternateBuffer, space);
+                    EraseChars(strlen(buffer));
+                    strcpy(buffer, alternateBuffer);
+                    free(alternateBuffer);
+                    EchoString(p);
                 }
             }
         }
@@ -291,7 +297,7 @@
 
 /// Init is the first function to call to configure the CommandProcessor.
 ///
-/// This function has a number of parameters, which make the CommandProcessor 
+/// This function has a number of parameters, which make the CommandProcessor
 /// quite flexible.
 ///
 /// @param SignOnBanner function, which is used as a signon banner
@@ -314,31 +320,30 @@
     CMD_T (*SignOnBanner),
     CONFIG_T config,
     int maxCmdLen,
-	int numInHistory,
+    int numInHistory,
     int (*kbhit)(void),
     int (*getch)(void),
     int (*putch)(int ch),
     int (*puts)(const char * s)
 ) {
-	if (SignOnBanner) {
-		CommandProcessor.Add(SignOnBanner);
-		cfg.SignOnBanner = SignOnBanner;
-		cfg.showSignOnBanner = 1;
-	}
+    if (SignOnBanner) {
+        CommandProcessor.Add(SignOnBanner);
+        cfg.SignOnBanner = SignOnBanner;
+        cfg.showSignOnBanner = 1;
+    }
     if (maxCmdLen < 6)
         maxCmdLen = 6;
-	buffer = (char *)malloc(maxCmdLen);			// users often error by one, so we'll be generous
-	historyDepth = numInHistory;
-	historyBuffer = (char *)malloc(historyDepth * maxCmdLen);
+    buffer = (char *)malloc(maxCmdLen);            // users often error by one, so we'll be generous
+    historyDepth = numInHistory;
+    historyBuffer = (char *)malloc(historyDepth * maxCmdLen);
     cfg.bufferSize = maxCmdLen;
     if (buffer && historyBuffer) {
-        if (config & CFG_ENABLE_SYSTEM)
-		{
+        if (config & CFG_ENABLE_SYSTEM) {
             CommandProcessor.Add(&QuestionMenu);
             CommandProcessor.Add(&HelpMenu);
-			CommandProcessor.Add(&HistoryMenu);
+            CommandProcessor.Add(&HistoryMenu);
             CommandProcessor.Add(&EchoMenu);
-		}
+        }
         if (config & CFG_ENABLE_TERMINATE)
             CommandProcessor.Add(&ExitMenu);
         //if (addDefaultMenu & 0x0002)
@@ -373,7 +378,7 @@
 
     if (strlen(menu->command) > longestCommand)
         longestCommand = strlen(menu->command);
-    
+
     // Allocate the storage for this menu item
     temp = (CMDLINK_T *)malloc(sizeof(CMDLINK_T));
     if (!temp)
@@ -391,20 +396,20 @@
         prev = ptr;
         ptr = ptr->next;
     }
-	if (prev == head) {
-		head = temp;
-		head->next = prev;
-	} else {
-		prev->next = temp;
-		prev = temp;
-		prev->next = ptr;
-	}
+    if (prev == head) {
+        head = temp;
+        head->next = prev;
+    } else {
+        prev->next = temp;
+        prev = temp;
+        prev->next = ptr;
+    }
     return addok;
 }
 
 static void EchoString(char *p) {
-	while (*p)
-		cfg.putch(*p++);
+    while (*p)
+        cfg.putch(*p++);
 }
 
 static void EraseChars(int keycount) {
@@ -416,6 +421,170 @@
 }
 
 
+static int ProcessComplexSequence(int c) {
+    switch (c) {
+        case 0x42:
+        case 0x50:    // down arrow - toward the newest (forward in time)
+            // if there is anything in the history, copy it out
+            if (historyCount && whereInHistory < historyCount) {
+                char *p;
+
+                EraseChars(keycount);
+                p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]);
+                EchoString(p);
+                keycount = strlen(buffer);
+                whereInHistory++;
+            }
+            c = 0;
+            break;
+        case 0x41:
+        case 0x48:    // up arrow - from newest to oldest (backward in time)
+            // same as escape
+            if (historyCount && --whereInHistory >= 0) {
+                char *p;
+
+                EraseChars(keycount);
+                p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]);
+                EchoString(p);
+                keycount = strlen(buffer);
+                c = 0;
+            } else {
+                whereInHistory = 0;
+                c = 0x1B;
+            }
+            break;
+        default:
+            // ignore this char
+            c = 0;
+            break;
+    }
+    leadinChar = 0;
+    return c;
+}
+
+
+static RUNRESULT_T ProcessStandardSequence(int c) {
+    int foundCount = 0;
+    CMD_T *cbk = NULL;
+    char * params = NULL;
+    RUNRESULT_T val = runok;
+
+    // Process Character
+    switch (c) {
+        case 0:
+            // null - do nothing
+            break;
+        case 0x5B:
+            // ANSI (VT100) sequence
+            // <ESC>[A is up
+            // <ESC>[B is down
+            // <ESC>[C is right
+            // <ESC>[D is left
+            leadinChar = 1;
+            break;
+        case 0xE0:
+            // Windows Command Shell (DOS box)
+            // Lead-in char
+            // 0xE0 0x48 is up arrow
+            // 0xE0 0x50 is down arrow
+            // 0xE0 0x4B is left arrow
+            // 0xE0 0x4D is right arrow
+            leadinChar = 1;
+            break;
+        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);
+                    EchoString(p);
+                    //cfg.printf("%s", p);
+                }
+            }
+            break;
+        case 0x1b:    // <ESC> to empty the command buffer
+            EraseChars(keycount);
+            keycount = 0;
+            buffer[keycount] = '\0';
+            break;
+        case '\x08':    // <bs>
+            if (keycount) {
+                buffer[--keycount] = '\0';
+                EraseChars(1);
+            } else
+                cfg.putch(0x07);    // bell
+            break;
+        case '\r':
+        case '\n':
+            if (strlen(buffer)) {
+                foundCount = CommandMatches(buffer, TRUE, &cbk, &params);
+                if (foundCount == 1) {
+                    val = (*cbk->callback)(params);        // Execute the command
+                    if (mystrnicmp(buffer, (const char *)&historyBuffer[(historyCount-1) * cfg.bufferSize], strlen(&historyBuffer[(historyCount-1) * cfg.bufferSize])) != 0) {
+                        // not repeating the last command, so enter into the history
+                        if (historyCount == historyDepth) {
+                            int i;
+                            historyCount--;
+                            for (i=0; i<historyCount; i++)
+                                strcpy(&historyBuffer[i * cfg.bufferSize], &historyBuffer[(i+1) * cfg.bufferSize]);
+                        }
+                        strcpy(&historyBuffer[historyCount * cfg.bufferSize], buffer);
+                        whereInHistory = historyCount;
+                        historyCount++;
+                    }
+                } 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:
+            // any other character is assumed to be part of the command
+            if (myisprint(c) && keycount < cfg.bufferSize) {
+                buffer[keycount++] = (char)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;
+}
+
+
+#if 0
+static void PutCharToHex(int c) {
+    int upper = c >> 4;
+    int lower = c & 0x0F;
+
+    cfg.putch('[');
+    if (upper >= 10)
+        cfg.putch(upper - 10 + 'A');
+    else
+        cfg.putch(upper + '0');
+    if (lower >= 10)
+        cfg.putch(lower - 10 + 'A');
+    else
+        cfg.putch(lower + '0');
+    cfg.putch(']');
+}
+#endif
+
 /// Run the CommandProcessor
 ///
 /// This will peek to see if there is a keystroke ready. It will pull that into a
@@ -431,146 +600,25 @@
 /// @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?
-	static int leadinChar = 0;
-	static int whereInHistory = 0;		// navigates history
-    int foundCount = 0;
     RUNRESULT_T val = runok;            // return true when happy, false to exit the prog
-    CMD_T *cbk = NULL;
-    char * params = NULL;
 
-	if (cfg.showSignOnBanner) {
-		cfg.SignOnBanner->callback("");
-		cfg.showSignOnBanner = 0;
-	}
+    if (cfg.showSignOnBanner) {
+        cfg.SignOnBanner->callback("");
+        cfg.showSignOnBanner = 0;
+    }
     if (showPrompt && cfg.echo) {
         cfg.putch('>');
         showPrompt = FALSE;
     }
     if (cfg.kbhit()) {
         int c = cfg.getch();
-
-		if (leadinChar) {
-        switch (c) {
-			case 0x50:	// down arrow - toward the newest (forward in time)
-				// if there is anything in the history, copy it out
-				if (historyCount && whereInHistory < historyCount) {
-					char *p;
-
-					EraseChars(keycount);
-					p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]);
-					EchoString(p);
-					keycount = strlen(buffer);
-					whereInHistory++;
-				}
-				c = 0;
-				break;
-			case 0x48:	// up arrow - from newest to oldest (backward in time)
-				// same as escape
-				if (historyCount && --whereInHistory >= 0) {
-					char *p;
-
-					EraseChars(keycount);
-					p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]);
-					EchoString(p);
-					keycount = strlen(buffer);
-					c = 0;
-				} else {
-					whereInHistory = 0;
-					c = 0x1B;
-				}
-				break;
-			default:
-				// ignore this char
-				c = 0;
-				break;
-			}
-			leadinChar = 0;
-		}
-		switch (c) {
-			case 0:
-				// null - do nothing
-				break;
-			case 0xE0:
-				// Lead-in char
-				// 0xE0 0x48 is up arrow
-				// 0xE0 0x50 is down arrow
-				// 0xE0 0x4B is left arrow
-				// 0xE0 0x4D is right arrow
-				leadinChar = 1;
-				break;
-            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);
-                        EchoString(p);
-						//cfg.printf("%s", p);
-                    }
-                }
-                break;
-            case 0x1b:    // <ESC> to empty the command buffer
-				EraseChars(keycount);
-                keycount = 0;
-                buffer[keycount] = '\0';
-                break;
-            case '\x08':    // <bs>
-                if (keycount) {
-                    buffer[--keycount] = '\0';
-                    EraseChars(1);
-                } else
-                    cfg.putch(0x07);    // bell
-                break;
-            case '\r':
-            case '\n': 
-                if (strlen(buffer)) {
-                    foundCount = CommandMatches(buffer, TRUE, &cbk, &params);
-                    if (foundCount == 1) {
-                        val = (*cbk->callback)(params);        // Execute the command
-						if (mystrnicmp(buffer, (const char *)&historyBuffer[(historyCount-1) * cfg.bufferSize], strlen(&historyBuffer[(historyCount-1) * cfg.bufferSize])) != 0) {
-							// not repeating the last command, so enter into the history
-							if (historyCount == historyDepth) {
-								int i;
-								historyCount--;
-								for (i=0; i<historyCount; i++)
-									strcpy(&historyBuffer[i * cfg.bufferSize], &historyBuffer[(i+1) * cfg.bufferSize]);
-							}
-							strcpy(&historyBuffer[historyCount * cfg.bufferSize], buffer);
-							whereInHistory = historyCount;
-							historyCount++;
-						}
-                    } 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++] = (char)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;
+        //PutCharToHex(c);      // a debug utility
+        if (leadinChar) {
+            // some previous character was a lead-in to a more complex sequence
+            // to be processed
+            c = ProcessComplexSequence(c);
         }
+        ProcessStandardSequence(c);
     }
     return val;
 }
--- a/CommandProcessor.h	Sat Oct 01 20:01:44 2011 +0000
+++ b/CommandProcessor.h	Sun Oct 30 19:57:39 2011 +0000
@@ -5,7 +5,7 @@
 /// The CommandProcessor is the interface to install a run-time menu into an embedded system.
 /// This contains the complete interface to the CommandProcessor.
 ///
-/// @version 1.04
+/// @version 1.05
 ///
 /// @note The CommandProcessor is text-based, because it is intended to interact with a
 ///       user.
@@ -56,10 +56,10 @@
 ///
 /// RUNRESULT_T SignOnBanner(char *p)
 /// {
-/// 	puts("\r\nThis great program was built " __DATE__ " " __TIME__ ".");
+///     puts("\r\nThis great program was built " __DATE__ " " __TIME__ ".");
 ///     if (*p == '?')
 ///        puts("\r\nMore details shown here.\r\n");
-/// 	return runok;
+///     return runok;
 /// }
 /// RUNRESULT_T Who(char *p)
 /// {
@@ -100,6 +100,8 @@
 ///
 /// @note
 /// History
+/// v1.05 20111030
+/// \li Added support for VT100 cursor code for up/down history access
 /// v1.04 1 October 2011
 /// \li Added configurable command line history for easy recall of previous commands
 /// \li Clean up dead-code
@@ -120,6 +122,8 @@
 #ifndef COMMANDPROCESSOR_H
 #define COMMANDPROCESSOR_H
 
+#define VERSION "1.05"
+
 #ifndef TRUE
 #define TRUE 1        ///< Definition for TRUE, if not already provided
 #define FALSE 0        ///< Definition for FALSE, if not already provided
@@ -218,15 +222,15 @@
     ///
     /// This function has a number of parameters, which make the CommandProcessor quite flexible.
     ///
-	/// @param SignOnBanner function, which is used as a signon banner
+    /// @param SignOnBanner function, which is used as a signon banner
     /// @param config enables various default menu items, based on the bit values, combine the following:
-	///   \li CFG_ENABLE_TERMINATE - enables the Exit command
-	///   \li CFG_ENABLE_SYSTEM    - enables system commands Echo, Help, etc.
-	///   \li CFG_ECHO_ON          - initialize with echo on
-	///   \li CFG_CASE_INSENSITIVE - Command Parser is case insensitive
-	/// @param maxCmdLen sizes the buffer, and is the maximum number of characters in a single
-	///        command, including all command arguments
-	/// @param historyLen sets the number of items that can be recalled from history
+    ///   \li CFG_ENABLE_TERMINATE - enables the Exit command
+    ///   \li CFG_ENABLE_SYSTEM    - enables system commands Echo, Help, etc.
+    ///   \li CFG_ECHO_ON          - initialize with echo on
+    ///   \li CFG_CASE_INSENSITIVE - Command Parser is case insensitive
+    /// @param maxCmdLen sizes the buffer, and is the maximum number of characters in a single
+    ///        command, including all command arguments
+    /// @param historyLen sets the number of items that can be recalled from history
     /// @param kbhit is a user provided function to detect if a character is available for the CommandProcessor,
     ///        and when using standard io, you can typically use kbhit, or _kbhit as your system provides.
     /// @param getch is a user provided function that provides a single character to the CommandProcessor
@@ -234,12 +238,12 @@
     /// @param puts is a user provided function that permits the CommandProcessor to output a string
     ///        to which is automatically appended a \\n
     /// @returns INITRESULT_T to indicate if the init was successful or failed
-	///
+    ///
     INITRESULT_T (*Init)(
-		CMD_T *SignOnBanner,
+        CMD_T *SignOnBanner,
         CONFIG_T config,
         int maxCmdLen,
-		int historyLen,
+        int historyLen,
         int (*kbhit)(void),
         int (*getch)(void),
         int (*putch)(int ch),
@@ -293,7 +297,7 @@
 /// The CommandProcessor is the interface to install a run-time menu into an embedded system.
 /// This contains the complete interface to the CommandProcessor.
 ///
-/// @version 1.0
+/// @version 1.05
 ///
 /// @note The CommandProcessor is text-based, because it is intended to interact with a
 ///       user.