Implemented variable substitution and did various cleanups to the parser.

Changed Cmd_TokenizeString to accept a flag that controls the application
of filters (tags, variables, escape characters) to the tokens and modified
a few places in the source that called it.  Added a secondary command
buffer that is parsed without filters for legacy command support.
Currently, it is only used for commands stuffed into the console from the
server.  It is hacky, and I hope to eventually generalize the console
interface to support any number of buffers and audit the rest of the code
to recognize it.  For now, the legacy buffer at least keeps escape
character parsing from destroying info strings.
This commit is contained in:
Brian Koropoff 2002-03-03 23:28:57 +00:00
parent f78ec56975
commit 8d124f9c2b
6 changed files with 254 additions and 168 deletions

View file

@ -29,7 +29,9 @@
#ifndef __cmd_h
#define __cmd_h
#include "QF/qtypes.h"
#include "QF/dstring.h"
//===========================================================================
@ -45,9 +47,11 @@ The game starts with a Cbuf_AddText ("exec quake.rc\n"); Cbuf_Execute ();
*/
void Cbuf_Init (void);
// allocates an initial text buffer that will grow as needed
void Cbuf_AddTextTo (dstring_t *buffer, const char *text);
void Cbuf_AddText (const char *text);
// as new commands are generated from the console or keybindings,
// the text is added to the end of the command buffer.
@ -126,7 +130,7 @@ int Cmd_CheckParm (const char *parm);
// Returns the position (1 to argc-1) in the command's argument list
// where the given parameter apears, or 0 if not present
void Cmd_TokenizeString (const char *text);
void Cmd_TokenizeString (const char *text, qboolean filter);
// Takes a null terminated string. Does not need to be /n terminated.
// breaks the string up into arg tokens.
@ -147,8 +151,8 @@ void Cmd_Exec_File (const char *path);
extern char *com_token;
const char *COM_Parse (const char *data);
void Cmd_ParseSpecial (char *s);
extern struct cvar_s *cmd_warncmd;
extern dstring_t *cmd_legacybuffer; // Allow access to the legacy buffer as an alternate console buffer
#endif // __cmd_h

View file

