Added embedded commands with ~{} and the return command, created a token

structure to help tidy the command buffer struct up a bit, fixed a few
bugs and probably created 100 more.  A lot of things were changed to
allow embedded commands:
- Old buffers on the stack are left alone for Cbuf_ExecuteStack to
clean up.  This is so return values can be extracted from them.
- The imperative flag has been added.  An imperative buffer and all buffers
following it can not be paused with the wait command.
- The returning flag and retval dstring have been added.  If a buffer
returned something, the flag will be set.
- The return command was added so there was something to use in the
substitution.  Return nukes all loop buffers since they don't really count
as independent functions, sets the return value on the top buffer, and
clears it.
- Who knows what else.
This commit is contained in:
Brian Koropoff 2002-03-29 07:43:02 +00:00
parent 676bf39d9d
commit 93c9f96416
3 changed files with 260 additions and 89 deletions

View file

@ -36,21 +36,33 @@ typedef struct cmd_localvar_s {
struct dstring_s *key, *value;
} cmd_localvar_t;
typedef struct cmd_token_s {
struct dstring_s *original, *processed; // Token before and after processing
unsigned int state; // Will be used later
} cmd_token_t;
typedef struct cmd_buffer_s {
// Data
struct dstring_s *buffer; // Actual text
qboolean wait; // Execution stopped until next frame
qboolean legacy; // Backwards compatible with old command buffer style
qboolean ownvars; // Buffer has its own private local variables
qboolean loop; // Buffer is in a loop
unsigned int argc, maxargc; // Number of args, number of args allocated
struct dstring_s **argv; // Array of processed tokens
struct dstring_s **argu; // Array of unprocessed tokens
struct cmd_token_s **argv; // Tokens
struct dstring_s *realline; // Actual command being processed
struct dstring_s *line; // Tokenized and reassembled command
struct dstring_s *line; // Reassembled line
struct dstring_s *looptext; // If a looping buffer, the text we are looping on
int *args; // Array of positions of each token in above string
struct dstring_s *retval; // Return value
unsigned int *args; // Array of positions of each token in composite line
struct hashtab_s *locals; // Local variables
struct cmd_buffer_s *prev, *next; // Next buffer in the stack
// Flags
qboolean imperative; // Execution cannot be paused
qboolean wait; // Execution paused until next frame
qboolean legacy; // Backwards compatible with old console buffer
qboolean ownvars; // Buffer has its own private local variables
qboolean loop; // Buffer loops itself
qboolean returning; // Buffer is returning a value
// Stack
struct cmd_buffer_s *prev, *next; // Neighboring buffers in stack
} cmd_buffer_t;
//===========================================================================
@ -142,6 +154,7 @@ const char *Cmd_CompleteAlias (const char *partial);
int Cmd_Argc (void);
const char *Cmd_Argv (int arg);
const char *Cmd_Args (int start);
const char *Cmd_Argu (int arg);
// The functions that execute commands get their parameters with these
// functions. Cmd_Argv () will return an empty string, not a NULL
// if arg > argc, so string operations are always safe.

View file

@ -308,9 +308,9 @@ C_ExecLine (const char *line)
static void
C_Say (const char *line)
{
Cbuf_AddText ("say \"");
Cbuf_AddText (line);
Cbuf_AddText ("\"\n");
Cbuf_AddTextTo (cmd_legacybuffer, "say \"");
Cbuf_AddTextTo (cmd_legacybuffer, line);
Cbuf_AddTextTo (cmd_legacybuffer, "\"\n");
key_dest = key_game;
game_target = IMT_0;
}
@ -318,9 +318,9 @@ C_Say (const char *line)
static void
C_SayTeam (const char *line)
{
Cbuf_AddText ("say_team \"");
Cbuf_AddText (line);
Cbuf_AddText ("\"\n");
Cbuf_AddTextTo (cmd_legacybuffer, "say_team \"");
Cbuf_AddTextTo (cmd_legacybuffer, line);
Cbuf_AddTextTo (cmd_legacybuffer, "\"\n");
key_dest = key_game;
game_target = IMT_0;
}

View file

