A Command Interpreter with support for used defined commands, subsystems, macros, help and parameter parsing.
Diff: cmdb.cpp
- Revision:
- 0:4d95ee0b4c37
- Child:
- 3:abbf43fab7d5
diff -r 000000000000 -r 4d95ee0b4c37 cmdb.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmdb.cpp Thu Feb 10 18:30:04 2011 +0000 @@ -0,0 +1,883 @@ +#include <vector> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <ctype.h> +#include <string.h> + +#include "cmdb.h" +#include "mbed.h" + +//DONE Pass Serial into constructor for printf, putc and getc. +//DONE CID_<subsystem> must be handled internally. +// +//TODO ADD Documentation. +//TODO CID_HELP must be handled internally (like all system commands (IDLE/MACRO etc). +//TODO CID_HELP should be function (so we can call it easier in the switche's else branch). +//TODO Link CID_LAST to Vector Size. + +//Constructor (see http://www.daniweb.com/forums/thread293338.html) +Cmdb::Cmdb(const Serial serial, const std::vector<cmdb_cmd>& cmds) : _serial(serial), _cmds(cmds) { + echo = true; + bold = true; + subsystem = -1; + + CID_LAST = _cmds.back().id; + CMD_TBL_LEN = cmds.size(); + + cmdb_init(true); +} + +//Public +bool Cmdb::cmdb_macro_hasnext() { + return macro_ptr!=-1 && macro_ptr<MAX_CMD_LEN && macro_buf[macro_ptr]; +} + +char Cmdb::cmdb_macro_next() { + char ch = macro_buf[macro_ptr++]; + if (macro_ptr==MAX_CMD_LEN) { + cmdb_macro_reset(); + } + return ch; +} + +char Cmdb::cmdb_macro_peek() { + return macro_buf[macro_ptr]; +} + +void Cmdb::cmdb_macro_reset() { + macro_ptr = -1; + macro_buf[0] = '\0'; +} + +bool Cmdb::cmdb_hasnext() { + return _serial.readable()==1; +} + +char Cmdb::cmdb_next() { + return _serial.getc(); +} + +//Private Utilities #1 + +int Cmdb::cmdb_escid_search(char *escstr) { + for (int i=0; i<ESC_TBL_LEN; i++) { + if (strcmp (esc_tbl[i].escstr, escstr) == 0) + return (esc_tbl[i].id); + } + + return (EID_LAST); +} + +int Cmdb::cmdb_cmdid_search(char *cmdstr) { + //Warning, we return the ID but somewhere assume it's equal to the array index! + for (int i=0; i<CMD_TBL_LEN; i++) { + if ((stricmp (_cmds[i].cmdstr, cmdstr) == 0) && ((_cmds[i].subs == subsystem) || (_cmds[i].subs<0))) + return (_cmds[i].id); + } + + return (CID_LAST); +} + +int Cmdb::cmdb_cmdid_index(int cmdid) { + for (int i=0; i<CMD_TBL_LEN; i++) { + if (_cmds[i].id==cmdid) + return i; + } + + return -1; +} + +int Cmdb::cmdb_parse(char *cmd) { + //Command + char cmdstr_buf [1 + MAX_CMD_LEN]; + + //Parameters + char argstr_buf [1 + MAX_CMD_LEN]; + char *argsep; + + char prmstr_buf [1 + MAX_CMD_LEN]; //copy of sscanf pattern + char *tok; //current token + void *toks[MAX_ARGS]; //pointers to string tokens IN commandline (argstr_buf) + char *prms[MAX_ARGS]; //patterns IN copy of sscanf string (*parms) + + char typ = '\0'; //Var type + char mod = '\0'; //Var modifier (for cardinal types) + unsigned int base; //Var number base (8,10,16) + //unsigned int bytes; //Var size in bytes (used for malloc) + + float f; //Temp var for conversion, 4 bytes + //unsigned char b; //Temp var for conversion, 1 byte + //char c; //Temp var for conversion, 1 byte + //short h; //Temp var for conversion, 2 bytes + //int k; //Temp var for conversion, 2 bytes + long l; //Temp var for conversion, 4 bytes + + char* endptr; //strtoXX() Error detection + + signed char id; + unsigned int i; + + //Init (global) variables. + argfnd=0; + argcnt=0; + error =0; + + //Signals empty string... + id=-1; + + //Zero the two string buffers for splitting cmd string into. + zeromemory((char*)cmdstr_buf,sizeof(cmdstr_buf)); + zeromemory(argstr_buf,sizeof(argstr_buf)); + + //Make it worse in Lint + for (i=0;i<MAX_ARGS;i++) { + parms[i].type=PARM_UNUSED; + zeromemory((char*)&(parms[i].val),sizeof(parms[i].val)); + } + + /*------------------------------------------------ + First, copy the command and convert it to all + uppercase. + ------------------------------------------------*/ + + strncpy(cmdstr_buf, cmd, sizeof (cmdstr_buf) - 1); + cmdstr_buf [sizeof (cmdstr_buf) - 1] = '\0'; + + /*------------------------------------------------ + Next, find the end of the first thing in the + buffer. Since the command ends with a space, + we'll look for that. NULL-Terminate the command + and keep a pointer to the arguments. + ------------------------------------------------*/ + + argsep = strchr(cmdstr_buf, ' '); + + if (argsep == NULL) { + argstr_buf [0] = '\0'; + } else { + strcpy (argstr_buf, argsep + 1); + *argsep = '\0'; + } + + /*------------------------------------------------ + Search for a command ID, then switch on it. Note + that I removed my action items for each command, + but you would invoke each function here. + VEG:Watch out ID not neccesarily equal to Array Index! + ------------------------------------------------*/ + + //1) Find the Command Id + id = cmdb_cmdid_search(cmdstr_buf); + + if (id!=CID_LAST) { + //2) Tokenize a copy of the parms from the cmd_tbl. + + //Get Format patterns from cmd_tbl[id].parms. + //Note: strtok inserts \0 into the original string. Hence the copy. + zeromemory((char *)(&prmstr_buf),sizeof(prmstr_buf)); + + strncpy (prmstr_buf, _cmds[id].parms, sizeof (prmstr_buf) - 1); + + argcnt=0; + tok = strtok(prmstr_buf, " "); + while (tok != NULL) { + //Store Pointers + prms[argcnt++] = tok; + + //cmdb_printf("prm_%2.2d='%s'\r\n",argcnt, tok); + + tok = strtok(NULL, " "); + } + + //3) Tokenize the commandline. + + //Get Tokens from arguments. + //Note: strtok inserts \0 into the original string. Won't harm here as we do not re-use it. + + argfnd=0; + + if (strlen(argstr_buf)!=0) { + tok = strtok(argstr_buf, " "); + } else { + tok=NULL; + } + + while (tok != NULL) { + //Store Pointers + toks[argfnd++]=tok; + + //cmdb_printf("tok_%2.2d='%s'\r\n",argfnd, tok); + + tok = strtok(NULL, " "); + } + + if (argfnd==argcnt || (id==CID_HELP && argfnd==0)) { + + error = 0; + + for (i=0;i<argcnt;i++) { + //cmdb_printf("prm_%2.2d=%s\r\n",i, prms[i]); + + switch (strlen(prms[i])) { + case 0: + break; + case 1: + break; + case 2: //Simple pattern, no modifier + mod='\0'; + typ=prms[i][1]; + + break; + case 3: //pattern with Modifier. + mod=prms[i][1]; + typ=prms[i][2]; + + break; + default: + break; + } + + switch (typ) { + case 'o' : + base=8; + break; + case 'x' : + base=16; + break; + default: + base=10; + break; + } + + endptr = (char*)toks[i]; + + //Cardinal Types + switch (typ) { + case 'd' : //Check mod + case 'i' : //Check mod + case 'u' : //Check mod + case 'o' : //Check mod + case 'x' : //Check mod + switch (mod) { + case 'b' : //char + //test range + l=strtol((char*)toks[i], &endptr, base); + if (l>=MIN_BYTE && l<=MAX_BYTE) { + parms[i].type=PARM_CHAR; + parms[i].val.uc =(unsigned char)l; + } else { + error = i+1; + } + + break; + case 'h' : //short + l=strtol((char*)toks[i], &endptr, base); + if (l>=MIN_SHORT && l<=MAX_SHORT) { + parms[i].type=PARM_SHORT; + parms[i].val.w=(short)l; + } else { + error = i+1; + } + + break; + case 'l' : //long + l=strtol((char*)toks[i], &endptr, base); + parms[i].type=PARM_LONG; + parms[i].val.l=l; + + break; + default: + l=strtol((char*)toks[i], &endptr, base); + if (l>=MIN_INT && l<=MAX_INT) { + parms[i].type=PARM_INT; + parms[i].val.l=(int)l; + } else { + error = i+1; + } + break; + } + + if (error==0 && + (endptr==toks[i] //No Conversion at all. + || *endptr)) { //Incomplete conversion. + error = i+1; + } + + break; + } + + //Floating Point Types + switch (typ) { + case 'e' : + case 'f' : + case 'g' : + f = strtod((char*)toks[i], &endptr); + + parms[i].type=PARM_FLOAT; + parms[i].val.f=f; + + if (error==0 && + (endptr==toks[i] //No Conversion at all. + || *endptr)) { //Incomplete conversion. + error = i; + } + + break; + } + + //String types + switch (typ) { + case 'c' : + parms[i].type=PARM_CHAR; + parms[i].val.c=((char*)toks[i])[0]; + + if (error==0 && strlen((char*)toks[i])!=1) { //Incomplete conversion. + error = i; + } + + break; + + case 's' : + parms[i].type=PARM_STRING; + strncpy(parms[i].val.s,(char*)toks[i], strlen((char*)toks[i])); + + break; + } + } + } else { + //id=CID_LAST; + } + } + + return id; +} + +void Cmdb::cmdb_cmd_proc(char *cmd) { + int cid; + int ndx; + + cid = cmdb_parse(cmd); + ndx = cmdb_cmdid_index(cid); + + if (cid!=-1) { + + /*------------------------------------------------ + Process the command and it's arguments that are + found. id contains the command id and argcnt & + argfnd the number of found and expected paramaters + parms contains the parsed argument values and their + types. + ------------------------------------------------*/ + + if (cid==CID_LAST) { + cmdb_print("Unknown command, type 'Help' for a list of available commands.\r\n"); + } else { + + //Test for more commandline than allowed too. + //i.e. run 1 is wrong. + + //TODO Fix Index/Id problem. + + if (argcnt==0 && argfnd==0 && error==0 && ndx!=-1 && _cmds[ndx].subs==SUBSYSTEM) { + subsystem=cid; + } else if ( ((cid==CID_HELP) || (argcnt==argfnd)) && error==0 ) { + switch (cid) { + + /////// GLOBAL MACRO COMMANDS /////// + + //Define Macro from commandline + case CID_MACRO: + macro_ptr=-1; + strncpy(macro_buf, STRINGPARM(0), sizeof(macro_buf) - 1); + break; + + //Run Macro + case CID_RUN: + macro_ptr=0; + break; + + //List Macro's + case CID_MACROS: + cmdb_print("[Macro]\r\n"); + if (macro_buf[0]) { + cmdb_printf("Value=%s\r\n",macro_buf); + } else { + cmdb_printf(";No Macro Defined\r\n"); + } + break; + + /////// GLOBAL STATEMACHINE COMMANDS /////// + +#ifdef STATEMACHINE + + //Start State Machine + case CID_STATE: + statemachine(BYTEPARM(0)); + + break; +#endif + + /////// GLOBAL COMMANDS /////// + + //Echo + case CID_ECHO: + echo = BOOLPARM(0); + break; + + //Bold + case CID_BOLD: + bold = BOOLPARM(0); + break; + + //Warm Boot + case CID_BOOT: + //reset(); + break; + + //Sends an ANSI escape code to clear the screen. + case CID_CLS: + cmdb_print(cls); + break; + + //Returns to CMD> prompt where most commands are disabled. + case CID_IDLE: + subsystem=-1; + break; + + //Help + case CID_HELP: { + +//TODO Handle Subsystem + +//TODO Call command processor callback and if it returns false we supply help. + + cmdb_print("\r\n"); + + if (argfnd>0) { + cid = cmdb_cmdid_search(STRINGPARM(0)); + } else { + cid=CID_LAST; + } + + if (argfnd>0 && cid!=CID_LAST) { + + //Help with a valid command as first parameter + ndx = cmdb_cmdid_index(cid); + + switch (_cmds[ndx].subs) { + case SUBSYSTEM: //Dump whole subsystem + cmdb_printf("%s subsystem commands:\r\n\r\n",_cmds[ndx].cmdstr); + + for (int i=0;i<CMD_TBL_LEN-1;i++) { + if (_cmds[i].subs==ndx) { + cmdb_cmdhelp("",i,",\r\n"); + } + } + + break; + + case GLOBALCMD: //Dump command only + //cmdb_print("Global command:\r\n\r\n",cmd_tbl[cmd_tbl[ndx].subs].cmdstr); + cmdb_cmdhelp("Syntax: ",ndx,".\r\n"); + break; + + default: //Dump one subsystem command + cmdb_printf("%s subsystem command:\r\n\r\n",_cmds[_cmds[ndx].subs].cmdstr); + cmdb_cmdhelp("Syntax: ",ndx,".\r\n"); + break; + } + } else { + if (argfnd>0) { + //Help with invalid command as first parameter + cmdb_print("Unknown command, type 'Help' for a list of available commands.\r\n"); + } else { + //Help + + //Dump Active Subsystem, Global & Other (dormant) Subsystems + for (int i=0;i<CMD_TBL_LEN-1;i++) { + if ((_cmds[i].subs<0) || (_cmds[i].subs==subsystem)) { + cmdb_cmdhelp("",i,",\r\n"); + } + } + cmdb_cmdhelp("",CMD_TBL_LEN-1,".\r\n"); + } + } + cmdb_print("\r\n"); + break; + } //CID_HELP + } + } else { + cmdb_cmdhelp("Syntax: ",ndx,".\r\n"); + } + } + } +} + +//Private Utilities #2 + +void Cmdb::cmdb_init(const char full) { + if (full) { + echo = true; + bold = true; + + subsystem = -1; + + lstbuf [cmdndx] = '\0'; + + cmdb_macro_reset(); + + cmdb_prompt(); + } + + cmdndx = 0; + cmdbuf [cmdndx] = '\0'; + + escndx = 0; + escbuf [escndx] = '\0'; +} + +void Cmdb::cmdb_prompt(void) { + if (subsystem!=-1) { + cmdb_printf("%s>",_cmds[subsystem].cmdstr); + } else { + cmdb_print(prompt); + } +} + +bool Cmdb::cmdb_scan(const char c) { + int i; + + //See http://www.interfacebus.com/ASCII_Table.html + + if (c == '\r') { // cr? + cmdb_print(crlf); // Output it and ... + if (cmdndx) { + strncpy(lstbuf,cmdbuf,cmdndx); + lstbuf[cmdndx]='\0'; + cmdb_cmd_proc(cmdbuf); + } + cmdb_init(false); + cmdb_prompt(); + + return true; + } + + //TODO BACKSPACE NOT CORRECT FOR TELNET! + + if (c == '\b') { // Backspace + if (cmdndx != 0) { + cmdb_print(bs); + cmdbuf [--cmdndx] = '\0'; + } else { + cmdb_printch(bell); // Output Error + } + return false; + } + + if (c == '\177') { // Delete + while (cmdndx>0) { + cmdb_print(bs); + cmdbuf [--cmdndx] = '\0'; + } + return false; + } + + //Reset Escape Buffer. + if (c == '\033') { + if (escndx!=0) { + //_putchar(bell); // Output Error + //printf("%s\r\n",escbuf); + } + escndx = 0; + escbuf [escndx] = '\0'; // NULL-Terminate buffer + } + + //Extract Escape Sequence. + if (c == '\033' || escndx ) { // Wait for escape + escbuf [escndx++] = (unsigned char) c; // Add to the buffer + escbuf [escndx] = '\0'; // NULL-Terminate buffer + if (isalpha(c)) { + switch (cmdb_escid_search(escbuf)) { + case EID_CURSOR_LEFT : { + if (cmdndx != 0) { // Backspace? + cmdb_print(bs); + cmdbuf [--cmdndx] = '\0'; + } else { + cmdb_printch(bell); // Output char + } + break; + } + case EID_CURSOR_UP : { + for (i=0;i<cmdndx;i++) { + cmdb_print(bs); + } + cmdndx=strlen(lstbuf); + strncpy(cmdbuf,lstbuf,cmdndx); + cmdbuf[cmdndx]='\0'; + cmdb_printf("%s",cmdbuf); + break; + } + case EID_CURSOR_RIGHT: + break; + case EID_CURSOR_DOWN : + break; + case EID_LAST : + break; + default : + cmdb_printch(bell); + break; + } + escndx=0; + escbuf [escndx] = '\0'; // NULL-Terminate buffer + } + return false; + } + + if (c=='\n') { // LF + return false; // Dump it + } + + if (!isprint (c)) { // Printable character? + cmdb_printch(bell); + return false; + } + + if (cmdndx >= MAX_CMD_LEN) { // Past buffer length? + cmdb_printch(bell); + return false; + } + + cmdbuf [cmdndx++] = (unsigned char) c; // Add to the buffer + cmdbuf [cmdndx] = '\0'; // NULL-Terminate buffer + + if (echo) { + cmdb_printch(c); // Output char + } + + return false; +} + +//Private Utilities #3 + +int Cmdb::cmdb_printf(const char *format, ...) { + int cnt; + + va_list args; + char buf[1024]; + + memset(buf,'\0',sizeof(buf)); + + va_start(args, format); + cnt = vsnprintf(buf, sizeof(buf), format, args); + if (cnt==-1) { + //Error + } + va_end(args); + + return cmdb_print(buf); +} + +//Link to outside world. +int Cmdb::cmdb_print(const char *msg) { + return _serial.printf(msg); +} + +//Link to outside world. +char Cmdb::cmdb_printch(const char ch) { + return _serial.putc(ch); +} + +void Cmdb::cmdb_cmdhelp(char *pre, int ndx, char *post) { + int j; + int k; + int lastmod; + + k=0; + lastmod=0; + + switch (_cmds[ndx].subs) { + case SUBSYSTEM : + break; + case GLOBALCMD : + break; + case HIDDENSUB : + return; + default : + if (strlen(pre)==0 && bold) { + cmdb_print(boldon); + } + break; + } + + cmdb_print(pre); + k+=strlen(pre); + + if (k==0) { + cmdb_printf("%12s",_cmds[ndx].cmdstr); + k+=12; + } else { + if (strlen(pre)>0 && bold) { + cmdb_print(boldon); + } + + cmdb_printf("%s",_cmds[ndx].cmdstr); + k+=strlen(_cmds[ndx].cmdstr); + + if (strlen(pre)>0 && bold) { + cmdb_print(boldoff); + } + } + + if (strlen(_cmds[ndx].parms)) { + cmdb_printch(sp); + k++; + } + + for (j=0;j<strlen(_cmds[ndx].parms);j++) { + switch (_cmds[ndx].parms[j]) { + case '%' : + lastmod=0; + break; + + case 'b' : + lastmod=8; + break; + case 'h' : + lastmod=16; + break; + case 'l' : + lastmod=32; + break; + + case 'd' : + case 'i' : { + switch (lastmod) { + case 0 : + case 16 : + cmdb_print("int"); + k+=3; + break; + case 8 : + cmdb_print("shortint"); + k+=8; + break; + case 32: + cmdb_print("longint"); + k+=7; + break; + } + break; + } + + case 'u' : + case 'o' : + case 'x' : { + switch (lastmod) { + case 0 : + case 16 : + cmdb_print("word"); + k+=4; + break; + case 8 : + cmdb_print("byte"); + k+=4; + break; + case 32 : + cmdb_print("dword"); + k+=5; + break; + } + + switch (_cmds[ndx].parms[j]) { + case 'o' : + cmdb_print("[o]"); + k+=3; + break; + case 'x' : + cmdb_print("[h]"); + k+=3; + break; + } + + break; + } + + case 'e' : + case 'f' : + case 'g' : + cmdb_print("float"); + k+=5; + break; + + case 'c' : + cmdb_print("char"); + k+=4; + break; + + case 's' : + cmdb_print("string"); + k+=6; + break; + + case ' ' : + cmdb_printch(sp); + k++; + break; + } + } + + for (j=k;j<40;j++) cmdb_printch(sp); + + switch (_cmds[ndx].subs) { + case SUBSYSTEM : + if (ndx==subsystem) { + cmdb_printf("- %s (active subsystem)%s",_cmds[ndx].cmddescr,post); + } else { + cmdb_printf("- %s (dormant subsystem)%s",_cmds[ndx].cmddescr,post); + } + break; + case HIDDENSUB : + break; + case GLOBALCMD : + cmdb_printf("- %s (global command)%s",_cmds[ndx].cmddescr,post); + break; + default : + cmdb_printf("- %s%s",_cmds[ndx].cmddescr,post); + if (strlen(pre)==0 && bold) { + cmdb_print(boldoff); + } + break; + } + + if (strlen(pre)>0 && strlen(_cmds[ndx].parmdescr)) { + cmdb_printf("Params: %s",_cmds[ndx].parmdescr); + cmdb_print("\r\n"); + } +} + +//------------------------------------------------------------------------------ +//----Wrappers +//------------------------------------------------------------------------------ + +void Cmdb::zeromemory(char *p,unsigned int siz) { + memset(p,'\0',siz); +} + +int Cmdb::stricmp (char *s1, char *s2) { + int i; + int len1,len2; + + len1=strlen(s1); + len2=strlen(s2); + + for (i = 0; (i<len1) && (i<len2);i++) { + if ( toupper (s1[i])<toupper(s2[i]) ) return (-1); + if ( toupper (s1[i])>toupper(s2[i]) ) return (+1); + } + + if (len1<len2) return (-1); + if (len1>len2) return (+1); + + return (0); +}