I am pleased to present a runtime "expression evaluator" (parser) which is intended to allow you to defer to run-time calculations (like, how many seconds a weapon takes to reach its target).
The intention here is that, rather than hard-coding things like:
power_of_weapon = str * 3 + dex / 2
... you could put them into "area files" (or the equivalent) and have them evaluated by the MUD server.
This is done by processing a "string" expression (ie. text) and producing a result. The calculations are done using floating-point numbers (doubles) to give maximum accuracy and allow for fractional results.
Example: 2 + 2
Example: log10 (1234)
The parser builds up results "on the fly" reporting syntax problems by throwing an exception.
Example: 1 + 3 + )
exception: Unexpected token: )
Run-time errors (principally "divide by zero") are also thrown as an exception:
Example: 2 / 0
exception: Divide by zero
Basic arithmetic: + - / *
Example: 1000 + 123
Example: 44 * 55
Expressions are evaluated from left-to-right, with divide and multiply taking precedence over add and subtract.
Example: 2 + 3 * 6
Result: 20 (the multiply is done first)
Parentheses can be used to change evaluation order.
Example: (2 + 3) * 6
Result: 30 (the add is done first)
Whitespace is ignored. However 2-character symbols (like ==) cannot have imbedded spaces.
The parser supports an unlimited number of named symbols (eg. "str", "dex") which can be pre-assigned values, or assigned during use.
Example (in C++):
Parser p; // create a parser instance
p ["str"] = 55; // assign value to "str"
p ["dex"] = 67; // assign value to "dex"
double result = p.Evaluate ("str + dex * 2"); // use in expression
double str = p ["str"]; // retrieve value of "str"
Example: a=42, b=6, a*b
There are two built-in symbols:
pi = 3.1415926535897932385
e = 2.7182818284590452354
Symbols can be any length, and must consist of A-Z, a-z, or 0-9, or the underscore character. They must start with A-Z or a-z. Symbols are case-sensitive.
Pre-loaded symbols, or ones created on-the-fly, can be assigned to, including the standard C operators of +=, -=, *= and /=.
Example: a=42, a/=7
Example: dex = 10, dex += 22
You can compare values for less, greater, greater-or-equal etc. using the normal C operators.
Example: 2 + 3 > 6 + 8
Result: 0 (false)
Example; 2 + 3 < 6 + 8
Result: 1 (true)
The comparison operators are: <, <=, >, >=, ==, !=
These are a lower precedence than arithmetic, in other words addition and subtraction, divide and multiply will be done before comparisons.
You can use AND, OR, and NOT (using these C symbols: &&, ||, ! )
Example: a > 4 && b > 8 // (a > 4 AND b > 8)
Example: a < 10 || b == 5 // (a < 10 OR b == 5)
Example: !(a < 4) // NOT (a < 4)
These are a lower precedence than comparisons, so the examples above will work "naturally".
Various standard scientific functions are supported, by using:
function (argument) or function (argument1, argument2)
Single-argument functions are:
abs acos asin atan atanh ceil cos cosh exp exp floor log log10 sin sinh sqrt tan tanh
These behave as documented for the C runtime library.
Example: sqrt (64)
Note that functions like sin, cos and tan use radians, not degrees. To convert to radians, take degrees and multiply by pi / 180 (the value of pi is built-in).
Example: sin (45 * pi / 180)
Three other functions which are not directly in the standard library are:
int (arg) <-- drops the fractional part
Example: int (1.2)
Example: int (-1.2)
rand (arg) <-- returns a number in the range 0 to arg
The rand function returns an integer (whole number) result, it will never return arg itself.
eg. rand (3): might return: 0, 1 or 2 (and nothing else)
percent (arg) <-- returns true (1.0) arg % of the time, and false (0.0) the rest of the time
eg. percent (40) will be true 40% of the time
Two-argument functions are:
min (arg1, arg2) <-- returns whichever is the lower
max (arg1, arg2) <-- returns whichever is the higher
mod (arg1, arg2) <-- returns the remainder of arg1 / arg2 - throws an exception if arg2 is zero
pow (arg1, arg2) <-- returns arg1 to the power arg2
roll (arg1, arg2) <-- rolls an arg2-sided dice arg1 times.
Example: roll (2, 4) (in other words 2d4)
Finally you can do "if" tests by using the "if" function.
if (test-value, true-value, false-value)
Example: if (a > 5, 22, 33)
Result: if a > 5, returns 22
if a <= 5, returns 33
You can easily expand the inbuilt functions by adding more to the source code using the existing ones as an example.
Distinguishing symbols from functions
In order to allow for the case where people may need to use a symbol that happens to be the name of an inbuilt function (eg. abs = 5, pow += 3), the parser distinguishes functions from symbols by looking ahead for the parenthesis following the function name. This distinction is required otherwise if someone added a user-function one day that happened to be the name of a symbol used in many places, considerably confusion would result.
Example: pow = pow + 3
Result: 3 (pow is a symbol)
Example: pow (4, 3)
Result: 64 (4 to the power 3)
Example: pow = pow (4, 3), pow = pow + 1
In this example we are mixing pow (4, 3) - a function - with pow as a symbol name. Confusing, perhaps, but it works consistently.
Example: abc (20)
Result: (exception) Function 'abc' not implemented.
Using in a program
To use the parser in C++ code, use it like this:
Parser p ("2 + 3 * 6");
double result = p.Evaluate ();
The expression is not evaluated until the Evaluate function is called. If you need change the expression to be evaluated you can do it by passing a string to Evalute, eg.
Parser p ();
double result = p.Evaluate ("2 + 3 * 6");
Variables can be fed in, or retrieved, using operator, like this:
Parser p ();
p ["a"] = 22; // value for symbol "a"
p ["b"] = 33; // value for symbol "b"
double result = p.Evaluate ("c = a + b");
double c = p ["c"]; // retrieve symbol "c"
This effectively lets you not only return a result (the evaluated expression) but change other symbols as side-effects.
Using in fight calculations etc. in a MUD
Basically follow the guidelines above for doing calculations.
You could "feed in" the relevant variables from the player's stats (eg. str, dex, wis).
Parser p ("str * 3 + dex / 3");
p ["str"] = player->str;
p ["dex"] = player->dex;
double result = p.Evalute ();
A fighter fixes an arrow to his bow. He takes between 2 and 8 seconds, depending on his strength:
time_taken = 8 - 6 * (str / max_str)
In this case if his normalised strength is the maximum (1) then the calculation will effectively be:
However if his strength is zero, the calculation will be:
To throw in a bit of luck you might roll a dice. For example:
time_taken = 11 - (6 * (str / max_str)) - roll (1, 3)
This is rolling a 3-sided die once, giving a result in the range 1 to 3.
Thus the worst case would be 10 seconds (11 - 0 - 1) and the best case 2 seconds (11 - 6 - 3).
Maybe 20% of the time he has really bad luck, and takes another 5 seconds:
time_taken = 11 - (6 * (str / max_str)) - roll (1, 3) + (5 * percent (20))
The "percent (20)" part will return true (that is, 1.0) 20% of the time, which will then be multiplied by 5 to add on another 5 seconds.
To use other variables you might add in dexterity, like this:
time_taken = 8 - (6 * (str / max_str)) - (dex / max_dex)
Since "dex / max_dex" will be in the range 0 to 1, then this might shave off another second for very dextrous players. For players with 50% dexterity it would shave off half a second.
The parser is available for anyone to use, at no charge, It is written in C++ and uses STL (the Standard Template Library).
You can download it from:
File size: 8 Kb (source code only). (Now out-of-date).
The latest version is now available on GitHub:
If you have git installed, you can do this to get a copy:
git clone git://github.com/nickgammon/parser.git
It has been compiled without errors or warnings under:
- Cygwin (Windows) - gcc (GCC) 3.3.1 (cygming special)
- Microsoft Visual Studio 6.0
- Linux - gcc (GCC) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)
- Mac OS/X 10.3.5 (Panther) - gcc (GCC) 3.3 20030304 (Apple Computer, Inc. build 1495)
To compile the test program under Cygwin, Linux or Mac OS/X, just run the Makefile (ie. type "make"). To compiler under Visual Studio open the project file parser.dsw.
If you compile as described above you will have a test program (parser, or parser.exe under Windows) which you can run and enter expressions into to test. Note that under Visual C++ it seems to be "a line behind" due to a bug in the standard library. In other words, you need to type 2 expressions before it will evaluate the first. This is only a problem in the test program which you could fix by changing the line:
getline (cin, inputLine);
To use in your own (C++) programs, simply do this:
// and further on
Parser p ("2+2"); // or whatever
double result = p.Evaluate ();
The test program test.cpp can be used as an example of using it in another program.
- Nick Gammon
| top |