]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-9643 evalmath library improvement - sample calc functions
authorskodak <skodak>
Fri, 25 May 2007 06:07:45 +0000 (06:07 +0000)
committerskodak <skodak>
Fri, 25 May 2007 06:07:45 +0000 (06:07 +0000)
lib/evalmath/evalmath.class.php
lib/evalmath/readme_moodle.txt [new file with mode: 0644]

index 8b3f6e0f7053a762ae54138e23726d1001a66ff3..f689e2840d4acadfc182a4d979353d779208a071 100644 (file)
@@ -99,6 +99,9 @@ class EvalMath {
         'cos','cosh','arccos','acos','arccosh','acosh',\r
         'tan','tanh','arctan','atan','arctanh','atanh',\r
         'sqrt','abs','ln','log');\r
+\r
+    var $fc = array( // calc functions emulation\r
+        'sum'=>array(-1), 'pi'=>array(0), 'power'=>array(2), 'round'=>array(2,1), 'average'=>array(-1));\r
     \r
     function EvalMath() {\r
         // make the variables a little more accurate\r
@@ -198,7 +201,8 @@ class EvalMath {
             //===============\r
             } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?\r
                 if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?\r
-                    $op = '*'; $index--; // it's an implicit multiplication\r
+                    return $this->trigger("expecting operand");\r
+                    //$op = '*'; $index--; // it's an implicit multiplication\r
                 }\r
                 // heart of the algorithm:\r
                 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])) {\r
@@ -217,10 +221,16 @@ class EvalMath {
                 if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did we just close a function?\r
                     $fnn = $matches[1]; // get the function name\r
                     $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)\r
-                    $output[] = $stack->pop(); // pop the function and push onto the output\r
+                    $fn = $stack->pop();\r
+                    $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>$arg_count); // send function to output\r
                     if (in_array($fnn, $this->fb)) { // check the argument count\r
                         if($arg_count > 1)\r
                             return $this->trigger("too many arguments ($arg_count given, 1 expected)");\r
+                    } elseif (array_key_exists($fnn, $this->fc)) {\r
+                        $counts = $this->fc[$fnn];\r
+                        if (in_array(-1, $counts) and $arg_count > 0) {}\r
+                        elseif (!in_array($arg_count, $counts))\r
+                            return $this->trigger("wrong number of arguments ($arg_count given, " . implode('/',$this->fc[$fnn]) . " expected)");\r
                     } elseif (array_key_exists($fnn, $this->f)) {\r
                         if ($arg_count != count($this->f[$fnn]['args']))\r
                             return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . " expected)");\r
