Added math evaluation, the ability for commands to span multiple lines

within braces, and put support for comments back in (oops).  To use math
evaluation, put a math expression inside $().  If you have spaces in your
expression, you'll need to enclose the entire thing in quotes so it doesn't
get split up into multiple tokens.
This commit is contained in:
Brian Koropoff 2002-03-17 06:57:03 +00:00
parent aa00b7742c
commit f4180e7ad8
6 changed files with 556 additions and 3 deletions

74
include/QF/exp.h Normal file
View file

@ -0,0 +1,74 @@
/*
EXP equation evaluation routines
Copyright (C) 2000, 2001 Brian Koropoff
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, US
*/
#ifndef __exp_h
#define __exp_h
typedef enum {TOKEN_GENERIC, TOKEN_NUM, TOKEN_OP, TOKEN_OPAREN, TOKEN_CPAREN}
token_type;
typedef enum {EXP_E_NORMAL = 0, EXP_E_PARSE, EXP_E_INVOP, EXP_E_PAREN,
EXP_E_INVPARAM, EXP_E_SYNTAX} exp_error_t;
typedef float (*opfunc) (float op1, float op2);
typedef struct token_generic_s
{
token_type type;
union token_u *prev, *next;
} token_generic;
typedef struct token_num_s
{
token_type type;
union token_u *prev, *next;
float value;
} token_num;
typedef struct token_op_s
{
token_type type;
union token_u *prev, *next;
opfunc func;
} token_op;
typedef union token_u
{
token_generic generic;
token_num num;
token_op op;
} token;
typedef struct optable_s
{
char *str;
opfunc func;
} optable_t;
extern exp_error_t EXP_ERROR;
token *EXP_ParseString (char *str);
void EXP_SimplifyTokens (token *chain);
void EXP_RemoveToken (token *tok);
void EXP_DestroyTokens (token *chain);
float EXP_Evaluate (char *str);
void EXP_InsertTokenAfter (token *spot, token *new);
exp_error_t EXP_Validate (token *chain);
void EXP_PrintTokens (token *chain);
#endif // __exp_h

37
include/QF/ops.h Normal file
View file

@ -0,0 +1,37 @@
/*
EXP equation evaluation routines
Copyright (C) 2000, 2001 Brian Koropoff
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, US
*/
#ifndef __ops_h
#define __ops_h
float OP_Add (float op1, float op2);
float OP_Sub (float op1, float op2);
float OP_Mult (float op1, float op2);
float OP_Div (float op1, float op2);
float OP_Exp (float op1, float op2);
float OP_Eq (float op1, float op2);
float OP_Or (float op1, float op2);
float OP_And (float op1, float op2);
float OP_GreaterThan (float op1, float op2);
float OP_LessThan (float op1, float op2);
float OP_GreaterThanEqual (float op1, float op2);
float OP_LessThanEqual (float op1, float op2);
#endif // __ops_h

View file

@ -29,6 +29,6 @@ libQFutil_la_SOURCES= \
checksum.c cmd.c crc.c cvar.c fendian.c getopt.c getopt1.c hash.c \
info.c link.c mathlib.c mdfour.c msg.c pcx.c plugin.c qargs.c \
qendian.c qfplist.c quakefs.c quakeio.c sizebuf.c string.c sys.c \
tga.c va.c ver_check.c wad.c zone.c dstring.c $(fnmatch)
tga.c va.c ver_check.c wad.c zone.c dstring.c exp.c ops.c $(fnmatch)
EXTRA_DIST= $(asm_src) $(fnmatch_src)

View file

