/* cmd.c script command processing module Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ static const char rcsid[] = "$Id$"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include #include "QF/cmd.h" #include "QF/cvar.h" #include "QF/hash.h" #include "QF/qargs.h" #include "QF/qendian.h" #include "QF/sizebuf.h" #include "QF/sys.h" #include "QF/vfs.h" #include "QF/zone.h" #include "compat.h" #include "QF/dstring.h" #include "QF/exp.h" #include "QF/va.h" typedef struct cmdalias_s { struct cmdalias_s *next; const char *name; const char *value; } cmdalias_t; cmdalias_t *cmd_alias; cmd_source_t cmd_source; /* FIXME: All these separate buffers are sort of hacky The command buffer interface should be generalized and each part of QF (console, stufftext, config files and scripts) that needs one should allocate and maintain its own. */ cmd_buffer_t *cmd_consolebuffer; // Console buffer cmd_buffer_t *cmd_legacybuffer; // Server stuffcmd buffer with // absolute backwards-compatibility cmd_buffer_t *cmd_activebuffer; // Buffer currently being executed cmd_buffer_t *cmd_recycled; // Recycled buffers dstring_t *cmd_backtrace; qboolean cmd_error; cvar_t *cmd_warncmd; cvar_t *cmd_maxloop; hashtab_t *cmd_alias_hash; hashtab_t *cmd_hash; //============================================================================= /* Local variable stuff */ cmd_localvar_t * Cmd_NewLocal (const char *key, const char *value) { cmd_localvar_t *new; dstring_t *dkey, *dvalue; new = malloc (sizeof (cmd_localvar_t)); if (!new) Sys_Error ("Cmd_NewLocal: Memory allocation failed!"); dkey = dstring_newstr (); dvalue = dstring_newstr (); dstring_appendstr (dkey, key); dstring_appendstr (dvalue, value); new->key = dkey; new->value = dvalue; return new; } void Cmd_SetLocal (cmd_buffer_t *buffer, const char *key, const char *value) { cmd_localvar_t *var; var = (cmd_localvar_t *)Hash_Find(buffer->locals, key); if (!var) { var = Cmd_NewLocal (key, value); Hash_Add (buffer->locals, (void *) var); } else { dstring_clearstr (var->value); dstring_appendstr (var->value, value); } } const char * Cmd_LocalGetKey (void *ele, void *ptr) { return ((cmd_localvar_t *) ele)->key->str; } void Cmd_LocalFree (void *ele, void *ptr) { dstring_delete (((cmd_localvar_t *) ele)->key); dstring_delete (((cmd_localvar_t *) ele)->value); free (ele); } /* Token management stuff */ cmd_token_t * Cmd_NewToken (void) { cmd_token_t *new; new = calloc(1,sizeof(cmd_token_t)); SYS_CHECKMEM (new); new->original = dstring_newstr(); new->processed = dstring_newstr(); return new; } /* Command buffer management */ cmd_buffer_t * Cmd_NewBuffer (qboolean ownvars) { cmd_buffer_t *new; if (cmd_recycled) { // If we have old buffers lying around new = cmd_recycled; cmd_recycled = new->next; } else { new = calloc (1, sizeof (cmd_buffer_t)); new->buffer = dstring_newstr (); new->line = dstring_newstr (); new->realline = dstring_newstr (); new->looptext = dstring_newstr (); new->retval = dstring_newstr (); } if (ownvars) new->locals = Hash_NewTable (512, Cmd_LocalGetKey, Cmd_LocalFree, 0); new->ownvars = ownvars; new->prev = new->next = 0; return new; } /* Cmd_FreeBuffer Actually just "recycles" buffer for later use */ void Cmd_FreeBuffer (cmd_buffer_t *free) { if (free->ownvars) Hash_DelTable(free->locals); // Local variables are always deadbeef free->wait = free->loop = free->ownvars = free->legacy = free->returning = free->imperative = false; dstring_clearstr (free->buffer); dstring_clearstr (free->line); dstring_clearstr (free->realline); dstring_clearstr (free->looptext); dstring_clearstr (free->retval); free->locals = 0; free->next = cmd_recycled; cmd_recycled = free; } void Cmd_FreeStack (cmd_buffer_t *stack) { cmd_buffer_t *temp; for (;stack; stack = temp) { temp = stack->next; Cmd_FreeBuffer (stack); } } /*void Cmd_FreeBuffer (cmd_buffer_t *del) { int i; dstring_delete (del->buffer); dstring_delete (del->line); dstring_delete (del->realline); dstring_delete (del->looptext); if (del->maxargc) { for (i = 0; i < del->maxargc; i++) { if (del->argv[i]) dstring_delete (del->argv[i]); if (del->argu[i]) dstring_delete (del->argu[i]); } free (del->argv); free (del->argu); } if (del->args) free(del->args); if (del->ownvars) Hash_DelTable(del->locals); free(del); }*/ /* Quick function to determine if a character is escaped */ qboolean escaped (const char *str, int i) { int n, c; if (!i) return 0; for (n = i - 1, c = 0; n >= 0 && str[n] == '\\'; n--, c++) ; return c & 1; } /* Quick function to escape stuff in a dstring */ void escape (dstring_t * dstr) { int i; for (i = 0; i < strlen (dstr->str); i++) { switch (dstr->str[i]) { case '\\': case '$': case '#': case '{': case '}': case '\"': case '\'': case '<': case '>': dstring_insertstr (dstr, "\\", i); i++; break; } } } /* Cmd_Wait_f Causes execution of the remainder of the command buffer and stack to be delayed until next frame. This allows commands like: bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" */ void Cmd_Wait_f (void) { cmd_buffer_t *cur; if (!cmd_activebuffer->imperative) for (cur = cmd_activebuffer; cur; cur = cur->prev) cur->wait = true; } void Cmd_Error (const char *message) { cmd_buffer_t *cur; Sys_Printf ("GIB: Error in execution. " "Type backtrace for a description and execution path to " "the error\n"); cmd_error = true; dstring_clearstr (cmd_backtrace); dstring_appendstr (cmd_backtrace, message); dstring_appendstr (cmd_backtrace, "Path of execution:\n"); for (cur = cmd_activebuffer; cur; cur = cur->prev) { dstring_appendstr (cmd_backtrace, va ("--> %s\n", cur->realline->str)); } } /* COMMAND BUFFER */ void Cbuf_Init (void) { cmd_consolebuffer = Cmd_NewBuffer (true); cmd_legacybuffer = Cmd_NewBuffer (true); cmd_legacybuffer->legacy = true; cmd_activebuffer = cmd_consolebuffer; cmd_recycled = 0; cmd_backtrace = dstring_newstr (); } void Cbuf_AddTextTo (cmd_buffer_t *buffer, const char *text) { dstring_appendstr (buffer->buffer, text); } /* Cbuf_AddText Add text to the active buffer */ void Cbuf_AddText (const char *text) { Cbuf_AddTextTo (cmd_activebuffer, text); } void Cbuf_InsertTextTo (cmd_buffer_t *buffer, const char *text) { dstring_insertstr (buffer->buffer, "\n", 0); dstring_insertstr (buffer->buffer, text, 0); } /* Cbuf_InsertText Add text to the beginning of the active buffer */ void Cbuf_InsertText (const char *text) { Cbuf_InsertTextTo (cmd_activebuffer, text); } /* Cbuf_ExtractLine Finds the next \n,\r, or ;-delimeted line in the command buffer and copies it into a buffer. Also shifts the rest of the command buffer back to the start. */ void Cbuf_ExtractLine (dstring_t * buffer, dstring_t * line, qboolean legacy) { int i, squotes = 0, dquotes = 0, braces = 0, n; char *tmp; for (i = 0; buffer->str[i]; i++) { if (!legacy && buffer->str[i] == '\'' && !escaped (buffer->str, i) && !dquotes) squotes ^= 1; if (buffer->str[i] == '"' && !escaped (buffer->str, i) && !squotes) dquotes ^= 1; if (buffer->str[i] == ';' && !escaped (buffer->str, i) && !squotes && !dquotes && !braces) break; if (!legacy && buffer->str[i] == '{' && !escaped (buffer->str, i) && !squotes && !dquotes) braces++; if (!legacy && buffer->str[i] == '}' && !escaped (buffer->str, i) && !squotes && !dquotes) braces--; if (buffer->str[i] == '/' && buffer->str[i + 1] == '/' && !squotes && !dquotes) { // Filter out comments until newline if ((tmp = strchr (buffer->str+i, '\n'))) n = tmp - (buffer->str+i); else if ((tmp = strchr (buffer->str+i, '\r'))) n = tmp - (buffer->str+i); else n = strlen(buffer->str+i); // snip till \0 dstring_snip (buffer, i, n); } if (buffer->str[i] == '\n' || buffer->str[i] == '\r') { if (braces) { dstring_snip (buffer, i, 1); i--; } else break; } } if (i) dstring_insert (line, buffer->str, i, 0); if (buffer->str[i]) { dstring_snip (buffer, 0, i + 1); } else { // We've hit the end of the buffer, just clear it dstring_clearstr (buffer); } } /* Cbuf_ExecuteBuffer Extracts and executes each line in the command buffer, until it is empty or a wait command is executed */ void Cbuf_ExecuteBuffer (cmd_buffer_t *buffer) { dstring_t *buf = dstring_newstr (); cmd_buffer_t *temp = cmd_activebuffer; // save old context cmd_activebuffer = buffer; buffer->wait = false; while (1) { if (!strlen(buffer->buffer->str)) { if (buffer->loop) { if (cmd_maxloop->value && buffer->loop > cmd_maxloop->value) { Cmd_Error(va("GIB: Loop lasted longer than %i iterations, forcefully terminating.\n", (int)cmd_maxloop->value)); break; } Cbuf_InsertTextTo(buffer, buffer->looptext->str); buffer->loop++; } else break; } Cbuf_ExtractLine (buffer->buffer, buf, buffer->legacy); Cmd_ExecuteString (buf->str, src_command); if (buffer->wait) break; if (cmd_error) break; dstring_clearstr (buf); } dstring_delete (buf); cmd_activebuffer = temp; // restore old context } void Cbuf_ExecuteStack (cmd_buffer_t *buffer) { qboolean wait = false; cmd_buffer_t *cur, *temp; cmd_error = false; for (cur = buffer; cur->next; cur = cur->next); for (; cur != buffer; cur = temp) { temp = cur->prev; Cbuf_ExecuteBuffer (cur); if (cur->wait) { wait = true; break; } else if (cmd_error) break; else { cur->prev->next = 0; Cmd_FreeBuffer (cur); } } if (!wait) Cbuf_ExecuteBuffer (buffer); if (cmd_error) { // If an error occured, nuke the entire stack Cmd_FreeStack (buffer->next); buffer->next = 0; dstring_clearstr (buffer->buffer); // And the root buffer } } /* Cbuf_Execute Executes both root buffers */ void Cbuf_Execute (void) { Cbuf_ExecuteStack (cmd_consolebuffer); Cbuf_ExecuteStack (cmd_legacybuffer); } /* Cmd_ExecuteSubroutine Executes a buffer as a subroutine on top of the execution stack. Execute a buffer this way and forget about it, it will be handled properly. */ void Cmd_ExecuteSubroutine (cmd_buffer_t *buffer) { // Inherit some flags from the current buffer buffer->imperative = cmd_activebuffer->imperative; cmd_activebuffer->next = buffer; buffer->prev = cmd_activebuffer; Cbuf_ExecuteBuffer (buffer); /* if (!buffer->wait) { Cmd_FreeBuffer (buffer); cmd_activebuffer->next = 0; }*/ return; } /* Cbuf_Execute_Sets Similar to Cbuf_Execute, but only executes set and setrom commands, and only in the console buffer. Used for reading config files before the cmd subsystem is entirely loaded. */ void Cbuf_Execute_Sets (void) { dstring_t *buf = dstring_newstr (); while (strlen (cmd_consolebuffer->buffer->str)) { Cbuf_ExtractLine (cmd_consolebuffer->buffer, buf, cmd_consolebuffer->legacy); if (!strncmp (buf->str, "set", 3) && isspace ((int) buf->str[3])) { Cmd_ExecuteString (buf->str, src_command); } else if (!strncmp (buf->str, "setrom", 6) && isspace ((int) buf->str[6])) { Cmd_ExecuteString (buf->str, src_command); } dstring_clearstr (buf); } dstring_delete (buf); } /* SCRIPT COMMANDS */ /* Cmd_StuffCmds_f Adds command line parameters as script statements Commands lead with a +, and continue until a - or another + quake +prog jctest.qp +cmd amlev1 quake -nosound +cmd amlev1 */ void Cmd_StuffCmds_f (void) { int i, j; int s; char *build, c; s = strlen (com_cmdline); if (!s) return; // pull out the commands build = malloc (s + 1); if (!build) Sys_Error ("Cmd_StuffCmds_f: Memory Allocation Failure\n"); build[0] = 0; for (i = 0; i < s - 1; i++) { if (com_cmdline[i] == '+') { i++; for (j = i; !((com_cmdline[j] == '+') || (com_cmdline[j] == '-' && (j == 0 || com_cmdline[j - 1] == ' ')) || (com_cmdline[j] == 0)); j++) ; c = com_cmdline[j]; com_cmdline[j] = 0; strncat (build, com_cmdline + i, s - strlen (build)); strncat (build, "\n", s - strlen (build)); com_cmdline[j] = c; i = j - 1; } } // Sys_Printf("[\n%s]\n",build); if (build[0]) Cbuf_InsertText (build); free (build); } void Cmd_Exec_File (const char *path) { char *f; int len; VFile *file; if (!path || !*path) return; if ((file = Qopen (path, "r")) != NULL) { len = COM_filelength (file); f = (char *) malloc (len + 1); if (f) { f[len] = 0; Qread (file, f, len); Qclose (file); // Always insert into console Cbuf_InsertTextTo (cmd_consolebuffer, f); free (f); } } } void Cmd_Exec_f (void) { char *f; int mark; cmd_buffer_t *sub; if (Cmd_Argc () != 2) { Sys_Printf ("exec : execute a script file\n"); return; } mark = Hunk_LowMark (); f = (char *) COM_LoadHunkFile (Cmd_Argv (1)); if (!f) { Sys_Printf ("couldn't exec %s\n", Cmd_Argv (1)); return; } if (!Cvar_Command () && (cmd_warncmd->int_val || (developer && developer->int_val))) Sys_Printf ("execing %s\n", Cmd_Argv (1)); sub = Cmd_NewBuffer (true); Cbuf_AddTextTo (sub, f); Hunk_FreeToLowMark (mark); Cmd_ExecuteSubroutine (sub); // Execute file in it's own buffer } /* Cmd_Echo_f Just prints the rest of the line to the console */ void Cmd_Echo_f (void) { if (Cmd_Argc() == 2) Sys_Printf ("%s\n", Cmd_Argv(1)); else Sys_Printf ("%s\n", Cmd_Args (1)); } /* Cmd_Alias_f Creates a new command that executes a command string (possibly ; seperated) */ void Cmd_Alias_f (void) { cmdalias_t *alias; char *cmd; int i, c; const char *s; if (Cmd_Argc () == 1) { Sys_Printf ("Current alias commands:\n"); for (alias = cmd_alias; alias; alias = alias->next) Sys_Printf ("alias %s \"%s\"\n", alias->name, alias->value); return; } s = Cmd_Argv (1); // if the alias already exists, reuse it alias = (cmdalias_t *) Hash_Find (cmd_alias_hash, s); if (alias) { free ((char *) alias->value); } else { cmdalias_t **a; alias = calloc (1, sizeof (cmdalias_t)); if (!alias) Sys_Error ("Cmd_Alias_f: Memory Allocation Failure\n"); alias->name = strdup (s); Hash_Add (cmd_alias_hash, alias); for (a = &cmd_alias; *a; a = &(*a)->next) if (strcmp ((*a)->name, alias->name) >= 0) break; alias->next = *a; *a = alias; } // copy the rest of the command line cmd = malloc (strlen (Cmd_Args (1)) + 2); // can never be longer if (!cmd) Sys_Error ("Cmd_Alias_f: Memory Allocation Failure\n"); cmd[0] = 0; // start out with a null string c = Cmd_Argc (); for (i = 2; i < c; i++) { strcat (cmd, Cmd_Argv (i)); if (i != c - 1) strcat (cmd, " "); } alias->value = cmd; } void Cmd_UnAlias_f (void) { cmdalias_t *alias; const char *s; if (Cmd_Argc () != 2) { Sys_Printf ("unalias : erase an existing alias\n"); return; } s = Cmd_Argv (1); alias = Hash_Del (cmd_alias_hash, s); if (alias) { cmdalias_t **a; for (a = &cmd_alias; *a != alias; a = &(*a)->next) ; *a = alias->next; free ((char *) alias->name); free ((char *) alias->value); free (alias); } else { Sys_Printf ("Unknown alias \"%s\"\n", s); } } /* COMMAND EXECUTION */ typedef struct cmd_function_s { struct cmd_function_s *next; const char *name; xcommand_t function; const char *description; qboolean pure; } cmd_function_t; static cmd_function_t *cmd_functions; // possible commands to execute int Cmd_Argc (void) { return cmd_activebuffer->argc; } const char * Cmd_Argv (int arg) { if (arg >= cmd_activebuffer->argc) return ""; if (cmd_activebuffer->argv[arg]->state == 1) return cmd_activebuffer->argv[arg]->processed->str; else return cmd_activebuffer->argv[arg]->original->str; } const char * Cmd_Argu (int arg) { if (arg >= cmd_activebuffer->argc) return ""; return cmd_activebuffer->argv[arg]->original->str; } /* Cmd_Args Returns a single string containing argv(start) to argv(argc()-1) */ const char * Cmd_Args (int start) { if (start >= cmd_activebuffer->argc) return ""; return cmd_activebuffer->line->str + cmd_activebuffer->args[start]; } int Cmd_EndDoubleQuote (const char *str) { int i; for (i = 1; i < strlen (str); i++) { if (str[i] == '\"' && !escaped (str, i)) return i; } return -1; // Not found } int Cmd_EndSingleQuote (const char *str) { int i; for (i = 1; i < strlen (str); i++) { if (str[i] == '\'' && !escaped (str, i)) return i; } return -1; // Not found } int Cmd_EndBrace (const char *str) { int i, n; for (i = 1; i < strlen (str); i++) { if (str[i] == '{' && !escaped (str, i)) { n = Cmd_EndBrace (str + i); if (n < 0) return n; else i += n; } else if (str[i] == '\"' && !escaped (str, i)) { n = Cmd_EndDoubleQuote (str + i); if (n < 0) return n; else i += n; } else if (str[i] == '\'' && !escaped (str, i)) { n = Cmd_EndSingleQuote (str + i); if (n < 0) return n; else i += n; } else if (str[i] == '}' && !escaped (str, i)) return i; } return -1; // No matching brace found } int Cmd_GetToken (const char *str, qboolean legacy) { int i; if (!legacy) { if (*str == '\'') return Cmd_EndSingleQuote (str); if (*str == '{') return Cmd_EndBrace (str); if (*str == '}') return -1; } if (*str == '\"') return Cmd_EndDoubleQuote (str); for (i = 0; i < strlen (str); i++) if (isspace (str[i])) break; return i; } int tag_shift = 0; int tag_special = 0; struct stable_s { char a, b; } stable1[] = { {'f', 0x0D}, // Fake message {'[', 0x90}, // Gold braces {']', 0x91}, {'(', 0x80}, // Scroll bar characters {'=', 0x81}, {')', 0x82}, {'|', 0x83}, {'<', 0x9D}, // Vertical line characters {'-', 0x9E}, {'>', 0x9F}, {'.', 0x05}, // White dot {'#', 0x0B}, // White block {'a', 0x7F}, // White arrow. // DO NOT USE WITH TAG IN ANYTHING SENT TO SERVER. PERIOD. {'A', 0x8D}, // Brown arrow {'0', 0x92}, // Golden numbers {'1', 0x93}, {'2', 0x94}, {'3', 0x95}, {'4', 0x96}, {'5', 0x97}, {'6', 0x98}, {'7', 0x99}, {'8', 0x9A}, {'9', 0x9B}, {0, 0} }; /* Cmd_ProcessTags Looks for html-like tags in a dstring and modifies the string accordingly FIXME: This has become messy. Create tag.[ch] and write a more generalized tag parser using callbacks */ void Cmd_ProcessTags (dstring_t * dstr) { int close = 0, ignore = 0, i, n, c; for (i = 0; i < strlen (dstr->str); i++) { if (dstr->str[i] == '<' && !escaped (dstr->str, i)) { close = 0; for (n = 1; dstr->str[i + n] != '>' || escaped (dstr->str, i + n); n++) if (dstr->str[n] == 0) return; if (dstr->str[i + 1] == '/') close = 1; if (!strncmp (dstr->str + i + close + 1, "i", 1)) { if (ignore && !close) // If we are ignoring, ignore a non close continue; else if (close && ignore) // If we are closing, turn off ignore ignore--; else if (!close) ignore++; // Otherwise, turn ignore on } else if (ignore) // If ignore isn't being changed and we are ignore, go on continue; else if (!strncmp (dstr->str + i + close + 1, "b", 1)) tag_shift = close ? tag_shift - 1 : tag_shift + 1; else if (!strncmp (dstr->str + i + close + 1, "s", 1)) tag_special = close ? tag_special - 1 : tag_special + 1; if (tag_shift < 0) tag_shift = 0; if (tag_special < 0) tag_special = 0; dstring_snip (dstr, i, n + 1); i--; continue; } c = dstr->str[i]; /* This ignores escape characters, unless it is itself escaped */ if (c == '\\' && !escaped (dstr->str, i)) continue; if (tag_special) { for (n = 0; stable1[n].a; n++) if (c == stable1[n].a) c = dstr->str[i] = stable1[n].b; } if (tag_shift && c < 128) c = (dstr->str[i] += 128); } } /* Cmd_ProcessVariablesRecursive Looks for occurances of ${varname} and replaces them with the contents of the variable. Will first replace occurances within itself. */ int Cmd_ProcessVariablesRecursive (dstring_t * dstr, int start) { dstring_t *varname; cmd_localvar_t *lvar; cvar_t *cvar; int i, n, braces = 0; varname = dstring_newstr (); if (dstr->str[start+1] == '{') braces = 1; for (i = start + 1 + braces;; i++) { if (dstr->str[i] == '$' && (braces || dstr->str[i+1] == '{') && !escaped (dstr->str, i)) { n = Cmd_ProcessVariablesRecursive (dstr, i); if (n < 0) { break; } else { i += n - 1; continue; } } else if (braces && dstr->str[i] == '}' && !escaped (dstr->str, i)) { dstring_clearstr (varname); dstring_insert (varname, dstr->str + start + 2, i - start - 2, 0); // Nuke it, even if no match is found dstring_snip (dstr, start, i - start + 1); lvar = (cmd_localvar_t *) Hash_Find (cmd_activebuffer->locals, varname->str); if (lvar) { // Local variables get precedence dstring_insertstr (dstr, lvar->value->str, start); n = strlen (lvar->value->str); } else if ((cvar = Cvar_FindVar (varname->str))) { // Then cvars // Stick in the value of variable dstring_insertstr (dstr, cvar->string, start); n = strlen (cvar->string); } else n = 0; break; } else if (!dstr->str[i] && braces) { // No closing brace n = -1; break; } else if (!braces && !isalnum(dstr->str[i]) && dstr->str[i] != '_') { dstring_clearstr (varname); dstring_insert (varname, dstr->str + start + 1, i - start - 1, 0); // Nuke it, even if no match is found dstring_snip (dstr, start, i - start); lvar = (cmd_localvar_t *) Hash_Find (cmd_activebuffer->locals, varname->str); if (lvar) { // Local variables get precedence dstring_insertstr (dstr, lvar->value->str, start); n = strlen (lvar->value->str); } else if ((cvar = Cvar_FindVar (varname->str))) { // Then cvars // Stick in the value of variable dstring_insertstr (dstr, cvar->string, start); n = strlen (cvar->string); } else n = 0; break; } } dstring_delete (varname); return n; } int Cmd_ProcessEmbedded (dstring_t * dstr) { int i, n, braces = 0, ret = 0; cmd_buffer_t *temp; dstring_t *command; command = dstring_newstr (); for (i = 0; i < strlen(dstr->str); i++) { if (dstr->str[i] == '~' && dstr->str[i+1] == '{' && !escaped (dstr->str,i)) { braces++; for (n = 2; dstr->str[i+n]; n++) { if (dstr->str[i+n] == '{') braces++; if (dstr->str[i+n] == '}') { braces--; if (!braces) break; } } if (braces) { Cmd_Error ("Parse error: Unmatched brace in embedded command expression.\n"); ret = -1; break; } dstring_clearstr (command); dstring_insert (command, dstr->str + i + 2, n - 2, 0); temp = Cmd_NewBuffer (false); temp->imperative = true; temp->locals = cmd_activebuffer->locals; Cbuf_InsertTextTo (temp, command->str); Cbuf_ExecuteBuffer (temp); if (!cmd_error) { dstring_snip(dstr, i, n +1); if (temp->next && temp->next->returning) { dstring_insertstr (dstr, temp->next->retval->str, i); i += strlen(temp->next->retval->str) - 1; } else { Cmd_Error ("GIB: Embedded command expression resulted in no return value.\n"); ret = -1; Cmd_FreeStack (temp); break; } } Cmd_FreeStack (temp); } } dstring_delete (command); return ret; } int Cmd_ProcessVariables (dstring_t * dstr) { int i, n; for (i = 0; i < strlen (dstr->str); i++) { if (dstr->str[i] == '$' && !escaped (dstr->str, i)) { n = Cmd_ProcessVariablesRecursive (dstr, i); if (n < 0) return n; else i += n - 1; } } return 0; } int Cmd_ProcessMath (dstring_t * dstr) { dstring_t *statement; int i, n, paren; float value; char *temp; int ret = 0; statement = dstring_newstr (); for (i = 0; i < strlen (dstr->str); i++) { if (dstr->str[i] == '#' && dstr->str[i + 1] == '(' && !escaped (dstr->str, i)) { paren = 1; for (n = 2;dstr->str[i+n]; n++) { if (dstr->str[i + n] == '(') paren++; else if (dstr->str[i + n] == ')') { paren--; if (!paren) break; } } if (paren) { ret = -1; break; } /* Copy text between parentheses into a buffer */ dstring_clearstr (statement); dstring_insert (statement, dstr->str + i + 2, n - 2, 0); value = EXP_Evaluate (statement->str); if (EXP_ERROR == EXP_E_NORMAL) { temp = va ("%g", value); dstring_snip (dstr, i, n + 1); // Nuke the statement dstring_insertstr (dstr, temp, i); // Stick in the value i += strlen (temp) - 1; } else { ret = -2; Cmd_Error (va("Math error: invalid expression %s\n", statement->str)); break; // Math evaluation error } } } dstring_delete (statement); return ret; } /* Cmd_ProcessEscapes Looks for the escape character \ and removes it. Special cases exist for \\ and \n; otherwise, it is simply filtered. This should be the last step in the parser so that quotes, tags, etc. can be escaped */ void Cmd_ProcessEscapes (dstring_t * dstr) { int i; for (i = 0; i < strlen (dstr->str); i++) { if (dstr->str[i] == '\\' && dstr->str[i+1]) { dstring_snip (dstr, i, 1); if (dstr->str[i] == '\\') i++; if (dstr->str[i] == 'n') dstr->str[i] = '\n'; i--; } } } int Cmd_ProcessToken (cmd_token_t *token) { int res; dstring_clearstr (token->processed); dstring_appendstr (token->processed, token->original->str); Cmd_ProcessTags (token->processed); res = Cmd_ProcessEmbedded (token->processed); if (res < 0) return res; res = Cmd_ProcessVariables (token->processed); if (res < 0) { Cmd_Error ("Parse error: Unmatched braces in " "variable substitution expression.\n"); return res; } res = Cmd_ProcessMath (token->processed); if (res == -1) { Cmd_Error ("Parse error: Unmatched parenthesis\n"); return res; } if (res == -2) { return res; } Cmd_ProcessEscapes (token->processed); return 0; } void Cmd_TokenizeString (const char *text, qboolean legacy) { int i = 0, len = 0, quotes, braces, space, res; const char *str = text; unsigned int cmd_argc = 0; cmd_function_t *cmd; qboolean process = true; dstring_clearstr (cmd_activebuffer->realline); dstring_appendstr (cmd_activebuffer->realline, text); /* Turn off tags at the beginning of a command. This causes tags to continue past token boundaries. */ tag_shift = 0; tag_special = 0; if (text[0] == '|') str++; dstring_clearstr (cmd_activebuffer->line); while (strlen (str + i)) { if (!legacy && cmd_argc == 1) { // See if command wants unprocessed tokens cmd = (cmd_function_t *) Hash_Find (cmd_hash, cmd_activebuffer->argv[0]->processed->str); if (cmd && cmd->pure) process = false; } space = 0; while (isspace (str[i])) { i++; space++; } dstring_appendsubstr (cmd_activebuffer->line, str + i - space, space); len = Cmd_GetToken (str + i, legacy); if (len < 0) { Cmd_Error ("Parse error: Unmatched quotes, braces, or " "double quotes\n"); cmd_activebuffer->argc = 0; return; } else if (len == 0) break; cmd_argc++; if (cmd_argc > cmd_activebuffer->maxargc) { cmd_activebuffer->argv = realloc (cmd_activebuffer->argv, sizeof (cmd_token_t *) * cmd_argc); SYS_CHECKMEM (cmd_activebuffer->argv); cmd_activebuffer->args = realloc (cmd_activebuffer->args, sizeof (int) * cmd_argc); SYS_CHECKMEM (cmd_activebuffer->args); cmd_activebuffer->argv[cmd_argc - 1] = Cmd_NewToken (); cmd_activebuffer->maxargc++; } dstring_clearstr (cmd_activebuffer->argv[cmd_argc-1]->original); /* Remove surrounding quotes or double quotes or braces */ quotes = 0; braces = 0; cmd_activebuffer->args[cmd_argc - 1] = strlen (cmd_activebuffer->line->str); if ((str[i] == '\'' && str[i + len] == '\'') || (str[i] == '"' && str[i + len] == '"')) { dstring_appendsubstr (cmd_activebuffer->line, str + i, 1); dstring_appendsubstr (cmd_activebuffer->line, str + i, 1); i++; len -= 1; quotes = 1; } if (str[i] == '{' && str[i + len] == '}') { i++; len -= 1; braces = 1; } dstring_insert (cmd_activebuffer->argv[cmd_argc-1]->original, str + i, len, 0); if (!legacy && !braces && process && text[0] != '|') { res = Cmd_ProcessToken (cmd_activebuffer->argv[cmd_argc-1]); if (res < 0) { cmd_activebuffer->argc = 0; return; } cmd_activebuffer->argv[cmd_argc-1]->state = 1; dstring_insertstr (cmd_activebuffer->line, cmd_activebuffer->argv[cmd_argc-1]->processed->str, strlen (cmd_activebuffer->line->str) - quotes); } else { cmd_activebuffer->argv[cmd_argc-1]->state = 0; dstring_insertstr (cmd_activebuffer->line, cmd_activebuffer->argv[cmd_argc-1]->original->str, strlen (cmd_activebuffer->line->str) - quotes); } i += len + quotes + braces; /* If we ended on a quote or brace, skip it */ } cmd_activebuffer->argc = cmd_argc; } void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description) { cmd_function_t *cmd; cmd_function_t **c; // fail if the command is a variable name if (Cvar_FindVar (cmd_name)) { Sys_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name); return; } // fail if the command already exists cmd = (cmd_function_t *) Hash_Find (cmd_hash, cmd_name); if (cmd) { Sys_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); return; } cmd = calloc (1, sizeof (cmd_function_t)); if (!cmd) Sys_Error ("Cmd_AddCommand: Memory_Allocation_Failure\n"); cmd->name = cmd_name; cmd->function = function; cmd->description = description; Hash_Add (cmd_hash, cmd); for (c = &cmd_functions; *c; c = &(*c)->next) if (strcmp ((*c)->name, cmd->name) >= 0) break; cmd->next = *c; *c = cmd; } void Cmd_SetPure (const char *name) { cmd_function_t *cmd; cmd = (cmd_function_t *) Hash_Find (cmd_hash, name); if (cmd) cmd->pure = true; } qboolean Cmd_Exists (const char *cmd_name) { cmd_function_t *cmd; cmd = (cmd_function_t *) Hash_Find (cmd_hash, cmd_name); if (cmd) { return true; } return false; } const char * Cmd_CompleteCommand (const char *partial) { cmd_function_t *cmd; int len; cmdalias_t *a; len = strlen (partial); if (!len) return NULL; // check for exact match for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strcasecmp (partial, cmd->name)) return cmd->name; for (a = cmd_alias; a; a = a->next) if (!strcasecmp (partial, a->name)) return a->name; // check for partial match for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp (partial, cmd->name, len)) return cmd->name; for (a = cmd_alias; a; a = a->next) if (!strncasecmp (partial, a->name, len)) return a->name; return NULL; } /* Cmd_CompleteCountPossible New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ int Cmd_CompleteCountPossible (const char *partial) { cmd_function_t *cmd; int len; int h; h = 0; len = strlen (partial); if (!len) return 0; // Loop through the command list and count all partial matches for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp (partial, cmd->name, len)) h++; return h; } /* Cmd_CompleteBuildList New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ const char ** Cmd_CompleteBuildList (const char *partial) { cmd_function_t *cmd; int len = 0; int bpos = 0; int sizeofbuf; const char **buf; sizeofbuf = (Cmd_CompleteCountPossible (partial) + 1) * sizeof (char *); len = strlen (partial); buf = malloc (sizeofbuf + sizeof (char *)); if (!buf) Sys_Error ("Cmd_CompleteBuildList: Memory Allocation Failure\n"); // Loop through the alias list and print all matches for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp (partial, cmd->name, len)) buf[bpos++] = cmd->name; buf[bpos] = NULL; return buf; } /* Cmd_CompleteAlias New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ const char * Cmd_CompleteAlias (const char *partial) { cmdalias_t *alias; int len; len = strlen (partial); if (!len) return NULL; // Check functions for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp (partial, alias->name, len)) return alias->name; return NULL; } /* Cmd_CompleteAliasCountPossible New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ int Cmd_CompleteAliasCountPossible (const char *partial) { cmdalias_t *alias; int len; int h; h = 0; len = strlen (partial); if (!len) return 0; // Loop through the command list and count all partial matches for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp (partial, alias->name, len)) h++; return h; } /* Cmd_CompleteAliasBuildList New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ const char ** Cmd_CompleteAliasBuildList (const char *partial) { cmdalias_t *alias; int len = 0; int bpos = 0; int sizeofbuf = (Cmd_CompleteAliasCountPossible (partial) + 1) * sizeof (char *); const char **buf; len = strlen (partial); buf = malloc (sizeofbuf + sizeof (char *)); if (!buf) Sys_Error ("Cmd_CompleteAliasBuildList: Memory Allocation Failure\n"); // Loop through the alias list and print all matches for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp (partial, alias->name, len)) buf[bpos++] = alias->name; buf[bpos] = NULL; return buf; } /* Cmd_ExecuteString A complete command line has been parsed, so try to execute it */ void Cmd_ExecuteString (const char *text, cmd_source_t src) { cmd_function_t *cmd; cmdalias_t *a; cmd_source = src; Cmd_TokenizeString (text, cmd_activebuffer->legacy); // execute the command line if (!Cmd_Argc ()) return; // no tokens // check functions cmd = (cmd_function_t *) Hash_Find (cmd_hash, Cmd_Argv (0)); if (cmd) { if (cmd->function) cmd->function (); return; } // Tonik: check cvars if (Cvar_Command ()) return; // check alias a = (cmdalias_t *) Hash_Find (cmd_alias_hash, Cmd_Argv (0)); if (a) { int i; cmd_buffer_t *sub; // Create a new buffer to execute the alias in sub = Cmd_NewBuffer (true); Cbuf_InsertTextTo (sub, a->value); for (i = 0; i < Cmd_Argc (); i++) Cmd_SetLocal (sub, va ("%i", i), Cmd_Argv (i)); Cmd_SetLocal (sub, "argn", va ("%i", Cmd_Argc ())); // This will handle freeing the buffer for us, leave it alone Cmd_ExecuteSubroutine (sub); return; } if (cmd_warncmd->int_val || developer->int_val) Sys_Printf ("Unknown command \"%s\"\n", Cmd_Argv (0)); } /* Cmd_CheckParm Returns the position (1 to argc-1) in the command's argument list where the given parameter apears, or 0 if not present */ int Cmd_CheckParm (const char *parm) { int i; if (!parm) Sys_Error ("Cmd_CheckParm: NULL"); for (i = 1; i < Cmd_Argc (); i++) if (!strcasecmp (parm, Cmd_Argv (i))) return i; return 0; } void Cmd_CmdList_f (void) { cmd_function_t *cmd; int i; int show_description = 0; if (Cmd_Argc () > 1) show_description = 1; for (cmd = cmd_functions, i = 0; cmd; cmd = cmd->next, i++) { if (show_description) { Sys_Printf ("%-20s :\n%s\n", cmd->name, cmd->description); } else { Sys_Printf ("%s\n", cmd->name); } } Sys_Printf ("------------\n%d commands\n", i); } void Cmd_Help_f (void) { const char *name; cvar_t *var; cmd_function_t *cmd; if (Cmd_Argc () != 2) { Sys_Printf ("usage: help \n"); return; } name = Cmd_Argv (1); for (cmd = cmd_functions; cmd && strcasecmp (name, cmd->name); cmd = cmd->next) ; if (cmd) { Sys_Printf ("%s\n", cmd->description); return; } var = Cvar_FindVar (name); if (!var) var = Cvar_FindAlias (name); if (var) { Sys_Printf ("%s\n", var->description); return; } Sys_Printf ("variable/command not found\n"); } /* Scripting commands The following functions are commands for enhanced scripting */ void Cmd_If_f (void) { long int num; if (Cmd_Argc () < 3) { Sys_Printf ("Usage: if {condition} {commands}\n"); return; } num = strtol (Cmd_Argv(1), 0, 10); if (!strcmp(Cmd_Argv(0), "ifnot")) num = !num; if (num) Cbuf_InsertText (Cmd_Argv (2)); return; } void Cmd_While_f (void) { cmd_buffer_t *sub; if (Cmd_Argc() < 3) { Sys_Printf("Usage: while {condition} {commands}\n"); return; } sub = Cmd_NewBuffer (false); sub->locals = cmd_activebuffer->locals; // Use current local variables sub->loop = true; dstring_appendstr (sub->looptext, va("ifnot '%s' break\n", Cmd_Argv(1))); dstring_appendstr (sub->looptext, Cmd_Argv(2)); Cmd_ExecuteSubroutine (sub); return; } void Cmd_For_f (void) { cmd_buffer_t *sub; if (Cmd_Argc() < 4) { Sys_Printf("Usage: for {initializer} [condition] {commands}\n"); return; } sub = Cmd_NewBuffer (false); sub->locals = cmd_activebuffer->locals; // Use current local variables sub->loop = true; dstring_appendstr (sub->looptext, va("ifnot '%s' break\n", Cmd_Argv(2))); dstring_appendstr (sub->looptext, va("%s\n", Cmd_Argv(4))); dstring_appendstr (sub->looptext, va("%s", Cmd_Argv(3))); Cbuf_InsertTextTo (sub, Cmd_Argv(1)); Cmd_ExecuteSubroutine (sub); return; } void Cmd_Break_f (void) { if (cmd_activebuffer->loop) { cmd_activebuffer->loop = false; dstring_clearstr(cmd_activebuffer->buffer); return; } else { Cmd_Error("break command used outside of loop!\n"); return; } } void Cmd_Return_f (void) { if (Cmd_Argc() > 2) { Cmd_Error("GIB: Invalid return statement. Return takes either one argument or none.\n"); return; } if (!cmd_activebuffer->prev) { Cmd_Error("GIB: Return attempted in a root buffer\n"); return; } while (cmd_activebuffer->loop) // We need to get out of any loops cmd_activebuffer = cmd_activebuffer->prev; if (cmd_activebuffer->next) { Cmd_FreeStack (cmd_activebuffer->next); cmd_activebuffer->next = 0; } dstring_clearstr (cmd_activebuffer->buffer); // Clear the buffer out no matter what if (Cmd_Argc() == 2) { dstring_clearstr (cmd_activebuffer->retval); dstring_appendstr (cmd_activebuffer->retval, Cmd_Argv(1)); cmd_activebuffer->returning = true; } } void Cmd_Lset_f (void) { if (Cmd_Argc () != 3) { Sys_Printf ("Usage: lset [local variable] [value]\n"); return; } Cmd_SetLocal (cmd_activebuffer, Cmd_Argv (1), Cmd_Argv (2)); } void Cmd_Backtrace_f (void) { Sys_Printf ("%s", cmd_backtrace->str); } void Cmd_Hash_Stats_f (void) { Sys_Printf ("alias hash table:\n"); Hash_Stats (cmd_alias_hash); Sys_Printf ("command hash table:\n"); Hash_Stats (cmd_hash); } static void cmd_alias_free (void *_a, void *unused) { cmdalias_t *a = (cmdalias_t *) _a; free ((char *) a->name); free ((char *) a->value); free (a); } static const char * cmd_alias_get_key (void *_a, void *unused) { cmdalias_t *a = (cmdalias_t *) _a; return a->name; } static const char * cmd_get_key (void *c, void *unused) { cmd_function_t *cmd = (cmd_function_t *) c; return cmd->name; } /* Cmd_Init_Hash initialise the command and alias hash tables */ void Cmd_Init_Hash (void) { cmd_hash = Hash_NewTable (1021, cmd_get_key, 0, 0); cmd_alias_hash = Hash_NewTable (1021, cmd_alias_get_key, cmd_alias_free, 0); } void Cmd_Init (void) { // register our commands Cmd_AddCommand ("stuffcmds", Cmd_StuffCmds_f, "Execute the commands given " "at startup again"); Cmd_AddCommand ("exec", Cmd_Exec_f, "Execute a script file"); Cmd_AddCommand ("echo", Cmd_Echo_f, "Print text to console"); Cmd_AddCommand ("alias", Cmd_Alias_f, "Used to create a reference to a " "command or list of commands.\n" "When used without parameters, displays all current " "aliases.\n" "Note: Enclose multiple commands within quotes and " "seperate each command with a semi-colon."); Cmd_AddCommand ("unalias", Cmd_UnAlias_f, "Remove the selected alias"); Cmd_AddCommand ("wait", Cmd_Wait_f, "Wait a game tic"); Cmd_AddCommand ("cmdlist", Cmd_CmdList_f, "List all commands"); Cmd_AddCommand ("help", Cmd_Help_f, "Display help for a command or " "variable"); Cmd_AddCommand ("if", Cmd_If_f, "Conditionally execute a set of commands."); Cmd_AddCommand ("ifnot", Cmd_If_f, "Conditionally execute a set of commands if the condition is false."); Cmd_AddCommand ("while", Cmd_While_f, "Execute a set of commands while a condition is true."); Cmd_SetPure ("while"); Cmd_AddCommand ("for", Cmd_For_f, "A while loop with initialization and iteration commands."); Cmd_SetPure ("for"); Cmd_AddCommand ("break", Cmd_Break_f, "Break out of a loop."); Cmd_AddCommand ("return", Cmd_Return_f, "Return a value to calling buffer."); Cmd_AddCommand ("lset", Cmd_Lset_f, "Sets the value of a local variable (not cvar)."); Cmd_AddCommand ("backtrace", Cmd_Backtrace_f, "Show a description of the last GIB error and a backtrace."); //Cmd_AddCommand ("cmd_hash_stats", Cmd_Hash_Stats_f, "Display statistics " // "alias and command hash tables"); cmd_warncmd = Cvar_Get ("cmd_warncmd", "0", CVAR_NONE, NULL, "Toggles the " "display of error messages for unknown commands"); cmd_maxloop = Cvar_Get ("cmd_maxloop", "0", CVAR_NONE, NULL, "Controls the " "maximum number of iterations a loop in GIB can do " "before being forcefully terminated. 0 is infinite."); } char *com_token; static size_t com_token_size; static inline void write_com_token (size_t pos, char c) { if (pos + 1 <= com_token_size) { write: com_token[pos] = c; return; } com_token_size = (pos + 1024) & ~1023; com_token = realloc (com_token, com_token_size); if (!com_token) Sys_Error ("COM_Parse: could not allocate %ld bytes", (long) com_token_size); goto write; } /* COM_Parse Parse a token out of a string FIXME: Does anything still need this crap? */ const char * COM_Parse (const char *_data) { const byte *data = (const byte *) _data; unsigned int c; size_t len = 0; write_com_token (len, 0); if (!data) return NULL; // skip whitespace skipwhite: while ((c = *data) <= ' ') { if (c == 0) return NULL; // end of file; data++; } // skip // comments if (c == '/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; while (1) { c = *data++; if (c == '\"' || !c) { write_com_token (len, 0); return c ? data : data - 1; } write_com_token (len, c); len++; } } // parse a regular word do { write_com_token (len, c); data++; len++; c = *data; } while (c > 32); write_com_token (len, 0); return data; }