Merge branch 'MDL-28133' of git://github.com/timhunt/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 30 Jun 2011 23:10:06 +0000 (01:10 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 30 Jun 2011 23:10:06 +0000 (01:10 +0200)
lib/evalmath/evalmath.class.php
lib/simpletest/testmathslib.php

index 0597f18..aa5539f 100644 (file)
@@ -135,7 +135,7 @@ class EvalMath {
         if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
         //===============
         // is it a variable assignment?
-        if (preg_match('/^\s*([a-z][a-z0-9]*)\s*=\s*(.+)$/', $expr, $matches)) {
+        if (preg_match('/^\s*('.self::$namepat.')\s*=\s*(.+)$/', $expr, $matches)) {
             if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
                 return $this->trigger(get_string('cannotassigntoconstant', 'mathslib', $matches[1]));
             }
@@ -144,7 +144,7 @@ class EvalMath {
             return $this->v[$matches[1]]; // and return the resulting value
         //===============
         // is it a function assignment?
-        } elseif (preg_match('/^\s*([a-z][a-z0-9]*)\s*\(\s*([a-z][a-z0-9]*(?:\s*,\s*[a-z][a-z0-9]*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
+        } elseif (preg_match('/^\s*('.self::$namepat.')\s*\(\s*('.self::$namepat.'(?:\s*,\s*'.self::$namepat.')*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
             $fnn = $matches[1]; // get the function name
             if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
                 return $this->trigger(get_string('cannotredefinebuiltinfunction', 'mathslib', $matches[1]));
@@ -153,7 +153,7 @@ class EvalMath {
             if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
             for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
                 $token = $stack[$i];
-                if (preg_match('/^[a-z][a-z0-9]*$/', $token) and !in_array($token, $args)) {
+                if (preg_match('/^'.self::$namepat.'$/', $token) and !in_array($token, $args)) {
                     if (array_key_exists($token, $this->v)) {
                         $stack[$i] = $this->v[$token];
                     } else {
@@ -212,7 +212,7 @@ class EvalMath {
         while(1) { // 1 Infinite Loop ;)
             $op = substr($expr, $index, 1); // get the first character at the current index
             // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
-            $ex = preg_match('/^([a-z][a-z0-9]*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
+            $ex = preg_match('/^('.self::$namepat.'\(?|\d+(?:\.\d*)?(?:(e[+-]?)\d*)?|\.\d+|\()/', substr($expr, $index), $match);
             //===============
             if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
                 $stack->push('_'); // put a negation on the stack
@@ -243,7 +243,7 @@ class EvalMath {
                     if (is_null($o2)) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
                     else $output[] = $o2;
                 }
-                if (preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(2), $matches)) { // did we just close a function?
+                if (preg_match('/^('.self::$namepat.')\($/', $stack->last(2), $matches)) { // did we just close a function?
                     $fnn = $matches[1]; // get the function name
                     $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
                     $fn = $stack->pop();
@@ -283,7 +283,7 @@ class EvalMath {
                     else $output[] = $o2; // pop the argument expression stuff and push onto the output
                 }
                 // make sure there was a function
-                if (!preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(2), $matches))
+                if (!preg_match('/^('.self::$namepat.')\($/', $stack->last(2), $matches))
                     return $this->trigger(get_string('unexpectedcomma', 'mathslib'));
                 $stack->push($stack->pop()+1); // increment the argument count
                 $stack->push('('); // put the ( back on, we'll need to pop back to it again
@@ -298,7 +298,7 @@ class EvalMath {
             } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
                 $expecting_op = true;
                 $val = $match[1];
-                if (preg_match("/^([a-z][a-z0-9]*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
+                if (preg_match('/^('.self::$namepat.')\($/', $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
                     if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f) or array_key_exists($matches[1], $this->fc)) { // it's a func
                         $stack->push($val);
                         $stack->push(1);
@@ -316,7 +316,7 @@ class EvalMath {
             } elseif ($op == ')') {
                 //it could be only custom function with no params or general error
                 if ($stack->last() != '(' or $stack->last(2) != 1) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
-                if (preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(3), $matches)) { // did we just close a function?
+                if (preg_match('/^('.self::$namepat.')\($/', $stack->last(3), $matches)) { // did we just close a function?
                     $stack->pop();// (
                     $stack->pop();// 1
                     $fn = $stack->pop();
@@ -383,8 +383,7 @@ class EvalMath {
                     for ($i = $count-1; $i >= 0; $i--) {
                         if (is_null($args[] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
                     }
-                    $classname = 'EvalMathCalcEmul_'.$fnn;
-                    $res = call_user_func(array($classname, 'calculate'), $args);
+                    $res = call_user_func_array(array('EvalMathFuncs', $fnn), array_reverse($args));
                     if ($res === FALSE) {
                         return $this->trigger(get_string('internalerror', 'mathslib'));
                     }
@@ -473,16 +472,15 @@ class EvalMathStack {
 
 
 // spreadsheet functions emulation
-// watch out for reversed args!!
-class EvalMathCalcEmul_average {
+class EvalMathFuncs {
 
-    static function calculate($args) {
-        return (EvalMathCalcEmul_sum::calculate($args)/count($args));
+    static function average() {
+        $args = func_get_args();
+        return (call_user_func_array(array('self', 'sum'), $args) / count($args));
     }
-}
 
-class EvalMathCalcEmul_max  {
-    static function calculate($args) {
+    static function max() {
+        $args = func_get_args();
         $res = array_pop($args);
         foreach($args as $a) {
             if ($res < $a) {
@@ -491,10 +489,9 @@ class EvalMathCalcEmul_max  {
         }
         return $res;
     }
-}
 
-class EvalMathCalcEmul_min  {
-    static function calculate($args) {
+    static function min() {
+        $args = func_get_args();
         $res = array_pop($args);
         foreach($args as $a) {
             if ($res > $a) {
@@ -503,42 +500,32 @@ class EvalMathCalcEmul_min  {
         }
         return $res;
     }
-}
-class EvalMathCalcEmul_mod {
-    static function calculate($args) {
-        return $args[1] % $args[0];
+
+    static function mod($op1, $op2) {
+        return $op1 % $op2;
     }
-}
-class EvalMathCalcEmul_pi {
-    static function calculate($args) {
+
+    static function pi() {
         return pi();
     }
-}
-class EvalMathCalcEmul_power {
-    static function calculate($args) {
-        return $args[1]^$args[0];
+
+    static function power($op1, $op2) {
+        return pow($op1, $op2);
     }
-}
 
-class EvalMathCalcEmul_round {
-    static function calculate($args) {
-        if (count($args)==1) {
-            return round($args[0]);
-        } else {
-            return round($args[1], $args[0]);
-        }
+    static function round($val, $precision = 0) {
+        return round($val, $precision);
     }
-}
-class EvalMathCalcEmul_sum {
-    static function calculate($args) {
+
+    static function sum() {
+        $args = func_get_args();
         $res = 0;
         foreach($args as $a) {
            $res += $a;
         }
         return $res;
     }
-}
-class EvalMathCalcEmul_randomised {
+
     protected static $randomseed = null;
 
     static function set_random_seed($randomseed) {
@@ -553,12 +540,7 @@ class EvalMathCalcEmul_randomised {
         }
     }
 
-}
-
-class EvalMathCalcEmul_rand_int extends EvalMathCalcEmul_randomised {
-    static function calculate($args){
-        $min = $args[1];
-        $max = $args[0];
+    static function rand_int($min, $max){
         if ($min >= $max) {
             return false; //error
         }
@@ -574,9 +556,8 @@ class EvalMathCalcEmul_rand_int extends EvalMathCalcEmul_randomised {
         } while (($min + $randomno) > $max);
         return $min + $randomno;
     }
-}
-class EvalMathCalcEmul_rand_float extends EvalMathCalcEmul_randomised {
-    static function calculate(){
+
+    static function rand_float(){
         $randomvalue = array_shift(unpack('v', md5(self::get_random_seed(), true)));
         return $randomvalue / 65536;
     }
index a17bd0a..f1cfb56 100644 (file)
@@ -1,7 +1,20 @@
 <?php
-
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+    die('Direct access to this script is forbidden.');///  It must be included from a Moodle page
 }
 
 require_once($CFG->libdir . '/mathslib.php');
@@ -19,7 +32,7 @@ class mathsslib_test extends UnitTestCase {
     /**
      * Tests the basic formula evaluation
      */
-    function test__basic() {
+    public function test__basic() {
         $formula = new calc_formula('=1+2');
         $res = $formula->evaluate();
         $this->assertEqual($res, 3, '3+1 is: %s');
@@ -28,8 +41,8 @@ class mathsslib_test extends UnitTestCase {
     /**
      * Tests the formula params
      */
-    function test__params() {
-        $formula = new calc_formula('=a+b+c', array('a'=>10,'b'=>20,'c'=>30));
+    public function test__params() {
+        $formula = new calc_formula('=a+b+c', array('a'=>10, 'b'=>20, 'c'=>30));
         $res = $formula->evaluate();
         $this->assertEqual($res, 60, '10+20+30 is: %s');
     }
@@ -37,11 +50,11 @@ class mathsslib_test extends UnitTestCase {
     /**
      * Tests the changed params
      */
-    function test__changing_params() {
-        $formula = new calc_formula('=a+b+c', array('a'=>10,'b'=>20,'c'=>30));
+    public function test__changing_params() {
+        $formula = new calc_formula('=a+b+c', array('a'=>10, 'b'=>20, 'c'=>30));
         $res = $formula->evaluate();
         $this->assertEqual($res, 60, '10+20+30 is: %s');
-        $formula->set_params(array('a'=>1,'b'=>2,'c'=>3));
+        $formula->set_params(array('a'=>1, 'b'=>2, 'c'=>3));
         $res = $formula->evaluate();
         $this->assertEqual($res, 6, 'changed params 1+2+3 is: %s');
     }
@@ -49,20 +62,31 @@ class mathsslib_test extends UnitTestCase {
     /**
      * Tests the spreadsheet emulation function in formula
      */
-    function test__calc_function() {
-        $formula = new calc_formula('=sum(a,b,c)', array('a'=>10,'b'=>20,'c'=>30));
+    public function test__calc_function() {
+        $formula = new calc_formula('=sum(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30));
         $res = $formula->evaluate();
-        $this->assertEqual($res, 60, 'sum(a,b,c) is: %s');
+        $this->assertEqual($res, 60, 'sum(a, b, c) is: %s');
+    }
+
+    public function test_other_functions() {
+        $formula = new calc_formula('=average(1,2,3)');
+        $this->assertEqual($formula->evaluate(), 2);
+
+        $formula = new calc_formula('=mod(10,3)');
+        $this->assertEqual($formula->evaluate(), 1);
+
+        $formula = new calc_formula('=power(2,3)');
+        $this->assertEqual($formula->evaluate(), 8);
     }
 
     /**
      * Tests the min and max functions
      */
-    function test__minmax_function() {
-        $formula = new calc_formula('=min(a,b,c)', array('a'=>10,'b'=>20,'c'=>30));
+    public function test__minmax_function() {
+        $formula = new calc_formula('=min(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30));
         $res = $formula->evaluate();
         $this->assertEqual($res, 10, 'minimum is: %s');
-        $formula = new calc_formula('=max(a,b,c)', array('a'=>10,'b'=>20,'c'=>30));
+        $formula = new calc_formula('=max(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30));
         $res = $formula->evaluate();
         $this->assertEqual($res, 30, 'maximum is: %s');
     }
@@ -70,8 +94,8 @@ class mathsslib_test extends UnitTestCase {
     /**
      * Tests special chars
      */
-    function test__specialchars() {
-        $formula = new calc_formula('=gi1 + gi2 + gi11', array('gi1'=>10,'gi2'=>20,'gi11'=>30));
+    public function test__specialchars() {
+        $formula = new calc_formula('=gi1 + gi2 + gi11', array('gi1'=>10, 'gi2'=>20, 'gi11'=>30));
         $res = $formula->evaluate();
         $this->assertEqual($res, 60, 'sum is: %s');
     }
@@ -79,49 +103,52 @@ class mathsslib_test extends UnitTestCase {
     /**
      * Tests some slightly more complex expressions
      */
-    function test__more_complex_expressions() {
+    public function test__more_complex_expressions() {
         $formula = new calc_formula('=pi() + a', array('a'=>10));
         $res = $formula->evaluate();
         $this->assertEqual($res, pi()+10);
         $formula = new calc_formula('=pi()^a', array('a'=>10));
         $res = $formula->evaluate();
-        $this->assertEqual($res, pow(pi(),10));
+        $this->assertEqual($res, pow(pi(), 10));
         $formula = new calc_formula('=-8*(5/2)^2*(1-sqrt(4))-8');
         $res = $formula->evaluate();
-        $this->assertEqual($res, -8*pow((5/2),2)*(1-sqrt(4))-8);
+        $this->assertEqual($res, -8*pow((5/2), 2)*(1-sqrt(4))-8);
     }
 
     /**
      * Tests some slightly more complex expressions
      */
-    function test__error_handling() {
-        if (debugging('', DEBUG_DEVELOPER)){
+    public function test__error_handling() {
+        if (debugging('', DEBUG_DEVELOPER)) {
             $this->expectError();
         }
         $formula = new calc_formula('=pi( + a', array('a'=>10));
         $res = $formula->evaluate();
         $this->assertEqual($res, false);
-        $this->assertEqual($formula->get_error(), get_string('unexpectedoperator', 'mathslib', '+'));
+        $this->assertEqual($formula->get_error(),
+                                        get_string('unexpectedoperator', 'mathslib', '+'));
 
-        if (debugging('', DEBUG_DEVELOPER)){
+        if (debugging('', DEBUG_DEVELOPER)) {
             $this->expectError();
         }
         $formula = new calc_formula('=pi(');
         $res = $formula->evaluate();
         $this->assertEqual($res, false);
-        $this->assertEqual($formula->get_error(), get_string('expectingaclosingbracket', 'mathslib'));
+        $this->assertEqual($formula->get_error(),
+                                        get_string('expectingaclosingbracket', 'mathslib'));
 
-        if (debugging('', DEBUG_DEVELOPER)){
+        if (debugging('', DEBUG_DEVELOPER)) {
             $this->expectError();
         }
         $formula = new calc_formula('=pi()^');
         $res = $formula->evaluate();
         $this->assertEqual($res, false);
-        $this->assertEqual($formula->get_error(), get_string('operatorlacksoperand', 'mathslib', '^'));
+        $this->assertEqual($formula->get_error(),
+                                        get_string('operatorlacksoperand', 'mathslib', '^'));
 
     }
 
-    function test_rounding_function() {
+    public function test_rounding_function() {
         $formula = new calc_formula('=round(2.5)');
         $this->assertEqual($formula->evaluate(), 3);
 
@@ -140,7 +167,6 @@ class mathsslib_test extends UnitTestCase {
         $formula = new calc_formula('=round(-2.5)');
         $this->assertEqual($formula->evaluate(), -3);
 
-
         $formula = new calc_formula('=ceil(2.5)');
         $this->assertEqual($formula->evaluate(), 3);
 
@@ -159,7 +185,6 @@ class mathsslib_test extends UnitTestCase {
         $formula = new calc_formula('=ceil(-2.5)');
         $this->assertEqual($formula->evaluate(), -2);
 
-
         $formula = new calc_formula('=floor(2.5)');
         $this->assertEqual($formula->evaluate(), 2);
 
@@ -180,6 +205,26 @@ class mathsslib_test extends UnitTestCase {
 
     }
 
+    public function test_scientific_notation() {
+        $formula = new calc_formula('=10e10');
+        $this->assertWithinMargin($formula->evaluate(), 1e11, 1e11*1e-15);
+
+        $formula = new calc_formula('=10e-10');
+        $this->assertWithinMargin($formula->evaluate(), 1e-9, 1e11*1e-15);
+
+        $formula = new calc_formula('=10e+10');
+        $this->assertWithinMargin($formula->evaluate(), 1e11, 1e11*1e-15);
+
+        $formula = new calc_formula('=10e10*5');
+        $this->assertWithinMargin($formula->evaluate(), 5e11, 1e11*1e-15);
+
+        $formula = new calc_formula('=10e10^2');
+        $this->assertWithinMargin($formula->evaluate(), 1e22, 1e22*1e-15);
+
+    }
+
+
+
 }