mirror of
https://github.com/Deutscher-Tischfussballbund/com_sportsmanager.git
synced 2026-06-10 06:27:52 +00:00
2672 lines
91 KiB
PHP
2672 lines
91 KiB
PHP
<?php
|
|
/*
|
|
*
|
|
* PRODUCT: Bestcode Math Parser for PHP
|
|
* COPYRIGHT: (C) COPYRIGHT Suavi Ali Demir 2010-2011
|
|
*
|
|
* The source code for this program is not GNU or is not free.
|
|
* The user is granted rights to modify and/or use source code
|
|
* to use it with a PHP application. The user of the software
|
|
* is not granted rights to modify and use source code to create
|
|
* a competitive product. Resulting modifications cannot be re-sold
|
|
* as a competitive product but they can be used as part of the
|
|
* application.
|
|
*
|
|
* Copyright and all rights of this software, irrespective of what
|
|
* has been deposited with the U.S. Copyright Office belongs
|
|
* to Suavi Ali Demir.
|
|
*
|
|
* http://www.bestcode.com
|
|
*
|
|
* This notice shall remain in modified versions of this file.
|
|
*/
|
|
|
|
use Joomla\CMS\Log\Log;
|
|
|
|
/**
|
|
* MathParser is a mathematical expression parser class for PHP. MathParser parses and evaluates
|
|
* mathematical formulas given as strings at runtime. Math expressions can contain variables,
|
|
* functions, numeric literals combined with basic mathematical operators +, -, /, *, ^, % (modulus).
|
|
* MathParser can utilize user defined variables, user defined functions that are implemented in PHP
|
|
* language.
|
|
* <br /><br />
|
|
* Math parser library features at a glance include:<br /><br />
|
|
*
|
|
* Easy to use, simple class API.<br />
|
|
* Functions with 0 or more number of parameters in the form of: f(x,y,z, ...)<br />
|
|
* Functions with unknown number of parameters, e.g. SUM(a,b,c,...)<br />
|
|
* Comes with predefined functions.<br />
|
|
* You can create custom functions/variables with callbacks to the functions that you define in your source code, either through delegates or interfaces.<br />
|
|
* VariableResolver, a callback function to provide values for undefined variables.<br />
|
|
* Function/variable names start with letters and can contain letters, numbers and underscore (_).<br />
|
|
* Expression can contain string literals, variable values and function return values can be strings.<br />
|
|
* Arithmetic Operators: +, -, /, *, ^, %(mod)<br />
|
|
* Boolean Operators: <, >, =, &, |, ! ,<>, >=, <=<br />
|
|
* String concatenation with & or +<br />
|
|
* Optimization: Constant expression elimination for repeated tasks.<br />
|
|
* Paranthesis: (<br />
|
|
* List of predefined functions is available in the documentation.<br />
|
|
* Provides localization support which is a dictionary of message-keys to messages you can set.<br />
|
|
* Royalty free distribution.<br />
|
|
* Comes as source code.<br /><br />
|
|
*
|
|
* You may ask why not use PHP eval() function?
|
|
* The answer is security. eval() is a security nightmare if you ask for formula input from the user.
|
|
* PHP syntax for formulas with $ signs is not intuitive either. The aim of MathParser is to be a
|
|
* a secure, simple math evaluator alternative to eval(). Please keep in mind, MathParser only evaluates
|
|
* mathematical expressions given using a simple syntax that consists of numbers, variables, function calls
|
|
* and paranthesis. MathParser does not evaluate PHP expressions.<br /><br />
|
|
*
|
|
* Predefined functions are:
|
|
* <br /><br />
|
|
* SQR: Square function which can be used as SQR(X)
|
|
* <br /><br />
|
|
* SIN: Sinus function which can be used as SIN(X), X is a real-type expression.
|
|
* Sin returns the sine of the angle X in radians.
|
|
* <br /><br />
|
|
* COS: Cosinus function which can be used as COS(X), X is a real-type expression.
|
|
* COS returns the cosine of the angle X in radians.
|
|
* <br /><br />
|
|
* ATAN: ArcTangent function which can be used as ATAN(X)
|
|
* Returns the arctangent of a number as a numeric value between -PI/2 and PI/2 radians.
|
|
* <br /><br />
|
|
* SINH: Sinus Hyperbolic function which can be used as SINH(X)
|
|
* <br /><br />
|
|
* COSH: Cosinus Hyperbolic function which can be used as COSH(X)
|
|
* <br /><br />
|
|
* COTAN: which can be used as COTAN(X)
|
|
* <br /><br />
|
|
* TAN: which can be used as TAN(X)
|
|
* <br /><br />
|
|
* EXP: which can be used as EXP(X)
|
|
* <br /><br />
|
|
* LN: natural log, which can be used as LN(X)
|
|
* <br /><br />
|
|
* LOG: 10 based log, which can be used as LOG(X)
|
|
* <br /><br />
|
|
* SQRT: which can be used as SQRT(X)
|
|
* <br /><br />
|
|
* ABS: absolute value, which can be used as ABS(X)
|
|
* <br /><br />
|
|
* SIGN: SIGN(X) returns -1 if X<0; +1 if X>0, 0 if X=0; it can be used as SQR(X)
|
|
* <br /><br />
|
|
* TRUNC: Discards the fractional part of a number. e.g. TRUNC(-3.2) is -3, TRUNC(3.2) is 3.
|
|
* <br /><br />
|
|
* CEIL: CEIL(-3.2) = 3, CEIL(3.2) = 4
|
|
* <br /><br />
|
|
* FLOOR: FLOOR(-3.2) = -4, FLOOR(3.2) = 3
|
|
* <br /><br />
|
|
* VAL: VAL("3.1") = 3.1 Returns the floating point numeric value of the string argument.
|
|
* <br /><br />
|
|
* Predefined functions that take two parameters are:
|
|
* <br /><br />
|
|
* POW: The Power function raises Base to any power. For fractional exponents or
|
|
* exponents greater than MaxInt, Base must be greater than 0.
|
|
* <br /><br />
|
|
* LOGN: The LogN function returns the log base N of X. Example: LOGN(10, 100) = 2
|
|
* <br /><br />
|
|
* MIN: MIN(2, 3) is 2.
|
|
* <br /><br />
|
|
* MAX: MAX(2, 3) is 3.
|
|
* <br /><br />
|
|
* IF: The IF(b, case1, case2) function provides branching capability.
|
|
* If b is not 0, then it returns case1, else it returns case2.
|
|
* Behavior is similar to PHP's: <b>return b ? case1 : case2;</b><br />
|
|
* If b==0 then case1 will not be Evaluated, and vice versa.
|
|
* Example: IF(HEIGHT, 3/HEIGHT, 3) will make sure 3/HEIGHT does not cause division by zero.
|
|
* <br /><br />
|
|
* Predefined functions that take no parameters are:
|
|
* RND: RND() function generates a random number (double value) between 0 and 1.
|
|
* <br /><br />
|
|
* STR: STR(123) function returns the string representation of the passed value: "123".
|
|
* <br /><br />
|
|
* SUBSTR: SUBSTR("Hello", 1,3) function returns the substring just like PHP substr function.
|
|
* The first parameter is the string, the second parameter is which index (0 based) to start copying,
|
|
* and the last parameter is the number of characters to copy. For example, SUBSTR("Hello", 1,3) returns "ell".
|
|
* <br /><br />
|
|
* STRLEN: STRLEN("abc") function returns the length of the string parameter. For example, for "abc" it returns 3.
|
|
* <br /><br />
|
|
* CONCAT: CONCAT("abc","def",...) function returns the concatanated strings: "abcdef".
|
|
* There is no preset limit on the number of parameters.
|
|
* <br /><br />
|
|
* TRIM: TRIM(" abc ") function returns the trimmed version of the string parameter: " abc " -> "abc".
|
|
* <br /><br />
|
|
* SUM: SUM(2,3,5,...) functions returns the sum of it's arguments. There is no preset limit on the number of parameters.
|
|
* <br /><br />
|
|
* @since 1.0.0
|
|
*/
|
|
class MathParser
|
|
{
|
|
/**
|
|
* protected variable that holds the expression to parse.
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
protected string $Expression;
|
|
/**
|
|
* non-public flag that shows if parsing is needed or not. if false, existing
|
|
* parse tree will be re-used.
|
|
* @var boolean
|
|
* @since 1.0.0
|
|
*/
|
|
protected bool $Dirty;
|
|
/**
|
|
* when optimization on, after the parse tree is compiled, it will be further optimized
|
|
* by removing sub branches that evaluate to a constant. This will make parse() operation
|
|
* slower and getValue() operation faster.
|
|
* @var boolean
|
|
* @since 1.0.0
|
|
*/
|
|
protected bool $OptimizationOn;
|
|
|
|
/**
|
|
* Starting node for the internal compiled representation of the
|
|
* expression in parsed form.
|
|
* @var $Node
|
|
* @since 1.0.0
|
|
*/
|
|
protected $Node;
|
|
|
|
/**
|
|
* internal list of variables that are available for use in an expression.
|
|
* @var array
|
|
* @since 1.0.0
|
|
*/
|
|
protected array $Variables;
|
|
|
|
/**
|
|
* internal list of functions that take more than 2 parameters that are available for use in an expression.
|
|
* @var array
|
|
* @since 1.0.0
|
|
*/
|
|
protected array $Functions;
|
|
|
|
/**
|
|
* A dictionary that holds translated messages.
|
|
* @var array
|
|
* @since 1.0.0
|
|
*/
|
|
static protected array $translator;
|
|
|
|
/**
|
|
* Does the expression allows string literals or not?
|
|
* For example, is an expression like this allowed: "Hello"+"World"
|
|
* @var boolean
|
|
* @since 1.0.0
|
|
*/
|
|
protected bool $StringLiteralsAllowed = true;
|
|
|
|
/**
|
|
* The parser can use ampersand(&) or the plus(+) operator for string concatenations.
|
|
* You can set which one you want using StrConcatOperator property.
|
|
* The default is ampersand(&). Valid values are STR_CONCAT_OP.PLUS or
|
|
* STR_CONCAT_OP.AMPERSAND.
|
|
* @var string
|
|
* @since 1.0.0
|
|
*/
|
|
protected string $StrConcatOperator;
|
|
|
|
/**
|
|
* Public for performance reasons.
|
|
* @see getVariableResolver function for more info.
|
|
* @var string|null
|
|
* @since 1.0.0
|
|
*/
|
|
public ?string $VariableResolver = null;
|
|
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $add__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $subtract__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $mult__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $div__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $power__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $mod__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $unaryadd__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $negate__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $not__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $notequals__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $equals__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $lt__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $gt__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $ltequals__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $gtequals__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $and__;
|
|
/**
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static protected Mathparser_ParserFunction $or__;
|
|
|
|
/**
|
|
* Implementation of plus (+) operator.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
protected Mathparser_ParserFunction $AddOp;
|
|
|
|
/**
|
|
* Implementation of ampersand (&) operator for strings.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
protected Mathparser_ParserFunction $AndOp;
|
|
|
|
/**
|
|
* VariableResolver is used as a callback function that returns the values of variables when asked.
|
|
* This is useful when a variable cannot be defined before the parse operation.
|
|
* If VariableResolver property is set, it will be used to resolve variables that were not defined
|
|
* at the time parse operation has started. This means that syntax errors regarding undefined variables
|
|
* will not be caught at parse time. This VariableResolver (user implementation) will decide whether
|
|
* a variable name is valid or not.
|
|
* This is useful when for example the value comes from a database lookup. In some cases,
|
|
* the problem domain is too big to define the variables ahead of time. In such cases, it makes sense
|
|
* to parse with the assumption that it is a valid variable and resolve it's value when needed on demand during
|
|
* evaluation.
|
|
* @return string|null
|
|
* @since 1.0.0
|
|
*/
|
|
public function getVariableResolver(): ?string
|
|
{
|
|
return $this->VariableResolver;
|
|
}
|
|
|
|
/**
|
|
* Constructor for the MathParser.
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->Expression = '';
|
|
$this->Node = null;
|
|
$this->Dirty = true; //means it is not Parsed yet.
|
|
$this->OptimizationOn = false;
|
|
|
|
$this->Variables = array();
|
|
$this->Functions = array();
|
|
|
|
//Comment out if you don't want these functions and variables:
|
|
$this->createDefaultFuncs();
|
|
$this->createDefaultVars();
|
|
|
|
if (!isset(self::$add__)) {
|
|
self::$add__ = new Mathparser_ParserFunction('+', 'mp_add_', 2);
|
|
self::$subtract__ = new Mathparser_ParserFunction('-', 'mp_subtract_', 2);
|
|
self::$mult__ = new Mathparser_ParserFunction('*', 'mp_mult_', 2);
|
|
self::$div__ = new Mathparser_ParserFunction('/', 'mp_div_', 2);
|
|
self::$power__ = new Mathparser_ParserFunction('/', 'mp_power_', 2);
|
|
self::$mod__ = new Mathparser_ParserFunction('/', 'mp_mod_', 2);
|
|
|
|
self::$notequals__ = new Mathparser_ParserFunction('!=', 'mp_notequals_', 2);
|
|
self::$equals__ = new Mathparser_ParserFunction('=', 'mp_equals_', 2);
|
|
self::$lt__ = new Mathparser_ParserFunction('<', 'mp_ltequals_', 2);
|
|
self::$gt__ = new Mathparser_ParserFunction('>', 'mp_gtequals_', 2);
|
|
self::$ltequals__ = new Mathparser_ParserFunction('<=', 'mp_ltequals_', 2);
|
|
self::$gtequals__ = new Mathparser_ParserFunction('>=', 'mp_gtequals_', 2);
|
|
|
|
self::$unaryadd__ = new Mathparser_ParserFunction('+', 'mp_unaryadd_', 1);
|
|
self::$negate__ = new Mathparser_ParserFunction('-', 'mp_negate_', 1);
|
|
self::$not__ = new Mathparser_ParserFunction('!', 'mp_not_', 1);
|
|
|
|
self::$and__ = new Mathparser_ParserFunction('&', 'mp_and_', 2);
|
|
self::$or__ = new Mathparser_ParserFunction('|', 'mp_or_', 2);
|
|
}
|
|
|
|
$this->AddOp = self::$add__;
|
|
$this->AndOp = self::$and__;
|
|
$this->StrConcatOperator = '&'; //default.
|
|
}
|
|
|
|
/**
|
|
* Returns the existing translatable strings used to report error messages.
|
|
* To use this component in a localized application, set the translated versions of
|
|
* strings for each value that exists in the hashtable. By default, same hashtable is shared
|
|
* between all instances of the parser. To change translation strings, set a new hashtable
|
|
* of your own for the parser instance instead of editing the values contained in this hashtable if
|
|
* you do not wish other parser instances not to be effected.
|
|
* Returns dictionary (array) of key/value pairs where value is a Locale dependent, typically translated String, that
|
|
* may contain placeholder parameters using %s.
|
|
* @return array
|
|
* @since 1.0.0
|
|
*/
|
|
public static function &getTranslationStrings(): array
|
|
{
|
|
if (!isset(self::$translator)) {
|
|
self::$translator = array();
|
|
|
|
//#Strings used by the math Parser.
|
|
self::$translator['ExpEmpty'] = 'Expression is empty.';
|
|
|
|
//#parameter is the name of a variable:
|
|
self::$translator['VarNtExst'] = 'Variable %s does not exist.';
|
|
|
|
//variable is not numeric:
|
|
self::$translator['VarNtDbl'] = 'Variable %s is not numeric.';
|
|
|
|
//variable is not a string:
|
|
self::$translator['VarNtStr'] = 'Variable %s is not a string.';
|
|
|
|
//variable is not a string or a double:
|
|
self::$translator['VarNtDblStr'] = 'Variable %s should be a Double or a String. It is {1}.';
|
|
|
|
//variable already exists:
|
|
self::$translator['VarExt'] = 'Variable %s already exists.';
|
|
|
|
//#parameter is the name of a variable:
|
|
self::$translator['NtVarNm'] = '%s is not a valid variable name.';
|
|
|
|
//#parameter is the math expression that failed to Parse:
|
|
self::$translator['SntxErr'] = 'Syntax error in expression %s';
|
|
|
|
//#parameter is a function name:
|
|
self::$translator['NtFncNm'] = '%s is not a valid function name.';
|
|
|
|
//#parameter is a function name:
|
|
self::$translator['FncExst'] = 'Function %s already exists.';
|
|
|
|
//#Parameters are function name and number of parameters:
|
|
self::$translator['WrngNPrms'] = 'Function %s must accept at least 0 parameters not %s.';
|
|
|
|
self::$translator['WrngNPrms2'] = 'Function %s requires at least %s parameters.';
|
|
|
|
//#sub expression is not valid:
|
|
self::$translator['ExpNtVld'] = 'Sub expression "%s" in "%s" is not valid.';
|
|
|
|
self::$translator['InvNPrm'] = 'Invalid number of parameters in "%s".';
|
|
|
|
//#bracket mismatch.
|
|
self::$translator['BrcktMis'] = 'Bracket mismatch in expression "%s" at index %s.';
|
|
|
|
//#missing brackets.
|
|
self::$translator['MisBrckt'] = 'Missing bracket ")" in expression "%s".';
|
|
|
|
//applying logical and & to string values:
|
|
self::$translator['InvConcatOper'] = 'Parameter #2 %s is not a numeric value. Cannot apply logical and (&) operator. If you want to concatenate 2 strings, use STR() function to convert first parameter to string.';
|
|
|
|
//applying logical and + to string values:
|
|
self::$translator['InvConcatOper2'] = 'Parameter #2 %s is not a numeric value. Cannot apply plus (+) operator. If you want to concatenate 2 strings, use STR() function to convert first parameter to string.';
|
|
|
|
//converting str to number:
|
|
self::$translator['InvNum'] = 'Cannot convert to a number. %s is not a numeric value.';
|
|
}
|
|
return self::$translator;
|
|
}
|
|
|
|
/**
|
|
* Variable property is a way to set and get variable values.
|
|
* setVariable function creates the variable if the variable does not exist.
|
|
* Variable name is not case sensitive. Throws exception if the variable needs
|
|
* to be created and the name is not a valid variable name. CreateVar is just an
|
|
* alias for this method.
|
|
* @param $varName - Name of the variable whose value is being set.
|
|
* @param $newVal - New value of the variable. It should be either a Double or a String.
|
|
* @param $fn_valueProvider - The delegate that returns the value for a variable when asked by name.
|
|
* When a $fn_valueProvider is set for a Variable, it's assigned value will not be used and instead the $fn_valueProvider of type
|
|
* <br /><br /> function VariableDelegate($parser, $varName);<br /><br />
|
|
* will be called. This is useful in cases when the value of a variable comes from a database and it is not easy to set it's value
|
|
* (or it is costly) ahead of time. So, when you want to compute the variable value only when it is needed, then you can register a $fn_valueProvider
|
|
* to be called. You can use the same $fn_valueProvider for all or some of your variables or you can have a different valueProvider for each variable.
|
|
* @return void
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function setVariable($varName, $newVal, $fn_valueProvider = null): void
|
|
{
|
|
if ($varName == null) {
|
|
throw new Exception("Variable name cannot be null.");
|
|
}
|
|
if (is_numeric($newVal)) {
|
|
$newVal = (float)$newVal;
|
|
} else
|
|
if (!is_string($newVal)) {
|
|
throw new Exception("Variable should be floating point or string value: " . $newVal);
|
|
}
|
|
$upcName = strtoupper($varName);
|
|
$existing = $this->Variables[$upcName];
|
|
if ($existing != null) {
|
|
$existing->Value = $newVal;
|
|
} else {
|
|
if (!$this->isValidName($upcName)) {
|
|
throw new Exception($this->getMessage1("NtVarNm", $varName));
|
|
}
|
|
//create the variable:
|
|
$var = new Mathparser_Variable($this, $upcName, $newVal, $fn_valueProvider);
|
|
$this->Variables[$upcName] = $var;
|
|
$this->Dirty = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evaluates the expression and returns the result of it. If it cannot be parsed or
|
|
* evaluated then this method throws Exception.
|
|
* <br /><br />
|
|
* Calling this method is identical to calling getValue()
|
|
* @return double|string - Returns the value of the parsed expression.
|
|
* The return type is an object that can be interpreted as either Double or String.
|
|
* @throws Mathparser_ParserException
|
|
* @since 1.0.0
|
|
*/
|
|
public function evaluate(): float|string
|
|
{
|
|
if ($this->Dirty) { //if the expression has been changed, we need to Parse it again
|
|
$this->parse();
|
|
}
|
|
return $this->Node->getValue(); //this will start the chain reaction to get the
|
|
//value of all nodes
|
|
}
|
|
|
|
/**
|
|
* Parses the expression and forms a parse tree. Throws Exception if it cannot parse.
|
|
* Upon successful completion of parsing, it will set the Dirty flag to false, so that
|
|
* unless the expression is changed or variables and functions added or removed,
|
|
* expression does not need to be re-parsed. Users may want to call the parse method
|
|
* directly to check the validity of an input expression using a try-except block.
|
|
* <br /><br />
|
|
* If OptimizationOn property is true, Parse method will optimize the parse tree by
|
|
* evaluating constant branches of the parse tree at that moment, so that Evaluate
|
|
* function will run faster.
|
|
* @return void
|
|
* @throws Mathparser_ParserException
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function parse(): void
|
|
{
|
|
if (!isset($this->Expression) || !(strlen($this->Expression) > 0)) {
|
|
$this->Node = null;
|
|
throw new Exception($this->getMessage("ExpEmpty"));
|
|
}
|
|
|
|
$formula = $this->Expression;
|
|
$this->upperCase($formula);
|
|
//echo $formula;
|
|
|
|
$len = strlen($formula);
|
|
$brackets = self::checkBrackets($formula);
|
|
if ($brackets > -1 && $brackets < $len) {
|
|
throw new Mathparser_ParserException($this->getMessage2("BrcktMis", $formula, $brackets), substr($formula, $brackets), $formula);
|
|
} else
|
|
if ($brackets == 'len') {
|
|
throw new Mathparser_ParserException($this->getMessage1("MisBrckt", $formula), $formula, $formula);
|
|
}
|
|
|
|
if (($this->Node = $this->createParseTree($formula)) == null) {
|
|
throw new Mathparser_ParserException($this->getMessage2("ExpNtVld", $formula, $formula), $formula, $formula);
|
|
}
|
|
|
|
if ($this->OptimizationOn) {
|
|
$this->optimize(); //will make sure FNode tree is lean and mean
|
|
}
|
|
$this->Dirty = false; //note that we Parsed it once. Unless the expression is changed we do not need to reParse it.
|
|
}
|
|
|
|
/**
|
|
* Uppercases the expression in char array form without touching the string literals.
|
|
* @param $c - to convert to uppercase.
|
|
* @return void
|
|
* @since 1.0.0
|
|
*/
|
|
private function upperCase(&$c): void
|
|
{
|
|
$len = strlen($c);
|
|
$insideStringConst = false;
|
|
for ($i = 0; $i < $len; $i++) {
|
|
$ch = $c[$i];
|
|
if ($ch == '"')//support string literals in the expression.
|
|
{
|
|
$insideStringConst = !$insideStringConst;
|
|
continue;
|
|
}
|
|
if (!$insideStringConst) {
|
|
$c[$i] = strtoupper($ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a variable with given name and initial value.
|
|
* If the variable already exists, sets it's value to the specified value.
|
|
* Throws Exception if $varName is not a valid variable name.
|
|
* @param $varName - Variable name (case insensitive).
|
|
* @param $varValue - Initial value for the variable.
|
|
* @param $valueProvider - The callback delegate (a plain old PHP function) that decides the value of the variable at runtime.
|
|
* <br /><br /> function VariableDelegate($parser, $varName);<br /><br />
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function createVar($varName, $varValue, $valueProvider = null): void
|
|
{
|
|
$this->setVariable($varName, $varValue, $valueProvider);
|
|
}
|
|
|
|
/**
|
|
* CreateFunc method creates a new function that takes n number of parameters (could be 0) in
|
|
* the parser's list of functions. If the function name already exists then this method throws Exception.
|
|
* Function name is not case sensitive.
|
|
* <br /><br />
|
|
* The second parameter is the name of the user defined function that is globally available:
|
|
* <br /><br />
|
|
* function funcName($parameter1 [, $parameter2,...]);<br />
|
|
* <br /><br />
|
|
* During evaluation of the expression, when the registered function name is
|
|
* encountered, the user supplied funcName will be called and variables will be passed.
|
|
* <br /><br />
|
|
* This user defined function
|
|
* will need to return a result (representing the value of the function)
|
|
* based on the parameters passed as an array of double values.
|
|
* If the number of parameters that a function takes is not known ahead of time,
|
|
* then the $paramCount parameter should be -1. Otherwise, it will be the
|
|
* number of parameters that will be passed to the function at runtime. Knowing the number of
|
|
* parameters ahead of time helps detect syntax errors during parsing. For example, if SIN(x) function
|
|
* descriptor did not tell us that it was taking only 1 parameter, then an expression like:
|
|
* SIN(x,y) would not cause syntax error during parsing, and if the implementation of SIN(x) did not check
|
|
* actual runtime number of parameters passed, then the problem could go undetected
|
|
* and the second y parameter could be ignored silently.
|
|
* @param $newFuncName - Name of the new function to create.
|
|
* @param $funcAddr - name of the PHP function that will be invoked when parser needs to execute this user defined function.
|
|
* @param $paramCount - number of parameters that this function takes. -1 means any number of parameters.
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function createFunc($newFuncName, $funcAddr, $paramCount): void
|
|
{
|
|
if ($newFuncName == null) {
|
|
throw new Exception("Function name cannot be null.");
|
|
}
|
|
if ($funcAddr == null) {
|
|
throw new Exception("Function implementation cannot be null.");
|
|
}
|
|
|
|
$upcName = strtoupper($newFuncName);
|
|
if (!$this->isValidName($upcName)) { //must contain uppercase letters only
|
|
throw new Exception($this->getMessage1("NtFncNm", $newFuncName));
|
|
}
|
|
|
|
if ($this->isFunction($upcName)) {
|
|
throw new Exception($this->getMessage1("FncExst", $newFuncName));
|
|
} else if ($paramCount < -1)//-1 means num params unknown.
|
|
{
|
|
throw new Exception($this->getMessage2("WrngNPrms", $newFuncName, (string)$paramCount));
|
|
} else {
|
|
$func = new Mathparser_ParserFunction($upcName, $funcAddr, $paramCount);
|
|
//if newFuncName doesn't exist it is inserted:
|
|
$this->Functions[$upcName] = $func;
|
|
}
|
|
$this->Dirty = true; //previously bad expression may now be ok, we should reParse it
|
|
}
|
|
|
|
/**
|
|
* CreateDefaultFuncs method creates some predefined functions in the parser's list of functions.
|
|
* <br /><br />
|
|
* Predefined functions are:
|
|
* <br /><br />
|
|
* SQR: Square function which can be used as SQR(X)
|
|
* <br /><br />
|
|
* SIN: Sinus function which can be used as SIN(X), X is a real-type expression.
|
|
* Sin returns the sine of the angle X in radians.
|
|
* <br /><br />
|
|
* COS: Cosinus function which can be used as COS(X), X is a real-type expression.
|
|
* COS returns the cosine of the angle X in radians.
|
|
* <br /><br />
|
|
* ATAN: ArcTangent function which can be used as ATAN(X)
|
|
* Returns the arctangent of a number as a numeric value between -PI/2 and PI/2 radians.
|
|
* <br /><br />
|
|
* SINH: Sinus Hyperbolic function which can be used as SINH(X)
|
|
* <br /><br />
|
|
* COSH: Cosinus Hyperbolic function which can be used as COSH(X)
|
|
* <br /><br />
|
|
* COTAN: which can be used as COTAN(X)
|
|
* <br /><br />
|
|
* TAN: which can be used as TAN(X)
|
|
* <br /><br />
|
|
* EXP: which can be used as EXP(X)
|
|
* <br /><br />
|
|
* LN: natural log, which can be used as LN(X)
|
|
* <br /><br />
|
|
* LOG: 10 based log, which can be used as LOG(X)
|
|
* <br /><br />
|
|
* SQRT: which can be used as SQRT(X)
|
|
* <br /><br />
|
|
* ABS: absolute value, which can be used as ABS(X)
|
|
* <br /><br />
|
|
* SIGN: SIGN(X) returns -1 if X<0; +1 if X>0, 0 if X=0; it can be used as SQR(X)
|
|
* <br /><br />
|
|
* TRUNC: Discards the fractional part of a number. e.g. TRUNC(-3.2) is -3, TRUNC(3.2) is 3.
|
|
* <br /><br />
|
|
* CEIL: CEIL(-3.2) = 3, CEIL(3.2) = 4
|
|
* <br /><br />
|
|
* FLOOR: FLOOR(-3.2) = -4, FLOOR(3.2) = 3
|
|
* <br /><br />
|
|
* VAL: VAL("3.1") = 3.1 Returns the floating point numeric value of the string argument.
|
|
* <br /><br />
|
|
* Predefined functions that take two parameters are:
|
|
* <br /><br />
|
|
* POW: The Power function raises Base to any power. For fractional exponents or
|
|
* exponents greater than MaxInt, Base must be greater than 0.
|
|
* <br /><br />
|
|
* LOGN: The LogN function returns the log base N of X. Example: LOGN(10, 100) = 2
|
|
* <br /><br />
|
|
* MIN: MIN(2, 3) is 2.
|
|
* <br /><br />
|
|
* MAX: MAX(2, 3) is 3.
|
|
* <br /><br />
|
|
* IF: The IF(b, case1, case2) function provides branching capability.
|
|
* If b is not 0, then it returns case1, else it returns case2.
|
|
* Behavior is similar to PHP's: <b>return b ? case1 : case2;</b><br />
|
|
* If b==0 then case1 will not be Evaluated, and vice versa.
|
|
* Example: IF(HEIGHT, 3/HEIGHT, 3) will make sure 3/HEIGHT does not cause division by zero.
|
|
* <br /><br />
|
|
* Predefined functions that take no parameters are:
|
|
* RND: RND() function generates a random number (double value) between 0 and 1.
|
|
* <br /><br />
|
|
* STR: STR(123) function returns the string representation of the passed value: "123".
|
|
* <br /><br />
|
|
* SUBSTR: SUBSTR("Hello", 1,3) function returns the substring just like PHP substring function.
|
|
* The first parameter is the string, the second parameter is which index (0 based) to start copying,
|
|
* and the last parameter is the number of characters to copy. For example, SUBSTR("Hello", 1,3) returns "ell".
|
|
* <br /><br />
|
|
* STRLEN: STRLEN("abc") function returns the length of the string parameter. For example, for "abc" it returns 3.
|
|
* <br /><br />
|
|
* CONCAT: CONCAT("abc","def",...) function returns the concatanated strings: "abcdef".
|
|
* There is no preset limit on the number of parameters.
|
|
* <br /><br />
|
|
* TRIM: TRIM(" abc ") function returns the trimmed version of the string parameter: " abc " -> "abc".
|
|
* <br /><br />
|
|
* SUM: SUM(2,3,5,...) functions returns the sum of it's arguments. There is no preset limit on the number of parameters.
|
|
* <br /><br />
|
|
* User functions can be added using CreateFunc method.
|
|
* Functions and Variables can be deleted using DeleteVar, DeleteFunc,
|
|
* DeleteAllVars, DeleteAllFuncs methods.
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function createDefaultFuncs(): void
|
|
{
|
|
$this->createFunc("SQR", 'mp_square_', 1);
|
|
$this->createFunc("SIN", 'sin', 1);
|
|
$this->createFunc("COS", 'cos', 1);
|
|
$this->createFunc("ATAN", 'atan', 1);
|
|
$this->createFunc("SINH", 'sinh', 1);
|
|
$this->createFunc("COSH", 'cosh', 1);
|
|
$this->createFunc("COTAN", 'mp_cotan_', 1);
|
|
$this->createFunc("TAN", 'tan', 1);
|
|
$this->createFunc("EXP", 'exp', 1);
|
|
$this->createFunc("LN", 'log', 1);
|
|
$this->createFunc("LOG", 'log10', 1);
|
|
$this->createFunc("SQRT", 'sqrt', 1);
|
|
$this->createFunc("ABS", 'abs', 1);
|
|
$this->createFunc("SIGN", 'mp_sign_', 1);
|
|
$this->createFunc("TRUNC", 'mp_trunc_', 1);
|
|
$this->createFunc("CEIL", 'ceil', 1);
|
|
$this->createFunc("FLOOR", 'floor', 1);
|
|
$this->createFunc("RND", 'mp_rand_', 0);
|
|
$this->createFunc("VAL", 'mp_float_', 1);
|
|
|
|
$this->createFunc("POW", 'pow', 2);
|
|
$this->createFunc("LOGN", 'mp_logn_', 2);
|
|
$this->createFunc("MIN", 'mp_min_', -1);
|
|
$this->createFunc("MAX", 'mp_max_', -1);
|
|
$this->createFunc("MOD", 'modulo', 2);
|
|
|
|
$this->createFunc("IF", 'if_', 3);
|
|
|
|
$this->createFunc("STRLEN", 'strlen', 1);
|
|
$this->createFunc("STR", 'strval', 1);
|
|
$this->createFunc("SUBSTR", 'substr', 3);
|
|
$this->createFunc("CONCAT", 'mp_concat_', -1);
|
|
$this->createFunc("TRIM", 'trim', 1);
|
|
$this->createFunc("RTRIM", 'rtrim', 1);
|
|
$this->createFunc("LTRIM", 'ltrim', 1);
|
|
$this->createFunc("CHR", 'chr', 1);
|
|
$this->createFunc("NUM", 'mp_num_', 1);
|
|
|
|
$this->createFunc("SUM", 'sum_', -1);
|
|
|
|
}
|
|
|
|
/**
|
|
* X, Y and PI variables can be predefined and can be immediately used in the expression.
|
|
* Initial values of X and Y are 0. PI is 3.14159265358979
|
|
* @since 1.0.0
|
|
*/
|
|
public function createDefaultVars(): void
|
|
{
|
|
try {
|
|
$this->createVar("PI", 3.14159265358979);
|
|
$this->createVar("X", 0.0);
|
|
$this->createVar("Y", 0.0);
|
|
} catch (Exception $e) {
|
|
//we know that these are valid names, so there won't be an exception.
|
|
Log::add("Pi, X or Y could not be created: " . $e->getMessage(), Log::WARNING,"com_sportsmanager");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns true if a function with the name 'funcName' is present in any of
|
|
* the current functions lists. Returns true if a function with the given name is defined. False otherwise.
|
|
* @param string $funcName - Name of the function in question.
|
|
* @return boolean
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function isFunction(string $funcName): bool
|
|
{
|
|
if ($funcName == null) {
|
|
throw new Exception("Function name cannot be null.");
|
|
}
|
|
$funcName = strtoupper($funcName);
|
|
return isset($this->Functions[$funcName]);
|
|
}
|
|
|
|
/**
|
|
* Optimizes the parse tree by finding branches that evaluate to a constant and
|
|
* replacing them with a leaf representing the constant.
|
|
* Until the expression is changed and reparsed, further evaluation requests will be
|
|
* quicker.
|
|
* <br /><br />
|
|
* If the same expression will not be evaluated repeatedly with varying
|
|
* values of parameters used in it, then optimization will not bring any gain,
|
|
* but will slow performance.
|
|
* <br /><br />
|
|
* If OptimizationOn property is set to true, this method is called automatically when an
|
|
* evaluation is requested by calling Evaluate method or getValue() method.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
public function optimize(): void
|
|
{
|
|
$this->Node = self::optimizeNode($this->Node);
|
|
}
|
|
|
|
/**
|
|
* Value property is a read only property which returns the result of the expression.
|
|
* throws Exception if expression cannot be parsed. Return value is either Double or String.
|
|
* @return double|string
|
|
* @throws Mathparser_ParserException
|
|
* @since 1.0.0
|
|
*/
|
|
public function getValue(): float|string
|
|
{
|
|
return $this->evaluate();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//END OF PUBLIC METHODS.
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @param $index
|
|
* @param $c
|
|
* @return bool
|
|
* @ignore
|
|
* Valid char definition for function and variable names.
|
|
* @since 1.0.0
|
|
*/
|
|
private function isValidChar($index, $c): bool
|
|
{
|
|
if ($index == 0) {
|
|
if (($c >= 'A' && $c <= 'Z')) {
|
|
return true;
|
|
}
|
|
if ($c == '_') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if ((($c >= '0' && $c <= '9') || ($c >= 'A' && $c <= 'Z'))) {
|
|
return true;
|
|
}
|
|
if ($c == '_') {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Valid name definition for function and variable names.
|
|
* @param $name
|
|
* @return bool
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
private function isValidName($name): bool
|
|
{
|
|
$len = strlen($name);
|
|
for ($i = 0; $i < $len; $i++) {
|
|
if (!$this->isValidChar($i, $name[$i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Makes sure number of opening brackets "(" are equal to
|
|
* the number of closing brackets ")" and they are consistent.
|
|
* @param $formula
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
protected static function checkBrackets($formula): int
|
|
{
|
|
//this function checks to see if the order and double of brackets are correct
|
|
//it will say ok if it sees something like 3+()()
|
|
$n = 0;
|
|
$len = strlen($formula);
|
|
$insideStringConst = false;
|
|
for ($i = 0; $i < $len; $i++) { //if length<1 loop won't execute
|
|
$ch = $formula[$i];
|
|
if ($ch == '\"')//support string literals in the expression.
|
|
{
|
|
$insideStringConst = !$insideStringConst;
|
|
continue;
|
|
}
|
|
if (!$insideStringConst) {
|
|
if ($ch == '(')
|
|
++$n;
|
|
else if ($ch == ')')
|
|
--$n;
|
|
|
|
if ($n < 0) return $i; //at any moment if expression is valid we cannot have more ) then (
|
|
}
|
|
}
|
|
return ($n == 0) ? -1 : $len; //true if double of brackets matches
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Removes unncessary outer brackets in an expression.
|
|
* @param $formula
|
|
* @return mixed|string
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
protected static function removeOuterBrackets($formula): mixed
|
|
{
|
|
//has to be careful about (X+1)-(Y-1)
|
|
//should not remove the outer brackets here thinking that they are unnecessary
|
|
//but should remove when ((X+1)-(Y-1))
|
|
$temp = $formula;
|
|
$Len = strlen($temp);
|
|
while (($Len > 2) && ($temp[0] == '(') && ($temp[$Len - 1] == ')')) {
|
|
$temp = trim(substr($temp, 1, $Len - 2));
|
|
if (self::checkBrackets($temp) == -1) { //if we did not screw up then assign to the return value
|
|
$formula = $temp;
|
|
}
|
|
$Len = strlen($temp);
|
|
}
|
|
return $formula;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Determines if a given string is a valid double number or not.
|
|
* If it is valid, the actual double value is returned out in the out parameter dblVal.
|
|
* @param $formula - Expression to parse.
|
|
* @param $dblVal - Output double value
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
protected static function isValidDouble($formula, &$dblVal): bool
|
|
{
|
|
if (is_numeric($formula)) {
|
|
$dblVal = (float)$formula;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the string literal if this formula is a string literal such as "abc".
|
|
* Escape character is " inside the string. Meaning, to have quotes inside the
|
|
* string it would be like this:
|
|
* "abc""defg".<br />
|
|
* Returns null if this is not a valid string literal.
|
|
* @param $formula - The sub expression in question.
|
|
* @return string|null the string that is inside the "" after "" escapes inside the string are processed. Null if the string is not valid.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
private static function getStringLiteral($formula): ?string
|
|
{
|
|
$len = strlen($formula);
|
|
if (!($len > 1 && $formula[0] == '"' && $formula[$len - 1] == '"')) {
|
|
return null;
|
|
}
|
|
$temp = substr($formula, 1, $len - 2);
|
|
if (self::checkEscapes($temp)) {
|
|
return str_replace('""', '"', $temp);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param $formula
|
|
* @return boolean
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
private static function checkEscapes($formula): bool
|
|
{
|
|
//this function checks to see if the escaped quotes match.
|
|
$len = strlen($formula);
|
|
$insideStringConst = false;
|
|
for ($i = 0; $i < $len; $i++) { //if length<1 loop won't execute
|
|
$ch = $formula[$i];
|
|
if ($ch == '"') //support string literals in the expression.
|
|
{
|
|
if ($insideStringConst) {
|
|
return false; //too many quotes.
|
|
}
|
|
++$i;
|
|
if ($i < $len && $formula[$i] == '"') {
|
|
$insideStringConst = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
$insideStringConst = false;
|
|
}
|
|
return true;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Compiles the parse tree of the given expression.
|
|
* @param $expToParse - Mathematical expression.
|
|
* @return mixed node of the parse tree.
|
|
* @throws Mathparser_ParserException
|
|
* @since 1.0.0
|
|
* @ignore
|
|
*/
|
|
protected function createParseTree($expToParse): mixed
|
|
{
|
|
$expToParse = trim($expToParse);
|
|
if (($len = strlen($expToParse)) == 0) {
|
|
return null;
|
|
}
|
|
|
|
$formula = $this->removeOuterBrackets($expToParse); //remove unnecessary brackets
|
|
|
|
if (strlen($formula) != $len) {
|
|
$formula = trim($formula);
|
|
if (strlen($formula) == 0) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
//is this text a simple double value?
|
|
$dblVal = null;
|
|
if ($this->isValidDouble($formula, $dblVal)) { //attach a double node in the structure
|
|
return new Mathparser_BasicNode($dblVal); //we create a double node and attach it to the *Node reference.
|
|
}
|
|
|
|
if ($this->StringLiteralsAllowed) {
|
|
$tempFormula = $this->getStringLiteral($formula);
|
|
if ($tempFormula != null) {
|
|
return new Mathparser_BasicNode($tempFormula); //we create a double node and attach it to the *Node reference.
|
|
}
|
|
}
|
|
|
|
//if it is not a simple double or a string, maybe it is a variable?
|
|
$varNode = $this->createVarNode($formula);
|
|
if ($varNode != null) {
|
|
return $varNode;
|
|
}
|
|
|
|
//if it is not a variable
|
|
$LastOper = $this->findLastOper($formula);
|
|
|
|
$funcAddr = null;
|
|
if (!($LastOper > 0)) //if it is 0 then it is a unary operation which is a one param function
|
|
{
|
|
$param = null;
|
|
if ($this->isOneParamFunc($formula, $funcAddr, $param, $LastOper)) {
|
|
if (($leftNode = $this->createParseTree($param)) == null) {
|
|
throw new Mathparser_ParserException($this->getMessage2("ExpNtVld", $param, $formula), $param, $formula);
|
|
}
|
|
if ($funcAddr != null) {
|
|
return new Mathparser_NParamNode(array($leftNode), $funcAddr);
|
|
}
|
|
}
|
|
}
|
|
|
|
$paramLeft = null;
|
|
$paramRight = null;
|
|
if ($this->isTwoParamFunc($formula, $funcAddr, $paramLeft, $paramRight, $LastOper)) {
|
|
if (($leftNode = $this->createParseTree($paramLeft)) == null) {
|
|
throw new Mathparser_ParserException($this->getMessage2("ExpNtVld", $paramLeft, $formula), $paramLeft, $formula);
|
|
}
|
|
if (($rightNode = $this->createParseTree($paramRight)) == null) {
|
|
throw new Mathparser_ParserException($this->getMessage2("ExpNtVld", $paramRight, $formula), $paramRight, $formula);
|
|
}
|
|
//if there is a function address returned then we use it, otherwise we use function name
|
|
if ($funcAddr != null) {
|
|
return new Mathparser_NParamNode(array($leftNode, $rightNode), $funcAddr);
|
|
}
|
|
}
|
|
|
|
$parms = null;
|
|
//if it is none of the above:
|
|
if ($this->isNParamFunc($formula, /*out*/ $funcAddr, /*out*/ $parms)) {
|
|
if (!isset($parms)) {
|
|
//We now allow functions with no parameters.
|
|
throw new Mathparser_ParserException($this->getMessage1("InvNPrm", $formula), $formula, $formula);
|
|
}
|
|
$nParam = count($parms);
|
|
$nodes = array();
|
|
for ($i = 0; $i < $nParam; $i++) {
|
|
if (($leftNode = $this->createParseTree($parms[$i])) == null) {
|
|
throw new Mathparser_ParserException($this->getMessage2("ExpNtVld", $parms[$i], $formula), $parms[$i], $formula);
|
|
}
|
|
$nodes[$i] = $leftNode;
|
|
}
|
|
|
|
//if there is a function address returned then we use it, otherwise we use function name
|
|
if ($funcAddr != null) {
|
|
return new Mathparser_NParamNode($nodes, $funcAddr);
|
|
}
|
|
}
|
|
//when code reaches here it means we did not return true so after compiling the expression.
|
|
return null;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Finds the index of the last math operation in an expression.
|
|
* @param $formula - Math expression.
|
|
* @return int index of the last operand.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
private static function findLastOper($formula): int
|
|
{ //returns -1 if it cannot find anything
|
|
$Precedence = 13; //There are 12 operands and 13 is higher then all
|
|
$BracketLevel = 0; //shows the level of brackets we moved through
|
|
$Result = -1;
|
|
$Len = strlen($formula);
|
|
$lastWasOperator = 0;
|
|
$insideStringLiteral = false;
|
|
for ($i = 0; $i < $Len; $i++) //from left to right scan...
|
|
{
|
|
$current_ch = $formula[$i];
|
|
if ($current_ch == '"') {
|
|
$insideStringLiteral = !$insideStringLiteral;
|
|
$lastWasOperator = 0;
|
|
continue;
|
|
}
|
|
if ($insideStringLiteral) {
|
|
continue; //skip string literals.
|
|
}
|
|
if ($lastWasOperator > 2) {
|
|
return -1;
|
|
}
|
|
switch ($current_ch) {
|
|
case ')' :
|
|
--$BracketLevel; //counting bracket levels
|
|
$lastWasOperator = 0;
|
|
break;
|
|
case '(' :
|
|
++$BracketLevel;
|
|
$lastWasOperator = 0;
|
|
break;
|
|
|
|
case '|' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 1) {
|
|
$Precedence = 1;
|
|
$Result = $i;
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
|
|
case '&' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 2) {
|
|
$Precedence = 2;
|
|
$Result = $i;
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
|
|
case '!' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 3) {
|
|
$Precedence = 3;
|
|
$Result = $i;
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
|
|
case '=' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 4) {
|
|
$Precedence = 4;
|
|
$Result = $i;
|
|
}
|
|
if ($lastWasOperator > 0) {
|
|
$prevOperIndex = $i - $lastWasOperator;
|
|
if ($formula[$prevOperIndex] == '<' || $formula[$prevOperIndex] == '>') {
|
|
break; //skip incrementing lastWasOperator variable.
|
|
}
|
|
}
|
|
|
|
++$lastWasOperator;
|
|
break;
|
|
|
|
case '>' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 5) {
|
|
$Precedence = 5;
|
|
$Result = $i;
|
|
}
|
|
if ($lastWasOperator > 0) {
|
|
if ($formula[$i - $lastWasOperator] == '<') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
++$lastWasOperator;
|
|
break;
|
|
|
|
case '<' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 5) {
|
|
$Precedence = 5;
|
|
$Result = $i;
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
|
|
case '-' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0)) //a main operation has to be outside the brackets
|
|
if ($Precedence >= 7) //seeking for lowest precedence
|
|
{
|
|
$Precedence = 7;
|
|
$Result = $i; //record the current index.
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
case '+' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 7) {
|
|
$Precedence = 7;
|
|
$Result = $i;
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
case '/':
|
|
case '*':
|
|
case '%' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 9) {
|
|
$Precedence = 9;
|
|
$Result = $i;
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
case '^' :
|
|
if (!($BracketLevel > 0 || $lastWasOperator > 0))
|
|
if ($Precedence >= 12) {
|
|
$Precedence = 12;
|
|
$Result = $i;
|
|
}
|
|
++$lastWasOperator;
|
|
break;
|
|
|
|
case 'E' :
|
|
if ($i > 0 && $lastWasOperator == 0) {
|
|
$ch = $formula[$i - 1];
|
|
if ($ch >= '0' && $ch <= '9') {//this E may be part of a number in scientific notation.
|
|
$j = $i;
|
|
while ($j > 0) { //trace back.
|
|
--$j;
|
|
$ch = $formula[$j];
|
|
if ($ch == '.' || ($ch >= '0' && $ch <= '9')) { //if it is not a function or variable name.
|
|
continue;
|
|
}
|
|
if ($ch == '_' || ($ch >= 'A' && $ch <= 'Z')) {//is it a func or var name?
|
|
$lastWasOperator = 0;
|
|
break; //break the while loop.
|
|
}
|
|
++$lastWasOperator; //it must be an operator or a paranthesis.
|
|
break; //break the while loop.
|
|
}
|
|
if ($j == 0 && ($ch >= '0' && $ch <= '9')) {
|
|
++$lastWasOperator;
|
|
}
|
|
} else {
|
|
$lastWasOperator = 0;
|
|
}
|
|
} else {
|
|
$lastWasOperator = 0;
|
|
}
|
|
break;
|
|
case ' ': //space:
|
|
break;
|
|
default :
|
|
$lastWasOperator = 0;
|
|
break;
|
|
}
|
|
}
|
|
return $Result;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Determines if a given expression by itself is a function that takes 2 parameters.
|
|
* For example: and input of "MAX(x,y+1)" would return true.
|
|
* @param $formula - expression
|
|
* @param $funcAddr - The IFunction that this expression represents (if it is a function call)
|
|
* @param $paramLeft - Left parameter whose values will be sent out in a wrapper utility object.
|
|
* @param $paramRight - Right parameter group whose values will be sent out in a wrapper utility object.
|
|
* @param $CurrChar - current operand index in the expression
|
|
* @return true if formula is a valid two parameter function.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
private function isTwoParamFunc($formula, &$funcAddr, &$paramLeft, &$paramRight,
|
|
$CurrChar //gives the last operation index in the string
|
|
): bool
|
|
{
|
|
$funcAddr = null;
|
|
$paramLeft = null;
|
|
$paramRight = null;
|
|
$Len = strlen($formula);
|
|
|
|
if ($CurrChar > 0) //if function in question is an operand
|
|
{
|
|
if ($CurrChar > $Len - 2) {
|
|
return false;
|
|
}
|
|
$CurrCh = $formula[$CurrChar];
|
|
|
|
//was it an operand also? we want to find <>, >=, <=
|
|
if ($CurrCh == '<') {
|
|
$nextCh = $formula[$CurrChar + 1]; //look ahead.
|
|
if ($nextCh == '>') {
|
|
$funcAddr = self::$notequals__;
|
|
$paramLeft = substr($formula, 0, $CurrChar);
|
|
$paramRight = substr($formula, $CurrChar + 2);
|
|
} else if ($nextCh == '=') {
|
|
$funcAddr = self::$ltequals__;
|
|
$paramLeft = substr($formula, 0, $CurrChar);
|
|
$paramRight = substr($formula, $CurrChar + 2);
|
|
} else {
|
|
$funcAddr = self::$lt__; //default case.
|
|
$paramLeft = substr($formula, 0, $CurrChar);
|
|
$paramRight = substr($formula, $CurrChar + 1);
|
|
}
|
|
|
|
if (!(strlen($paramLeft) > 0)) {
|
|
return false;
|
|
}
|
|
if (!(strlen($paramRight) > 0)) {
|
|
return false;
|
|
}
|
|
return true; //all output is assigned, now we return true.
|
|
} else if ($CurrCh == '>') {
|
|
$nextCh = $formula[$CurrChar + 1];
|
|
if ($nextCh == '=') {
|
|
$funcAddr = self::$gtequals__;
|
|
$paramLeft = substr($formula, 0, $CurrChar);
|
|
$paramRight = substr($formula, $CurrChar + 2);
|
|
} else {
|
|
$funcAddr = self::$gt__; //default case.
|
|
$paramLeft = substr($formula, 0, $CurrChar);
|
|
$paramRight = substr($formula, $CurrChar + 1);
|
|
}
|
|
if (!(strlen($paramLeft) > 0)) {
|
|
return false;
|
|
}
|
|
if (!(strlen($paramRight) > 0)) {
|
|
return false;
|
|
}
|
|
return true; //all output is assigned, now we return true.
|
|
} else {
|
|
|
|
$paramLeft = substr($formula, 0, $CurrChar);
|
|
if (!(strlen($paramLeft) > 0)) {
|
|
return false;
|
|
}
|
|
|
|
$paramRight = substr($formula, $CurrChar + 1);
|
|
if (!(strlen($paramRight) > 0)) {
|
|
return false;
|
|
}
|
|
|
|
switch ($formula[$CurrChar]) {
|
|
//analytical operators:
|
|
case '+':
|
|
$funcAddr = $this->AddOp;
|
|
break;
|
|
case '-':
|
|
$funcAddr = self::$subtract__;
|
|
break;
|
|
case '*':
|
|
$funcAddr = self::$mult__;
|
|
break;
|
|
case '/':
|
|
$funcAddr = self::$div__;
|
|
break;
|
|
case '^':
|
|
$funcAddr = self::$power__;
|
|
break;
|
|
case '%':
|
|
$funcAddr = self::$mod__;
|
|
break;
|
|
|
|
//logical operators:
|
|
case '<':
|
|
$funcAddr = self::$lt__;
|
|
break;
|
|
case '>':
|
|
$funcAddr = self::$gt__;
|
|
break;
|
|
case '=':
|
|
$funcAddr = self::$equals__;
|
|
break;
|
|
case '&':
|
|
$funcAddr = $this->AndOp;
|
|
break;
|
|
case '|':
|
|
$funcAddr = self::$or__;
|
|
break;
|
|
}
|
|
}
|
|
return true; //all output is assigned, now we return true.
|
|
}
|
|
//if we reach here, result is false
|
|
//if main operation is not an operand but a function
|
|
$temp = '';
|
|
if ($formula[$Len - 1] == ')') //last character must be brackets closing function param list
|
|
{
|
|
$i = 0;
|
|
while ($this->isValidChar($i, $formula[$i])) {
|
|
$temp .= ($formula[$i]);
|
|
++$i;
|
|
}
|
|
while ($formula[$i] == ' ') {
|
|
++$i;
|
|
}
|
|
|
|
if (($formula[$i] == '(') && ($i < $Len - 1)) {
|
|
$func = $this->Functions[$temp];
|
|
if ($func != null && $func->paramCount == 2) {
|
|
$funcAddr = $func;
|
|
$paramStart = $i + 1;
|
|
$BracketLevel = 1;
|
|
$insideStringLiteral = false;
|
|
while (!($i > $Len - 1 - 1)) //last character is a ')', that's why we use i>Len-1
|
|
{
|
|
++$i;
|
|
switch ($formula[$i]) {
|
|
case '"':
|
|
$insideStringLiteral = !$insideStringLiteral;
|
|
break;
|
|
case '(':
|
|
if (!$insideStringLiteral) ++$BracketLevel;
|
|
break;
|
|
case ')':
|
|
if (!$insideStringLiteral) --$BracketLevel;
|
|
break;
|
|
case ',':
|
|
if ((!$insideStringLiteral) && (1 == $BracketLevel) && ($i < $Len - 2)) //last character is a ')', that's why we use i>Len-2
|
|
{
|
|
$paramLeft = substr($formula, $paramStart, $i - $paramStart);
|
|
$paramRight = substr($formula, $i + 1, $Len - 1 - ($i + 1)); //last character is a ')', that's why we use Len-1-i
|
|
return true; //we are sure that it is a two parameter function
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false; //means we could not find it
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Determines if a given expression by itself is a function that takes 1 parameter.
|
|
* For example: and input of "SIN(x)" would return true.
|
|
* @param $formula - expression
|
|
* @param $funcAddr - The IFunction that this node represents.
|
|
* @param $param - the parameter that will be passed into this function.
|
|
* @param $CurrChar - current operand index in the expression
|
|
* @return true if formula is a valid one parameter function.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
private function isOneParamFunc($formula, &$funcAddr, &$param, $CurrChar): bool
|
|
{
|
|
$funcAddr = null;
|
|
$param = null;
|
|
$Len = strlen($formula);
|
|
if ($CurrChar == 0) //if function in question is an unary operand
|
|
{
|
|
$param = substr($formula, 1);
|
|
if (!(strlen($param) > 0)) {
|
|
return false;
|
|
}
|
|
|
|
switch ($formula[$CurrChar]) {
|
|
case '+':
|
|
$funcAddr = self::$unaryadd__;
|
|
break;
|
|
case '-':
|
|
$funcAddr = self::$negate__;
|
|
break;
|
|
case '!':
|
|
$funcAddr = self::$not__;
|
|
break;
|
|
default:
|
|
return false; //only + and - can be unary operators
|
|
}
|
|
return true; //all output is assigned, now we exit.
|
|
}
|
|
//if we reach here, result is false
|
|
//if main operation is not an operand but a function
|
|
if ($formula[$Len - 1] == ')') //last character must be brackets closing function param list
|
|
{
|
|
$i = 0;
|
|
$temp = '';
|
|
while ($this->isValidChar($i, $formula[$i])) {
|
|
$temp .= ($formula[$i]);
|
|
++$i;
|
|
}
|
|
while ($formula[$i] == ' ') {
|
|
++$i;
|
|
}
|
|
if (($formula[$i] == '(') && ($i < $Len - 2)) {
|
|
$func = $this->Functions[$temp];
|
|
if ($func != null && $func->paramCount == 1) {
|
|
$funcAddr = $func;
|
|
$paramStart = $i + 1;
|
|
$param = substr($formula, $paramStart, $Len - 1 - $paramStart); //check example: SIN(30)
|
|
return true; //we are sure that it is a two parameter function
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Determines if a given expression by itself is a function that takes N parameters.
|
|
* For example: and input of "IF(x, 3/x, 3)" would return true.
|
|
* @param $formula - expression
|
|
* @param $funcAddr - the IFunction that this node represents.
|
|
* @param $parms - the parameters that this function takes in this expression.
|
|
* @return true if formula is a valid N parameter function.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
private function isNParamFunc($formula, &$funcAddr, &$parms): bool
|
|
{
|
|
$funcAddr = null;
|
|
$parms = null;
|
|
//if main operation is not an operand but a function
|
|
$Len = strlen($formula);
|
|
$temp = '';
|
|
if ($formula[$Len - 1] == ')') //last character must be brackets closing function param list
|
|
{
|
|
$i = 0;
|
|
while ($this->isValidChar($i, $formula[$i])) {
|
|
$temp .= ($formula[$i]);
|
|
++$i;
|
|
}
|
|
while ($formula[$i] == ' ') {
|
|
++$i;
|
|
}
|
|
if (($formula[$i] == '(') && ($i < $Len - 1)) {
|
|
//we first check if it is one param func or two param func,
|
|
//so here if it is a func, we know it is N param func.
|
|
$funcAddr = $this->Functions[$temp];
|
|
|
|
if ($funcAddr != null) {
|
|
$nParams = $funcAddr->paramCount;
|
|
if ($nParams > -1) {
|
|
$parms = array();
|
|
if ($nParams == 0) //a function that takes no param.
|
|
{
|
|
if ($formula[$i + 1] == ')') {
|
|
return true;
|
|
}
|
|
}
|
|
$paramStart = $i + 1;
|
|
$BracketLevel = 1;
|
|
$pIndex = 0;
|
|
$insideStringLiteral = false;
|
|
while (!($i > $Len - 1 - 1)) //last character is a ')', that's why we use i>Len-1
|
|
{
|
|
++$i;
|
|
switch ($formula[$i]) {
|
|
case '"':
|
|
$insideStringLiteral = !$insideStringLiteral;
|
|
break;
|
|
case '(':
|
|
if (!$insideStringLiteral) ++$BracketLevel;
|
|
break;
|
|
case ')':
|
|
if (!$insideStringLiteral) --$BracketLevel;
|
|
break;
|
|
case ',':
|
|
if ((!$insideStringLiteral) && (1 == $BracketLevel) && ($i < $Len - 2)) //last character is a ')', that's why we use i>Len-2
|
|
{
|
|
//must have at least 2 params for this part to work:
|
|
if ($nParams > -1 && !($pIndex < $nParams)) {
|
|
return false; //wrong number of parameters.
|
|
}
|
|
$parms[$pIndex++] = substr($formula, $paramStart, $i - $paramStart);
|
|
|
|
if ($pIndex == $nParams - 1) {
|
|
//assign the last one:
|
|
$parms[$pIndex] = substr($formula, $i + 1, $Len - 1 - ($i + 1));
|
|
return true;
|
|
}
|
|
$paramStart = $i + 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
$list = array();
|
|
$paramStart = $i + 1;
|
|
$BracketLevel = 1;
|
|
$insideStringLiteral = false;
|
|
while (!($i > $Len - 1 - 1)) //last character is a ')', that's why we use i>Len-1
|
|
{
|
|
++$i;
|
|
switch ($formula[$i]) {
|
|
case '"':
|
|
$insideStringLiteral = !$insideStringLiteral;
|
|
break;
|
|
case '(':
|
|
if (!$insideStringLiteral) ++$BracketLevel;
|
|
break;
|
|
case ')':
|
|
if (!$insideStringLiteral) --$BracketLevel;
|
|
break;
|
|
case ',':
|
|
if (!$insideStringLiteral && (1 == $BracketLevel) && ($i < $Len - 2)) //last character is a ')', that's why we use i>Len-2
|
|
{
|
|
$list[] = substr($formula, $paramStart, $i - $paramStart);
|
|
$paramStart = $i + 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
//add the remaining:
|
|
$remaining = trim(substr($formula, $paramStart, $Len - 1 - $paramStart));
|
|
if (strlen($remaining) > 0) {
|
|
$list[] = $remaining;
|
|
}
|
|
|
|
$parms = $list;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false; //means we could not find it
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Optimizes a compiled tree where root is the given Node.
|
|
* @param $aNode - Root node.
|
|
* @return mixed - Starting root node of the optimized tree.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static function optimizeNode($aNode): mixed
|
|
{
|
|
$aNode->optimize();
|
|
//Below code changes the value of the Node pointer,
|
|
//thus it cannot be done inside a polymorphic method of the
|
|
//class instance pointed to by that pointer.
|
|
//Therefore, "instanceof" is required.
|
|
if ($aNode instanceof Mathparser_NParamNode) {
|
|
$count = count($aNode->nodes);
|
|
for ($i = 0; $i < $count; $i++) {
|
|
//if any node is not constant, just return as is:
|
|
if (!($aNode->nodes[$i] instanceof Mathparser_BasicNode)) {
|
|
return $aNode;
|
|
}
|
|
}
|
|
//if all parameters of the function are constants (basic nodes):
|
|
return new Mathparser_BasicNode($aNode->getValue());
|
|
}
|
|
return $aNode;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Expression property represents the mathematical expression which is input to be Evaluated by the user.
|
|
*
|
|
* The expression can contain variables such as X, Y, T, HEIGHT, WEIGHT and so on.
|
|
* The values of these constants are set before the expression is evaluated.
|
|
* The values of the variables can be set before the expression is evaluated or the values can be provided
|
|
* during evaluation using a PHP function registered as $VariableResolver.
|
|
* @return string
|
|
* @since 1.0.0
|
|
*/
|
|
public function getExpression(): string
|
|
{
|
|
return $this->Expression;
|
|
}
|
|
|
|
/**
|
|
* @param $value
|
|
* @return void
|
|
* @see getExpression
|
|
* @since 1.0.0
|
|
*/
|
|
public function setExpression($value): void
|
|
{
|
|
if ($value != $this->Expression) {
|
|
$this->Expression = $value;
|
|
$this->Dirty = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method can be overridden to return a Node that is fancy enough to lookup
|
|
* values from a database etc to come up with variable values at evaluation time.
|
|
* @param string $varName
|
|
* @return Mathparser_UnknownVarNode|Mathparser_VarNode|null
|
|
* @since 1.0.0
|
|
*/
|
|
protected function createVarNode(string $varName): Mathparser_UnknownVarNode|null|Mathparser_VarNode
|
|
{
|
|
//if it is not a simple double, maybe it is a variable?
|
|
$variable = $this->Variables[$varName];
|
|
if ($variable != null) {
|
|
return new Mathparser_VarNode($variable); //recursion will end on these points when we get to the basics
|
|
} else if ($this->VariableResolver != null) {
|
|
$Len = strlen($varName);
|
|
for ($i = 0; $i < $Len; $i++) {
|
|
$ch = $varName[$i];
|
|
if (!$this->isValidChar($i, $ch)) {
|
|
return null;
|
|
}
|
|
}
|
|
return new Mathparser_UnknownVarNode($this, $varName);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Translation support.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static function getMessage($key)
|
|
{
|
|
try {
|
|
if (self::$translator == null) {
|
|
self::getTranslationStrings();
|
|
}
|
|
$s = self::$translator[$key];
|
|
if ($s == null) {
|
|
$s = $key;
|
|
}
|
|
return $s;
|
|
} catch (Exception $e) {
|
|
Log::add("Could not translate: " . $e->getMessage(), Log::WARNING,"com_sportsmanager");
|
|
return $key;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Translation support.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static function getMessage1($key, $param): string
|
|
{
|
|
$temp = self::getMessage($key);
|
|
//assuming {0} is not used in the real string,
|
|
//and it is only a placeholder for the first parameter.
|
|
return sprintf($temp, $param);
|
|
}
|
|
|
|
/**
|
|
* Translation support.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
static function getMessage2($key, $param0, $param1 = ''): string
|
|
{
|
|
$temp = self::getMessage($key);
|
|
return sprintf($temp, $param0, $param1);
|
|
}
|
|
|
|
} //End of Math Parser Implementation.
|
|
|
|
|
|
/**
|
|
* Internal support class to help internal representation of a variable and a constant.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
class Mathparser_Variable
|
|
{
|
|
|
|
/**
|
|
* The parent parser instance, used to call the VariableResolver if needed.
|
|
* @since 1.0.0
|
|
*/
|
|
private $Parser;
|
|
|
|
/**
|
|
* Name of this variable.
|
|
* @since 1.0.0
|
|
*/
|
|
public string $Name;
|
|
|
|
/**
|
|
* The preset value of this variable.
|
|
* @since 1.0.0
|
|
*/
|
|
public float|string $Value;
|
|
|
|
/**
|
|
* The delegate function name (string value) that supplies variable values.
|
|
* If this is not null, then it is called to get
|
|
* the value for this variable.
|
|
* <br /><br /> function VariableResolver($parser, $varName);<br /><br />
|
|
* @since 1.0.0
|
|
*/
|
|
public $ValueProvider;
|
|
|
|
/**
|
|
* If the $this->ValueProvider is set, then this method uses it to get the runtime value.
|
|
* Otherwise, this method returns the value of $this->Value;
|
|
* @since 1.0.0
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getRuntimeValue(){
|
|
return !is_callable($this->ValueProvider) ? $this->Value : ($this->ValueProvider)($this->Parser, $this->Name);
|
|
}
|
|
|
|
public function __construct($parser, $aName, $newVal, $valueProvider)
|
|
{
|
|
$this->Parser = $parser;
|
|
$this->Name = $aName;
|
|
$this->Value = $newVal;
|
|
$this->ValueProvider = $valueProvider;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal support class to represent a user defined function.
|
|
* User defined functions can be used in expressions.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
class Mathparser_ParserFunction
|
|
{
|
|
/**
|
|
* $EventHandler that is the name of the actual PHP function to call.
|
|
* @since 1.0.0
|
|
*/
|
|
public $EventHandler;
|
|
/**
|
|
* Name of the user defined function as used in an expression.
|
|
* @since 1.0.0
|
|
*/
|
|
public string $Name;
|
|
/**
|
|
* Number of parameters that the user defined function accepts.
|
|
* -1 means any number of parameters.
|
|
* 0 means no parameters, 1 means 1 parameter like SIN(X) etc.
|
|
* @since 1.0.0
|
|
*/
|
|
public int $paramCount;
|
|
|
|
/**
|
|
* Constructor.
|
|
* @param $name - Name of the user defined function as used in an expression.
|
|
* @param $eventHandler - is the name of the actual PHP function to call.
|
|
* @param $paramCount - Number of parameters that the user defined function accepts.
|
|
* @since 1.0.0
|
|
*/
|
|
public function __construct($name, $eventHandler, $paramCount)
|
|
{
|
|
$this->EventHandler = $eventHandler;
|
|
$this->Name = $name;
|
|
$this->paramCount = $paramCount;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal support class that represents constants such as 3, 5, 7 in the formula.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
class Mathparser_BasicNode extends Mathparser_Node
|
|
{
|
|
public $Value;
|
|
|
|
public function __construct($Val)
|
|
{
|
|
$this->Value = $Val;
|
|
}
|
|
|
|
public function getValue()
|
|
{
|
|
return $this->Value;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function getValueAsDouble(): float
|
|
{
|
|
if (is_float($this->Value)) {
|
|
return $this->Value;
|
|
} else if (is_numeric($this->Value)) {
|
|
return (float)$this->Value;
|
|
}
|
|
throw new Exception("Value is not numeric: " . $this->Value);
|
|
}
|
|
|
|
public function getValueAsString(): string
|
|
{
|
|
return (string)$this->Value;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
public function isVariableUsed($Name): bool
|
|
{
|
|
return false; //a basic node does not store any variable or function info
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
public function isFunctionUsed($Name): bool
|
|
{
|
|
return false; //a basic node does not store any variable or function info
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//since basic node cannot be Optimized further, this function does nothing
|
|
public function optimize()
|
|
{
|
|
//Optimize Evaluates constant values at compile time.
|
|
//do nothing.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal support class that represents N parameter functions such as IF(X, Y, Z) etc.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
class Mathparser_NParamNode extends Mathparser_Node
|
|
{
|
|
public array $nodes; //array of nodes (these are the parameters).
|
|
public $fPtr; //function to execute
|
|
|
|
public function __construct($n, $FuncAddr)
|
|
{
|
|
$this->nodes = &$n;
|
|
$this->fPtr = $FuncAddr;
|
|
}
|
|
|
|
public function getValue()
|
|
{
|
|
if ($this->fPtr->Name == 'IF') {
|
|
//special case for short circuit boolean logic.
|
|
//helps to avoid div by zero etc.
|
|
//assume there are 3 parameters:
|
|
$cond = $this->nodes[0]->getValue();
|
|
if ($cond == 0) { //false:
|
|
return $this->nodes[2]->getValue();
|
|
} else {
|
|
return $this->nodes[1]->getValue();
|
|
}
|
|
} else {
|
|
$p = array();
|
|
$count = count($this->nodes);
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$p[] = $this->nodes[$i]->getValue();
|
|
}
|
|
return call_user_func_array($this->fPtr->EventHandler, $p);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function getValueAsDouble(): float
|
|
{
|
|
$Value = $this->getValue();
|
|
if (is_float($Value)) {
|
|
return $Value;
|
|
} else if (is_numeric($Value)) {
|
|
return (float)$Value;
|
|
}
|
|
throw new Exception("Value is not numeric: " . $Value);
|
|
}
|
|
|
|
public function getValueAsString(): string
|
|
{
|
|
return (string)$this->getValue();
|
|
}
|
|
|
|
public function isFunctionUsed($Name): bool
|
|
{
|
|
if ($this->fPtr->Name == $Name) {
|
|
return true;
|
|
}
|
|
foreach ($this->nodes as $n) {
|
|
if ($n->isFunctionUsed($Name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function isVariableUsed($Name): bool
|
|
{
|
|
foreach ($this->nodes as $n) {
|
|
if ($n->isVariableUsed($Name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function optimize(): void
|
|
{
|
|
$i = 0;
|
|
foreach ($this->nodes as $n) {
|
|
$this->nodes[$i] = MathParser::optimizeNode($n);
|
|
$i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal support class that represents a variable Node.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
class Mathparser_VarNode extends Mathparser_Node
|
|
{
|
|
private $pVar; //address of the variable in the variable list
|
|
|
|
public function __construct($variable)
|
|
{
|
|
$this->pVar = $variable;
|
|
}
|
|
|
|
public function getValue()
|
|
{
|
|
return $this->pVar->getRuntimeValue();
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function getValueAsDouble(): float
|
|
{
|
|
$Value = $this->pVar->getRuntimeValue();
|
|
if (is_float($Value)) {
|
|
return $Value;
|
|
} else if (is_numeric($Value)) {
|
|
return (float)$Value;
|
|
}
|
|
throw new Exception("Value is not numeric: " . $Value);
|
|
}
|
|
|
|
public function getValueAsString(): string
|
|
{
|
|
return (string)$this->pVar->getRuntimeValue();
|
|
}
|
|
|
|
public function isVariableUsed($Name): bool
|
|
{
|
|
return $this->pVar->Name == $Name;
|
|
}
|
|
|
|
public function isFunctionUsed($Name): bool
|
|
{
|
|
return false;
|
|
}
|
|
//since there is no parameter used to get the value of a variable, no further
|
|
//optimization can be made.
|
|
public function optimize()
|
|
{ //Optimize Evaluates constant values at compile time.
|
|
//do nothing
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Internal support class that represents a variable Node that was not defined before the parse.
|
|
* We will resolve it's value during evaluation.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
class Mathparser_UnknownVarNode extends Mathparser_Node
|
|
{
|
|
private string $VarName; //name of the variable that was defined on the fly.
|
|
|
|
private $MathParser;
|
|
|
|
public function __construct($parser, $varName)
|
|
{
|
|
$this->MathParser = $parser;
|
|
$this->VarName = $varName;
|
|
}
|
|
|
|
public function getValue()
|
|
{
|
|
return call_user_func($this->MathParser->VariableResolver, $this->MathParser, $this->VarName);
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
*/
|
|
public function getValueAsDouble(): float
|
|
{
|
|
$Value = $this->getValue();
|
|
if (is_float($Value)) {
|
|
return $Value;
|
|
} else if (is_numeric($Value)) {
|
|
return (float)$Value;
|
|
}
|
|
throw new Exception("Value is not numeric: " . $Value);
|
|
}
|
|
|
|
public function getValueAsString(): string
|
|
{
|
|
return (string)$this->getValue();
|
|
}
|
|
|
|
public function isVariableUsed($Name): bool
|
|
{
|
|
return $this->VarName == $Name;
|
|
}
|
|
|
|
public function isFunctionUsed($Name): bool
|
|
{
|
|
return false;
|
|
}
|
|
//since there is no parameter used to get the value of a variable, no further
|
|
//optimization can be made.
|
|
public function optimize()
|
|
{ //Optimize Evaluates constant values at compile time.
|
|
//do nothing
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Base class for expression tree nodes.
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
abstract class Mathparser_Node
|
|
{
|
|
/**
|
|
* Returns the computed value of this Node.
|
|
* @since 1.0.0
|
|
*/
|
|
abstract public function getValue();
|
|
|
|
/**
|
|
* Returns the computed value of this Node as a double precision value.
|
|
* @since 1.0.0
|
|
*/
|
|
abstract public function getValueAsDouble();
|
|
|
|
/**
|
|
* Returns the value as a String.
|
|
* @since 1.0.0
|
|
*/
|
|
abstract public function getValueAsString();
|
|
|
|
/**
|
|
* Is this variable used in the expression under this node?
|
|
* @since 1.0.0
|
|
*/
|
|
abstract public function isVariableUsed($Name);
|
|
|
|
/**
|
|
* Is this function used in the expression under this node?
|
|
* @since 1.0.0
|
|
*/
|
|
abstract public function isFunctionUsed($Name);
|
|
|
|
/**
|
|
* Optimize Evaluates constant values at compile time (When parse() is called).
|
|
* Once this is done, Evaluate will run faster.
|
|
* @since 1.0.0
|
|
*/
|
|
abstract public function optimize();
|
|
}
|
|
|
|
/**
|
|
* ParserException is thrown by some methods of MathParser implementation if an
|
|
* expression cannot be parsed. These methods are:
|
|
* parse(), evaluate(), getValue()
|
|
* User defined functions can choose to throw this exception too.
|
|
* @since 1.0.0
|
|
*/
|
|
class Mathparser_ParserException extends Exception
|
|
{
|
|
/**
|
|
* Member variable that holds the error portion of the expression.
|
|
* @since 1.0.0
|
|
*/
|
|
public $err;
|
|
|
|
/**
|
|
* Member variable that holds the parsed expression itself.
|
|
* @since 1.0.0
|
|
*/
|
|
public $exp;
|
|
|
|
/**
|
|
* Constructor.
|
|
* @since 1.0.0
|
|
*/
|
|
public function __construct($msg, $errPart, $expression)
|
|
{
|
|
parent::__construct($msg);
|
|
$this->err = $errPart;
|
|
$this->exp = $expression;
|
|
}
|
|
}
|
|
|
|
//Globals
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return number or string
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_add_($p1, $p2)
|
|
{
|
|
return $p1 + $p2;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return float|int|string
|
|
* @throws Exception
|
|
* @since 1.0.0
|
|
* @ignore
|
|
*/
|
|
function mp_add_str_($p1, $p2): float|int|string
|
|
{
|
|
if (is_string($p1)) {
|
|
return $p1 . $p2;
|
|
} else if (!is_numeric($p2)) {
|
|
throw new Exception(MathParser::getMessage1('InvConcatOper2', $p2));
|
|
}
|
|
return $p1 + $p2;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return number
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_subtract_($p1, $p2)
|
|
{
|
|
return $p1 - $p2;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return float|int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_mult_($p1, $p2): float|int
|
|
{
|
|
return $p1 * $p2;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return float|int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_div_($p1, $p2): float|int
|
|
{
|
|
if ($p2 == 0) {
|
|
Log::add("Division by zero detected in mp_div_: divisor was 0. Returning 0 as fallback.", Log::WARNING, "com_sportsmanager");
|
|
return 0;
|
|
}
|
|
return $p1 / $p2;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return number
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_power_($p1, $p2)
|
|
{
|
|
return pow($p1, $p2);
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_mod_($p1, $p2): int
|
|
{
|
|
return $p1 % $p2;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_notequals_($p1, $p2): int
|
|
{
|
|
return ($p1 != $p2) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_equals_($p1, $p2): int
|
|
{
|
|
return ($p1 == $p2) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_lt_($p1, $p2): int
|
|
{
|
|
return ($p1 < $p2) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_gt_($p1, $p2): int
|
|
{
|
|
return ($p1 > $p2) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_ltequals_($p1, $p2): int
|
|
{
|
|
return ($p1 <= $p2) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_gtequals_($p1, $p2): int
|
|
{
|
|
return ($p1 >= $p2) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @return number
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_unaryadd_($p1)
|
|
{
|
|
return $p1;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @return float|int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_negate_($p1): float|int
|
|
{
|
|
return -$p1;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_not_($p1): int
|
|
{
|
|
return ((!$p1) == 0) ? 0 : 1;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_and_($p1, $p2): int
|
|
{
|
|
return (!($p1 == 0) && !($p2 == 0)) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return bool|string
|
|
* @throws Exception
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_and_str_($p1, $p2): bool|string
|
|
{
|
|
if (is_string($p1)) {
|
|
return $p1 . $p2;
|
|
} else if (!is_numeric($p2)) {
|
|
throw new Exception(MathParser::getMessage1('InvConcatOper', $p2));
|
|
}
|
|
return $p1 && $p2;
|
|
}
|
|
|
|
/**
|
|
* @param $p1
|
|
* @param $p2
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_or_($p1, $p2): int
|
|
{
|
|
return (!($p1 == 0) || !($p2 == 0)) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @return number
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function sum_(): int
|
|
{
|
|
$p = func_get_args();
|
|
$count = func_num_args();
|
|
$tot = 0;
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$tot += $p[$i];
|
|
}
|
|
return $tot;
|
|
}
|
|
|
|
/**
|
|
* @return number
|
|
* @throws Mathparser_ParserException
|
|
* @since 1.0.0
|
|
* @ignore
|
|
*/
|
|
function mp_max_()
|
|
{
|
|
$p = func_get_args();
|
|
$count = func_num_args();
|
|
if ($count < 1) {
|
|
throw new Mathparser_ParserException(MathParser::getMessage2('WrngNPrms2', 'MAX', '1'), 'max', 'max');
|
|
}
|
|
return max($p);
|
|
}
|
|
|
|
/**
|
|
* @return number
|
|
* @throws Mathparser_ParserException
|
|
* @since 1.0.0
|
|
* @ignore
|
|
*/
|
|
function mp_min_()
|
|
{
|
|
$p = func_get_args();
|
|
$count = func_num_args();
|
|
if ($count < 1) {
|
|
throw new Mathparser_ParserException(MathParser::getMessage2('WrngNPrms2', 'MIN', '1'), 'min', 'min');
|
|
}
|
|
return min($p);
|
|
}
|
|
|
|
/**
|
|
* @param $cond
|
|
* @param $trueCase
|
|
* @param $falseCase
|
|
* @return number
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function if_($cond, $trueCase, $falseCase)
|
|
{
|
|
return $cond != 0 ? $trueCase : $falseCase;
|
|
}
|
|
|
|
/**
|
|
* @param $val
|
|
* @return float
|
|
* @throws Mathparser_ParserException
|
|
* @since 1.0.0
|
|
* @ignore
|
|
*/
|
|
function mp_num_($val): float
|
|
{
|
|
if (is_numeric($val)) {
|
|
return (float)$val;
|
|
}
|
|
throw new Mathparser_ParserException(MathParser::getMessage2('InvNum', $val), 'NUM', 'NUM');
|
|
}
|
|
|
|
/**
|
|
* @param $val
|
|
* @return float|int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_square_($val): float|int
|
|
{
|
|
return $val * $val;
|
|
}
|
|
|
|
/**
|
|
* @param $val
|
|
* @return float
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_cotan_($val): float
|
|
{
|
|
return 1.0 / tan($val);
|
|
}
|
|
|
|
/**
|
|
* @param $val
|
|
* @return int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_sign_($val): int
|
|
{
|
|
if ($val > 0) return 1;
|
|
if ($val < 0) return -1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param $val
|
|
* @return float|int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_trunc_($val): float|int
|
|
{
|
|
if ($val > 0) return floor($val);
|
|
if ($val < 0) return floor($val) + 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param $val
|
|
* @return float
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_float_($val): float
|
|
{
|
|
return (float)$val;
|
|
}
|
|
|
|
/**
|
|
* @param $base
|
|
* @param $val
|
|
* @return float|int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_logn_($base, $val): float|int
|
|
{
|
|
return log($val) / log($base);
|
|
}
|
|
|
|
/**
|
|
* @return float|int
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_rand_(): float|int
|
|
{
|
|
$max = getrandmax();
|
|
return rand(0, $max) / $max;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
* @ignore
|
|
* @since 1.0.0
|
|
*/
|
|
function mp_concat_(): string
|
|
{
|
|
$p = func_get_args();
|
|
return implode('', $p);
|
|
}
|