@ -1,3 +1,34 @@
/*
dstring.h
Dynamic string buffer functions
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
$Id$
*/
#ifndef __dstring_h
#define __dstring_h
typedef struct dstring_s {
unsigned long int size, truesize;
char *str;
@ -18,3 +49,4 @@ dstring_t *dstring_newstr (void);
void dstring_appendstr (dstring_t *dstr, const char *str);
void dstring_insertstr (dstring_t *dstr, const char *str, unsigned int pos);
void dstring_clearstr (dstring_t *dstr);
#endif // __dstring_h

View file

@ -59,9 +59,9 @@ typedef struct cmdalias_s {
cmdalias_t *cmd_alias;
cmd_source_t cmd_source;
dstring_t *cmd_buffer;
dstring_t *cmd_argbuf;
dstring_t *cmd_buffer, *cmd_legacybuffer, *cmd_activebuffer, *cmd_argbuf;
qboolean cmd_wait = false;
qboolean cmd_legacy = false;
int cmd_argc;
int cmd_maxargc = 0;
dstring_t **cmd_argv = 0;
@ -96,26 +96,69 @@ Cmd_Wait_f (void)
byte cmd_text_buf[8192];
void Cbuf_Init (void) {
void
Cbuf_Init (void)
{
cmd_buffer = dstring_newstr ();
cmd_legacybuffer = dstring_newstr ();
cmd_activebuffer = cmd_buffer;
cmd_argbuf = dstring_newstr ();
}
void Cbuf_AddText (const char *text) {
dstring_appendstr (cmd_buffer, text);
void
Cbuf_AddTextTo (dstring_t *buffer, const char *text)
{
dstring_appendstr (buffer, text);
}
void Cbuf_InsertText (const char *text) {
dstring_insertstr (cmd_buffer, "\n", 0);
dstring_insertstr (cmd_buffer, text, 0);
/*
Cbuf_AddText
Add text to the active buffer
*/
void
Cbuf_AddText (const char *text)
{
Cbuf_AddTextTo (cmd_activebuffer, text);
}
void extract_line (dstring_t *buffer) {
void
Cbuf_InsertTextTo (dstring_t *buffer, const char *text)
{
dstring_insertstr (buffer, "\n", 0);
dstring_insertstr (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);
}
/*
extract_line
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
extract_line (dstring_t *buffer, dstring_t *line)
{
int i, squotes = 0, dquotes = 0;
char *text = cmd_buffer->str;
char *text = buffer->str;
for (i = 0; text[i]; i++) {
if (text[i] == '\'' && !dquotes)
@ -128,18 +171,28 @@ void extract_line (dstring_t *buffer) {
break;
}
if (i)
dstring_insert (buffer, cmd_buffer->str, i, 0);
dstring_insert (line, buffer->str, i, 0);
if (text[i])
dstring_snip (cmd_buffer, 0, i + 1);
dstring_snip (buffer, 0, i + 1);
else // We've hit the end of the buffer, just clear it
dstring_clearstr (cmd_buffer);
dstring_clearstr (buffer);
}
void Cbuf_Execute (void) {
dstring_t *buf = dstring_newstr ();
/*
Cbuf_ExecuteBuffer
while (strlen(cmd_buffer->str)) {
extract_line (buf);
Extracts and executes each line in the
command buffer, until it is empty or
a wait command is executed
*/
void
Cbuf_ExecuteBuffer (dstring_t *buffer)
{
dstring_t *buf = dstring_newstr ();
while (strlen(buffer->str)) {
extract_line (buffer, buf);
Cmd_ExecuteString(buf->str, cmd_source);
if (cmd_wait) {
cmd_wait = false;
@ -150,12 +203,34 @@ void Cbuf_Execute (void) {
dstring_delete (buf);
}
void Cbuf_Execute_Sets (void)
void
Cbuf_Execute (void)
{
cmd_activebuffer = cmd_legacybuffer; // Put legacy buffer into context
cmd_legacy = true;
Cbuf_ExecuteBuffer (cmd_activebuffer);
cmd_legacy = false;
cmd_activebuffer = cmd_buffer; // Next, do modern filtered buffer
Cbuf_ExecuteBuffer (cmd_activebuffer);
cmd_activebuffer = cmd_buffer; // The modern buffer should be in context by default
}
/*
Cbuf_Execute_Sets
Similar to Cbuf_Execute, but only
executes set and setrom commands. 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_buffer->str)) {
extract_line (buf);
extract_line (cmd_buffer, buf);
if (!strncmp (buf->str, "set", 3) && isspace ((int) buf->str[3])) {
Cmd_ExecuteString (buf->str, cmd_source);
} else if (!strncmp (buf->str, "setrom", 6) && isspace ((int) buf->str[6])) {
@ -322,7 +397,6 @@ Cmd_Alias_f (void)
alias->next = *a;
*a = alias;
}
printf("Aliasing %s to %s\n", Cmd_Argv(1), Cmd_Args(1));
// copy the rest of the command line
cmd = malloc (strlen (Cmd_Args (1)) + 2);// can never be longer
if (!cmd)
@ -398,7 +472,7 @@ Cmd_Argv (int arg)
/*
Cmd_Args
Returns a single string containing argv(1) to argv(argc()-1)
Returns a single string containing argv(start) to argv(argc()-1)
*/
const char *
Cmd_Args (int start)
@ -450,34 +524,59 @@ int Cmd_GetToken (const char *str) {
return i;
}
int tag_gold = 0;
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},
{'.', 0x8E}, // Gold dot
{',', 0x0E}, // White dot
{'G', 0x86}, // Ocrana leds from ocrana.wad
{'R', 0x87},
{'Y', 0x88},
{'B', 0x89},
{'a', 0x7F}, // White arrow
{'.', 0x05}, // White dot
{'#', 0x0B}, // White block
{'a', 0x7F}, // White arrow. DO NOT USE WITH <b> 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}
};
void Cmd_ProcessTags (dstring_t *dstr) {
/*
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;
char *str = dstr->str;
@ -499,14 +598,10 @@ void Cmd_ProcessTags (dstring_t *dstr) {
}
else if (ignore) // If ignore isn't being changed and we are ignore, go on
continue;
else if (!strncmp(str+i+close+1, "g", 1))
tag_gold = close ? tag_gold - 1 : tag_gold + 1;
else if (!strncmp(str+i+close+1, "b", 1))
tag_shift = close ? tag_shift - 1 : tag_shift + 1;
else if (!strncmp(str+i+close+1, "s", 1))
tag_special = close ? tag_special - 1 : tag_special + 1;
if (tag_gold < 0)
tag_gold = 0;
if (tag_shift < 0)
tag_shift = 0;
if (tag_special < 0)
@ -519,19 +614,66 @@ void Cmd_ProcessTags (dstring_t *dstr) {
/* This ignores escape characters, unless it is itself escaped */
if (c == '\\' && (!i || str[i-1] != '\\'))
continue;
else if (tag_gold && c >='0' && c <= '9')
c = (str[i] += (146 - '0'));
else if (tag_special) {
if (tag_special) {
for (n = 0; stable1[n].a; n++)
if (c == stable1[n].a)
c = str[i] = stable1[n].b;
}
else if (tag_shift && c < 128)
if (tag_shift && c < 128)
c = (str[i] += 128);
}
}
void Cmd_ProcessEscapes (dstring_t *dstr) {
/*
Cmd_ProcessVariables
Looks for occurances of ${varname} and
replaces them with the contents of the
variable.
*/
void
Cmd_ProcessVariables (dstring_t *dstr)
{
char *str = dstr->str;
dstring_t *varname;
cvar_t *var;
int i, n;
varname = dstring_newstr ();
for (i = 0; i < strlen(str); i++) {
if (str[i] == '$' && str[i+1] == '{' && (!i || str[i-1] != '\\')) {
for (n = 0; str[i+n] != '}'; n++)
if (!str[i+n])
return; // Open curly braces, give up
/* Copy text between braces into a buffer */
dstring_clearstr (varname);
dstring_insert (varname, str+i+2, n-2, 0);
var = 0;
var = Cvar_FindVar(varname->str);
if (var) {// Do we have a match?
dstring_snip (dstr, i, n+1); // Nuke it
dstring_insertstr (dstr, var->string, i); // Stick in the value of variable
}
}
}
dstring_delete (varname);
}
/*
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;
char *str = dstr->str;
@ -547,18 +689,39 @@ void Cmd_ProcessEscapes (dstring_t *dstr) {
}
}
void Cmd_TokenizeString (const char *text) {
/*
Cmd_TokenizeString
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.
*/
void
Cmd_TokenizeString (const char *text, qboolean filter)
{
int i = 0, n, len = 0, quotes = 0, space;
const char *str = text;
cmd_argc = 0;
/* Turn off tags at the beginning of a command.
This causes tags to continue past token boundaries. */
tag_shift = 0;
tag_gold = 0;
tag_special = 0;
printf("String in: %s\n", text);
if (text[0] == '|')
str++;
printf("Tokenizing: %s\n", str);
while (strlen(str + i)) {
space = 0;
while (isspace(str[i])) {
@ -583,8 +746,8 @@ void Cmd_TokenizeString (const char *text) {
cmd_maxargc++;
}
dstring_clearstr(cmd_argv[cmd_argc-1]);
/* Remove surrounding quotes or double quotes */
cmd_argspace[cmd_argc-1] = space;
/* Remove surrounding quotes or double quotes */
quotes = 0;
if ((str[i] == '\'' && str[i+len] == '\'') || (str[i] == '"' && str[i+len] == '"')) {
i++;
@ -592,8 +755,9 @@ void Cmd_TokenizeString (const char *text) {
quotes = 1;
}
dstring_insert(cmd_argv[cmd_argc-1], str + i, len, 0);
if (text[0] != '|') { // Lines beginning with | are not modified
if (filter) {
Cmd_ProcessTags(cmd_argv[cmd_argc-1]);
Cmd_ProcessVariables(cmd_argv[cmd_argc-1]);
Cmd_ProcessEscapes(cmd_argv[cmd_argc-1]);
}
i += len + quotes; /* If we ended on a quote, skip it */
@ -831,76 +995,6 @@ Cmd_CompleteAliasBuildList (const char *partial)
return buf;
}
/*
Cmd_ExpandVariables
Expand $fov-like expressions
FIXME: better handling of buffer overflows?
*/
// dest must point to a 1024-byte buffer
void
Cmd_ExpandVariables (const char *data, char *dest)
{
unsigned int c;
char buf[1024];
int i, len;
cvar_t *bestvar;
int quotes = 0;
len = 0;
// parse a regular word
while ((c = *data) != 0) {
if (c == '"')
quotes++;
if (c == '$' && *(data+1) == '{' && !(quotes & 1)) {
data+=2;
// Copy the text between the braces to a temp buffer
i = 0;
buf[0] = 0;
bestvar = NULL;
while ((c = *data) != 0 && c != '}') {
data++;
buf[i++] = c;
buf[i] = 0;
if (i >= sizeof (buf) - 1)
break;
}
data++;
bestvar = Cvar_FindVar(buf);
if (bestvar) {
// check buffer size
if (len + strlen (bestvar->string) >= 1024 - 1)
break;
strcpy (&dest[len], bestvar->string);
len += strlen (bestvar->string);
i = strlen (bestvar->name);
while (buf[i])
dest[len++] = buf[i++];
} else {
// no matching cvar name was found
dest[len++] = '$';
dest[len++] = '{';
if (len + strlen (buf) >= 1024)
break;
strcpy (&dest[len], buf);
len += strlen (buf);
dest[len++] = '}';
}
} else {
dest[len] = c;
data++;
len++;
if (len >= 1024 - 1)
break;
}
};
dest[len] = 0;
}
/*
Cmd_ExecuteString
@ -913,7 +1007,7 @@ Cmd_ExecuteString (const char *text, cmd_source_t src)
cmdalias_t *a;
cmd_source = src;
Cmd_TokenizeString (text);
Cmd_TokenizeString (text, !cmd_legacy);
// execute the command line
if (!Cmd_Argc ())
@ -1167,46 +1261,3 @@ skipwhite:
write_com_token (len, 0);
return data;
}
void Cmd_ParseSpecial (char *s)
{
char *d;
int i, i2;
char c = 0;
i = 0;
d = s;
while (*s) {
if ((*s == '\\') && ((s[1] == '#') || (s[1] == '$') || (s[1] == '\\'))) {
d[i++] = s[1];
s+=2;
continue;
}
if ((*s == '$') && (s[1] != '\0')) {
for (i2 = 0; stable1[i2].a; i2++) {
if (s[1] == stable1[i2].a) {
c = stable1[i2].b;
break;
}
}
if (c) {
d[i++] = c;
s += 2;
continue;
}
}
if ((*s == '#') && (s[1] != '\0')) {
d[i++] = s[1] ^ 128;
s += 2;
continue;
}
d[i++] = *s++;
}
d[i] = 0;
return;
}

View file

@ -1208,8 +1208,7 @@ CL_ParseServerMessage (void)
case svc_stufftext:
s = MSG_ReadString (net_message);
Con_DPrintf ("stufftext: %s\n", s);
Cbuf_AddText ("|"); // Make command be executed in a backward-compatible fashion
Cbuf_AddText (s);
Cbuf_AddTextTo (cmd_legacybuffer, s);
break;
case svc_damage:

View file

@ -512,7 +512,7 @@ SVC_Status (void)
return;
con_printf_no_log = 1;
Cmd_TokenizeString ("status");
Cmd_TokenizeString ("status", false);
SV_BeginRedirect (RD_PACKET);
SV_Printf ("%s\n", Info_MakeString (svs.info, 0));
for (i = 0; i < MAX_CLIENTS; i++) {
@ -1026,7 +1026,7 @@ SV_ConnectionlessPacket (void)
s = MSG_ReadString (net_message);
Cmd_TokenizeString (s);
Cmd_TokenizeString (s, false);
c = Cmd_Argv (0);

View file

@ -1240,7 +1240,7 @@ SV_ExecuteUserCommand (const char *s)
ucmd_t *u;
ucmd_t cmd;
Cmd_TokenizeString (s);
Cmd_TokenizeString (s, false);
sv_player = host_client->edict;
cmd.name = Cmd_Argv(0);