m.E.M.E - Minimalist Evaluator for Mathematical Expressions
Posted: Tue Apr 08, 2008 4:47 pm
on filefront
Code: Select all
*****************************************************************
** m.E.M.E **
** - **
** Minimalist Evaluator for Mathematical Expressions **
*****************************************************************
#-----------------------------------------------------------#
# TITLE : m.E.M.E #
# Minimalist Evaluator for Mathematical Expressions #
# TYPE : Utility #
# VERSION : 0.1 #
# AUTHOR : Gamall Wednesday Ida #
# E-MAIL : gamall.ida@gmail.com #
# WEBSITE : http://gamall-ida.com #
# #
# FILESIZE : ~800 Ko #
# OS : Linux, Mac, Windows #
# RELEASE DATE : March 2008 #
#-----------------------------------------------------------#
+ TABLE OF CONTENTS
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-o +
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. BRIEF DESCRIPTION
2. WHAT IS AN EXPRESSION?
-Literals and simple expressions
-Bound expressions
-Units
-Logic and conditional expressions
3. THE STANDALONE PROGRAM
-The run modes
-Application of 'replace' mode to
Quake configuration files.
4. THE PACKAGE
5. CONTACT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ BRIEF DESCRIPTION
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-o +
m.E.M.E (pronounce m-ee-m-ee) stands for "Minimalist Evaluator
for Mathematical Expressions". And that is exactly what it is.
'Evaluator for Mathematical Expressions' because its purpose is
to compute mathematical expressions. For instance "2+2" yields
4. Less trivially, "let x = pi/2 in 1+ sqrt cos (x/2)" yields
1.84089641525. It is meant to be used as a library for other
programs, but the standalone executable can be used as a
command-line calculator and a preprocessor for configuration
files.
'Minimalist' because there are points which make it simpler and
faster (it is *very* fast) but limit its range:
-> It uses floating-point arithmetic (64bits, as per OCaml
`float' type) instead of exact arithmetic, hence a loss of
precision on some expressions:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4.63246686343E-08 + 1
+ exp(2+sin(log(cos(
acos(-pi/2+tan(atan(pi))-1)))))
/sqrt(pi*5E15)
> 1.00000009265
exp(2+sin(log(cos(
acos(-pi/2+tan(atan(pi))-1)))))
/sqrt(pi*5E15)
- 4.63246686343E-08 + 1
> 1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-> It supports variables but not functions. So while you can
write
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = pi/2 in 1+ sqrt cos (x/2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... you can't write anything like
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let f(x) = 1+ sqrt cos (x/2) in f(pi/2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-> It does not support any kind of loops. So for instance there
is no sum (big Sigma symbol in math) or product (big Pi
symbol), because that kind of functionality couldn't be added
elegantly to the program. This is a consequence of the previous
point.
-> It is not, of course, a system for symbolic computation.
+ WHAT IS AN EXPRESSION?
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-o +
Here I'll define informally what kinds of expressions mEME
recognises and computes. Formally, the complete grammar
generating the language of mEME can be found in the file
mathparser.mly, and all the lexems are in mathlexer.mll.
So, what is an expression ?
- Literals and simple expressions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
Numbers, such as "2" and "3.14", are expressions. So is "2 +
3.14", and any combination of numbers, operators, and functions
(among those supported) which makes sense.
Most common functions and operators, and a few more exotic
ones, are supported. Refer to the aforementionned files for a
complete list.
- Bound expressions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
Variables can be declared (that is, a value is bound to an
identifier) in an expression. The syntax is as follows:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = VALUE in EXPRESSION(x)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note: `in' can be replaced by `;', and `let' is optional.
Of course, the expression doesn't *have* to depend on x, and
can define other variables. Multiple declarations can also be
made:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = VALUE and y = VALUE and ... in EXPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note: `and' can be replaced by `,'
Note: The values are computed from left to right, so `y' can
depend on `x'.
Here is an example, convoluted and stupid, but coined to show
some non-trivial things about variables:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = 1, y = 5 in
(z=pi; 2) + x / (let a = 666/y in z**2- a*x)
> 1.99189169876
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the same expression as
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 + 1/( pi**2 - (666/5)*1 )
> 1.99189169876
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Or is it ? Well, not exactly. If you ask mEME to show you its
internal variables table after that (provided you didn't stop
the session after computing this)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@vars
Bound variables:
x = 1
y = 5
z = 3.14159265359
a = 133.2
Total: 4 variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...you will see that the variables are *still* there, and can
be used again or redefined in subsequent expressions.
This is where the term "bound expression" and the "let .. in"
syntax are somewhat misleading, since the variables are not
local to the expression in which they are defined. In fact,
they are defined everywhere to the *right* of the definition.
In the expression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = 1, y = 5 in
(z=pi; 2) + x / (let a = 666/y in z**2- a*x)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
this gives sense to "z**2". If z had been local to the
expression for which it was defined, z could not be used there.
By the way, you will also have noticed how strange the
expression "z=pi; 2" is:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let z = pi in 2
> 2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is just an expression where the bound variable is not
used. It amounts to a value, but with a change of state. But
does that mean that you have to return a value even if you just
want to define a variable? Of course not. This notion of
`change of state without value' is called a `Unit', just like
in ML.
- Units
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
Let's consider our silly example one more time. It could be
written by computing first
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x=1, y=5, z=pi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and then
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 + x / (z**2 - [666/y]*x)
> 1.99189169876
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You will notice that "let x=1, y=5, z=pi" does not seem to
yield any result. In fact it *does* yield the result "()",
which means `Unit', but the program is set to not print units
by default. If you activate unit printing you get
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./meme pu
let x=1, y=5, z=pi
> ()
@vars
Bound variables:
x = 1.000000
y = 5.000000
z = 3.141593
Total: 3 variables
> ()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`Unit' simply means: "There is no result to return, but
something has been done, like creating a variable or printing
something to the screen".
"()" is also an expression, which you can use. In fact
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x=1, y=5, z=pi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
is equivalent to
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x=1, y=5, z=pi in ()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Units are contagious, so any expression with a unit in it
becomes a unit.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 + ()/2
> ()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Beware, there is a catch: how many expressions do you think are
defined by the expression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 + x=1; () + (y = 2 in y)
> ()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The answer is: only one, because the parser stops as soon as it
meets (). It doesn't even read the right part of the
expression, so `y' is never defined.
A consequence of this is that an incorrect expression with a
unit in it can yield () instead of Error.
There are other kinds of side-effects, such as the print
function: 'print x' yields x, but also prints it on STDOUT.
Print has a very strong priority, and can be useful to display
intermediate results in a long expression.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = cos( print pi / 2 ) in x
3.14159265359
> 6.12303176911e-17
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Logic and conditional expressions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
Booleans are supported in a very dirty way, similar to that of
languages such as C, C++ and their ilk; since there is only one
datatype in mEME (floating point), the boolean values TRUE and
FALSE are defined as 1 and 0 respectively.
More specifically, the same point of view than in C is taken:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x = true <==> x is not 0
x = false <==> x is not true
<==> x = 0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With this in mind here is the syntax for conditional
expressions:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if EXPRESSION then EXPRESSION_TRUE
else EXPRESSION_FALSE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Of course, those combine naturally into "if...then... else
if... ...else ..." constructs.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x = 3
> ()
if x == 2 then 10 else if x == 3 then 20 else 90
> 20
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
the ternary operator, more compact but less readable, is also
supported:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EXPRESSION? EXPRESSION_TRUE : EXPRESSION_FALSE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is important to note that the if...then...else construct is
an *expression*, and has a *value*, unlike the equivalent in
imperative programming languages.
It is also worth mentionning that there is no short-circuit:
*both* EXPRESSION_TRUE and EXPRESSION_FALSE are evaluated, no
matter which one will be returned. Consider the following
examples:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if true then [x=6 in x] else [y=8 in y]
> 6
@vars
Bound variables:
x = 6.000000
y = 8.000000
Total: 2 variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Though the rightmost expression, [y=8 in y], is not returned,
it has been evaluated and the variable `y' has been created.
This becomes funny when you start involving units:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if condition then () else [a=5 in a]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
will return () and *not* define `a' regardless of the value of
`condition'...
Now, a few words of warning about those "floating-point
booleans"... let us say we are working with trigonometric
functions, for instance, and write the following:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = Pi/2 in if cos x then 1 else 2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Formally, this expression is supposed to yield 2, because
cos(Pi / 2) is 0, and 0 is false. But, when this expression is
run through mEME:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = Pi/2 in if cos x then 1 else 2
> 1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... it returns 1. Why? Is something broken in mEME? No. This is
a problem common to everything that uses floating-point
calculus: since the numbers are approximated, it is hard to
determine if they are formally the same. All you know is
whether they have the same approximated value, and as it
happens
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cos (Pi/2)
> 6.12303176911e-17
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... the approximation of pi combined with the series
approximating the cosine function yield a very small but
definitely non-zero value, therefore
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cos (Pi/2) == 0
> 0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cosine of Pi/2 is not zero as far as if...then...else
expressions go.
The right way to handle that kind of cases is to specify a
range around zero:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let x = cos(Pi/2) and notzero = |x| > E-10 in
if notzero then 1 else 2
> 1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|x| is the absolute value of x, of course. And this time it
works as expected.
Last word: the boolean operators are the same than in C: || for
`or' && for `and', == for `equals', and the usual <, >, <=, >=.
`different from' is noted <>, != or |=. `not' is written either
"not" or "!". There is also a `xor' operator: ^^
When in doubt about the syntax, try things. There are many
synonyms for everything. All identifiers are in the lexer
source file mathlexer.mll.
+ THE STANDALONE PROGRAM
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-o +
- The run modes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
First, let us see the help page:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./meme --help
m.E.M.E - Minimalist Evaluator for Mathematical Expressions
usage: calc [options] [mode]
modes:
+command-line (c) sequentially evaluate command-line arguments
+stdin (s) evaluate expressions from standard input
+replace (r) search and replace expressions inside a line (on stdin)
options: (modes c and s)
--print-xp (pxp) reprint the unevaluated expression
--print-newline (pn) goto newline before printing the result
--print-unit (pu) print units, ie. stand-alone assignments
--print-symbol (ps) print a symbol before the result
options (mode s)
--display-welcome (dw)
Display the welcome message at beginning of session
options (modes s and r)
--eval-now (en) xp1...xpN
Evaluate a list of command-line expressions before processing stdin.
options (mode r)
--delimiters (del) <left del> <right del>
Set the delimiters for mathematical expressions.
options (all modes)
--eval-file (ef) <str>
Silently parse a file. Useful to define a bunch of variables.
--unit-string (us) <str>
Set the string representing a unit (pure side-effect evaluation)
--print-errors (pe)
The parser prints debug error messages on stderr
--show-error (se)
The parser prints a line with ^^^ where the error is
--help
Print this help page.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are three main ways to run the mEME executable. Firstly,
you can run it in STDIN mode. Expressions will be read from the
standard input. Example:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./meme
** m.E.M.E. v0.1
** by Gamall Wednesday Ida
** email : gamall.ida@gmail.com
** web : gamall-ida.com
let x = cos(Pi/2) and notzero = |x| > E-10
@vars
Bound variables:
x = 6.12303176911e-17
notzero = 0
Total: 2 variables
sin [x*pi]
> 1.92360716235e-16
@quit
Bye-bye!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... or from a file:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./meme < testexpr
** m.E.M.E. v0.1
** by Gamall Wednesday Ida
** email : gamall.ida@gmail.com
** web : gamall-ida.com
> 2440
{End of file reached}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The second mode is the COMMAND-LINE mode, where command-line
arguments are run in a sequence:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./meme c "x = 5" "pi = x" "cos (x*pi)**2"
pi = x
^
{Parsing failed on pos 3: `='}
{Perhaps you are trying to bind a variable with a reserved name, such as `pi', `log' etc.}
> Error.
> -0.12476077673
$ ./meme c "x = 5" "cos (x*pi)**2"
> -0.12476077673
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Notice the error messages, which are printed to stderr, not
stdout. There are options to control how much is displayed in
case of errors.
The third mode is the REPLACE mode, in which mEME filters STDIN
line by line to find expressions inside any text, processes all
expressions, and returns the original line on STDOUT, replacing
the expression with its value. The default delimiters for
expressions are [[...]], but can be changed to anything.
Most of the time, this mode will be used reading from a file
(we will see a detailed application to configuration files
later on), so let us write a small text file, repex.txt, to
illustrate this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Hello !
Nothing Happens because there is no expression...
The value of Pi is [[pi]]
Let x be [[x = 25 in x]], and y = [[y = x**2 in y]] its square.
Ouups the order of evaluation in one line is unspecified!
[[x = 25, y = x**2]]
Let x be [[x]], and y = [[y]] its square.
Or
Let x be [[x = 25, y = x**2 in x]], and y = [[y]] its square.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This yields the following result: (again, note that the error
messages are on STDERR, not STDOUT, so if you do $ ./meme r <
repex.txt > result you will just see the errors.)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./meme r < repex.txt
Hello !
Nothing Happens because there is no expression...
The value of Pi is 3.14159265359
y = x**2 in y
^
{Parsing failed on pos 4: `x'}
{Unbound variable: `x'}
Let x be 25, and y = Error. its square.
Ouups the order of evaluation in one line is unspecified!
()
Let x be 25, and y = 625 its square.
Or
Let x be 25, and y = 625 its square.
{End of file reached}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There is a subtelty there in the order of the evaluation of
expressions written on the same line, which causes the error in
`y = x**2': the longuest expression is evaluated first. In
general, avoid relying on the order of evaluation: use [[let
... and ...]] or span declarations on several lines if the
expressions gradually depend on one another.
- Application of 'replace' mode
- to Quake configuration files.
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
This system can be used to preprocess configuration files, with
a view to inject some flexibility into otherwise static text
files. I will take some examples in Quake configuration files
for the Jedi Knight game servers, but the same ideas can be
applied to many systems.
Quake CFG files are absolutely static: there is no `if' or
anything like that. You just bind values to names, or CVARS
(Configuration VARiables). `value', in that context, means
`string'; numbers are directly converted from the string by the
game. So if you write
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
seta x 2
or
seta x "2"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and the game looks for an integer in `x', it will use the
integer value 2. Up to there everything is fine but let us
suppose you write
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
seta x "2+2"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
well then things begin to go downhill. The meaning of 2+2 is
quite clear to you: if x is seen as a number, then this "means"
4. But the game only sees the string of the characters 2, then
+, then 2. If it tries to get an integer out of an expression,
it will either fail or yield an incorrect result.
In fact, I would bet that "2+2" would be intepreted by the game
as... 2, because it probably uses the standard C atoi()
function to do its string -> int conversion, which simply stops
at the first non-numerical character it reads.
But then, if you use mEME to preprocess your configuration
file,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
seta x [[2+2]]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
will become
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
seta x 4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and the problem is solved.
Hallelujah! but WHY would you want to write things like 2+2 in
your configuration file? Because you don't just get the ability
to evaluate 2+2, you get all the mEME arsenal, namely standard
math, variables, logic and conditional expressions.
A configuration file is just a place where tons of options are
dumped. These options are not often, in practice, totally
independant from one another. Generally, when you set out to
modify your configuration file, there are groups of options you
want to alter together, or whose values obviously depend on one
another.
In short, some configuration variables are mathematically
and/or logically linked. (for your purposes at least)
But, as long as the system does not directly implement this
reliationship between them, you have to modify everything by
hand, which makes combining ideas difficult.
Time for some examples: first, a well-known example of a
strictly mathematical relationship between cvars in Quake
servers: the number of clients and the maximum rate. These two
parameters are two sides of the same coin, linked by the
formula
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
max rate =
speed of your connection
/
(8 * max number of clients)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using the appropriate unit: BPS. Since you are likely to know
the value in KBPS, you also need a small conversion before
applying the formula. Also you might want to put a fixed limit
on the rate.
You can do that by hand, but here is a way to do it
automatically:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EXAMPLE 1: Max rate and clients: A mathematical relation
// set the connection speed (in kbps): [[ let connspeed = 512, rcs = 1000*connspeed ]]
// set the number of clients on your server: [[ clients = 16 ]]
// set the maximum rate you want to allow: [[ maxrate = 30000 ]]
// get the rate as a function of the number of clients you wish to allow
seta sv_maxclients = [[clients]]
seta sv_maxrate = [[rate = floor rcs / (clients * 8) in if rate < maxrate then rate else maxrate]]
// a tad more original approach: get the clients as a function of the rate you desire
seta sv_maxrate = [[let rate = 4000 in rate]]
seta sv_maxclients = [[res = floor rcs / (8 * rate) in res <= 32 ? res : 32]]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Once you have set up the formulae, you just need to change what
you *want*, here the number of clients (or connspeed if you
change your modem), and the rate will compute by itself. Once
run through mEME, this file yields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./meme r < zaza.cfg
// EXAMPLE 1: Max rate and clients: A mathematical relation
// set the connection speed (in kbps): ()
// set the number of clients on your server: ()
// set the maximum rate you want to allow: ()
// get the rate as a function of the number of clients you wish to allow
seta sv_maxclients = 16
seta sv_maxrate = 4000
// a tad more original approach: get the clients as a function of the rate you desire
seta sv_maxrate = 4000
seta sv_maxclients = 16
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Notice that the formulae when the variables are declared have
been replaced by a unit: "()". The variable declarations were
in the comments, but those are useless know, and might grow
beyond the size limit on CFG files (around 16Ko if I remember
right). There are two ways around this: The first is to make
every computation in another file, loaded by the --eval-file
option, and the second to filter the output of mEME, using a
configuration cleaner like
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
http://www.gamall-ida.com/f/viewtopic.php?f=3&t=391
->
http://www.gamall-ida.com/f/viewtopic.php?p=4847#p4847
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and then the output comes stripped of all but the values.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./meme r < zaza.cfg | ./confclean
seta sv_maxclients = 16
seta sv_maxrate = 4000
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let us take another, more common example: bitvalues. Quick
reminder: a bitvalue is a number which is interpreted as a set
of options, activated or deactivated. This is done by reading
the numbers in base two, as a series of 0 and 1, each position
(bit) corresponding to an option.
Bitvalues are commonly used in configuration files, for
instance in JKA, to select the weapons and force powers to be
allowed on the server, and in various mods such as JKA to
determine access to admin privileges and so on.
The problem with bitvalues is that they are very impractical to
use. First when you want to write one. You have to remember the
value associated to each option, and compute their sum. And
then, when you want to modify your configuration file, you have
a *very* hard time remembering what the hell the value 9726693
means...
Programs out there, bivalues calculators, such as the one on
japlus.net, take partially care of those problems: to compute a
bitvalue you merely have to check boxes, and then you copy and
paste the result to your configuration file.
Still, none of those I know of take care of the other problem:
when you already have a bitvalue and want to know *what it
means*. For instance, is such or such admin provilege allowed
to Knights? You simply can't tell, unless you manually break
the number.
A trivial fix would be to make those programs check the
appropriate boxes when you paste a value. But there is a better
way: using mEME to define the values, and then sum them
directly in the configuration file.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EXAMPLE 2: Force powers (and any other bitvalue, the principle is the same...)
// Define the constants for force powers:
// [[heal = 1, jump = 2, speed = 2**2, push = 2**3, pull = 2**4,
mind_trick = 2**5, grip = 2**6, light = 2**7, rage = 2**8,
protect = 2**9, absorb = 2**10, team_heal = 2**11,
team_energize = 2**12]]
// [[drain = 2**13, seeing = 2**14, sab_attack = 2**15,
sab_defend = 2**16, sab_throw = 2**17]]
// then you can write the powers directly... you don't need a
calculator and you can see at first glance what your
configuration file really means.
seta g_forcePowerDisable = [[speed+push+pull+grip+drain]]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Which will be computed as
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
seta g_forcePowerDisable = 8284
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I have written a small file (jka.meme) containing declarations
for the most common JKA and JA+ bitvalues. If you load this
file with mEME, you can then write the bitvalues naturally
without needing anything in your configuration file:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Bitvalues computation examples
seta g_forcePowerDisable = [[lightning + drain + rage]]
seta g_weaponDisable = [[all_weapons - saber - bryar - blaster]]
seta jp_votesDisable = [[sleep + kick + silence]]
seta jp_councilAllowedCMD = [[all_admin]]
seta jp_knightAllowedCMD = [[all_admin - amweather - ammerc]]
seta jp_instructorAllowedCMD = [[amstatus + amorigin + amtele + amwhois + amvstr]]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
becomes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./meme ef jka.meme r < bitvals.cfg
{Evaluating file `jka.meme'}
// Bitvalues computation examples
seta g_forcePowerDisable = 8576
seta g_weaponDisable = 524230
seta jp_votesDisable = 2592
seta jp_councilAllowedCMD = 1073741822
seta jp_knightAllowedCMD = 536805374
seta jp_instructorAllowedCMD = 270270480
{End of file reached}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... it's much more convenient, I think, if you want to disable
'ammap' for your Knights, to just type '- ammap' rather than
going to a bitvalue calculator, trying to find out what the
current value means and whether or not ammap is activated, then
modifying it accordingly and pasting it again in the CFG.
And you can do some nice things, like defining the privileges
of one group in function of those of another:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
seta jp_councilAllowedCMD =
[[let adm_council = all_admin in adm_council]]
seta jp_knightAllowedCMD =
[[let adm_knight = adm_council - amweather - ammerc in adm_knight]]
seta jp_instructorAllowedCMD =
[[let adm_ins = adm_knight -amkick -amban -amempower -ammap in adm_ins]]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then if you happen to deactivate a feature for admins, it will
be automatically deactivated for knights and instructors.
You might also want to create variables to store groups of
options, for instance "adm_punitive = amban + amkick +
amslap..." and then simply use those variables.
Two more ideas:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EXAMPLE 3: Options
// Define "big" options: [[let bigop1 = true, bigop2 = false]] ... and little options follow logically
// for instance, this option will be activated if both the big options are, BUT if the rate is small, it will be activated regardless of the options...
seta someop = [[bigop1 && bigop2 || rate < 5000]]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EXAMPLE 4: Two random colours (distinct with a [[ 100* 6/7 ]]% probability)
// [[col1 = floor #7+1]]
// [[col2 = let x = floor #7+1 in x == col1 ? floor #7+1: x]]
seta message = "Here ^[[col1]]is a ^[[col2]]randomly^[[col1]] colored^[[col2]] message!"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ THE PACKAGE
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-o +
- The Source Code
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
The source code for m.E.M.E is released under the GNU General
Public License.
This program is written in Objective Caml, using ocamllex and
ocamlyacc to build the lexer and the parser.
- The Binaries
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~o -
m.E.M.E is shipped with statically compiled binaries for
GNU/Linux, which may or may not work on your own system. If
they don't you will just have to compile it from source. You
will need an OCaml compiler. In most cases, you will just need
to run the build script in the /src directory.
The Windows binary should work on any Windows system from 95
on.
+ CONTACT
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-o +
If you need help, or have suggestions, comments, insults,
praise or in general, anything to say about this program you
expect me to read and answer to, please post on the program's
topic on my website:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
http://gamall-ida.com/f/viewtopic.php?f=3&t=396
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Do NOT, *EVER*, contact me by mail unless I specifically ask
you to do so, or if my forum is unavailable for a long time.
+ END OF FILE
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-o +
+-----------------------------+
| File generated with 'GaTeX',|
| an ASCII typesetting system |
| by Gamall Wednesday Ida. |
| http://gamall-ida.com |
+-----------------------------+
Build: Tue Apr 15 15:53:03 2008
File : F:readme-mEME.GaTeX.source