m.E.M.E - Minimalist Evaluator for Mathematical Expressions

Miscellaneous programs and scripts, opensource or not, and sometimes, random mathematical stuff.
Post Reply
User avatar
Gamall
Hic sunt dracones
Posts: 4145
Joined: Fri May 26, 2006 11:09 pm
Contact:

m.E.M.E - Minimalist Evaluator for Mathematical Expressions

Post by Gamall » Tue Apr 08, 2008 4:47 pm

->
mEME_Mini-Eval-Math-Exp.zip
(791.17 KiB) Downloaded 467 times
-> 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
{<§ Gamall Wednesday Ida §>}
{ Mods and Programs - Mods TES-IV Oblivion }

User avatar
Gamall
Hic sunt dracones
Posts: 4145
Joined: Fri May 26, 2006 11:09 pm
Contact:

Re: m.E.M.E - Minimalist Evaluator for Mathematical Expressions

Post by Gamall » Tue Apr 15, 2008 6:02 pm

Released 0.1
{<§ Gamall Wednesday Ida §>}
{ Mods and Programs - Mods TES-IV Oblivion }

User avatar
Maikoru
Jedi Perpétuellement Affamé
Posts: 481
Joined: Sun Aug 27, 2006 11:15 pm

Re: m.E.M.E - Minimalist Evaluator for Mathematical Expressions

Post by Maikoru » Tue Apr 22, 2008 12:22 pm

Hey, t'es en première page sur jk3files. ^^
"..." -- Link

User avatar
Gamall
Hic sunt dracones
Posts: 4145
Joined: Fri May 26, 2006 11:09 pm
Contact:

Re: m.E.M.E - Minimalist Evaluator for Mathematical Expressions

Post by Gamall » Tue Apr 22, 2008 3:26 pm

La gloire :hum

edit: make.sh for use as a library:

Code: Select all

ocamlyacc -v mathparser.mly
ocamlopt -c mathparser.mli

ocamllex mathlexer.mll
ocamlopt -for-pack Meme -c toolkit.ml mathlexer.ml mathcommon.ml mathparser.ml  matheval.ml

ocamlopt -pack -o meme.cmx toolkit.cmx mathlexer.cmx mathcommon.cmx mathparser.cmx matheval.cmx
{<§ Gamall Wednesday Ida §>}
{ Mods and Programs - Mods TES-IV Oblivion }

Post Reply

Who is online

Users browsing this forum: No registered users and 30 guests