2014-11-29 06:30:26 +01:00
< ? php
/*
2020-03-13 04:12:42 +01:00
* ================================================================================
*
* EvalMath - PHP Class to safely evaluate math expressions
* Copyright ( C ) 2005 Miles Kaufmann < http :// www . twmagic . com />
*
* ================================================================================
*
* NAME
2020-03-13 11:29:27 +01:00
* EvalMath - safely evaluate math expressions
2020-03-13 04:12:42 +01:00
*
* SYNOPSIS
2020-03-13 11:29:27 +01:00
* include ( 'evalmath.class.php' );
* $m = new EvalMath ;
* // basic evaluation:
* $result = $m -> evaluate ( '2+2' );
* // supports: order of operation; parentheses; negation; built-in functions
* $result = $m -> evaluate ( '-8(5/2)^2*(1-sqrt(4))-8' );
* // create your own variables
* $m -> evaluate ( 'a = e^(ln(pi))' );
* // or functions
* $m -> evaluate ( 'f(x,y) = x^2 + y^2 - 2x*y + 1' );
* // and then use them
* $result = $m -> evaluate ( '3*f(42,a)' );
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* DESCRIPTION
* Use the EvalMath class when you want to evaluate mathematical expressions
* from untrusted sources . You can define your own variables and functions ,
* which are stored in the object . Try it , it ' s fun !
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* METHODS
2024-01-13 19:48:20 +01:00
* $m -> evaluate ( $expr )
2020-03-13 11:29:27 +01:00
* Evaluates the expression and returns the result . If an error occurs ,
* prints a warning and returns false . If $expr is a function assignment ,
* returns true on success .
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* $m -> e ( $expr )
* A synonym for $m -> evaluate () .
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* $m -> vars ()
* Returns an associative array of all user - defined variables and values .
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* $m -> funcs ()
* Returns an array of all user - defined functions .
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* PARAMETERS
* $m -> suppress_errors
* Set to true to turn off warnings when evaluating expressions
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* $m -> last_error
* If the last evaluation failed , contains a string describing the error .
* ( Useful when suppress_errors is on ) .
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* $m -> last_error_code
* If the last evaluation failed , 2 element array with numeric code and extra info
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* AUTHOR INFORMATION
* Copyright 2005 , Miles Kaufmann .
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* LICENSE
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are
* met :
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* 1 Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* 2. Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior written
* permission .
2020-03-13 04:12:42 +01:00
*
2020-03-13 11:29:27 +01:00
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR `` AS IS '' AND ANY EXPRESS OR
* IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT ,
* INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION )
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT ,
* STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE .
2020-03-13 04:12:42 +01:00
*/
2014-11-29 06:30:26 +01:00
2020-03-13 04:12:42 +01:00
/**
2020-03-13 11:29:27 +01:00
* \file core / class / evalmath . class . php
* \ingroup core
* \brief This file for Math evaluation
2020-03-13 04:12:42 +01:00
*/
2018-03-25 19:21:00 +02:00
2020-03-13 04:12:42 +01:00
/**
* Class EvalMath
*/
2018-03-25 19:22:08 +02:00
class EvalMath
{
2020-03-13 11:29:27 +01:00
public $suppress_errors = false ;
public $last_error = null ;
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
public $last_error_code = null ;
2018-03-25 19:21:00 +02:00
2020-04-10 10:59:32 +02:00
public $v = array ( 'e' => 2.71 , 'pi' => 3.14159 );
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
// variables (and constants)
public $f = array ();
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
// user-defined functions
2020-04-10 10:59:32 +02:00
public $vb = array ( 'e' , 'pi' );
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
// constants
public $fb = array ( // built-in functions
2022-10-27 10:29:18 +02:00
'sin' , 'sinh' , 'arcsin' , 'asin' , 'arcsinh' , 'asinh' , 'cos' , 'cosh' , 'arccos' , 'acos' , 'arccosh' , 'acosh' , 'tan' , 'tanh' , 'arctan' , 'atan' , 'arctanh' , 'atanh' , 'sqrt' , 'abs' , 'ln' , 'log' , 'intval' , 'ceil' ,
);
2014-11-29 06:30:26 +01:00
2020-03-13 11:29:27 +01:00
/**
* Constructor
*/
public function __construct ()
{
// make the variables a little more accurate
$this -> v [ 'pi' ] = pi ();
$this -> v [ 'e' ] = exp ( 1 );
}
2020-03-13 04:12:42 +01:00
2020-03-13 11:29:27 +01:00
/**
* Evaluate
*
2020-03-13 13:11:04 +01:00
* @ param string $expr String
2024-03-15 04:22:49 +01:00
* @ return boolean | int | float | NULL | mixed Result
2020-03-13 11:29:27 +01:00
*/
2020-03-13 13:11:04 +01:00
public function e ( $expr )
2020-03-13 11:29:27 +01:00
{
return $this -> evaluate ( $expr );
}
2014-11-29 06:30:26 +01:00
2020-03-13 11:29:27 +01:00
/**
* Evaluate
*
2020-03-13 13:11:04 +01:00
* @ param string $expr String
2024-03-15 04:22:49 +01:00
* @ return boolean | int | float | NULL | mixed Result
2020-03-13 11:29:27 +01:00
*/
2020-03-13 13:11:04 +01:00
public function evaluate ( $expr )
2020-03-13 11:29:27 +01:00
{
2022-06-15 15:18:37 +02:00
if ( empty ( $expr )) {
return false ;
}
2020-03-13 11:29:27 +01:00
$this -> last_error = null ;
$this -> last_error_code = null ;
$expr = trim ( $expr );
2021-02-23 22:03:23 +01:00
if ( substr ( $expr , - 1 , 1 ) == ';' ) {
2020-03-13 11:29:27 +01:00
$expr = substr ( $expr , 0 , strlen ( $expr ) - 1 ); // strip semicolons at the end
2021-02-23 22:03:23 +01:00
}
2023-12-04 12:04:36 +01:00
// ===============
// is it a variable assignment?
2020-03-13 11:29:27 +01:00
$matches = array ();
if ( preg_match ( '/^\s*([a-z]\w*)\s*=\s*(.+)$/' , $expr , $matches )) {
if ( in_array ( $matches [ 1 ], $this -> vb )) { // make sure we're not assigning to a constant
return $this -> trigger ( 1 , " cannot assign to constant ' $matches[1] ' " , $matches [ 1 ]);
}
2021-02-23 22:03:23 +01:00
if (( $tmp = $this -> pfx ( $this -> nfx ( $matches [ 2 ]))) === false ) {
2020-03-13 11:29:27 +01:00
return false ; // get the result and make sure it's good
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
$this -> v [ $matches [ 1 ]] = $tmp ; // if so, stick it in the variable array
return $this -> v [ $matches [ 1 ]]; // and return the resulting value
2024-03-15 04:22:49 +01:00
// ===============
// is it a function assignment?
2020-03-13 11:29:27 +01:00
} elseif ( preg_match ( '/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\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 ( 2 , " cannot redefine built-in function ' $matches[1] ()' " , $matches [ 1 ]);
}
$args = explode ( " , " , preg_replace ( " / \ s+/ " , " " , $matches [ 2 ])); // get the arguments
2021-02-23 22:03:23 +01:00
if (( $stack = $this -> nfx ( $matches [ 3 ])) === false ) {
2020-03-13 11:29:27 +01:00
return false ; // see if it can be converted to postfix
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
$nbstack = count ( $stack );
2020-04-10 10:59:32 +02:00
for ( $i = 0 ; $i < $nbstack ; $i ++ ) { // freeze the state of the non-argument variables
2020-03-13 11:29:27 +01:00
$token = $stack [ $i ];
2020-04-10 10:59:32 +02:00
if ( preg_match ( '/^[a-z]\w*$/' , $token ) and ! in_array ( $token , $args )) {
2020-03-13 11:29:27 +01:00
if ( array_key_exists ( $token , $this -> v )) {
$stack [ $i ] = $this -> v [ $token ];
} else {
return $this -> trigger ( 3 , " undefined variable ' $token ' in function definition " , $token );
}
}
}
2020-04-10 10:59:32 +02:00
$this -> f [ $fnn ] = array ( 'args' => $args , 'func' => $stack );
2020-03-13 11:29:27 +01:00
return true ;
// ===============
} else {
return $this -> pfx ( $this -> nfx ( $expr )); // straight up evaluation, woo
}
}
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
/**
2023-01-04 18:34:54 +01:00
* Function vars
2020-03-13 11:29:27 +01:00
*
2023-01-04 18:34:54 +01:00
* @ return array Output
2020-03-13 11:29:27 +01:00
*/
2020-06-28 17:53:48 +02:00
public function vars ()
2020-03-13 11:29:27 +01:00
{
$output = $this -> v ;
unset ( $output [ 'pi' ]);
unset ( $output [ 'e' ]);
return $output ;
}
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
/**
2023-01-04 18:34:54 +01:00
* Function funcs
2020-03-13 11:29:27 +01:00
*
2023-01-04 18:34:54 +01:00
* @ return array Output
2020-03-13 11:29:27 +01:00
*/
2020-03-13 13:11:04 +01:00
private function funcs ()
2020-03-13 11:29:27 +01:00
{
$output = array ();
2021-02-23 22:03:23 +01:00
foreach ( $this -> f as $fnn => $dat ) {
2020-04-10 10:59:32 +02:00
$output [] = $fnn . '(' . implode ( ',' , $dat [ 'args' ]) . ')' ;
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
return $output ;
}
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
// ===================== HERE BE INTERNAL METHODS ====================\\
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
/**
* Convert infix to postfix notation
*
2023-01-04 18:34:54 +01:00
* @ param string $expr Expression
* @ return boolean | array Output
2020-03-13 11:29:27 +01:00
*/
2020-03-13 13:11:04 +01:00
private function nfx ( $expr )
2020-03-13 11:29:27 +01:00
{
$index = 0 ;
$stack = new EvalMathStack ();
$output = array (); // postfix form of expression, to be passed to pfx()
$expr = trim ( strtolower ( $expr ));
2018-03-25 19:21:00 +02:00
2020-04-10 10:59:32 +02:00
$ops = array ( '+' , '-' , '*' , '/' , '^' , '_' );
$ops_r = array ( '+' => 0 , '-' => 0 , '*' => 0 , '/' => 0 , '^' => 1 ); // right-associative operator?
$ops_p = array ( '+' => 0 , '-' => 0 , '*' => 1 , '/' => 1 , '_' => 1 , '^' => 2 ); // operator precedence
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
$expecting_op = false ; // we use this in syntax-checking the expression
2023-12-04 12:04:36 +01:00
// and determining when a - is a negation
2014-11-29 06:30:26 +01:00
2020-03-13 11:29:27 +01:00
$matches = array ();
if ( preg_match ( " /[^ \ w \ s+*^ \ /() \ .,-]/ " , $expr , $matches )) { // make sure the characters are all good
2023-05-01 14:33:08 +02:00
return $this -> trigger ( 4 , " illegal character ' " . $matches [ 0 ] . " ' " , $matches [ 0 ]);
2020-03-13 11:29:27 +01:00
}
2020-03-13 04:12:42 +01:00
2020-03-13 11:29:27 +01:00
while ( 1 ) { // 1 Infinite Loop ;)
$op = substr ( $expr , $index , 1 ); // get the first character at the current index
2024-03-15 04:22:49 +01:00
// find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
2020-03-13 11:29:27 +01:00
$match = array ();
$ex = preg_match ( '/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/' , substr ( $expr , $index ), $match );
// ===============
2020-04-10 10:59:32 +02:00
if ( $op == '-' and ! $expecting_op ) { // is it a negation instead of a minus?
2020-03-13 11:29:27 +01:00
$stack -> push ( '_' ); // put a negation on the stack
2020-04-10 10:59:32 +02:00
$index ++ ;
2020-03-13 11:29:27 +01:00
} elseif ( $op == '_' ) { // we have to explicitly deny this, because it's legal on the stack
return $this -> trigger ( 4 , " illegal character '_' " , " _ " ); // but not in the input expression
2024-03-15 04:22:49 +01:00
// ===============
2020-03-13 11:29:27 +01:00
} elseif (( in_array ( $op , $ops ) or $ex ) and $expecting_op ) { // are we putting an operator on the stack?
2024-01-13 19:48:20 +01:00
if ( $ex ) { // are we expecting an operator but have a number/variable/function/opening parenthesis?
2020-03-13 11:29:27 +01:00
$op = '*' ;
2020-04-10 10:59:32 +02:00
$index -- ; // it's an implicit multiplication
2020-03-13 11:29:27 +01:00
}
// heart of the algorithm:
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 ])) {
$output [] = $stack -> pop (); // pop stuff off the stack into the output
}
// many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
$stack -> push ( $op ); // finally put OUR operator onto the stack
2020-04-10 10:59:32 +02:00
$index ++ ;
2020-03-13 11:29:27 +01:00
$expecting_op = false ;
// ===============
} elseif ( $op == ')' and $expecting_op ) { // ready to close a parenthesis?
while (( $o2 = $stack -> pop ()) != '(' ) { // pop off the stack back to the last (
2020-07-03 00:36:56 +02:00
if ( is_null ( $o2 )) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 5 , " unexpected ')' " , " ) " );
2020-07-03 00:36:56 +02:00
} else {
2020-03-13 11:29:27 +01:00
$output [] = $o2 ;
2020-07-03 00:36:56 +02:00
}
2020-03-13 11:29:27 +01:00
}
if ( preg_match ( " /^([a-z] \ w*) \ ( $ / " , $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)
$output [] = $stack -> pop (); // pop the function and push onto the output
if ( in_array ( $fnn , $this -> fb )) { // check the argument count
2021-02-23 22:03:23 +01:00
if ( $arg_count > 1 ) {
2020-04-10 10:59:32 +02:00
return $this -> trigger ( 6 , " wrong number of arguments ( $arg_count given, 1 expected) " , array ( $arg_count , 1 ));
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
} elseif ( array_key_exists ( $fnn , $this -> f )) {
2021-02-23 22:03:23 +01:00
if ( $arg_count != count ( $this -> f [ $fnn ][ 'args' ])) {
2020-04-10 10:59:32 +02:00
return $this -> trigger ( 6 , " wrong number of arguments ( $arg_count given, " . count ( $this -> f [ $fnn ][ 'args' ]) . " expected) " , array ( $arg_count , count ( $this -> f [ $fnn ][ 'args' ])));
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
} else { // did we somehow push a non-function on the stack? this should never happen
return $this -> trigger ( 7 , " internal error " );
}
}
2020-04-10 10:59:32 +02:00
$index ++ ;
2020-03-13 11:29:27 +01:00
// ===============
} elseif ( $op == ',' and $expecting_op ) { // did we just finish a function argument?
while (( $o2 = $stack -> pop ()) != '(' ) {
2020-07-03 00:36:56 +02:00
if ( is_null ( $o2 )) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 5 , " unexpected ',' " , " , " ); // oops, never had a (
2020-07-03 00:36:56 +02:00
} else {
2020-03-13 11:29:27 +01:00
$output [] = $o2 ; // pop the argument expression stuff and push onto the output
2020-07-03 00:36:56 +02:00
}
2020-03-13 11:29:27 +01:00
}
// make sure there was a function
2021-02-23 22:03:23 +01:00
if ( ! preg_match ( " /^([a-z] \ w*) \ ( $ / " , $stack -> last ( 2 ), $matches )) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 5 , " unexpected ',' " , " , " );
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
$stack -> push ( $stack -> pop () + 1 ); // increment the argument count
$stack -> push ( '(' ); // put the ( back on, we'll need to pop back to it again
2020-04-10 10:59:32 +02:00
$index ++ ;
2020-03-13 11:29:27 +01:00
$expecting_op = false ;
// ===============
2020-04-10 10:59:32 +02:00
} elseif ( $op == '(' and ! $expecting_op ) {
2020-03-13 11:29:27 +01:00
$stack -> push ( '(' ); // that was easy
2020-04-10 10:59:32 +02:00
$index ++ ;
2020-03-13 11:29:27 +01:00
$allow_neg = true ;
// ===============
2020-04-10 10:59:32 +02:00
} elseif ( $ex and ! $expecting_op ) { // do we now have a function/variable/number?
2020-03-13 11:29:27 +01:00
$expecting_op = true ;
$val = $match [ 1 ];
if ( preg_match ( " /^([a-z] \ w*) \ ( $ / " , $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 )) { // it's a func
$stack -> push ( $val );
$stack -> push ( 1 );
$stack -> push ( '(' );
$expecting_op = false ;
} else { // it's a var w/ implicit multiplication
$val = $matches [ 1 ];
$output [] = $val ;
}
} else { // it's a plain old var or num
$output [] = $val ;
}
$index += strlen ( $val );
// ===============
} elseif ( $op == ')' ) { // miscellaneous error checking
return $this -> trigger ( 5 , " unexpected ')' " , " ) " );
2020-04-10 10:59:32 +02:00
} elseif ( in_array ( $op , $ops ) and ! $expecting_op ) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 8 , " unexpected operator ' $op ' " , $op );
} else { // I don't even want to know what you did to get here
2024-01-13 19:48:20 +01:00
return $this -> trigger ( 9 , " an unexpected error occurred " );
2020-03-13 11:29:27 +01:00
}
if ( $index == strlen ( $expr )) {
if ( in_array ( $op , $ops )) { // did we end with an operator? bad.
return $this -> trigger ( 10 , " operator ' $op ' lacks operand " , $op );
} else {
break ;
}
}
while ( substr ( $expr , $index , 1 ) == ' ' ) { // step the index past whitespace (pretty much turns whitespace
2020-04-10 10:59:32 +02:00
$index ++ ; // into implicit multiplication if no operator is there)
2020-03-13 11:29:27 +01:00
}
}
2020-04-10 10:59:32 +02:00
while ( ! is_null ( $op = $stack -> pop ())) { // pop everything off the stack and push onto output
2021-02-23 22:03:23 +01:00
if ( $op == '(' ) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 11 , " expecting ')' " , " ) " ); // if there are (s on the stack, ()s were unbalanced
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
$output [] = $op ;
}
2023-01-04 18:34:54 +01:00
2020-03-13 11:29:27 +01:00
return $output ;
}
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
/**
2022-06-14 18:19:02 +02:00
* Evaluate postfix notation
2020-03-13 11:29:27 +01:00
*
2022-06-13 13:34:18 +02:00
* @ param array $tokens Expression
2020-03-13 13:11:04 +01:00
* @ param array $vars Array
2024-01-31 13:28:44 +01:00
* @ return string | false Output or false if error
2020-03-13 11:29:27 +01:00
*/
2020-03-13 13:11:04 +01:00
private function pfx ( $tokens , $vars = array ())
2020-03-13 11:29:27 +01:00
{
$stack = new EvalMathStack ();
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
foreach ( $tokens as $token ) { // nice and easy
2023-12-04 12:04:36 +01:00
// if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
2020-03-13 11:29:27 +01:00
$matches = array ();
2020-04-10 10:59:32 +02:00
if ( in_array ( $token , array ( '+' , '-' , '*' , '/' , '^' ))) {
2021-02-23 22:03:23 +01:00
if ( is_null ( $op2 = $stack -> pop ())) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 12 , " internal error " );
2021-02-23 22:03:23 +01:00
}
if ( is_null ( $op1 = $stack -> pop ())) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 13 , " internal error " );
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
switch ( $token ) {
case '+' :
$stack -> push ( $op1 + $op2 );
break ;
case '-' :
$stack -> push ( $op1 - $op2 );
break ;
case '*' :
$stack -> push ( $op1 * $op2 );
break ;
case '/' :
2021-02-23 22:03:23 +01:00
if ( $op2 == 0 ) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 14 , " division by zero " );
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
$stack -> push ( $op1 / $op2 );
break ;
case '^' :
$stack -> push ( pow ( $op1 , $op2 ));
break ;
}
// if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
} elseif ( $token == " _ " ) {
2020-04-10 10:59:32 +02:00
$stack -> push ( - 1 * $stack -> pop ());
2020-03-13 11:29:27 +01:00
// if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
} elseif ( preg_match ( " /^([a-z] \ w*) \ ( $ / " , $token , $matches )) { // it's a function!
$fnn = $matches [ 1 ];
if ( in_array ( $fnn , $this -> fb )) { // built-in function:
2021-02-23 22:03:23 +01:00
if ( is_null ( $op1 = $stack -> pop ())) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 15 , " internal error " );
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
$fnn = preg_replace ( " /^arc/ " , " a " , $fnn ); // for the 'arc' trig synonyms
2021-02-23 22:03:23 +01:00
if ( $fnn == 'ln' ) {
2020-03-13 11:29:27 +01:00
$fnn = 'log' ;
2021-02-23 22:03:23 +01:00
}
2024-03-17 13:38:35 +01:00
// @phan-suppress-next-line PhanPluginUnsafeEval
2020-04-10 10:59:32 +02:00
eval ( '$stack->push(' . $fnn . '($op1));' ); // perfectly safe eval()
2020-03-13 11:29:27 +01:00
} elseif ( array_key_exists ( $fnn , $this -> f )) { // user function
2024-03-15 04:22:49 +01:00
// get args
2020-03-13 11:29:27 +01:00
$args = array ();
2020-04-10 10:59:32 +02:00
for ( $i = count ( $this -> f [ $fnn ][ 'args' ]) - 1 ; $i >= 0 ; $i -- ) {
2021-02-23 22:03:23 +01:00
if ( is_null ( $args [ $this -> f [ $fnn ][ 'args' ][ $i ]] = $stack -> pop ())) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 16 , " internal error " );
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
}
$stack -> push ( $this -> pfx ( $this -> f [ $fnn ][ 'func' ], $args )); // yay... recursion!!!!
}
// if the token is a number or variable, push it on the stack
} else {
if ( is_numeric ( $token )) {
$stack -> push ( $token );
} elseif ( array_key_exists ( $token , $this -> v )) {
$stack -> push ( $this -> v [ $token ]);
} elseif ( array_key_exists ( $token , $vars )) {
$stack -> push ( $vars [ $token ]);
} else {
return $this -> trigger ( 17 , " undefined variable ' $token ' " , $token );
}
}
}
// when we're out of tokens, the stack should have a single element, the final result
2021-02-23 22:03:23 +01:00
if ( $stack -> count != 1 ) {
2020-03-13 11:29:27 +01:00
return $this -> trigger ( 18 , " internal error " );
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
return $stack -> pop ();
}
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
/**
* trigger an error , but nicely , if need be
*
2020-03-13 13:11:04 +01:00
* @ param string $code Code
* @ param string $msg Msg
* @ param string | null $info String
2024-01-31 13:28:44 +01:00
* @ return false
2020-03-13 11:29:27 +01:00
*/
2020-03-13 16:46:07 +01:00
public function trigger ( $code , $msg , $info = null )
2020-03-13 11:29:27 +01:00
{
$this -> last_error = $msg ;
2020-04-10 10:59:32 +02:00
$this -> last_error_code = array ( $code , $info );
2021-02-23 22:03:23 +01:00
if ( ! $this -> suppress_errors ) {
2020-03-13 11:29:27 +01:00
trigger_error ( $msg , E_USER_WARNING );
2021-02-23 22:03:23 +01:00
}
2020-03-13 11:29:27 +01:00
return false ;
}
2014-11-29 06:30:26 +01:00
}
2018-03-25 19:21:00 +02:00
/**
2020-03-13 11:29:27 +01:00
* Class for internal use
*/
2018-03-25 19:22:08 +02:00
class EvalMathStack
{
2020-03-13 16:46:07 +01:00
public $stack = array ();
2020-03-13 11:29:27 +01:00
2020-03-13 16:46:07 +01:00
public $count = 0 ;
2020-03-13 11:29:27 +01:00
/**
* push
*
2023-01-06 19:34:45 +01:00
* @ param string $val Val
* @ return void
2020-03-13 11:29:27 +01:00
*/
2020-03-13 13:11:04 +01:00
public function push ( $val )
2020-03-13 11:29:27 +01:00
{
$this -> stack [ $this -> count ] = $val ;
2020-04-10 10:59:32 +02:00
$this -> count ++ ;
2020-03-13 11:29:27 +01:00
}
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
/**
* pop
*
* @ return mixed Stack
*/
2020-03-13 13:11:04 +01:00
public function pop ()
2020-03-13 11:29:27 +01:00
{
if ( $this -> count > 0 ) {
2020-04-10 10:59:32 +02:00
$this -> count -- ;
2020-03-13 11:29:27 +01:00
return $this -> stack [ $this -> count ];
}
return null ;
}
2018-03-25 19:21:00 +02:00
2020-03-13 11:29:27 +01:00
/**
* last
*
2023-01-06 19:34:45 +01:00
* @ param int $n N
* @ return mixed Stack
2020-03-13 11:29:27 +01:00
*/
2020-03-13 13:11:04 +01:00
public function last ( $n = 1 )
2020-03-13 11:29:27 +01:00
{
if ( isset ( $this -> stack [ $this -> count - $n ])) {
return $this -> stack [ $this -> count - $n ];
}
2023-01-06 19:34:45 +01:00
return '' ;
2020-03-13 11:29:27 +01:00
}
2014-11-29 06:30:26 +01:00
}