annotate sites/all/modules/ctools/includes/math-expr.inc @ 0:ff03f76ab3fe

initial version
author danieleb <danielebarchiesi@me.com>
date Wed, 21 Aug 2013 18:51:11 +0100
parents
children
rev   line source
danielebarchiesi@0 1 <?php
danielebarchiesi@0 2
danielebarchiesi@0 3 /*
danielebarchiesi@0 4 ================================================================================
danielebarchiesi@0 5
danielebarchiesi@0 6 ctools_math_expr - PHP Class to safely evaluate math expressions
danielebarchiesi@0 7 Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
danielebarchiesi@0 8
danielebarchiesi@0 9 ================================================================================
danielebarchiesi@0 10
danielebarchiesi@0 11 NAME
danielebarchiesi@0 12 ctools_math_expr - safely evaluate math expressions
danielebarchiesi@0 13
danielebarchiesi@0 14 SYNOPSIS
danielebarchiesi@0 15 include('ctools_math_expr.class.php');
danielebarchiesi@0 16 $m = new ctools_math_expr;
danielebarchiesi@0 17 // basic evaluation:
danielebarchiesi@0 18 $result = $m->evaluate('2+2');
danielebarchiesi@0 19 // supports: order of operation; parentheses; negation; built-in functions
danielebarchiesi@0 20 $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
danielebarchiesi@0 21 // create your own variables
danielebarchiesi@0 22 $m->evaluate('a = e^(ln(pi))');
danielebarchiesi@0 23 // or functions
danielebarchiesi@0 24 $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
danielebarchiesi@0 25 // and then use them
danielebarchiesi@0 26 $result = $m->evaluate('3*f(42,a)');
danielebarchiesi@0 27
danielebarchiesi@0 28 DESCRIPTION
danielebarchiesi@0 29 Use the ctools_math_expr class when you want to evaluate mathematical expressions
danielebarchiesi@0 30 from untrusted sources. You can define your own variables and functions,
danielebarchiesi@0 31 which are stored in the object. Try it, it's fun!
danielebarchiesi@0 32
danielebarchiesi@0 33 METHODS
danielebarchiesi@0 34 $m->evalute($expr)
danielebarchiesi@0 35 Evaluates the expression and returns the result. If an error occurs,
danielebarchiesi@0 36 prints a warning and returns false. If $expr is a function assignment,
danielebarchiesi@0 37 returns true on success.
danielebarchiesi@0 38
danielebarchiesi@0 39 $m->e($expr)
danielebarchiesi@0 40 A synonym for $m->evaluate().
danielebarchiesi@0 41
danielebarchiesi@0 42 $m->vars()
danielebarchiesi@0 43 Returns an associative array of all user-defined variables and values.
danielebarchiesi@0 44
danielebarchiesi@0 45 $m->funcs()
danielebarchiesi@0 46 Returns an array of all user-defined functions.
danielebarchiesi@0 47
danielebarchiesi@0 48 PARAMETERS
danielebarchiesi@0 49 $m->suppress_errors
danielebarchiesi@0 50 Set to true to turn off warnings when evaluating expressions
danielebarchiesi@0 51
danielebarchiesi@0 52 $m->last_error
danielebarchiesi@0 53 If the last evaluation failed, contains a string describing the error.
danielebarchiesi@0 54 (Useful when suppress_errors is on).
danielebarchiesi@0 55
danielebarchiesi@0 56 AUTHOR INFORMATION
danielebarchiesi@0 57 Copyright 2005, Miles Kaufmann.
danielebarchiesi@0 58
danielebarchiesi@0 59 LICENSE
danielebarchiesi@0 60 Redistribution and use in source and binary forms, with or without
danielebarchiesi@0 61 modification, are permitted provided that the following conditions are
danielebarchiesi@0 62 met:
danielebarchiesi@0 63
danielebarchiesi@0 64 1 Redistributions of source code must retain the above copyright
danielebarchiesi@0 65 notice, this list of conditions and the following disclaimer.
danielebarchiesi@0 66 2. Redistributions in binary form must reproduce the above copyright
danielebarchiesi@0 67 notice, this list of conditions and the following disclaimer in the
danielebarchiesi@0 68 documentation and/or other materials provided with the distribution.
danielebarchiesi@0 69 3. The name of the author may not be used to endorse or promote
danielebarchiesi@0 70 products derived from this software without specific prior written
danielebarchiesi@0 71 permission.
danielebarchiesi@0 72
danielebarchiesi@0 73 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
danielebarchiesi@0 74 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
danielebarchiesi@0 75 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
danielebarchiesi@0 76 DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
danielebarchiesi@0 77 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
danielebarchiesi@0 78 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
danielebarchiesi@0 79 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
danielebarchiesi@0 80 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
danielebarchiesi@0 81 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
danielebarchiesi@0 82 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
danielebarchiesi@0 83 POSSIBILITY OF SUCH DAMAGE.
danielebarchiesi@0 84
danielebarchiesi@0 85 */
danielebarchiesi@0 86
danielebarchiesi@0 87 class ctools_math_expr {
danielebarchiesi@0 88 var $suppress_errors = false;
danielebarchiesi@0 89 var $last_error = null;
danielebarchiesi@0 90
danielebarchiesi@0 91 var $v = array('e'=>2.71,'pi'=>3.14); // variables (and constants)
danielebarchiesi@0 92 var $f = array(); // user-defined functions
danielebarchiesi@0 93 var $vb = array('e', 'pi'); // constants
danielebarchiesi@0 94 var $fb = array( // built-in functions
danielebarchiesi@0 95 'sin','sinh','arcsin','asin','arcsinh','asinh',
danielebarchiesi@0 96 'cos','cosh','arccos','acos','arccosh','acosh',
danielebarchiesi@0 97 'tan','tanh','arctan','atan','arctanh','atanh',
danielebarchiesi@0 98 'pow', 'exp',
danielebarchiesi@0 99 'sqrt','abs','ln','log',
danielebarchiesi@0 100 'time', 'ceil', 'floor', 'min', 'max', 'round');
danielebarchiesi@0 101
danielebarchiesi@0 102 function ctools_math_expr() {
danielebarchiesi@0 103 // make the variables a little more accurate
danielebarchiesi@0 104 $this->v['pi'] = pi();
danielebarchiesi@0 105 $this->v['e'] = exp(1);
danielebarchiesi@0 106 drupal_alter('ctools_math_expression_functions', $this->fb);
danielebarchiesi@0 107 }
danielebarchiesi@0 108
danielebarchiesi@0 109 function e($expr) {
danielebarchiesi@0 110 return $this->evaluate($expr);
danielebarchiesi@0 111 }
danielebarchiesi@0 112
danielebarchiesi@0 113 function evaluate($expr) {
danielebarchiesi@0 114 $this->last_error = null;
danielebarchiesi@0 115 $expr = trim($expr);
danielebarchiesi@0 116 if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
danielebarchiesi@0 117 //===============
danielebarchiesi@0 118 // is it a variable assignment?
danielebarchiesi@0 119 if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
danielebarchiesi@0 120 if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
danielebarchiesi@0 121 return $this->trigger("cannot assign to constant '$matches[1]'");
danielebarchiesi@0 122 }
danielebarchiesi@0 123 if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
danielebarchiesi@0 124 $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
danielebarchiesi@0 125 return $this->v[$matches[1]]; // and return the resulting value
danielebarchiesi@0 126 //===============
danielebarchiesi@0 127 // is it a function assignment?
danielebarchiesi@0 128 } elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
danielebarchiesi@0 129 $fnn = $matches[1]; // get the function name
danielebarchiesi@0 130 if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
danielebarchiesi@0 131 return $this->trigger("cannot redefine built-in function '$matches[1]()'");
danielebarchiesi@0 132 }
danielebarchiesi@0 133 $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
danielebarchiesi@0 134 if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
danielebarchiesi@0 135 for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
danielebarchiesi@0 136 $token = $stack[$i];
danielebarchiesi@0 137 if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
danielebarchiesi@0 138 if (array_key_exists($token, $this->v)) {
danielebarchiesi@0 139 $stack[$i] = $this->v[$token];
danielebarchiesi@0 140 } else {
danielebarchiesi@0 141 return $this->trigger("undefined variable '$token' in function definition");
danielebarchiesi@0 142 }
danielebarchiesi@0 143 }
danielebarchiesi@0 144 }
danielebarchiesi@0 145 $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
danielebarchiesi@0 146 return true;
danielebarchiesi@0 147 //===============
danielebarchiesi@0 148 } else {
danielebarchiesi@0 149 return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
danielebarchiesi@0 150 }
danielebarchiesi@0 151 }
danielebarchiesi@0 152
danielebarchiesi@0 153 function vars() {
danielebarchiesi@0 154 $output = $this->v;
danielebarchiesi@0 155 unset($output['pi']);
danielebarchiesi@0 156 unset($output['e']);
danielebarchiesi@0 157 return $output;
danielebarchiesi@0 158 }
danielebarchiesi@0 159
danielebarchiesi@0 160 function funcs() {
danielebarchiesi@0 161 $output = array();
danielebarchiesi@0 162 foreach ($this->f as $fnn=>$dat)
danielebarchiesi@0 163 $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
danielebarchiesi@0 164 return $output;
danielebarchiesi@0 165 }
danielebarchiesi@0 166
danielebarchiesi@0 167 //===================== HERE BE INTERNAL METHODS ====================\\
danielebarchiesi@0 168
danielebarchiesi@0 169 // Convert infix to postfix notation
danielebarchiesi@0 170 function nfx($expr) {
danielebarchiesi@0 171
danielebarchiesi@0 172 $index = 0;
danielebarchiesi@0 173 $stack = new ctools_math_expr_stack;
danielebarchiesi@0 174 $output = array(); // postfix form of expression, to be passed to pfx()
danielebarchiesi@0 175 $expr = trim(strtolower($expr));
danielebarchiesi@0 176
danielebarchiesi@0 177 $ops = array('+', '-', '*', '/', '^', '_');
danielebarchiesi@0 178 $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator?
danielebarchiesi@0 179 $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2); // operator precedence
danielebarchiesi@0 180
danielebarchiesi@0 181 $expecting_op = false; // we use this in syntax-checking the expression
danielebarchiesi@0 182 // and determining when a - is a negation
danielebarchiesi@0 183
danielebarchiesi@0 184 if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) { // make sure the characters are all good
danielebarchiesi@0 185 return $this->trigger("illegal character '{$matches[0]}'");
danielebarchiesi@0 186 }
danielebarchiesi@0 187
danielebarchiesi@0 188 while(1) { // 1 Infinite Loop ;)
danielebarchiesi@0 189 $op = substr($expr, $index, 1); // get the first character at the current index
danielebarchiesi@0 190 // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
danielebarchiesi@0 191 $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
danielebarchiesi@0 192 //===============
danielebarchiesi@0 193 if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
danielebarchiesi@0 194 $stack->push('_'); // put a negation on the stack
danielebarchiesi@0 195 $index++;
danielebarchiesi@0 196 } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
danielebarchiesi@0 197 return $this->trigger("illegal character '_'"); // but not in the input expression
danielebarchiesi@0 198 //===============
danielebarchiesi@0 199 } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
danielebarchiesi@0 200 if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
danielebarchiesi@0 201 $op = '*'; $index--; // it's an implicit multiplication
danielebarchiesi@0 202 }
danielebarchiesi@0 203 // heart of the algorithm:
danielebarchiesi@0 204 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])) {
danielebarchiesi@0 205 $output[] = $stack->pop(); // pop stuff off the stack into the output
danielebarchiesi@0 206 }
danielebarchiesi@0 207 // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
danielebarchiesi@0 208 $stack->push($op); // finally put OUR operator onto the stack
danielebarchiesi@0 209 $index++;
danielebarchiesi@0 210 $expecting_op = false;
danielebarchiesi@0 211 //===============
danielebarchiesi@0 212 } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
danielebarchiesi@0 213 while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
danielebarchiesi@0 214 if (is_null($o2)) return $this->trigger("unexpected ')'");
danielebarchiesi@0 215 else $output[] = $o2;
danielebarchiesi@0 216 }
danielebarchiesi@0 217 if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did we just close a function?
danielebarchiesi@0 218 $fnn = $matches[1]; // get the function name
danielebarchiesi@0 219 $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
danielebarchiesi@0 220 $output[] = $stack->pop(); // pop the function and push onto the output
danielebarchiesi@0 221 if (in_array($fnn, $this->fb)) { // check the argument count
danielebarchiesi@0 222 if($arg_count > 1)
danielebarchiesi@0 223 return $this->trigger("too many arguments ($arg_count given, 1 expected)");
danielebarchiesi@0 224 } elseif (array_key_exists($fnn, $this->f)) {
danielebarchiesi@0 225 if ($arg_count != count($this->f[$fnn]['args']))
danielebarchiesi@0 226 return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . " expected)");
danielebarchiesi@0 227 } else { // did we somehow push a non-function on the stack? this should never happen
danielebarchiesi@0 228 return $this->trigger("internal error");
danielebarchiesi@0 229 }
danielebarchiesi@0 230 }
danielebarchiesi@0 231 $index++;
danielebarchiesi@0 232 //===============
danielebarchiesi@0 233 } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
danielebarchiesi@0 234 while (($o2 = $stack->pop()) != '(') {
danielebarchiesi@0 235 if (is_null($o2)) return $this->trigger("unexpected ','"); // oops, never had a (
danielebarchiesi@0 236 else $output[] = $o2; // pop the argument expression stuff and push onto the output
danielebarchiesi@0 237 }
danielebarchiesi@0 238 // make sure there was a function
danielebarchiesi@0 239 if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches))
danielebarchiesi@0 240 return $this->trigger("unexpected ','");
danielebarchiesi@0 241 $stack->push($stack->pop()+1); // increment the argument count
danielebarchiesi@0 242 $stack->push('('); // put the ( back on, we'll need to pop back to it again
danielebarchiesi@0 243 $index++;
danielebarchiesi@0 244 $expecting_op = false;
danielebarchiesi@0 245 //===============
danielebarchiesi@0 246 } elseif ($op == '(' and !$expecting_op) {
danielebarchiesi@0 247 $stack->push('('); // that was easy
danielebarchiesi@0 248 $index++;
danielebarchiesi@0 249 $allow_neg = true;
danielebarchiesi@0 250 //===============
danielebarchiesi@0 251 } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
danielebarchiesi@0 252 $expecting_op = true;
danielebarchiesi@0 253 $val = $match[1];
danielebarchiesi@0 254 if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
danielebarchiesi@0 255 if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func
danielebarchiesi@0 256 $stack->push($val);
danielebarchiesi@0 257 $stack->push(1);
danielebarchiesi@0 258 $stack->push('(');
danielebarchiesi@0 259 $expecting_op = false;
danielebarchiesi@0 260 } else { // it's a var w/ implicit multiplication
danielebarchiesi@0 261 $val = $matches[1];
danielebarchiesi@0 262 $output[] = $val;
danielebarchiesi@0 263 }
danielebarchiesi@0 264 } else { // it's a plain old var or num
danielebarchiesi@0 265 $output[] = $val;
danielebarchiesi@0 266 }
danielebarchiesi@0 267 $index += strlen($val);
danielebarchiesi@0 268 //===============
danielebarchiesi@0 269 } elseif ($op == ')') { // miscellaneous error checking
danielebarchiesi@0 270 return $this->trigger("unexpected ')'");
danielebarchiesi@0 271 } elseif (in_array($op, $ops) and !$expecting_op) {
danielebarchiesi@0 272 return $this->trigger("unexpected operator '$op'");
danielebarchiesi@0 273 } else { // I don't even want to know what you did to get here
danielebarchiesi@0 274 return $this->trigger("an unexpected error occured");
danielebarchiesi@0 275 }
danielebarchiesi@0 276 if ($index == strlen($expr)) {
danielebarchiesi@0 277 if (in_array($op, $ops)) { // did we end with an operator? bad.
danielebarchiesi@0 278 return $this->trigger("operator '$op' lacks operand");
danielebarchiesi@0 279 } else {
danielebarchiesi@0 280 break;
danielebarchiesi@0 281 }
danielebarchiesi@0 282 }
danielebarchiesi@0 283 while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
danielebarchiesi@0 284 $index++; // into implicit multiplication if no operator is there)
danielebarchiesi@0 285 }
danielebarchiesi@0 286
danielebarchiesi@0 287 }
danielebarchiesi@0 288 while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
danielebarchiesi@0 289 if ($op == '(') return $this->trigger("expecting ')'"); // if there are (s on the stack, ()s were unbalanced
danielebarchiesi@0 290 $output[] = $op;
danielebarchiesi@0 291 }
danielebarchiesi@0 292 return $output;
danielebarchiesi@0 293 }
danielebarchiesi@0 294
danielebarchiesi@0 295 // evaluate postfix notation
danielebarchiesi@0 296 function pfx($tokens, $vars = array()) {
danielebarchiesi@0 297
danielebarchiesi@0 298 if ($tokens == false) return false;
danielebarchiesi@0 299
danielebarchiesi@0 300 $stack = new ctools_math_expr_stack;
danielebarchiesi@0 301
danielebarchiesi@0 302 foreach ($tokens as $token) { // nice and easy
danielebarchiesi@0 303 // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
danielebarchiesi@0 304 if (in_array($token, array('+', '-', '*', '/', '^'))) {
danielebarchiesi@0 305 if (is_null($op2 = $stack->pop())) return $this->trigger("internal error");
danielebarchiesi@0 306 if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
danielebarchiesi@0 307 switch ($token) {
danielebarchiesi@0 308 case '+':
danielebarchiesi@0 309 $stack->push($op1+$op2); break;
danielebarchiesi@0 310 case '-':
danielebarchiesi@0 311 $stack->push($op1-$op2); break;
danielebarchiesi@0 312 case '*':
danielebarchiesi@0 313 $stack->push($op1*$op2); break;
danielebarchiesi@0 314 case '/':
danielebarchiesi@0 315 if ($op2 == 0) return $this->trigger("division by zero");
danielebarchiesi@0 316 $stack->push($op1/$op2); break;
danielebarchiesi@0 317 case '^':
danielebarchiesi@0 318 $stack->push(pow($op1, $op2)); break;
danielebarchiesi@0 319 }
danielebarchiesi@0 320 // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
danielebarchiesi@0 321 } elseif ($token == "_") {
danielebarchiesi@0 322 $stack->push(-1*$stack->pop());
danielebarchiesi@0 323 // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
danielebarchiesi@0 324 } elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!
danielebarchiesi@0 325 $fnn = $matches[1];
danielebarchiesi@0 326 if (in_array($fnn, $this->fb)) { // built-in function:
danielebarchiesi@0 327 if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
danielebarchiesi@0 328 $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
danielebarchiesi@0 329 if ($fnn == 'ln') $fnn = 'log';
danielebarchiesi@0 330 eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
danielebarchiesi@0 331 } elseif (array_key_exists($fnn, $this->f)) { // user function
danielebarchiesi@0 332 // get args
danielebarchiesi@0 333 $args = array();
danielebarchiesi@0 334 for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
danielebarchiesi@0 335 if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");
danielebarchiesi@0 336 }
danielebarchiesi@0 337 $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
danielebarchiesi@0 338 }
danielebarchiesi@0 339 // if the token is a number or variable, push it on the stack
danielebarchiesi@0 340 } else {
danielebarchiesi@0 341 if (is_numeric($token)) {
danielebarchiesi@0 342 $stack->push($token);
danielebarchiesi@0 343 } elseif (array_key_exists($token, $this->v)) {
danielebarchiesi@0 344 $stack->push($this->v[$token]);
danielebarchiesi@0 345 } elseif (array_key_exists($token, $vars)) {
danielebarchiesi@0 346 $stack->push($vars[$token]);
danielebarchiesi@0 347 } else {
danielebarchiesi@0 348 return $this->trigger("undefined variable '$token'");
danielebarchiesi@0 349 }
danielebarchiesi@0 350 }
danielebarchiesi@0 351 }
danielebarchiesi@0 352 // when we're out of tokens, the stack should have a single element, the final result
danielebarchiesi@0 353 if ($stack->count != 1) return $this->trigger("internal error");
danielebarchiesi@0 354 return $stack->pop();
danielebarchiesi@0 355 }
danielebarchiesi@0 356
danielebarchiesi@0 357 // trigger an error, but nicely, if need be
danielebarchiesi@0 358 function trigger($msg) {
danielebarchiesi@0 359 $this->last_error = $msg;
danielebarchiesi@0 360 if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
danielebarchiesi@0 361 return false;
danielebarchiesi@0 362 }
danielebarchiesi@0 363 }
danielebarchiesi@0 364
danielebarchiesi@0 365 // for internal use
danielebarchiesi@0 366 class ctools_math_expr_stack {
danielebarchiesi@0 367
danielebarchiesi@0 368 var $stack = array();
danielebarchiesi@0 369 var $count = 0;
danielebarchiesi@0 370
danielebarchiesi@0 371 function push($val) {
danielebarchiesi@0 372 $this->stack[$this->count] = $val;
danielebarchiesi@0 373 $this->count++;
danielebarchiesi@0 374 }
danielebarchiesi@0 375
danielebarchiesi@0 376 function pop() {
danielebarchiesi@0 377 if ($this->count > 0) {
danielebarchiesi@0 378 $this->count--;
danielebarchiesi@0 379 return $this->stack[$this->count];
danielebarchiesi@0 380 }
danielebarchiesi@0 381 return null;
danielebarchiesi@0 382 }
danielebarchiesi@0 383
danielebarchiesi@0 384 function last($n=1) {
danielebarchiesi@0 385 return !empty($this->stack[$this->count-$n]) ? $this->stack[$this->count-$n] : NULL;
danielebarchiesi@0 386 }
danielebarchiesi@0 387 }
danielebarchiesi@0 388