quakeforge/libs/util/gib_parse.c
Brian Koropoff 876eaa467c Added a few useful builtins and began adding GIB hooks into quakeworld.
The player global branch variable is now updated with useful information
and the location.get builtin is available to return the current location
of the player based on loc files.  Fixed a bug with zooming out in zoom.gib.
2002-08-24 05:14:46 +00:00

488 lines
12 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
*/
static const char rcsid[] =
"$Id$";
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <ctype.h>
#include <string.h>
#include "QF/sys.h"
#include "QF/dstring.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
void GIB_Parse_Extract_Line (cbuf_t *cbuf);
void GIB_Parse_Tokenize_Line (cbuf_t *cbuf);
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,
};
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
*/
char
GIB_Parse_Match_Dquote (const char *str, unsigned int *i)
{
for ((*i)++; str[*i]; (*i)++) {
if (str[*i] == '\n')
return '\"'; // Newlines should never occur inside quotes, EVER
else if (str[*i] == '\"' && !GIB_Escaped (str, *i))
return 0;
}
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;
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;
}
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;
for ((*i)++; str[*i]; (*i)++) {
if (str[*i] == '(') {
if ((c = GIB_Parse_Match_Paren (str, i)))
return c;
} else if (str[*i] == ')')
return 0;
else if (str[*i] == '\n') // Newlines in math == bad
return '(';
}
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;
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;
}
}
return '`';
}
char
GIB_Parse_Match_Index (const char *str, unsigned int *i)
{
for ((*i)++; str[*i]; (*i)++) {
if (str[*i] == ']')
return 0;
}
return '[';
}
/*
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)
{
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))) {
Cbuf_Error ("parse", "Could not find matching %c", c);
return;
}
} else if (dstr->str[i] == '\"') {
if ((c = GIB_Parse_Match_Dquote (dstr->str, &i))) {
Cbuf_Error ("parse", "Could not find matching %c", c);
return;
}
} else if (dstr->str[i] == '`') {
if ((c = GIB_Parse_Match_Backtick (dstr->str, &i))) {
Cbuf_Error ("parse", "Could not find matching %c", c);
return;
}
} else if (dstr->str[i] == '\n' || dstr->str[i] == ';')
break;
else if (dstr->str[i] == '/' && dstr->str[i+1] == '/') {
char *n;
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;
}
}
}
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);
Sys_DPrintf ("extracted line: %s\n", cbuf->line->str);
}
// Clip out what we used or any leftover newlines or ;s
dstring_snip (dstr, 0, i + (dstr->str[i] == '\n' || dstr->str[i] == ';'));
}
// 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 && !dstr->str[0])
Cbuf_AddText (cbuf, GIB_DATA(cbuf)->loop_program->str);
return;
}
/*
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
}
}
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.
*/
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 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;
Sys_DPrintf("Got token: %s\n", arg->str);
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;
}
/*
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)))
GIB_Function_Execute (f);
else if (args->argc == 3 && !strcmp (args->argv[1]->str, "=")) {
// First, determine global versus local
int glob = 0;
char *c = 0;
if ((c = strchr (args->argv[0]->str, '.'))) // Only check stem
*c = 0;
glob = (!GIB_Var_Get_Local (cbuf, args->argv[0]->str) && GIB_Var_Get_Global (args->argv[0]->str));
if (c)
*c = '.';
if (glob)
GIB_Var_Set_Global (args->argv[0]->str, args->argv[2]->str); // Set the global
else
GIB_Var_Set_Local (cbuf, args->argv[0]->str, args->argv[2]->str); // Set the local
} else
Cmd_Command (cbuf->args);
dstring_clearstr (cbuf->line);
}