@ -50,6 +50,8 @@ static const char rcsid[] =
#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;
@ -202,7 +204,7 @@ Cbuf_InsertText (const char *text)
void
extract_line (dstring_t *buffer, dstring_t *line)
{
int i, squotes = 0, dquotes = 0, braces = 0;
int i, squotes = 0, dquotes = 0, braces = 0, n;
for (i = 0; buffer->str[i]; i++) {
if (buffer->str[i] == '\'' && !escaped(buffer->str,i) && !dquotes && !braces)
@ -215,6 +217,10 @@ extract_line (dstring_t *buffer, dstring_t *line)
braces++;
if (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
for (n = 0;buffer->str[i+n] != '\n' && buffer->str[i+n] != '\r';n++);
dstring_snip(buffer, i, n);
}
if (buffer->str[i] == '\n' || buffer->str[i] == '\r') {
if (braces)
dstring_snip(buffer, i, 1);
@ -746,6 +752,48 @@ Cmd_ProcessVariables (dstring_t *dstr)
dstring_delete (varname);
}
int
Cmd_ProcessMath (dstring_t *dstr)
{
dstring_t *statement;
int i, n, paren;
float value;
char *temp;
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;;n++) {
if (dstr->str[i+n] == '(')
paren++;
else if (dstr->str[i+n] == ')') {
paren--;
if (!paren)
break;
}
else if (!dstr->str[i+n])
return -1; // Open parentheses, give up
}
/* Copy text between braces 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("%f", value);
dstring_snip (dstr, i, n+1); // Nuke the statement
dstring_insertstr (dstr, temp, i); // Stick in the value
i += strlen(temp) - 1;
}
else
return -2; // Math evaluation error
}
}
dstring_delete (statement);
return 0;
}
/*
Cmd_ProcessEscapes
@ -824,7 +872,7 @@ Cmd_ProcessPercents (dstring_t *dstr)
void
Cmd_TokenizeString (const char *text, qboolean filter)
{
int i = 0, n, len = 0, quotes, braces, space;
int i = 0, n, len = 0, quotes, braces, space, res;
const char *str = text;
cmd_argc = 0;
@ -883,6 +931,17 @@ Cmd_TokenizeString (const char *text, qboolean filter)
if (!braces) {
Cmd_ProcessTags(cmd_argv[cmd_argc-1]);
Cmd_ProcessVariables(cmd_argv[cmd_argc-1]);
res = Cmd_ProcessMath(cmd_argv[cmd_argc-1]);
if (res == -1) {
Sys_Printf("Parse error: Unmatched parenthesis in following line:\n--> %s\n", text);
cmd_argc = 0;
return;
}
if (res == -2) {
Sys_Printf("Math error: Invalid math expression on the following line:\n--> %s\n", text);
cmd_argc = 0;
return;
}
}
Cmd_ProcessEscapes(cmd_argv[cmd_argc-1]);
}

304
libs/util/exp.c Normal file
View file

@ -0,0 +1,304 @@
/*
EXP equation evaluation routines
Copyright (C) 2000, 2001 Brian Koropoff
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, US
*/
#include "QF/exp.h"
#include "QF/ops.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
exp_error_t EXP_ERROR;
optable_t optable[] =
{
{"**", OP_Exp},
{"*", OP_Mult},
{"/", OP_Div},
{"+", OP_Add},
{"-", OP_Sub},
{"==", OP_Eq},
{">=", OP_GreaterThanEqual},
{">", OP_GreaterThan},
{"<=", OP_LessThanEqual},
{"<", OP_LessThan},
{"||", OP_Or},
{"&&", OP_And},
{"", 0}
};
token *EXP_NewToken (void)
{
token *new;
new = malloc(sizeof(token));
if (!new)
return 0;
new->generic.type = TOKEN_GENERIC;
return new;
}
token *EXP_ParseString (char *str)
{
char buf[256];
token *chain, *new, *cur;
int i,n,m;
cur = chain = EXP_NewToken();
chain->generic.type = TOKEN_OPAREN;
chain->generic.prev = 0;
chain->generic.next = 0;
for (i = 0; i < strlen(str); i++)
{
m = 0;
while(str[i] == ' ')
i++;
if (isdigit(str[i]) || str[i] == '.')
{
while ((isdigit(str[i]) || str[i] == '.') && i < strlen(str) && m < 256)
buf[m++] = str[i++];
buf[m] = 0;
new = EXP_NewToken();
new->generic.type = TOKEN_NUM;
new->num.value = atof(buf);
new->generic.prev = cur;
new->generic.next = 0;
cur->generic.next = new;
cur = new;
i--;
}
else if (str[i] == '(')
{
new = EXP_NewToken();
new->generic.type = TOKEN_OPAREN;
new->generic.prev = cur;
new->generic.next = 0;
cur->generic.next = new;
cur = new;
}
else if (str[i] == ')')
{
new = EXP_NewToken();
new->generic.type = TOKEN_CPAREN;
new->generic.prev = cur;
new->generic.next = 0;
cur->generic.next = new;
cur = new;
}
else
{
while(!(isdigit(str[i])) && str[i] != '.' && str[i] != '(' && str[i] != ')' && m < 256)
buf[m++] = str[i++];
buf[m] = 0;
if (m)
{
for (n = 0; optable[n].func; n++)
{
if (!(strncmp(optable[n].str, buf, strlen(optable[n].str))))
{
i -= (m - strlen(optable[n].str) + 1);
new = EXP_NewToken();
new->generic.type = TOKEN_OP;
new->op.func = optable[n].func;
new->generic.prev = cur;
new->generic.next = 0;
cur->generic.next = new;
cur = new;
break;
}
}
if (!(optable[n].func))
return 0;
}
}
}
new = EXP_NewToken();
new->generic.type = TOKEN_CPAREN;
new->generic.prev = cur;
new->generic.next = 0;
cur->generic.next = new;
return chain;
}
void EXP_SimplifyTokens (token *chain)
{
int i;
token *cur;
token *temp;
/* First, get rid of parentheses */
for (cur = chain->generic.next; cur->generic.type != TOKEN_CPAREN; cur = cur->generic.next)
{
if (cur->generic.type == TOKEN_OPAREN)
{
EXP_SimplifyTokens(cur); /* Call ourself to simplify parentheses content */
temp = cur;
cur = cur->generic.next;
EXP_RemoveToken(temp); /* Remove parentheses, leaving value behind */
EXP_RemoveToken(cur->generic.next);
}
}
/* Next, evaluate all operators in order of operations */
for (i = 0; optable[i].func; i++)
{
for (cur = chain->generic.next; cur->generic.type != TOKEN_CPAREN; cur = cur->generic.next)
{
if (cur->generic.type == TOKEN_OP && cur->op.func == optable[i].func && cur->generic.next)
if (cur->generic.prev->generic.type == TOKEN_NUM && cur->generic.next->generic.type == TOKEN_NUM)
{
cur->generic.prev->num.value = optable[i].func(cur->generic.prev->num.value, cur->generic.next->num.value);
temp = cur;
cur = cur->generic.prev;
EXP_RemoveToken(temp->generic.next);
EXP_RemoveToken(temp);
}
}
}
}
void EXP_RemoveToken (token *tok)
{
tok->generic.prev->generic.next = tok->generic.next;
tok->generic.next->generic.prev = tok->generic.prev;
free(tok);
}
void EXP_DestroyTokens (token *chain)
{
token *temp;
for (;chain; chain = temp)
{
temp = chain->generic.next;
free(chain);
}
}
float EXP_Evaluate (char *str)
{
token *chain;
float res;
if (!(chain = EXP_ParseString (str)))
{
EXP_ERROR = EXP_E_PARSE;
return 0;
}
res = EXP_Validate (chain);
if (res)
{
EXP_ERROR = res;
return 0;
}
EXP_SimplifyTokens (chain);
res = chain->generic.next->num.value;
EXP_DestroyTokens (chain);
return res;
}
void EXP_InsertTokenAfter (token *spot, token *new)
{
spot->generic.next->generic.prev = new;
new->generic.next = spot->generic.next;
new->generic.prev = spot;
spot->generic.next = new;
}
exp_error_t EXP_Validate (token *chain)
{
token *cur, *new;
int paren = 0;
for (cur = chain; cur->generic.next; cur = cur->generic.next)
{
if (cur->generic.type == TOKEN_OPAREN)
paren++;
if (cur->generic.type == TOKEN_CPAREN)
paren--;
/* Implied multiplication */
if ((cur->generic.type == TOKEN_NUM && cur->generic.next->generic.type == TOKEN_OPAREN) || /* 5(1+1) */
(cur->generic.type == TOKEN_CPAREN && cur->generic.next->generic.type == TOKEN_NUM) || /* (1+1)5 */
(cur->generic.type == TOKEN_CPAREN && cur->generic.next->generic.type == TOKEN_OPAREN)) /* (1+1)(1+1) */
{
new = EXP_NewToken ();
new->generic.type = TOKEN_OP;
new->op.func = OP_Mult;
EXP_InsertTokenAfter (cur, new);
}
if ((cur->generic.type == TOKEN_OP || cur->generic.type == TOKEN_OPAREN) && cur->generic.next->generic.type == TOKEN_OP)
{
if (cur->generic.next->op.func == OP_Sub) /* Stupid hack for negation */
{
cur = cur->generic.next;
cur->generic.type = TOKEN_NUM;
cur->num.value = -1.0;
new = EXP_NewToken();
new->generic.type = TOKEN_OP;
new->op.func = OP_Mult;
EXP_InsertTokenAfter (cur, new);
}
else
return EXP_E_SYNTAX; /* Operator misuse */
}
else if (cur->generic.type == TOKEN_NUM && cur->generic.next->generic.type == TOKEN_NUM)
return EXP_E_SYNTAX; /* Double number error */
else if (cur->generic.type == TOKEN_OPAREN && cur->generic.next->generic.type == TOKEN_CPAREN)
return EXP_E_PAREN; /* Pointless parentheses */
}
paren--;
if (paren)
return EXP_E_PAREN; /* Paren mismatch */
return 0;
}
void EXP_PrintTokens (token *chain)
{
int m;
for (; chain; chain = chain->generic.next)
switch (chain->generic.type)
{
case TOKEN_OPAREN:
printf("(");
break;
case TOKEN_CPAREN:
printf(")");
break;
case TOKEN_NUM:
printf("%f", chain->num.value);
break;
case TOKEN_OP:
for (m = 0; optable[m].func != chain->op.func; m++);
printf("%s", optable[m].str);
case TOKEN_GENERIC:
break;
}
}

79
libs/util/ops.c Normal file
View file

@ -0,0 +1,79 @@
/*
EXP equation evaluation routines
Copyright (C) 2000, 2001 Brian Koropoff
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, US
*/
#include <math.h>
float OP_Add (float op1, float op2)
{
return op1 + op2;
}
float OP_Sub (float op1, float op2)
{
return op1 - op2;
}
float OP_Mult (float op1, float op2)
{
return op1 * op2;
}
float OP_Div (float op1, float op2)
{
return op1 / op2;
}
float OP_Exp (float op1, float op2)
{
return pow(op1, op2);
}
float OP_Eq (float op1, float op2)
{
return op1 == op2;
}
float OP_Or (float op1, float op2)
{
return op1 || op2;
}
float OP_And (float op1, float op2)
{
return op1 && op2;
}
float OP_GreaterThan (float op1, float op2)
{
return op1 > op2;
}
float OP_LessThan (float op1, float op2)
{
return op1 < op2;
}
float OP_GreaterThanEqual (float op1, float op2)
{
return op1 >= op2;
}
float OP_LessThanEqual (float op1, float op2)
{
return op2 <= op1;
}