/* Quake-style Command Processor */ /* Malloc-less environment. */ #include "cmdproc.h" #if 0 Integrating the command processor into another program: * Create an instance of cmdproc_t (global variable or struct member). * Make regular frequent calls (e.g. once very 0.1 seconds) to: cmdproc_cycle(cmdproc_t *) * primary methods: cmdproc_append(cmdproc_t *, const char *); /* To append command lines. */ cmdproc_insert(cmdproc_t *, const char *); /* To prepend command lines. */ cmdproc_execute(cmdproc_t *, const char *); /* bypass buffer, direct exection (one line's worth). */ * extension methods: int cmdproc_register (cmdproc_t *, const char *, cmd_f *); /* Bind command name to function. Error (0) if command name already exists. */ cmdproc_deregister (cmdproc_t *, const char *); /* Remove command binding. */ For extension commands: * Properties for command arguments: const char * cmdproc_argv(cmdproc_t *, int); /* Get nth argument (0 == command) */ int cmdproc_argc(cmdproc_t *); /* Number of arguments. */ const char * cmdproc_args(cmdproc_t *); /* Copy of command line (unparsed) starting from first parameter (argv[1]). */ #endif /* 0 */ int cmdproc_strlen (const char *s) { int i; for (i = 0; *s; i++, s++); return i; } int cmdproc_tolower (int ch) { /* Assumes ASCII. */ if (('A' <= ch) && (ch <= 'Z')) return ('a' + (ch - 'A')); return ch; } int cmdproc_toupper (int ch) { /* Assumes ASCII. */ if (('a' <= ch) && (ch <= 'z')) return ('A' + (ch - 'a')); return ch; } char * cmdproc_strcpy (char *dst, const char *src) { if (!dst) return NULL; if (!src) return dst; do /* do..while lets us copy the nul term also. */ { *dst = *src; dst++; src++; } while (*src); return dst; } char * cmdproc_strncpyz (char *dst, const char *src, int len) { int i; if (!dst) return NULL; if (!src) return dst; /* one less, to guarantee space for NUL. */ for (i = 1; *src && (i < len); i++) { *dst = *src; dst++; src++; } *dst++ = 0; return dst; } int cmdproc_strcasecmp (const char *s1, const char *s2) { int a, b, d; if ((!s1) || (!s2)) return s1 - s2; while (*s1 && *s2) { a = cmdproc_tolower(*s1); b = cmdproc_tolower(*s2); d = b - a; if (d != 0) return d; s1++; s2++; } return *s1 - *s2; } int cmdproc_strncasecmp (const char *s1, const char *s2, int len) { int a, b, d; if ((!s1) || (!s2)) return s1 - s2; while (*s1 && *s2 && (len > 0)) { a = cmdproc_tolower(*s1); b = cmdproc_tolower(*s2); d = b - a; if (d != 0) return d; s1++; s2++; len--; } return *s1 - *s2; } /* Unformatted output. */ int cmdproc_putstr (cmdproc_t *self, const char *buf) { return printf("%s", buf); // return puts(buf); } /* Formatted output. */ int cmdproc_printf (cmdproc_t *self, const char *fmt, ...) { va_list vp; int retval; va_start(vp, fmt); retval = vprintf(fmt, vp); va_end(vp); return retval; } int cmdproc_dump (cmdproc_t *self) { int buflen; int i, c; buflen = (self->cbuf.tail - self->cbuf.head + self->cbuf.max) % self->cbuf.max; if (buflen < 0) buflen = self->cbuf.max - buflen - 1; printf("Command list:\n"); for (i = 0; i < self->maxlu; i++) { if (self->lu[i].used) printf(" '%s': %p\n", self->lu[i].cmdname, self->lu[i].cmdfunc); } printf("Command buffer size: %d\n", buflen); printf("Command buffer content:"); for (i = 0; i < buflen; i++) { c = self->cbuf.buf[(self->cbuf.head + i) % self->cbuf.max]; if (c < 32) printf("%%%02X", c); else printf("%c", c); } printf("\n"); return 0; } /* Append `text' to the end of command buffer. Returns how many characters in `text' were appended. */ int cmdproc_append (cmdproc_t *self, const char *text) { const char *p; int n, t, lastch; p = text; n = 0; /* crumb("cmdproc_append: appending '%s'\n", text); */ while (*p) { t = (self->cbuf.tail + 1) % self->cbuf.max; /* New tail spot. */ if (t == self->cbuf.head) { /* Ring buffer full. */ crumb("cmdproc_append: ring buffer full\n"); break; } self->cbuf.buf[self->cbuf.tail] = *p; /* crumb("cmdproc_append: append char (%d)%c\n", *p, *p); */ self->cbuf.tail = t; lastch = *p; n++; p++; } self->cbuf.buf[self->cbuf.tail] = 0; /* null-terminate. */ /* if ((lastch != ';') && (lastch != '\n')) self->cbuf.tail = (self->cbuf.tail + 1) % self->cbuf.max; */ return n; } /* Prepend (Insert) `text' into command buffer. Returns number of characters in `text' (from its end) inserted. */ int cmdproc_insert (cmdproc_t *self, const char *text) { const char *p; int copylen, n, h, lastch; if (!(text && *text)) return 0; p = text; copylen = 0; for (; *p; copylen++, p++); n = 0; h = 0; p--; while (n < copylen) { h = (self->cbuf.head - 1 + self->cbuf.max) % self->cbuf.max; /* New head spot. */ if (h == self->cbuf.tail) { /* Barging in on its tail. */ break; } self->cbuf.buf[h] = *p; self->cbuf.head = h; lastch = *p; p--; n++; } return n; } /* Add new command to commands lookup table. */ int cmdproc_register (cmdproc_t *self, const char *cmdname, cmd_f cmdfunc) { cmdlookup_t *c; int i, end; end = self->maxlu; c = self->lu; i = 0; while (i < end) { c = self->lu + i; if (c->used) { /* slot already used. */ // if (0 == strncasecmp(cmdname, c->cmdname, CMDLEN)) if (0 == cmdproc_strncasecmp(cmdname, c->cmdname, CMDLEN)) { return 0; /* Can't register same name. */ } } else { c->used = 1; // strncpy(c->cmdname, cmdname, CMDLEN); cmdproc_strncpyz(c->cmdname, cmdname, CMDLEN); // c->cmdname[CMDLEN-1] = 0; c->cmdfunc = cmdfunc; return 1; /* Successfully registered. */ } i++; } return 0; /* Failed to register -- out of space. */ } /* Remove command from commands lookup table. */ int cmdproc_deregister (cmdproc_t *self, const char *cmdname) { cmdlookup_t *c; int i, end; end = self->maxlu; c = self->lu; i = 0; while (i < end) { c = self->lu + i; if (c->used) { /* slot already used. */ if (0 == cmdproc_strncasecmp(cmdname, c->cmdname, CMDLEN)) { c->cmdfunc = NULL; c->cmdname[0] = 0; c->used = 0; return 1; /* Successfully deregistered. */ } } i++; } return 0; /* Couldn't deregister -- name not found. */ } /* Extract one line from command buffer. Returns pointer to extracted line. */ const char * cmdproc_extract (cmdproc_t *self) { int i, c, e; parse_t parse; e = 0; self->extract[e] = 0; parse = START; #if 0 for (i = self->cbuf.head; i != self->cbuf.tail; i = (i + 1) % self->cbuf.max) { c = self->cbuf.buf[i]; if ((c == 0) || (c == '\n') || ((parse != QUOTE) && (c == ';'))) break; self->extract[e++] = c; if (c == '"') parse = (parse == QUOTE) ? START : QUOTE; } #else for (i = self->cbuf.head; parse != STOP; i = (i + 1) % self->cbuf.max) { c = self->cbuf.buf[i]; if (i == self->cbuf.tail) parse = STOP; switch (parse) { case START: #undef EXTRASMARTEXTRACT case TOKEN: if ((c == 0) || (c == '\n') || (c == ';')) parse = STOP; #if EXTRASMARTEXTRACT else if (c == '/') parse = SEMICOMMENT; #endif /* EXTRASMARTEXTRACT */ else if (c == '"') parse = QUOTE; break; case QUOTE: if ((c == 0) || (c == '\n')) parse = STOP; else if (c == '"') parse = START; break; #ifdef EXTRASMARTEXTRACT case SEMICOMMENT: if ((c == 0) || (c == '\n') || (c == ';')) parse = STOP; else if (c == '/') parse = COMMENT; else parse = TOKEN; break; case COMMENT: if ((c == 0) || (c == '\n')) parse = STOP; break; #endif case STOP: break; } if (parse == STOP) i--; else self->extract[e++] = c; } #endif /* 0 */ self->extract[e] = 0; self->cbuf.head = (i + 1) % self->cbuf.max; // crumb("Extracted: '%s'\n", self->extract); return self->extract; } /* Parse command line to args fields in self->args. Returns number of tokens parsed. */ int cmdproc_parse (cmdproc_t *self, const char *line) { int c, i, slen; int start, stop, copylen; parse_t parse; char *dst; memset(&(self->cmdarg), 0, sizeof(self->cmdarg)); dst = self->cmdarg.arguments; /* Start of arguments list. */ parse = START; start = stop = 0; i = 0; // c = line[i]; slen = cmdproc_strlen(line); /* for failsafe. */ while (parse != STOP) { c = line[i]; switch (parse) { case START: // crumb("parsing start '%c'\n", c); if ((c == 0) || (c == '\n') || (c == ';')) { parse = STOP; } else if (c == '/') { start = i; parse = SEMICOMMENT; } else if (c == '"') { start = i + 1; parse = QUOTE; } else if (c > 32) { start = i; parse = TOKEN; } /* otherwise keep on going. */ break; case TOKEN: // crumb("parsing token '%c'\n", c); if ((c == 0) || (c == '\n') || (c == ';')) { stop = i; parse = STOP; } else if (c == '/') { /* Maybe a comment. Could just be a slash in mid-token. */ parse = TOKENSEMICOMMENT; } else if (c == '"') { /* token sep and token start. Backstep one. */ stop = i; parse = START; i--; } else if (c <= 32) { /* token sep. */ stop = i; parse = START; i--; } /* otherwise, still in token. Keep on counting. */ break; case QUOTE: // crumb("parsing quoted '%c'\n", c); if ((c == 0) || (c == '\n')) { stop = i; parse = STOP; } else if (c == '"') { stop = i; parse = START; } break; case SEMICOMMENT: if (c == '/') { parse = COMMENT; } else { /* Token starting with '/'. */ i--; parse = TOKEN; } break; case TOKENSEMICOMMENT: if (c == '/') { stop = i-1; parse = COMMENT; } else { parse = TOKEN; } case COMMENT: if ((c == 0) || (c == '\n') || (c == ';')) { /* no token. */ start = stop = i; parse = STOP; } /* else keep on going. */ break; default: /* Unknown parser state. */ break; } if (stop > start) { if (self->cmdarg.count == 1) self->cmdarg.argstr = line + start; copylen = stop - start + 1; // strncpy(dst, line + start, copylen); cmdproc_strncpyz(dst, line + start, copylen); self->cmdarg.vector[self->cmdarg.count] = dst; self->cmdarg.count++; dst += copylen; /* Shift over dst to new stop. */ stop = 0; } i++; if (i > slen) /* failsafe. (or is it?) */ parse = STOP; } return self->cmdarg.count; } /* Report unknown command. */ int cmdproc_unknown (cmdproc_t *self) { self->printf(self, "Unknown command: %s\n", self->cmdarg.vector[0]); return 0; } /* Dispatch command based on contents of args fields. Returns command return value. */ int cmdproc_dispatch (cmdproc_t *self) { cmdlookup_t *mapping; const char *cmd; cmd_f dispatchee; int i; mapping = self->lu; if (self->cmdarg.count < 1) /* No command. */ return 0; cmd = self->cmdarg.vector[0]; dispatchee = NULL; i = 0; while (i < self->maxlu) { if ((mapping->used) && (0 == cmdproc_strncasecmp(mapping->cmdname, cmd, CMDLEN))) { dispatchee = mapping->cmdfunc; break; } mapping++; i++; } if (dispatchee) { return dispatchee(self); } else { return cmdproc_unknown(self); } } /* Execute `text' as a command. Parse, then dispatch. */ int cmdproc_execute (cmdproc_t *self, const char *text) { cmdproc_parse(self, text); return cmdproc_dispatch(self); } /* One cycle of command processor. */ int cmdproc_cycle (cmdproc_t *self) { const char *line; if (self->suspend) { self->suspend--; crumb("Waiting %d more cycles\n", self->suspend); return 0; } line = cmdproc_extract(self); // crumb("Extracted line: %s\n", line); #if 0 cmdproc_parse(self, line); { int i; crumb("Args dump:\n"); for (i = 0; i < self->cmdarg.count; i++) { crumb(" arg[%d] = \"%s\"\n", i, self->cmdarg.vector[i]); } } return cmdproc_dispatch(self); #else return cmdproc_execute(self, line); #endif /* 0 */ } int cmdproc_argc (cmdproc_t *self) { return self->cmdarg.count; } const char * cmdproc_args (cmdproc_t *self) { const char *s; s = self->cmdarg.argstr; if (!s) s = ""; return s; } const char * cmdproc_argv (cmdproc_t *self, int n) { return self->cmdarg.vector[n]; } int cmd_crash (cmdproc_t *self) { self->putstr(self, "*** Enforced crash (because I can't be sure 'quit' works as expected).\n"); exit(0); return 0; /* Though I fail to see how exit() could fail. */ } int cmd_cmdlist (cmdproc_t *self) { cmdlookup_t *cmdbind; int i, n; i = 0; n = 0; cmdbind = self->lu; while (i < self->maxlu) { if (cmdbind->used) { self->printf(self, "%s\n", cmdbind->cmdname); n++; } cmdbind++; i++; } self->printf(self, "%d commands.\n", n); return n; } int cmd_echo (cmdproc_t *self) { int i; for (i = 1; i < cmdproc_argc(self); i++) { if (i != 1) self->putstr(self, " "); self->putstr(self, cmdproc_argv(self, i)); } self->putstr(self, "\n"); return 0; } int cmd_exec (cmdproc_t *self) { FILE *execfile; const char *filename; char buf[CBUFSIZE]; filename = cmdproc_argv(self, 1); execfile = fopen(filename, "rt"); if (!execfile) { self->printf(self, "Could not exec %s\n", filename); return 1; } self->printf(self, "Execing %s\n", filename); fread(buf, sizeof(char), sizeof(buf), execfile); buf[sizeof(buf)] = 0; /* Force nul-terminate. */ // cmdproc_execute(self, buf); cmdproc_append(self, buf); fclose(execfile); return 0; } int cmd_wait (cmdproc_t *self) { int i; if (cmdproc_argc(self) > 1) { i = atoi(cmdproc_argv(self, 1)); self->suspend += i; } else { self->suspend++; } return 0; } cmdproc_t * cmdproc_init (cmdproc_t *self) { memset(self, 0, sizeof(*self)); self->cbuf.max = sizeof(self->cbuf.buf); self->maxlu = sizeof(self->lu) / sizeof(*self->lu); // fflush(stdout); /* Methods. */ self->printf = cmdproc_printf; self->putstr = cmdproc_putstr; /* Built-in commands. */ cmdproc_register(self, "crash", cmd_crash); cmdproc_register(self, "cmdlist", cmd_cmdlist); cmdproc_register(self, "echo", cmd_echo); cmdproc_register(self, "exec", cmd_exec); cmdproc_register(self, "wait", cmd_wait); return self; } int main (int argc, char **argv) { cmdproc_t _CP, *CP = &_CP; char inbuf[1024]; cmdproc_init(CP); #if 0 cmdproc_append(CP, "echo hi\n"); cmdproc_append(CP, "echo lali ho\n"); cmdproc_append(CP, "yo\n"); cmdproc_append(CP, "quit\n"); cmdproc_insert(CP, "hrm.;"); cmdproc_append(CP, "echo \"hello; world\".\n"); cmdproc_dump(CP); while (CP->cbuf.head != CP->cbuf.tail) cmdproc_cycle(CP); // cmd_crash(CP); #endif /* 0 */ printf("Command Processor struct memory footprint: %d B\n", sizeof(*CP)); while (!feof(stdin)) { printf("] "); fflush(stdout); *inbuf = 0; fgets(inbuf, sizeof(inbuf), stdin); cmdproc_append(CP, inbuf); do { cmdproc_cycle(CP); } while (CP->cbuf.head != CP->cbuf.tail); } CP->putstr(CP, "\n"); return 0; }