mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-05 20:50:43 +00:00
761a7546dd
autoconfiscated so rcsid will continue to work with gcc 3.3
599 lines
14 KiB
C
599 lines
14 KiB
C
/*
|
|
gib_parse.c
|
|
|
|
GIB parser functions
|
|
|
|
Copyright (C) 2002 Brian Koropoff
|
|
|
|
Author: Brian Koropoff
|
|
Date: #DATE#
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
static __attribute__ ((unused)) const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "QF/sys.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/va.h"
|
|
#include "QF/cmd.h"
|
|
#include "QF/cbuf.h"
|
|
#include "QF/gib_buffer.h"
|
|
#include "QF/gib_process.h"
|
|
#include "QF/gib_builtin.h"
|
|
#include "QF/gib_function.h"
|
|
#include "QF/gib_vars.h"
|
|
#include "QF/gib_parse.h"
|
|
|
|
// Interpreter structure and prototypes
|
|
|
|
static void GIB_Parse_Execute_Line (cbuf_t *cbuf);
|
|
|
|
cbuf_interpreter_t gib_interp = {
|
|
GIB_Parse_Extract_Line,
|
|
GIB_Parse_Tokenize_Line,
|
|
GIB_Parse_Execute_Line,
|
|
GIB_Buffer_Construct,
|
|
GIB_Buffer_Destruct,
|
|
GIB_Buffer_Reset
|
|
};
|
|
|
|
/*
|
|
GIB_Escaped
|
|
|
|
Returns true if character i in str is
|
|
escaped with a backslash (and the backslash
|
|
is not itself escaped).
|
|
*/
|
|
inline qboolean
|
|
GIB_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;
|
|
}
|
|
|
|
|
|
/*
|
|
GIB_Parse_Match_Dquote
|
|
|
|
Progresses an index variable through a string
|
|
until a double quote is reached. Returns
|
|
0 on success, or the character it could not
|
|
match otherwise
|
|
*/
|
|
static char
|
|
GIB_Parse_Match_Dquote (const char *str, unsigned int *i)
|
|
{
|
|
unsigned int n = *i;
|
|
for ((*i)++; str[*i]; (*i)++) {
|
|
if (str[*i] == '\n')
|
|
break;
|
|
else if (str[*i] == '\"' && !GIB_Escaped (str, *i))
|
|
return 0;
|
|
}
|
|
*i = n;
|
|
return '\"';
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Match_Brace
|
|
|
|
Progresses an index variable through a string
|
|
until a closing brace (}) is reached. Calls
|
|
other matching functions to skip areas of a
|
|
string it does not want to handle. Returns
|
|
0 on success, or the character it could not
|
|
match otherwise.
|
|
*/
|
|
char
|
|
GIB_Parse_Match_Brace (const char *str, unsigned int *i)
|
|
{
|
|
char c;
|
|
unsigned int n = *i;
|
|
for ((*i)++; str[*i]; (*i)++) {
|
|
if (str[*i] == '\"') {
|
|
if ((c = GIB_Parse_Match_Dquote (str, i)))
|
|
return c;
|
|
} else if (str[*i] == '{') {
|
|
if ((c = GIB_Parse_Match_Brace (str, i)))
|
|
return c;
|
|
} else if (str[*i] == '}')
|
|
return 0;
|
|
}
|
|
*i = n;
|
|
return '{';
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Match_Paren
|
|
|
|
Progresses an index variable through a string
|
|
until a closing parenthesis is reached. Calls
|
|
other matching functions to skip areas of a
|
|
string it does not want to handle. Returns
|
|
0 on success, or the character it could not
|
|
match otherwise.
|
|
*/
|
|
char
|
|
GIB_Parse_Match_Paren (const char *str, unsigned int *i)
|
|
{
|
|
char c;
|
|
unsigned int n = *i;
|
|
for ((*i)++; str[*i]; (*i)++) {
|
|
if (str[*i] == '(') {
|
|
if ((c = GIB_Parse_Match_Paren (str, i)))
|
|
return c;
|
|
} else if (str[*i] == '\"') {
|
|
if ((c = GIB_Parse_Match_Dquote (str, i)))
|
|
return c;
|
|
} else if (str[*i] == ')')
|
|
return 0;
|
|
}
|
|
*i = n;
|
|
return '(';
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Match_Backtick
|
|
|
|
Progresses an index variable through a string
|
|
until a backtick (`) is reached. Calls
|
|
other matching functions to skip areas of a
|
|
string it does not want to handle. Returns
|
|
0 on success, or the character it could not
|
|
match otherwise.
|
|
*/
|
|
char
|
|
GIB_Parse_Match_Backtick (const char *str, unsigned int *i)
|
|
{
|
|
char c;
|
|
unsigned int n = *i;
|
|
for ((*i)++; str[*i]; (*i)++) {
|
|
if (str[*i] == '`')
|
|
return 0;
|
|
else if (str[*i] == '\"') { // Skip over strings as usual
|
|
if ((c = GIB_Parse_Match_Dquote (str, i)))
|
|
return c;
|
|
}
|
|
}
|
|
*i = n;
|
|
return '`';
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Match_Index
|
|
|
|
Progresses an index variable through a string
|
|
until a normal brace (]) is reached. Calls
|
|
other matching functions to skip areas of a
|
|
string it does not want to handle. Returns
|
|
0 on success, or the character it could not
|
|
match otherwise.
|
|
*/
|
|
char
|
|
GIB_Parse_Match_Index (const char *str, unsigned int *i)
|
|
{
|
|
unsigned int n = *i;
|
|
for ((*i)++; str[*i]; (*i)++) {
|
|
if (str[*i] == ']')
|
|
return 0;
|
|
}
|
|
*i = n;
|
|
return '[';
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Strip_Comments
|
|
|
|
Finds instances of // comments in a cbuf
|
|
outside of double quotes and snips between
|
|
the // and the next newline or the end of
|
|
the string.
|
|
*/
|
|
void
|
|
GIB_Parse_Strip_Comments (struct cbuf_s *cbuf)
|
|
{
|
|
unsigned int i;
|
|
dstring_t *dstr = cbuf->buf;
|
|
char c, *n;
|
|
|
|
for (i = 0; dstr->str[i]; i++) {
|
|
if (dstr->str[i] == '\"') {
|
|
if ((c = GIB_Parse_Match_Dquote (dstr->str, &i)))
|
|
// We don't care about parse errors here.
|
|
// Let the parser sort it out later.
|
|
return;
|
|
} else if (dstr->str[i] == '/' && dstr->str[i+1] == '/') {
|
|
if ((n = strchr (dstr->str+i, '\n'))) {
|
|
dstring_snip (dstr, i, n-dstr->str-i);
|
|
i--;
|
|
} else {
|
|
dstring_snip (dstr, i, strlen(dstr->str+i));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Extract_Line
|
|
|
|
Extracts the next command from a cbuf and
|
|
deposits it in cbuf->line, removing the
|
|
portion used from the buffer
|
|
*/
|
|
void
|
|
GIB_Parse_Extract_Line (struct cbuf_s *cbuf)
|
|
{
|
|
unsigned int i;
|
|
char c;
|
|
dstring_t *dstr = cbuf->buf;
|
|
|
|
if (GIB_DATA(cbuf)->ret.waiting ) // Not ready for the next line
|
|
return;
|
|
|
|
dstring_clearstr (cbuf->line);
|
|
|
|
for (i = 0; dstr->str[i]; i++) {
|
|
if (dstr->str[i] == '{') {
|
|
if ((c = GIB_Parse_Match_Brace (dstr->str, &i)))
|
|
goto PARSE_ERROR;
|
|
} else if (dstr->str[i] == '\"') {
|
|
if ((c = GIB_Parse_Match_Dquote (dstr->str, &i)))
|
|
goto PARSE_ERROR;
|
|
} else if (dstr->str[i] == '\n' || dstr->str[i] == ';')
|
|
break;
|
|
}
|
|
|
|
if (dstr->str[0]) { // If something is left in the buffer
|
|
if (i)// If we used any of it
|
|
dstring_insert (cbuf->line, 0, dstr->str, i);
|
|
// Clip out what we used
|
|
while (isspace ((byte) dstr->str[i]) || dstr->str[i] == ';') // Eliminate white space/null statements
|
|
i++;
|
|
dstring_snip (dstr, 0, i);
|
|
}
|
|
return;
|
|
|
|
PARSE_ERROR: // Extract out the line where the parse error occurred
|
|
|
|
for (; i && dstr->str[i] != '\n'; i--);
|
|
if (dstr->str[i] == '\n')
|
|
i++;
|
|
dstring_clearstr (cbuf->line);
|
|
dstring_appendstr (cbuf->line, dstr->str+i);
|
|
Cbuf_Error ("parse", "Could not find match for %c.", c);
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Get_Token
|
|
|
|
Progresses an index variable through a string
|
|
until the end of the next token is found.
|
|
Stores the token in dstr with or without
|
|
wrapping characters as specified by include_delim.
|
|
Returns 0 on error or the wrapping character of
|
|
the token otherwise
|
|
*/
|
|
inline static char
|
|
GIB_Parse_Get_Token (const char *str, unsigned int *i, dstring_t *dstr, qboolean include_delim)
|
|
{
|
|
int n;
|
|
char c;
|
|
|
|
n = *i; // Save start position
|
|
if (str[*i] == '\"') {
|
|
if ((c = GIB_Parse_Match_Dquote (str, i))) {
|
|
Cbuf_Error ("parse", "Could not find matching %c", c);
|
|
return 0; // Parse error
|
|
} else {
|
|
dstring_insert (dstr, 0, str+n+!include_delim, *i-n+1-!include_delim-!include_delim);
|
|
return '\"';
|
|
}
|
|
} else if (str[*i] == '{') {
|
|
if ((c = GIB_Parse_Match_Brace (str, i))) {
|
|
Cbuf_Error ("parse", "Could not find matching %c", c);
|
|
return 0; // Parse error
|
|
} else {
|
|
dstring_insert (dstr, 0, str+n+!include_delim, *i-n+1-!include_delim-!include_delim);
|
|
return '{';
|
|
}
|
|
} else if (str[*i] == '(') {
|
|
if ((c = GIB_Parse_Match_Paren (str, i))) {
|
|
Cbuf_Error ("parse", "Could not find matching %c", c);
|
|
return 0; // Parse error
|
|
} else {
|
|
dstring_insert (dstr, 0, str+n+!include_delim, *i-n+1-!include_delim-!include_delim);
|
|
return '(';
|
|
}
|
|
} else {
|
|
while (str[*i] && !isspace((byte)str[*i]) && str[*i] != ',') { // find end of token
|
|
if (str[*i] == '`') {
|
|
if ((c = GIB_Parse_Match_Backtick (str, i))) {
|
|
Cbuf_Error ("parse", "Could not find matching %c", c);
|
|
return 0; // Parse error
|
|
}
|
|
} else if (str[*i] == '(') {
|
|
if ((c = GIB_Parse_Match_Paren (str, i))) {
|
|
Cbuf_Error ("parse", "Could not find matching %c", c);
|
|
return 0; // Parse error
|
|
}
|
|
} else if (str[*i] == '{') {
|
|
if ((c = GIB_Parse_Match_Brace (str, i))) {
|
|
Cbuf_Error ("parse", "Could not find matching %c", c);
|
|
return 0; // Parse error
|
|
}
|
|
}
|
|
(*i)++;
|
|
}
|
|
dstring_insert (dstr, 0, str+n, *i-n);
|
|
return ' ';
|
|
}
|
|
return 0; // We should never get here
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Generate_Composite
|
|
|
|
Concatenates all parsed arguments in cbuf
|
|
into a single string and creates an array
|
|
of pointers to the beginnings of tokens
|
|
within it.
|
|
*/
|
|
inline static void
|
|
GIB_Parse_Generate_Composite (struct cbuf_s *cbuf)
|
|
{
|
|
cbuf_args_t *args = cbuf->args;
|
|
int i;
|
|
|
|
dstring_clearstr (GIB_DATA (cbuf)->arg_composite);
|
|
for (i = 0; i < args->argc; i++) {
|
|
// ->str could be moved by realloc when a dstring is resized
|
|
// so save the offset instead of the pointer
|
|
args->args[i] = (const char *) strlen (GIB_DATA (cbuf)->arg_composite->str);
|
|
dstring_appendstr (GIB_DATA (cbuf)->arg_composite, args->argv[i]->str);
|
|
dstring_appendstr (GIB_DATA (cbuf)->arg_composite, " ");
|
|
}
|
|
|
|
// Get rid of trailing space
|
|
GIB_DATA (cbuf)->arg_composite->str[strlen(GIB_DATA (cbuf)->arg_composite->str)-1] = 0;
|
|
|
|
for (i = 0; i < args->argc; i++)
|
|
// now that arg_composite is done we can add the pointer to the stored
|
|
// offsets and get valid pointers. This *should* be portable.
|
|
args->args[i] += (unsigned long int) GIB_DATA (cbuf)->arg_composite->str;
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Add_Token
|
|
|
|
Adds a new token to the argument list args
|
|
or concatenates it to the end of the last
|
|
according to cat.
|
|
*/
|
|
inline static void
|
|
GIB_Parse_Add_Token (struct cbuf_args_s *args, qboolean cat, dstring_t *token)
|
|
{
|
|
if (cat) {
|
|
dstring_appendstr (args->argv[args->argc-1], token->str);
|
|
} else
|
|
Cbuf_ArgsAdd (args, token->str);
|
|
}
|
|
|
|
/*
|
|
GIB_Parse_Tokenize_Line
|
|
|
|
Tokenizes and processes an extracted command,
|
|
producing an argument list suitable for executing
|
|
the command. This function can be interrupted
|
|
to call a GIB subroutine, in which case it will
|
|
save important variables and start where it left
|
|
off when called again.
|
|
*/
|
|
void
|
|
GIB_Parse_Tokenize_Line (struct cbuf_s *cbuf)
|
|
{
|
|
dstring_t *arg = GIB_DATA(cbuf)->current_token;
|
|
const char *str = cbuf->line->str;
|
|
cbuf_args_t *args = cbuf->args;
|
|
qboolean cat = false;
|
|
int noprocess;
|
|
char delim;
|
|
int i;
|
|
|
|
// This function can be interrupted to call a GIB
|
|
// subroutine. First we need to clean up anything
|
|
// we left unfinished
|
|
|
|
// Do we have a left-over token that needs processing?
|
|
|
|
if (GIB_DATA(cbuf)->ret.waiting) {
|
|
if (GIB_Process_Token (arg, GIB_DATA(cbuf)->ret.delim))
|
|
return; // Still not done, or error
|
|
GIB_Parse_Add_Token (cbuf->args, GIB_DATA(cbuf)->ret.cat, arg);
|
|
i = GIB_DATA(cbuf)->ret.line_pos; // Start tokenizing where we left off
|
|
noprocess = GIB_DATA(cbuf)->ret.noprocess ? 1 : 0; // Past second token, no sense in having it be 2
|
|
} else {
|
|
args->argc = 0; // Start from scratch
|
|
i = 0;
|
|
noprocess = 0;
|
|
}
|
|
|
|
while (str[i]) {
|
|
while (isspace((byte)str[i])) // Eliminate whitespace
|
|
i++;
|
|
if (!str[i]) // Blank token
|
|
break;
|
|
if (str[i] == ',') { // Concatenation
|
|
cat = true;
|
|
i++;
|
|
continue;
|
|
}
|
|
dstring_clearstr (arg);
|
|
delim = GIB_Parse_Get_Token (str, &i, arg, noprocess == 1);
|
|
if (!delim)
|
|
break;
|
|
if (delim != ' ') // Move into whitespace if we haven't already
|
|
i++;
|
|
|
|
if (noprocess != 1)
|
|
if (GIB_Process_Token (arg, delim))
|
|
goto FILTER_ERROR; // Error or GIB subroutine needs to be called
|
|
|
|
if (noprocess > 1)
|
|
noprocess--;
|
|
|
|
GIB_Parse_Add_Token (cbuf->args, cat, arg);
|
|
if (cat)
|
|
cat = false;
|
|
|
|
|
|
if (cbuf->args->argc == 1) {
|
|
gib_builtin_t *b;
|
|
if ((b = GIB_Builtin_Find (cbuf->args->argv[0]->str)))
|
|
noprocess = b->type;
|
|
}
|
|
}
|
|
GIB_Parse_Generate_Composite (cbuf);
|
|
return;
|
|
FILTER_ERROR:
|
|
GIB_DATA(cbuf)->ret.line_pos = i; // save our information in case
|
|
GIB_DATA(cbuf)->ret.cat = cat; // error is not fatal
|
|
GIB_DATA(cbuf)->ret.delim = delim;
|
|
GIB_DATA(cbuf)->ret.noprocess = noprocess;
|
|
return;
|
|
}
|
|
|
|
static inline qboolean
|
|
GIB_Parse_Execute_Varexp (cbuf_t *cbuf)
|
|
{
|
|
char *s;
|
|
const char *v;
|
|
double newval, other;
|
|
cbuf_args_t *args = cbuf->args;
|
|
if (args->argc == 1) {
|
|
s = args->argv[0]->str + strlen(args->argv[0]->str) - 2;
|
|
if (!strcmp (s, "++") || !strcmp (s, "--")) {
|
|
*s = 0;
|
|
if ((v = GIB_Var_Get (cbuf, args->argv[0]->str)))
|
|
newval = atof (v);
|
|
else
|
|
newval = 0.0;
|
|
switch (s[1]) {
|
|
case '+':
|
|
newval++;
|
|
break;
|
|
case '-':
|
|
newval--;
|
|
}
|
|
s = va("%.10g", newval);
|
|
GIB_Var_Set (cbuf, args->argv[0]->str, s);
|
|
GIB_Return (s);
|
|
return true;
|
|
} else
|
|
return false;
|
|
} else if (args->argc == 3 && strlen(args->argv[1]->str) <= 2 && strchr(args->argv[1]->str, '=')) {
|
|
s = args->argv[1]->str;
|
|
if (*s == '=') {
|
|
GIB_Return (args->argv[2]->str);
|
|
GIB_Var_Set (cbuf, args->argv[0]->str, args->argv[2]->str);
|
|
return true;
|
|
} else if (s[1] == '=') {
|
|
if ((v = GIB_Var_Get (cbuf, args->argv[0]->str)))
|
|
newval = atof (v);
|
|
else
|
|
newval = 0.0;
|
|
other = atof (args->argv[2]->str);
|
|
switch (*s) {
|
|
case '+':
|
|
newval += other;
|
|
break;
|
|
case '-':
|
|
newval -= other;
|
|
break;
|
|
case '*':
|
|
newval *= other;
|
|
break;
|
|
case '/':
|
|
newval /= other;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
s = va("%.10g", newval);
|
|
GIB_Return (s);
|
|
GIB_Var_Set (cbuf, args->argv[0]->str, s);
|
|
return true;
|
|
} else
|
|
return false;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
GIB_Parse_Execute_Line
|
|
|
|
After an argument list has been created,
|
|
this function executes the appropriate command,
|
|
searching in this order:
|
|
|
|
GIB builtins
|
|
GIB functions
|
|
Assignment to a local/global variable
|
|
Normal quake console commands
|
|
*/
|
|
void
|
|
GIB_Parse_Execute_Line (cbuf_t *cbuf)
|
|
{
|
|
cbuf_args_t *args = cbuf->args;
|
|
gib_builtin_t *b;
|
|
gib_function_t *f;
|
|
|
|
if ((b = GIB_Builtin_Find (args->argv[0]->str)))
|
|
b->func ();
|
|
else if ((f = GIB_Function_Find (args->argv[0]->str))) {
|
|
cbuf_t *sub = Cbuf_New (&gib_interp);
|
|
GIB_Function_Execute (sub, f, cbuf_active->args);
|
|
cbuf_active->down = sub;
|
|
sub->up = cbuf_active;
|
|
cbuf_active->state = CBUF_STATE_STACK;
|
|
} else if (!GIB_Parse_Execute_Varexp(cbuf))
|
|
Cmd_Command (cbuf->args);
|
|
dstring_clearstr (cbuf->line);
|
|
// If this is a looping buffer and it is now empty,
|
|
// copy the loop program back in
|
|
if (GIB_DATA(cbuf)->type == GIB_BUFFER_LOOP && !cbuf->buf->str[0])
|
|
Cbuf_AddText (cbuf, GIB_DATA(cbuf)->loop_program->str);
|
|
}
|
|
|