'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
//===============\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
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
$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
}\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
$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
// 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
}\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