Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  
   3  /*
   4  ================================================================================
   5  
   6  EvalMath - PHP Class to safely evaluate math expressions
   7  Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
   8  
   9  ================================================================================
  10  
  11  NAME
  12      EvalMath - safely evaluate math expressions
  13  
  14  SYNOPSIS
  15      <?
  16        include('evalmath.class.php');
  17        $m = new EvalMath;
  18        // basic evaluation:
  19        $result = $m->evaluate('2+2');
  20        // supports: order of operation; parentheses; negation; built-in functions
  21        $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
  22        // create your own variables
  23        $m->evaluate('a = e^(ln(pi))');
  24        // or functions
  25        $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
  26        // and then use them
  27        $result = $m->evaluate('3*f(42,a)');
  28      ?>
  29  
  30  DESCRIPTION
  31      Use the EvalMath class when you want to evaluate mathematical expressions
  32      from untrusted sources.  You can define your own variables and functions,
  33      which are stored in the object.  Try it, it's fun!
  34  
  35  METHODS
  36      $m->evalute($expr)
  37          Evaluates the expression and returns the result.  If an error occurs,
  38          prints a warning and returns false.  If $expr is a function assignment,
  39          returns true on success.
  40  
  41      $m->e($expr)
  42          A synonym for $m->evaluate().
  43  
  44      $m->vars()
  45          Returns an associative array of all user-defined variables and values.
  46  
  47      $m->funcs()
  48          Returns an array of all user-defined functions.
  49  
  50  PARAMETERS
  51      $m->suppress_errors
  52          Set to true to turn off warnings when evaluating expressions
  53  
  54      $m->last_error
  55          If the last evaluation failed, contains a string describing the error.
  56          (Useful when suppress_errors is on).
  57  
  58  AUTHOR INFORMATION
  59      Copyright 2005, Miles Kaufmann.
  60  
  61  LICENSE
  62      Redistribution and use in source and binary forms, with or without
  63      modification, are permitted provided that the following conditions are
  64      met:
  65  
  66      1   Redistributions of source code must retain the above copyright
  67          notice, this list of conditions and the following disclaimer.
  68      2.  Redistributions in binary form must reproduce the above copyright
  69          notice, this list of conditions and the following disclaimer in the
  70          documentation and/or other materials provided with the distribution.
  71      3.  The name of the author may not be used to endorse or promote
  72          products derived from this software without specific prior written
  73          permission.
  74  
  75      THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  76      IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  77      WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  78      DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  79      INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  80      (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  81      SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  82      HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  83      STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  84      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  85      POSSIBILITY OF SUCH DAMAGE.
  86  
  87  */
  88  
  89  /**
  90   * This class was heavily modified in order to get usefull spreadsheet emulation ;-)
  91   * skodak
  92   * This class was modified to allow comparison operators (<, <=, ==, >=, >)
  93   * and synonyms functions (for the 'if' function). See MDL-14274 for more details.
  94   */
  95  
  96  class EvalMath {
  97  
  98      /** @var string Pattern used for a valid function or variable name. Note, var and func names are case insensitive.*/
  99      private static $namepat = '[a-z][a-z0-9_]*';
 100  
 101      var $suppress_errors = false;
 102      var $last_error = null;
 103  
 104      var $v = array(); // variables (and constants)
 105      var $f = array(); // user-defined functions
 106      var $vb = array(); // constants
 107      var $fb = array(  // built-in functions
 108          'sin','sinh','arcsin','asin','arcsinh','asinh',
 109          'cos','cosh','arccos','acos','arccosh','acosh',
 110          'tan','tanh','arctan','atan','arctanh','atanh',
 111          'sqrt','abs','ln','log','exp','floor','ceil');
 112  
 113      var $fc = array( // calc functions emulation
 114          'average'=>array(-1), 'max'=>array(-1),  'min'=>array(-1),
 115          'mod'=>array(2),      'pi'=>array(0),    'power'=>array(2),
 116          'round'=>array(1, 2), 'sum'=>array(-1), 'rand_int'=>array(2),
 117          'rand_float'=>array(0), 'ifthenelse'=>array(3), 'cond_and'=>array(-1), 'cond_or'=>array(-1));
 118      var $fcsynonyms = array('if' => 'ifthenelse', 'and' => 'cond_and', 'or' => 'cond_or');
 119  
 120      var $allowimplicitmultiplication;
 121  
 122      public function __construct($allowconstants = false, $allowimplicitmultiplication = false) {
 123          if ($allowconstants){
 124              $this->v['pi'] = pi();
 125              $this->v['e'] = exp(1);
 126          }
 127          $this->allowimplicitmultiplication = $allowimplicitmultiplication;
 128      }
 129  
 130      /**
 131       * Old syntax of class constructor. Deprecated in PHP7.
 132       *
 133       * @deprecated since Moodle 3.1
 134       */
 135      public function EvalMath($allowconstants = false, $allowimplicitmultiplication = false) {
 136          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 137          self::__construct($allowconstants, $allowimplicitmultiplication);
 138      }
 139  
 140      function e($expr) {
 141          return $this->evaluate($expr);
 142      }
 143  
 144      function evaluate($expr) {
 145          $this->last_error = null;
 146          $expr = trim($expr);
 147          if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
 148          //===============
 149          // is it a variable assignment?
 150          if (preg_match('/^\s*('.self::$namepat.')\s*=\s*(.+)$/', $expr, $matches)) {
 151              if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
 152                  return $this->trigger(get_string('cannotassigntoconstant', 'mathslib', $matches[1]));
 153              }
 154              if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
 155              $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
 156              return $this->v[$matches[1]]; // and return the resulting value
 157          //===============
 158          // is it a function assignment?
 159          } elseif (preg_match('/^\s*('.self::$namepat.')\s*\(\s*('.self::$namepat.'(?:\s*,\s*'.self::$namepat.')*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
 160              $fnn = $matches[1]; // get the function name
 161              if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
 162                  return $this->trigger(get_string('cannotredefinebuiltinfunction', 'mathslib', $matches[1]));
 163              }
 164              $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
 165              if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
 166              for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
 167                  $token = $stack[$i];
 168                  if (preg_match('/^'.self::$namepat.'$/', $token) and !in_array($token, $args)) {
 169                      if (array_key_exists($token, $this->v)) {
 170                          $stack[$i] = $this->v[$token];
 171                      } else {
 172                          return $this->trigger(get_string('undefinedvariableinfunctiondefinition', 'mathslib', $token));
 173                      }
 174                  }
 175              }
 176              $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
 177              return true;
 178          //===============
 179          } else {
 180              return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
 181          }
 182      }
 183  
 184      function vars() {
 185          return $this->v;
 186      }
 187  
 188      function funcs() {
 189          $output = array();
 190          foreach ($this->f as $fnn=>$dat)
 191              $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
 192          return $output;
 193      }
 194  
 195      /**
 196       * @param string $name
 197       * @return boolean Is this a valid var or function name?
 198       */
 199      public static function is_valid_var_or_func_name($name){
 200          return preg_match('/'.self::$namepat.'$/iA', $name);
 201      }
 202  
 203      //===================== HERE BE INTERNAL METHODS ====================\\
 204  
 205      // Convert infix to postfix notation
 206      function nfx($expr) {
 207  
 208          $index = 0;
 209          $stack = new EvalMathStack;
 210          $output = array(); // postfix form of expression, to be passed to pfx()
 211          $expr = trim(strtolower($expr));
 212          // MDL-14274: new operators for comparison added.
 213          $ops   = array('+', '-', '*', '/', '^', '_', '>', '<', '<=', '>=', '==');
 214          $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator?
 215          $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2, '>'=>3, '<'=>3, '<='=>3, '>='=>3, '=='=>3); // operator precedence
 216  
 217          $expecting_op = false; // we use this in syntax-checking the expression
 218                                 // and determining when a - is a negation
 219  
 220          if (preg_match("/[^\w\s+*^\/()\.,-<>=]/", $expr, $matches)) { // make sure the characters are all good
 221              return $this->trigger(get_string('illegalcharactergeneral', 'mathslib', $matches[0]));
 222          }
 223  
 224          while(1) { // 1 Infinite Loop ;)
 225              // MDL-14274 Test two character operators.
 226              $op = substr($expr, $index, 2);
 227              if (!in_array($op, $ops)) {
 228                  // MDL-14274 Get one character operator.
 229                  $op = substr($expr, $index, 1); // get the first character at the current index
 230              }
 231              // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
 232              $ex = preg_match('/^('.self::$namepat.'\(?|\d+(?:\.\d*)?(?:(e[+-]?)\d*)?|\.\d+|\()/', substr($expr, $index), $match);
 233              //===============
 234              if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
 235                  $stack->push('_'); // put a negation on the stack
 236                  $index++;
 237              } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
 238                  return $this->trigger(get_string('illegalcharacterunderscore', 'mathslib')); // but not in the input expression
 239              //===============
 240              } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
 241                  if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
 242                      if (!$this->allowimplicitmultiplication){
 243                          return $this->trigger(get_string('implicitmultiplicationnotallowed', 'mathslib'));
 244                      } else {// it's an implicit multiplication
 245                          $op = '*';
 246                          $index--;
 247                      }
 248                  }
 249                  // heart of the algorithm:
 250                  while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
 251                      $output[] = $stack->pop(); // pop stuff off the stack into the output
 252                  }
 253                  // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
 254                  $stack->push($op); // finally put OUR operator onto the stack
 255                  $index += strlen($op);
 256                  $expecting_op = false;
 257              //===============
 258              } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
 259                  while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
 260                      if (is_null($o2)) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
 261                      else $output[] = $o2;
 262                  }
 263                  if (preg_match('/^('.self::$namepat.')\($/', $stack->last(2), $matches)) { // did we just close a function?
 264                      $fnn = $matches[1]; // get the function name
 265                      $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
 266                      $fn = $stack->pop();
 267                      $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>$arg_count); // send function to output
 268                      if (in_array($fnn, $this->fb)) { // check the argument count
 269                          if($arg_count > 1) {
 270                              $a= new stdClass();
 271                              $a->expected = 1;
 272                              $a->given = $arg_count;
 273                              return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
 274                          }
 275                      } elseif ($this->get_native_function_name($fnn)) {
 276                          $fnn = $this->get_native_function_name($fnn); // Resolve synonyms.
 277  
 278                          $counts = $this->fc[$fnn];
 279                          if (in_array(-1, $counts) and $arg_count > 0) {}
 280                          elseif (!in_array($arg_count, $counts)) {
 281                              $a= new stdClass();
 282                              $a->expected = implode('/',$this->fc[$fnn]);
 283                              $a->given = $arg_count;
 284                              return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
 285                          }
 286                      } elseif (array_key_exists($fnn, $this->f)) {
 287                          if ($arg_count != count($this->f[$fnn]['args'])) {
 288                              $a= new stdClass();
 289                              $a->expected = count($this->f[$fnn]['args']);
 290                              $a->given = $arg_count;
 291                              return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
 292                          }
 293                      } else { // did we somehow push a non-function on the stack? this should never happen
 294                          return $this->trigger(get_string('internalerror', 'mathslib'));
 295                      }
 296                  }
 297                  $index++;
 298              //===============
 299              } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
 300                  while (($o2 = $stack->pop()) != '(') {
 301                      if (is_null($o2)) return $this->trigger(get_string('unexpectedcomma', 'mathslib')); // oops, never had a (
 302                      else $output[] = $o2; // pop the argument expression stuff and push onto the output
 303                  }
 304                  // make sure there was a function
 305                  if (!preg_match('/^('.self::$namepat.')\($/', $stack->last(2), $matches))
 306                      return $this->trigger(get_string('unexpectedcomma', 'mathslib'));
 307                  $stack->push($stack->pop()+1); // increment the argument count
 308                  $stack->push('('); // put the ( back on, we'll need to pop back to it again
 309                  $index++;
 310                  $expecting_op = false;
 311              //===============
 312              } elseif ($op == '(' and !$expecting_op) {
 313                  $stack->push('('); // that was easy
 314                  $index++;
 315                  $allow_neg = true;
 316              //===============
 317              } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
 318                  $expecting_op = true;
 319                  $val = $match[1];
 320                  if (preg_match('/^('.self::$namepat.')\($/', $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
 321                      if (in_array($matches[1], $this->fb) or
 322                                  array_key_exists($matches[1], $this->f) or
 323                                  $this->get_native_function_name($matches[1])){ // it's a func
 324                          $stack->push($val);
 325                          $stack->push(1);
 326                          $stack->push('(');
 327                          $expecting_op = false;
 328                      } else { // it's a var w/ implicit multiplication
 329                          $val = $matches[1];
 330                          $output[] = $val;
 331                      }
 332                  } else { // it's a plain old var or num
 333                      $output[] = $val;
 334                  }
 335                  $index += strlen($val);
 336              //===============
 337              } elseif ($op == ')') {
 338                  //it could be only custom function with no params or general error
 339                  if ($stack->last() != '(' or $stack->last(2) != 1) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
 340                  if (preg_match('/^('.self::$namepat.')\($/', $stack->last(3), $matches)) { // did we just close a function?
 341                      $stack->pop();// (
 342                      $stack->pop();// 1
 343                      $fn = $stack->pop();
 344                      $fnn = $matches[1]; // get the function name
 345                      $fnn = $this->get_native_function_name($fnn); // Resolve synonyms.
 346                      $counts = $this->fc[$fnn];
 347                      if (!in_array(0, $counts)){
 348                          $a= new stdClass();
 349                          $a->expected = $this->fc[$fnn];
 350                          $a->given = 0;
 351                          return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
 352                      }
 353                      $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>0); // send function to output
 354                      $index++;
 355                      $expecting_op = true;
 356                  } else {
 357                      return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
 358                  }
 359              //===============
 360              } elseif (in_array($op, $ops) and !$expecting_op) { // miscellaneous error checking
 361                  return $this->trigger(get_string('unexpectedoperator', 'mathslib', $op));
 362              } else { // I don't even want to know what you did to get here
 363                  return $this->trigger(get_string('anunexpectederroroccured', 'mathslib'));
 364              }
 365              if ($index == strlen($expr)) {
 366                  if (in_array($op, $ops)) { // did we end with an operator? bad.
 367                      return $this->trigger(get_string('operatorlacksoperand', 'mathslib', $op));
 368                  } else {
 369                      break;
 370                  }
 371              }
 372              while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
 373                  $index++;                             // into implicit multiplication if no operator is there)
 374              }
 375  
 376          }
 377          while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
 378              if ($op == '(') return $this->trigger(get_string('expectingaclosingbracket', 'mathslib')); // if there are (s on the stack, ()s were unbalanced
 379              $output[] = $op;
 380          }
 381          return $output;
 382      }
 383      /**
 384       *
 385       * @param string $fnn
 386       * @return string|boolean false if function name unknown.
 387       */
 388      function get_native_function_name($fnn) {
 389          if (array_key_exists($fnn, $this->fcsynonyms)) {
 390              return $this->fcsynonyms[$fnn];
 391          } else if (array_key_exists($fnn, $this->fc)) {
 392              return $fnn;
 393          } else {
 394              return false;
 395          }
 396      }
 397      // evaluate postfix notation
 398      function pfx($tokens, $vars = array()) {
 399  
 400          if ($tokens == false) return false;
 401  
 402          $stack = new EvalMathStack;
 403  
 404          foreach ($tokens as $token) { // nice and easy
 405  
 406              // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
 407              if (is_array($token)) { // it's a function!
 408                  $fnn = $token['fnn'];
 409                  $count = $token['argcount'];
 410                  if (in_array($fnn, $this->fb)) { // built-in function:
 411                      if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
 412                      $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
 413                      if ($fnn == 'ln') $fnn = 'log';
 414                      eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
 415                  } elseif ($this->get_native_function_name($fnn)) { // calc emulation function
 416                      $fnn = $this->get_native_function_name($fnn); // Resolve synonyms.
 417                      // get args
 418                      $args = array();
 419                      for ($i = $count-1; $i >= 0; $i--) {
 420                          if (is_null($args[] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
 421                      }
 422                      $res = call_user_func_array(array('EvalMathFuncs', $fnn), array_reverse($args));
 423                      if ($res === FALSE) {
 424                          return $this->trigger(get_string('internalerror', 'mathslib'));
 425                      }
 426                      $stack->push($res);
 427                  } elseif (array_key_exists($fnn, $this->f)) { // user function
 428                      // get args
 429                      $args = array();
 430                      for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
 431                          if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
 432                      }
 433                      $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
 434                  }
 435              // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
 436              } elseif (in_array($token, array('+', '-', '*', '/', '^', '>', '<', '==', '<=', '>='), true)) {
 437                  if (is_null($op2 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
 438                  if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
 439                  switch ($token) {
 440                      case '+':
 441                          $stack->push($op1+$op2); break;
 442                      case '-':
 443                          $stack->push($op1-$op2); break;
 444                      case '*':
 445                          $stack->push($op1*$op2); break;
 446                      case '/':
 447                          if ($op2 == 0) return $this->trigger(get_string('divisionbyzero', 'mathslib'));
 448                          $stack->push($op1/$op2); break;
 449                      case '^':
 450                          $stack->push(pow($op1, $op2)); break;
 451                      case '>':
 452                          $stack->push((int)($op1 > $op2)); break;
 453                      case '<':
 454                          $stack->push((int)($op1 < $op2)); break;
 455                      case '==':
 456                          $stack->push((int)($op1 == $op2)); break;
 457                      case '<=':
 458                          $stack->push((int)($op1 <= $op2)); break;
 459                      case '>=':
 460                          $stack->push((int)($op1 >= $op2)); break;
 461                  }
 462              // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
 463              } elseif ($token == "_") {
 464                  $stack->push(-1*$stack->pop());
 465              // if the token is a number or variable, push it on the stack
 466              } else {
 467                  if (is_numeric($token)) {
 468                      $stack->push($token);
 469                  } elseif (array_key_exists($token, $this->v)) {
 470                      $stack->push($this->v[$token]);
 471                  } elseif (array_key_exists($token, $vars)) {
 472                      $stack->push($vars[$token]);
 473                  } else {
 474                      return $this->trigger(get_string('undefinedvariable', 'mathslib', $token));
 475                  }
 476              }
 477          }
 478          // when we're out of tokens, the stack should have a single element, the final result
 479          if ($stack->count != 1) return $this->trigger(get_string('internalerror', 'mathslib'));
 480          return $stack->pop();
 481      }
 482  
 483      // trigger an error, but nicely, if need be
 484      function trigger($msg) {
 485          $this->last_error = $msg;
 486          if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
 487          return false;
 488      }
 489  
 490  }
 491  
 492  // for internal use
 493  class EvalMathStack {
 494  
 495      var $stack = array();
 496      var $count = 0;
 497  
 498      function push($val) {
 499          $this->stack[$this->count] = $val;
 500          $this->count++;
 501      }
 502  
 503      function pop() {
 504          if ($this->count > 0) {
 505              $this->count--;
 506              return $this->stack[$this->count];
 507          }
 508          return null;
 509      }
 510  
 511      function last($n=1) {
 512          if ($this->count - $n >= 0) {
 513              return $this->stack[$this->count-$n];
 514          }
 515          return null;
 516      }
 517  }
 518  
 519  
 520  // spreadsheet functions emulation
 521  class EvalMathFuncs {
 522      /**
 523       * MDL-14274 new conditional function.
 524       * @param boolean $condition boolean for conditional.
 525       * @param variant $then value if condition is true.
 526       * @param unknown $else value if condition is false.
 527       * @author Juan Pablo de Castro <juan.pablo.de.castro@gmail.com>
 528       * @return unknown
 529       */
 530      static function ifthenelse($condition, $then, $else) {
 531          if ($condition == true) {
 532              return $then;
 533          } else {
 534              return $else;
 535          }
 536      }
 537  
 538      static function cond_and() {
 539          $args = func_get_args();
 540          foreach($args as $a) {
 541              if ($a == false) {
 542                  return 0;
 543              }
 544          }
 545          return 1;
 546      }
 547  
 548      static function cond_or() {
 549          $args = func_get_args();
 550          foreach($args as $a) {
 551              if($a == true) {
 552                  return 1;
 553              }
 554          }
 555          return 0;
 556      }
 557  
 558      static function average() {
 559          $args = func_get_args();
 560          return (call_user_func_array(array('self', 'sum'), $args) / count($args));
 561      }
 562  
 563      static function max() {
 564          $args = func_get_args();
 565          $res = array_pop($args);
 566          foreach($args as $a) {
 567              if ($res < $a) {
 568                  $res = $a;
 569              }
 570          }
 571          return $res;
 572      }
 573  
 574      static function min() {
 575          $args = func_get_args();
 576          $res = array_pop($args);
 577          foreach($args as $a) {
 578              if ($res > $a) {
 579                  $res = $a;
 580              }
 581          }
 582          return $res;
 583      }
 584  
 585      static function mod($op1, $op2) {
 586          return $op1 % $op2;
 587      }
 588  
 589      static function pi() {
 590          return pi();
 591      }
 592  
 593      static function power($op1, $op2) {
 594          return pow($op1, $op2);
 595      }
 596  
 597      static function round($val, $precision = 0) {
 598          return round($val, $precision);
 599      }
 600  
 601      static function sum() {
 602          $args = func_get_args();
 603          $res = 0;
 604          foreach($args as $a) {
 605             $res += $a;
 606          }
 607          return $res;
 608      }
 609  
 610      protected static $randomseed = null;
 611  
 612      static function set_random_seed($randomseed) {
 613          self::$randomseed = $randomseed;
 614      }
 615  
 616      static function get_random_seed() {
 617          if (is_null(self::$randomseed)){
 618              return microtime();
 619          } else {
 620              return self::$randomseed;
 621          }
 622      }
 623  
 624      static function rand_int($min, $max){
 625          if ($min >= $max) {
 626              return false; //error
 627          }
 628          $noofchars = ceil(log($max + 1 - $min, '16'));
 629          $md5string = md5(self::get_random_seed());
 630          $stringoffset = 0;
 631          do {
 632              while (($stringoffset + $noofchars) > strlen($md5string)){
 633                  $md5string .= md5($md5string);
 634              }
 635              $randomno = hexdec(substr($md5string, $stringoffset, $noofchars));
 636              $stringoffset += $noofchars;
 637          } while (($min + $randomno) > $max);
 638          return $min + $randomno;
 639      }
 640  
 641      static function rand_float() {
 642          $randomvalues = unpack('v', md5(self::get_random_seed(), true));
 643          return array_shift($randomvalues) / 65536;
 644      }
 645  }