From f4180e7ad8914fa04f0a07a09116dac991d7de3c Mon Sep 17 00:00:00 2001 From: Brian Koropoff Date: Sun, 17 Mar 2002 06:57:03 +0000 Subject: [PATCH] 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. --- include/QF/exp.h | 74 ++++++++++ include/QF/ops.h | 37 +++++ libs/util/Makefile.am | 2 +- libs/util/cmd.c | 63 ++++++++- libs/util/exp.c | 304 ++++++++++++++++++++++++++++++++++++++++++ libs/util/ops.c | 79 +++++++++++ 6 files changed, 556 insertions(+), 3 deletions(-) create mode 100644 include/QF/exp.h create mode 100644 include/QF/ops.h create mode 100644 libs/util/exp.c create mode 100644 libs/util/ops.c diff --git a/include/QF/exp.h b/include/QF/exp.h new file mode 100644 index 000000000..ff6fee377 --- /dev/null +++ b/include/QF/exp.h @@ -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 diff --git a/include/QF/ops.h b/include/QF/ops.h new file mode 100644 index 000000000..553e9bc7d --- /dev/null +++ b/include/QF/ops.h @@ -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 diff --git a/libs/util/Makefile.am b/libs/util/Makefile.am index 628e6e5ce..e0c89dc69 100644 --- a/libs/util/Makefile.am +++ b/libs/util/Makefile.am @@ -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) diff --git a/libs/util/cmd.c b/libs/util/cmd.c index c296b11ed..e5c65cdf7 100644 --- a/libs/util/cmd.c +++ b/libs/util/cmd.c @@ -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]); } diff --git a/libs/util/exp.c b/libs/util/exp.c new file mode 100644 index 000000000..4037fb86d --- /dev/null +++ b/libs/util/exp.c @@ -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 +#include +#include +#include + +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; + } +} diff --git a/libs/util/ops.c b/libs/util/ops.c new file mode 100644 index 000000000..fd5845bd4 --- /dev/null +++ b/libs/util/ops.c @@ -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 + +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; +}