A Command Interpreter with support for used defined commands, subsystems, macros, help and parameter parsing.
cmdb.cpp
- Committer:
- wvd_vegt
- Date:
- 2011-02-10
- Revision:
- 0:4d95ee0b4c37
- Child:
- 3:abbf43fab7d5
File content as of revision 0:4d95ee0b4c37:
#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); }