@ -135,6 +135,21 @@ Cmd_LocalFree (void *ele, void *ptr)
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)
{
@ -150,6 +165,7 @@ Cmd_NewBuffer (qboolean ownvars)
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);
@ -168,17 +184,29 @@ Cmd_NewBuffer (qboolean ownvars)
void
Cmd_FreeBuffer (cmd_buffer_t *free) {
if (free->ownvars)
Hash_DelTable(free->locals); // Local variables are dead, period
free->wait = free->loop = free->ownvars = free->legacy = false;
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)
{
@ -254,9 +282,9 @@ void
Cmd_Wait_f (void)
{
cmd_buffer_t *cur;
for (cur = cmd_activebuffer; cur; cur = cur->prev)
cur->wait = true;
if (!cmd_activebuffer->imperative)
for (cur = cmd_activebuffer; cur; cur = cur->prev)
cur->wait = true;
}
void
@ -272,8 +300,6 @@ Cmd_Error (const char *message)
dstring_appendstr (cmd_backtrace, message);
dstring_appendstr (cmd_backtrace, "Path of execution:\n");
for (cur = cmd_activebuffer; cur; cur = cur->prev) {
if (cmd_activebuffer->loop) // Skip loop buffers, they are not 'real'
continue;
dstring_appendstr (cmd_backtrace, va ("--> %s\n", cur->realline->str));
}
}
@ -337,7 +363,7 @@ Cbuf_InsertText (const char *text)
/*
extract_line
Cbuf_ExtractLine
Finds the next \n,\r, or ;-delimeted
line in the command buffer and copies
@ -346,7 +372,7 @@ Cbuf_InsertText (const char *text)
*/
void
extract_line (dstring_t * buffer, dstring_t * line, qboolean legacy)
Cbuf_ExtractLine (dstring_t * buffer, dstring_t * line, qboolean legacy)
{
int i, squotes = 0, dquotes = 0, braces = 0, n;
char *tmp;
@ -426,7 +452,7 @@ Cbuf_ExecuteBuffer (cmd_buffer_t *buffer)
else
break;
}
extract_line (buffer->buffer, buf, buffer->legacy);
Cbuf_ExtractLine (buffer->buffer, buf, buffer->legacy);
Cmd_ExecuteString (buf->str, src_command);
if (buffer->wait)
break;
@ -442,12 +468,13 @@ void
Cbuf_ExecuteStack (cmd_buffer_t *buffer)
{
qboolean wait = false;
cmd_buffer_t *cur;
cmd_buffer_t *cur, *temp;
cmd_error = false;
for (cur = buffer; cur->next; cur = cur->next);
for (; cur != buffer; cur = cur->prev) {
for (; cur != buffer; cur = temp) {
temp = cur->prev;
Cbuf_ExecuteBuffer (cur);
if (cur->wait) {
wait = true;
@ -463,8 +490,7 @@ Cbuf_ExecuteStack (cmd_buffer_t *buffer)
Cbuf_ExecuteBuffer (buffer);
if (cmd_error) {
// If an error occured, nuke the entire stack
for (cur = buffer->next; cur; cur = cur->next)
Cmd_FreeBuffer (cur);
Cmd_FreeStack (buffer->next);
buffer->next = 0;
dstring_clearstr (buffer->buffer); // And the root buffer
}
@ -483,16 +509,29 @@ Cbuf_Execute (void)
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) {
/* if (!buffer->wait) {
Cmd_FreeBuffer (buffer);
cmd_activebuffer->next = 0;
}
}*/
return;
}
@ -513,7 +552,7 @@ Cbuf_Execute_Sets (void)
dstring_t *buf = dstring_newstr ();
while (strlen (cmd_consolebuffer->buffer->str)) {
extract_line (cmd_consolebuffer->buffer, buf, cmd_consolebuffer->legacy);
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)
@ -740,6 +779,7 @@ typedef struct cmd_function_s {
const char *name;
xcommand_t function;
const char *description;
qboolean pure;
} cmd_function_t;
static cmd_function_t *cmd_functions; // possible commands to execute
@ -755,7 +795,10 @@ Cmd_Argv (int arg)
{
if (arg >= cmd_activebuffer->argc)
return "";
return cmd_activebuffer->argv[arg]->str;
if (cmd_activebuffer->argv[arg]->state == 1)
return cmd_activebuffer->argv[arg]->processed->str;
else
return cmd_activebuffer->argv[arg]->original->str;
}
const char *
@ -763,7 +806,7 @@ Cmd_Argu (int arg)
{
if (arg >= cmd_activebuffer->argc)
return "";
return cmd_activebuffer->argu[arg]->str;
return cmd_activebuffer->argv[arg]->original->str;
}
/*
@ -1037,6 +1080,58 @@ Cmd_ProcessVariablesRecursive (dstring_t * dstr, int start)
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)
{
@ -1069,18 +1164,19 @@ Cmd_ProcessMath (dstring_t * dstr)
if (dstr->str[i] == '#' && dstr->str[i + 1] == '('
&& !escaped (dstr->str, i)) {
paren = 1;
for (n = 2;; n++) {
for (n = 2;dstr->str[i+n]; n++) {
if (dstr->str[i + n] == '(')
paren++;
else if (dstr->str[i + n] == ')') {
paren--;
if (!paren)
break;
} else if (!dstr->str[i + n]) {
ret = -1;
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);
@ -1118,7 +1214,7 @@ Cmd_ProcessEscapes (dstring_t * dstr)
int i;
for (i = 0; i < strlen (dstr->str); i++) {
if (dstr->str[i] == '\\' && dstr->str[i + 1]) {
if (dstr->str[i] == '\\' && dstr->str[i+1]) {
dstring_snip (dstr, i, 1);
if (dstr->str[i] == '\\')
i++;
@ -1129,24 +1225,35 @@ Cmd_ProcessEscapes (dstring_t * dstr)
}
}
/*
Cmd_TokenizeString
int
Cmd_ProcessToken (cmd_token_t *token)
{
int res;
This takes a normal string, parses it
into tokens, runs various filters on
each token, and recombines them with the
correct white space into a string for
the purpose of executing a console
command. If the string begins with a \,
filters are not run and the \ is stripped.
Anything that stuffs commands into the
console that requires absolute backwards
compatibility should be changed to prepend
it with a \. An example of this is
fullserverinfo, which requires that \
be left alone since it is the delimeter
for info keys.
*/
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)
@ -1154,6 +1261,8 @@ 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);
@ -1166,6 +1275,11 @@ Cmd_TokenizeString (const char *text, qboolean legacy)
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++;
@ -1183,23 +1297,17 @@ Cmd_TokenizeString (const char *text, qboolean legacy)
cmd_argc++;
if (cmd_argc > cmd_activebuffer->maxargc) {
cmd_activebuffer->argv = realloc (cmd_activebuffer->argv,
sizeof (dstring_t *) * cmd_argc);
sizeof (cmd_token_t *) * cmd_argc);
SYS_CHECKMEM (cmd_activebuffer->argv);
cmd_activebuffer->argu = realloc (cmd_activebuffer->argu,
sizeof (dstring_t *) * cmd_argc);
SYS_CHECKMEM (cmd_activebuffer->argu);
cmd_activebuffer->args = realloc (cmd_activebuffer->args,
sizeof (int) * cmd_argc);
SYS_CHECKMEM (cmd_activebuffer->args);
cmd_activebuffer->argv[cmd_argc - 1] = dstring_newstr ();
cmd_activebuffer->argu[cmd_argc - 1] = dstring_newstr ();
cmd_activebuffer->argv[cmd_argc - 1] = Cmd_NewToken ();
cmd_activebuffer->maxargc++;
}
dstring_clearstr (cmd_activebuffer->argv[cmd_argc - 1]);
dstring_clearstr (cmd_activebuffer->argu[cmd_argc - 1]);
dstring_clearstr (cmd_activebuffer->argv[cmd_argc-1]->original);
/* Remove surrounding quotes or double quotes or braces */
quotes = 0;
braces = 0;
@ -1217,33 +1325,24 @@ Cmd_TokenizeString (const char *text, qboolean legacy)
len -= 1;
braces = 1;
}
dstring_insert (cmd_activebuffer->argv[cmd_argc - 1], str + i, len, 0);
dstring_insert (cmd_activebuffer->argu[cmd_argc - 1], str + i, len, 0);
if (!legacy && text[0] != '|' && !braces) {
Cmd_ProcessTags (cmd_activebuffer->argv[cmd_argc - 1]);
res =
Cmd_ProcessVariables (cmd_activebuffer->argv[cmd_argc - 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_Error ("Parse error: Unmatched braces in "
"variable substitution expression.\n");
cmd_activebuffer->argc = 0;
return;
}
res = Cmd_ProcessMath (cmd_activebuffer->argv[cmd_argc - 1]);
if (res == -1) {
Cmd_Error ("Parse error: Unmatched parenthesis\n");
cmd_activebuffer->argc = 0;
return;
}
if (res == -2) {
cmd_activebuffer->argc = 0;
return;
}
Cmd_ProcessEscapes (cmd_activebuffer->argv[cmd_argc - 1]);
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);
}
dstring_insertstr (cmd_activebuffer->line,
cmd_activebuffer->argv[cmd_argc - 1]->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 */
}
@ -1269,7 +1368,7 @@ Cmd_AddCommand (const char *cmd_name, xcommand_t function,
return;
}
cmd = malloc (sizeof (cmd_function_t));
cmd = calloc (1, sizeof (cmd_function_t));
if (!cmd)
Sys_Error ("Cmd_AddCommand: Memory_Allocation_Failure\n");
cmd->name = cmd_name;
@ -1283,6 +1382,17 @@ Cmd_AddCommand (const char *cmd_name, xcommand_t function,
*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)
{
@ -1639,12 +1749,32 @@ Cmd_While_f (void) {
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_Argu(1)));
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) {
@ -1658,6 +1788,30 @@ Cmd_Break_f (void) {
}
}
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)
{
@ -1745,7 +1899,11 @@ Cmd_Init (void)
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 "