@@ -252,7 +262,7 @@ class EvalMath {
                 $expecting_op = true;\r
                 $val = $match[1];\r
                 if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...\r
-                    if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func\r
+                    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\r
                         $stack->push($val);\r
                         $stack->push(1);\r
                         $stack->push('(');\r
@@ -266,9 +276,24 @@ class EvalMath {
                 }\r
                 $index += strlen($val);\r
             //===============\r
-            } elseif ($op == ')') { // miscellaneous error checking\r
-                return $this->trigger("unexpected ')'");\r
-            } elseif (in_array($op, $ops) and !$expecting_op) {\r
+            } elseif ($op == ')') {\r
+                //it could be only custom function with no params or general error\r
+                if ($stack->last() != '(' or $stack->last(2) != 1) return $this->trigger("unexpected ')'");\r
+                if (preg_match("/^([a-z]\w*)\($/", $stack->last(3), $matches)) { // did we just close a function?\r
+                    $stack->pop();// (\r
+                    $stack->pop();// 1\r
+                    $fn = $stack->pop();\r
+                    $fnn = $matches[1]; // get the function name\r
+                    $counts = $this->fc[$fnn];\r
+                    if (!in_array(0, $counts))\r
+                        return $this->trigger("wrong number of arguments ($arg_count given, " . implode('/',$this->fc[$fnn]) . " expected)");\r
+                    $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>0); // send function to output\r
+                    $index++;\r
+                } else {\r
+                    return $this->trigger("unexpected ')'");\r
+                }\r
+            //===============\r
+            } elseif (in_array($op, $ops) and !$expecting_op) { // miscellaneous error checking\r
                 return $this->trigger("unexpected operator '$op'");\r
             } else { // I don't even want to know what you did to get here\r
                 return $this->trigger("an unexpected error occured");\r
@@ -300,8 +325,37 @@ class EvalMath {
         $stack = new EvalMathStack;\r
         \r
         foreach ($tokens as $token) { // nice and easy\r
+\r
+            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on\r
+            if (is_array($token)) { // it's a function!\r
+                $fnn = $token['fnn'];\r
+                $count = $token['argcount'];\r
+                if (in_array($fnn, $this->fb)) { // built-in function:\r
+                    if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");\r
+                    $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms\r
+                    if ($fnn == 'ln') $fnn = 'log';\r
+                    eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()\r
+                } elseif (array_key_exists($fnn, $this->fc)) { // calc emulation function\r
+                    // get args\r
+                    $args = array();\r
+                    for ($i = $count-1; $i >= 0; $i--) {\r
+                        if (is_null($args[] = $stack->pop())) return $this->trigger("internal error");\r
+                    }\r
+                    $res = call_user_func(array('EvalMathCalcEmul', $fnn), $args);\r
+                    if ($res == FALSE) {\r
+                        return $this->trigger("internal error");\r
+                    }\r
+                    $stack->push($res);\r
+                } elseif (array_key_exists($fnn, $this->f)) { // user function\r
+                    // get args\r
+                    $args = array();\r
+                    for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {\r
+                        if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");\r
+                    }\r
+                    $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!\r
+                }\r
             // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on\r
-            if (in_array($token, array('+', '-', '*', '/', '^'))) {\r
+            } elseif (in_array($token, array('+', '-', '*', '/', '^'), true)) {\r
                 if (is_null($op2 = $stack->pop())) return $this->trigger("internal error");\r
                 if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");\r
                 switch ($token) {\r
@@ -320,22 +374,6 @@ class EvalMath {
             // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on\r
             } elseif ($token == "_") {\r
                 $stack->push(-1*$stack->pop());\r
-            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on\r
-            } elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!\r
-                $fnn = $matches[1];\r
-                if (in_array($fnn, $this->fb)) { // built-in function:\r
-                    if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");\r
-                    $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms\r
-                    if ($fnn == 'ln') $fnn = 'log';\r
-                    eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()\r
-                } elseif (array_key_exists($fnn, $this->f)) { // user function\r
-                    // get args\r
-                    $args = array();\r
-                    for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {\r
-                        if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");\r
-                    }\r
-                    $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!\r
-                }\r
             // if the token is a number or variable, push it on the stack\r
             } else {\r
                 if (is_numeric($token)) {\r
@@ -386,3 +424,34 @@ class EvalMathStack {
     }\r
 }\r
 \r
+// spreadsheed functions emulation\r
+// watch out for reversed args!!\r
+class EvalMathCalcEmul {\r
+    function average($args) {\r
+        return (EvalMathCalcEmul::sum($args)/count($args));\r
+    }\r
+\r
+    function pi($args) {\r
+        return pi();\r
+    }\r
+\r
+    function power($args) {\r
+        return $args[0]^$args[0];\r
+    }\r
+\r
+    function round($args) {\r
+        if (count($args)==1) {\r
+            return round($args[0]);\r
+        } else {\r
+            return round($args[1], $args[0]);\r
+        }\r
+    }\r
+\r
+    function sum($args) {\r
+        $res = 0;\r
+        foreach($args as $a) {\r
+           $res += $a; \r
+        }\r
+        return $res;\r
+    }\r
+}\r
diff --git a/lib/evalmath/readme_moodle.txt b/lib/evalmath/readme_moodle.txt
new file mode 100644 (file)
index 0000000..2030133
--- /dev/null
@@ -0,0 +1,11 @@
+Description of MathEval library import into Moodle
+
+Our changes:
+* implicit multiplication not allowed
+* new custom calc emulation functions
+
+To see all changes diff against version 1.1
+
+skodak
+
+$Id$