790 lines
26 KiB
PHP
790 lines
26 KiB
PHP
<?php
|
|
/**
|
|
* 2010-2023 Bl Modules.
|
|
*
|
|
* If you wish to customize this module for your needs,
|
|
* please contact the authors first for more information.
|
|
*
|
|
* It's not allowed selling, reselling or other ways to share
|
|
* this file or any other module files without author permission.
|
|
*
|
|
* @author Bl Modules
|
|
* @copyright 2010-2023 Bl Modules
|
|
* @license
|
|
*/
|
|
|
|
/**
|
|
* Formula Parser - A library for parsing and evaluating mathematical formulas given as strings.
|
|
*
|
|
* @author Denis Simon <denis.v.simon@gmail.com>
|
|
*
|
|
* @license MIT (https://github.com/denissimon/formula-parser/blob/master/LICENSE)
|
|
*
|
|
* @version 2.7.1-2019.11.05
|
|
*/
|
|
|
|
class FormulaParser
|
|
{
|
|
protected $formula, $original_formula = "";
|
|
protected $precision = "";
|
|
protected $expression = "";
|
|
protected $correct = true;
|
|
protected $error_type = 0;
|
|
protected $variables = [];
|
|
protected $valid_variables = ['x', 'y', 'z', 'a', 'b'];
|
|
protected $valid_functions = ['abs', 'sin', 'cos', 'tan', 'log', 'exp', 'sqrt'];
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $formula The formula given as a string
|
|
* @param integer $precision The rounding precision (number of digits after the decimal point)
|
|
*/
|
|
public function __construct($formula = "", $precision = 6)
|
|
{
|
|
if (!empty($formula))
|
|
$this->formula = $this->original_formula = trim($formula);
|
|
|
|
if (isset($precision))
|
|
$this->precision = $precision;
|
|
}
|
|
|
|
/**
|
|
* Magic overloading method
|
|
*
|
|
* @param string $name
|
|
* @param array $arguments
|
|
*
|
|
* @throws \Exception when the method doesn't exist
|
|
*/
|
|
public function __call($name, $arguments)
|
|
{
|
|
throw new \Exception("No such method exists: $name (".implode(', ', $arguments).")");
|
|
}
|
|
|
|
/**
|
|
* Overwrites default valid variables
|
|
*
|
|
* @param array $vars
|
|
*/
|
|
public function setValidVariables(array $vars)
|
|
{
|
|
$this->valid_variables = $vars;
|
|
}
|
|
|
|
/**
|
|
* Sets variables
|
|
*
|
|
* @param array $vars
|
|
*/
|
|
public function setVariables(array $vars)
|
|
{
|
|
$this->variables = $vars;
|
|
}
|
|
|
|
/**
|
|
* Returns the text of the formula passed to the constructor
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getFormula()
|
|
{
|
|
return $this->original_formula;
|
|
}
|
|
|
|
/**
|
|
* @return boolean
|
|
*/
|
|
private function validate()
|
|
{
|
|
$validate_str = count_chars(implode("", $this->valid_variables).
|
|
implode("", $this->valid_functions), 3);
|
|
if (preg_match('/[^0-9\*\+\-\/\^\.'.$validate_str.'EINF\s\(\)]/', $this->expression)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param string $v1
|
|
* @param string $v2
|
|
*/
|
|
private function checkInf(&$v1, &$v2 = '') {
|
|
$v1 = ($v1==='INF') ? INF : (($v1==='-INF') ? -INF : $v1);
|
|
$v2 = ($v2==='INF') ? INF : (($v2==='-INF') ? -INF : $v2);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
*
|
|
* @return mixed
|
|
*/
|
|
private function return_($value) {
|
|
if (abs($value) === INF) {
|
|
return $value;
|
|
} else if ($value === 'INF') {
|
|
return INF;
|
|
} else if ($value === '-INF') {
|
|
return -INF;
|
|
} else {
|
|
return (float) $value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates an operation ^
|
|
*
|
|
* @param array $array The subexpression of the formula
|
|
*
|
|
* @return array
|
|
*/
|
|
private function calculate1(array $array)
|
|
{
|
|
for ($i=count($array)-1; $i>=0; $i--) {
|
|
if (isset($array[$i]) && isset($array[$i-1]) && isset($array[$i+1])
|
|
&& $array[$i] === '^') {
|
|
$otp = 1;
|
|
$this->checkInf($array[$i-1], $array[$i+1]);
|
|
if (is_numeric($array[$i-1]) && is_numeric($array[$i+1])) {
|
|
if ($array[$i-1] < 0) {
|
|
$a = pow($array[$i-1]*-1, $array[$i+1]);
|
|
$otp = 2;
|
|
} else {
|
|
$a = pow($array[$i-1], $array[$i+1]);
|
|
}
|
|
} else {
|
|
$this->correct = false;
|
|
break;
|
|
}
|
|
unset($array[$i-1], $array[$i+1]);
|
|
$array[$i] = ($otp == 1) ? $a : $a*-1;
|
|
$array = array_values($array);
|
|
$i = count($array)-1;
|
|
}
|
|
}
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Calculates operations *, /
|
|
*
|
|
* @param array $array The subexpression of the formula
|
|
*
|
|
* @return array
|
|
*/
|
|
private function calculate2(array $array)
|
|
{
|
|
for ($i=0; $i<count($array); $i++) {
|
|
if (isset($array[$i]) && isset($array[$i-1]) && isset($array[$i+1])
|
|
&& ($array[$i] === '*' || $array[$i] === '/')) {
|
|
$this->checkInf($array[$i-1], $array[$i+1]);
|
|
if (!is_numeric($array[$i-1]) || !is_numeric($array[$i+1])) {
|
|
$this->correct = false;
|
|
break;
|
|
}
|
|
if ($array[$i] === '*') {
|
|
$a = $array[$i-1] * $array[$i+1];
|
|
} elseif ($array[$i] === '/') {
|
|
if ($array[$i+1] != 0) {
|
|
$a = $array[$i-1] / $array[$i+1];
|
|
} elseif ($array[$i-1] != 0 && $array[$i+1] == 0) {
|
|
$a = ($array[$i-1] > 0) ? INF : -INF;
|
|
} else {
|
|
$a = NAN;
|
|
}
|
|
}
|
|
unset($array[$i-1], $array[$i+1]);
|
|
$array[$i] = $a;
|
|
$array = array_values($array);
|
|
$i = 0;
|
|
}
|
|
}
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Calculates operations +, -
|
|
*
|
|
* @param array $array The subexpression of the formula
|
|
*
|
|
* @return array
|
|
*/
|
|
private function calculate3(array $array)
|
|
{
|
|
for ($i=0; $i<count($array); $i++) {
|
|
if (isset($array[$i]) && isset($array[$i-1]) && isset($array[$i+1])
|
|
&& ($array[$i] === '+' || $array[$i] === '-')) {
|
|
$this->checkInf($array[$i-1], $array[$i+1]);
|
|
if (!is_numeric($array[$i-1]) || !is_numeric($array[$i+1])) {
|
|
$this->correct = false;
|
|
break;
|
|
}
|
|
if ($array[$i] === '+') {
|
|
$a = $array[$i-1] + $array[$i+1];
|
|
} elseif ($array[$i] === '-') {
|
|
$a = $array[$i-1] - $array[$i+1];
|
|
}
|
|
unset($array[$i-1], $array[$i+1]);
|
|
$array[$i] = $a;
|
|
$array = array_values($array);
|
|
$i = 0;
|
|
}
|
|
}
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Calculates functions
|
|
*
|
|
* @param string $function
|
|
* @param string $str
|
|
* @param integer $strlen
|
|
* @param integer $i
|
|
*/
|
|
private function calculateFunction($function, &$str, &$strlen, $i)
|
|
{
|
|
$j = ($function == 'sqrt') ? $i+4 : $i+3;
|
|
$arg = null;
|
|
|
|
while (true) {
|
|
if (isset($str[$j])) {
|
|
if ((strstr('+-', $str[$j]) && $arg === null)
|
|
|| preg_match('/([\d\.eE\+\-])|([\d\.INF])/', $str[$j])) {
|
|
$arg .= $str[$j];
|
|
} elseif (strstr(' ', $str[$j]) && !strpbrk($arg, '0123456789')) {
|
|
} else {
|
|
$arg = trim($arg);
|
|
break;
|
|
}
|
|
$j++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->checkInf($arg);
|
|
if (!is_numeric($arg)) {
|
|
$this->correct = false;
|
|
} else {
|
|
if ($function == 'exp') {
|
|
$result = pow(M_E, $arg);
|
|
} else {
|
|
$result = $function($arg);
|
|
}
|
|
}
|
|
|
|
if (is_numeric($result) && $this->correct) {
|
|
$str1 = substr($str, 0, $i);
|
|
$str2 = substr($str, $j);
|
|
$str = $str1.' '.$result.$str2;
|
|
$strlen = strlen($str);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Combines operators and numbers
|
|
*
|
|
* @param array $array The subexpression of the formula
|
|
*
|
|
* @return array
|
|
*/
|
|
private function combine($array) {
|
|
$new_array = [];
|
|
$i = $j = $key = 0;
|
|
|
|
foreach ($array as $item) {
|
|
|
|
$array_ip1 = (isset($array[$i+1])) ? $array[$i+1] : null;
|
|
|
|
end($new_array);
|
|
$cur = current($new_array);
|
|
|
|
if ((@strstr('+-', (string) $item))
|
|
&& (!empty($cur) && @strstr('+-', (string) $cur))
|
|
&& (!empty($array_ip1) && @strstr('+-', (string) $array_ip1))) {
|
|
if ($item === '-')
|
|
$new_array[key($new_array)] = ($cur == '+') ? '-' : '+';
|
|
} elseif ((@strstr('+-', (string) $item))
|
|
&& (!empty($cur) && @strstr('*/^', (string) $cur))
|
|
&& (!empty($array_ip1) && @strstr('+-', (string) $array_ip1))) {
|
|
$new_array[] = $item;
|
|
} else {
|
|
if ((@strstr('+-', (string) $item)) && ($key+1 != $i)
|
|
&& (@strstr('*/^', (string) $array[$key]))
|
|
&& (!is_numeric($array[$key+1])) && (isset($array[$key+1]))) {
|
|
if ($item === '-')
|
|
$new_array[key($new_array)] = ($cur == '+') ? '-' : '+';
|
|
} else {
|
|
$new_array[] = $item;
|
|
$key = $i;
|
|
}
|
|
}
|
|
$i++;
|
|
}
|
|
|
|
$array = $new_array;
|
|
$new_array = [];
|
|
|
|
foreach ($array as $item) {
|
|
|
|
$array_i = $array[$j];
|
|
$array_ip1 = (isset($array[$j+1])) ? $array[$j+1] : null;
|
|
$array_im1 = (isset($array[$j-1])) ? $array[$j-1] : null;
|
|
$array_im2 = (isset($array[$j-2])) ? $array[$j-2] : null;
|
|
$array_im3 = (isset($array[$j-3])) ? $array[$j-3] : null;
|
|
|
|
if ($item === '+' || $item === '-') {
|
|
if (($j == 0 && is_numeric($array_ip1))
|
|
|| ($j > 0 && is_numeric($array_ip1)
|
|
&& (!empty($array_im1) && @stristr('+-*/^e', (string) $array_im1)))) {
|
|
if ($item === '+') {
|
|
$new_array[] = $array_ip1;
|
|
} else {
|
|
if ($array_im1 === '-' && $array_im2 !== '-') {
|
|
$new_array[] = $array_ip1;
|
|
} elseif ($array_im1 === '-' && $array_im3 === '-') {
|
|
$this->correct = false;
|
|
break;
|
|
} else {
|
|
$new_array[] = $item.$array_ip1;
|
|
}
|
|
}
|
|
} else {
|
|
if ($item === '-' && $array_ip1 === '-') {
|
|
if (count($new_array))
|
|
$new_array[] = '+';
|
|
} elseif ($item === '-' && $array_ip1 === '+') {
|
|
if (count($new_array))
|
|
$new_array[] = '+';
|
|
$new_array[] = '0';
|
|
$new_array[] = '-';
|
|
} else {
|
|
if (count($new_array) || $item != '+')
|
|
$new_array[] = $item;
|
|
}
|
|
}
|
|
} elseif (($j == 1 && is_numeric($item)
|
|
&& (!empty($array_im1) && @strstr('+-', (string) $array_im1)))
|
|
|| ($j > 1 && is_numeric($item)
|
|
&& (!empty($array_im1) && @strstr('+-', (string) $array_im1))
|
|
&& (!empty($array_im2) && @stristr('+-*/^e', (string) $array_im2)))) {
|
|
} else {
|
|
$new_array[] = $item;
|
|
}
|
|
$j++;
|
|
}
|
|
return $new_array;
|
|
}
|
|
|
|
/**
|
|
* Parses and evaluates the subexpression of the formula.
|
|
*
|
|
* @param string $str The subexpression inside parentheses, or entire formula
|
|
* if it doesn't contain parentheses.
|
|
*
|
|
* @return float
|
|
*/
|
|
private function parse($str)
|
|
{
|
|
$str = trim($str);
|
|
$this->expression = $str;
|
|
$strlen = strlen($str);
|
|
$main_array = [];
|
|
$count = 0;
|
|
|
|
for ($i=0; $i<$strlen; $i++) {
|
|
|
|
$str_i = $str[$i];
|
|
$str_ip1 = (isset($str[$i+1])) ? $str[$i+1] : null;
|
|
$str_ip2 = (isset($str[$i+2])) ? $str[$i+2] : null;
|
|
$str_ip3 = (isset($str[$i+3])) ? $str[$i+3] : null;
|
|
$str_ip4 = (isset($str[$i+4])) ? $str[$i+4] : null;
|
|
$str_im1 = (isset($str[$i-1])) ? $str[$i-1] : null;
|
|
$str_im2 = (isset($str[$i-2])) ? $str[$i-2] : null;
|
|
|
|
// NaN
|
|
if (stristr($str, 'NaN'))
|
|
return NAN;
|
|
// Spaces will be skipped
|
|
if ($str_i == ' ') {
|
|
$count++;
|
|
// Number
|
|
} elseif (is_numeric($str_i)) {
|
|
$main_array[$count] = (isset($main_array[$count])) ?
|
|
$main_array[$count].$str_i : $str_i;
|
|
// Constant pi
|
|
} elseif ($str_im1.$str_i == 'pi') {
|
|
} elseif (($str_i.$str_ip1 == 'pi')
|
|
&& (!$str_ip2 || (!empty($str_ip2) && strstr('+-*/^ ', $str_ip2)))) {
|
|
$count++;
|
|
$main_array[$count] = M_PI;
|
|
// Constant e
|
|
} elseif (($str_i == 'e') && ($str_ip1 != 'x') && (!is_numeric($str_im1))
|
|
&& (!$str_ip1 || (!empty($str_ip1) && strstr('+-*/^ ', $str_ip1)))) {
|
|
$count++;
|
|
$main_array[$count] = M_E;
|
|
// Number in E notation
|
|
} elseif ((strtolower($str_i) == 'e') && ($str_ip1 != 'x')
|
|
&& (is_numeric($str_im1))) {
|
|
$main_array[$count] = $main_array[$count].'E';
|
|
} elseif ((strstr('+-', $str_i)) && (strtolower($str_im1) == 'e')
|
|
&& (is_numeric($str_ip1)) && (is_numeric($str_im2))) {
|
|
$main_array[$count] = $main_array[$count].$str_i;
|
|
// Decimal point
|
|
} elseif (($str_i == '.') && ((isset($str_im1) && is_numeric($str_im1))
|
|
|| (isset($str_ip1)) && is_numeric($str_ip1))) {
|
|
$main_array[$count] = (isset($main_array[$count])) ?
|
|
$main_array[$count].'.' : '.';
|
|
// Function
|
|
} elseif ((in_array($str_i.$str_ip1.$str_ip2, $this->valid_functions))
|
|
|| ($str_i.$str_ip1.$str_ip2.$str_ip3 == 'sqrt')) {
|
|
if ($str_i.$str_ip1.$str_ip2.$str_ip3 == 'sqrt') {
|
|
if ($str_ip4 == ' ')
|
|
$this->calculateFunction('sqrt', $str, $strlen, $i);
|
|
} else {
|
|
if ($str_ip3 == ' ')
|
|
$this->calculateFunction($str_i.$str_ip1.$str_ip2, $str, $strlen, $i);
|
|
}
|
|
// Variable
|
|
} elseif (in_array($str_i, $this->valid_variables) && count($this->variables)) {
|
|
if (array_key_exists($str_i, $this->variables)
|
|
&& is_numeric($this->variables[$str_i])) {
|
|
$count++;
|
|
$main_array[$count] = (float) $this->variables[$str_i];
|
|
$count++;
|
|
} else {
|
|
$this->correct = false;
|
|
$this->error_type = 4;
|
|
break;
|
|
}
|
|
} else {
|
|
// Operator
|
|
$count++;
|
|
if (strstr('+-*/^', $str_i)) {
|
|
if (!count($main_array) && $str_i == '+')
|
|
continue;
|
|
$main_array[$count] = $str_i;
|
|
$count++;
|
|
} else {
|
|
// Constant Inf
|
|
if ($str_im2.$str_im1.$str_i == 'INF') {
|
|
} elseif ($str_im1.$str_i.$str_ip1 == 'INF') {
|
|
} elseif ($str_i.$str_ip1.$str_ip2 == 'INF') {
|
|
if ($str_im1 == '-' && !count($main_array)) {
|
|
$main_array[$count] = '-';
|
|
$count++;
|
|
}
|
|
$main_array[$count] = INF;
|
|
$count++;
|
|
} else {
|
|
// Nothing matches
|
|
$this->correct = false;
|
|
if (!$this->validate())
|
|
$this->error_type = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$this->correct)
|
|
return 0;
|
|
|
|
$main_array = array_values($main_array);
|
|
|
|
if (isset($main_array[1])) {
|
|
|
|
$main_array = $this->combine($main_array);
|
|
$main_array = $this->calculate1($main_array);
|
|
$main_array = $this->calculate2($main_array);
|
|
$main_array = $this->calculate3($main_array);
|
|
|
|
if (count($main_array) != 1) {
|
|
$this->correct = false;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!isset($main_array[0]) || strstr('+-*/^', (string) $main_array[0])
|
|
|| substr_count((string) $main_array[0], '.') > 1) {
|
|
$this->correct = false;
|
|
return 0;
|
|
}
|
|
|
|
return $this->return_($main_array[0]);
|
|
}
|
|
|
|
/**
|
|
* @param string $expression
|
|
* @param integer $length
|
|
* @param integer $cursor
|
|
* @param float $base
|
|
*
|
|
* @return \stdClass
|
|
*/
|
|
private function checkExp($expression, $length, $cursor, $base)
|
|
{
|
|
$response = new \stdClass();
|
|
|
|
if ($base < 0) {
|
|
$test_func = preg_replace('/\s+/', '', $expression);
|
|
$test_func = substr($test_func , 0, -strlen(preg_replace('/\s+/', '',
|
|
$base.substr($expression, $length-$cursor))));
|
|
$test_func_ok = true;
|
|
if (preg_match('/([nstpg]\()|(in|os|an|bs|rt|xp|og)/', substr($test_func, -2)))
|
|
$test_func_ok = false;
|
|
|
|
if ($test_func_ok) {
|
|
$expression = substr($expression, $length-$cursor+1);
|
|
$test_exp = ltrim($expression);
|
|
if (isset($test_exp[0]) && $test_exp[0] === '^') {
|
|
$exp = '';
|
|
for ($q=0; $q<=$cursor-1; $q++) {
|
|
if (isset($expression[$q]) && $expression[$q] === '^') {
|
|
$exp = ' ';
|
|
} elseif ($exp != '' && !strstr(' ', $expression[$q])) {
|
|
if (strstr('+-', $expression[$q]) && $exp == ' ') {
|
|
$exp .= $expression[$q];
|
|
} elseif (strstr('0123456789.(', $expression[$q])) {
|
|
if ($expression[$q] != '(')
|
|
$exp .= $expression[$q];
|
|
} else {
|
|
$exp = trim($exp);
|
|
$cursor = $cursor - $q;
|
|
if (isset($exp[0]) && $exp[0] == '+')
|
|
$exp = substr($exp, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
$response->cursor = $cursor;
|
|
if (substr($exp, -1) == '.')
|
|
$exp = substr($exp, 0, -1);
|
|
if (!is_numeric($exp) || strstr($exp, '.')) {
|
|
$this->correct = false;
|
|
$response->result = 0;
|
|
} else {
|
|
$response->result = pow(abs($base),$exp) * pow(-1,$exp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Returns an error message
|
|
*
|
|
* @return string
|
|
*/
|
|
private function errorMsg()
|
|
{
|
|
switch ($this->error_type) {
|
|
case 1:
|
|
return 'Invalid character';
|
|
case 2:
|
|
return 'Empty string';
|
|
case 3:
|
|
return 'Mismatched parentheses';
|
|
case 4:
|
|
return 'Variable error';
|
|
default:
|
|
return 'Syntax error';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $formula
|
|
*/
|
|
private function prepare(&$formula) {
|
|
$formula = '( '.$formula.' )';
|
|
$formula = str_replace('Inf', 'INF', $formula);
|
|
// Spaces and tab characters will be transformed
|
|
$formula = preg_replace('/[\+\-\*\/\^\(\)]/', ' ${0} ', preg_replace('/\s+/', '', $formula));
|
|
}
|
|
|
|
/**
|
|
* @param array $matches
|
|
*
|
|
* @return string
|
|
*/
|
|
private function match($matches) {
|
|
return ' ( '.preg_replace('/\s+/', '', $matches[0]).' ) ';
|
|
}
|
|
|
|
/**
|
|
* @param array $matches
|
|
*
|
|
* @return string
|
|
*/
|
|
private function match1($matches) {
|
|
return $matches[0][0] . '( ( '.substr($matches[0],1,1).' ) )' . $matches[0][2];
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
private function match2($matches) {
|
|
return ' ( ( '.$matches[0].' ) ) ';
|
|
}
|
|
|
|
/**
|
|
* Groups the parts of the formula that must be evaluated first into parentheses
|
|
*
|
|
* @param string $formula
|
|
* @param string $opt
|
|
*/
|
|
private function group(&$formula, $opt = 'init') {
|
|
if ($opt == 'init') {
|
|
// Numbers in E notation
|
|
$formula = preg_replace_callback('/[\d\.]+( )?[eE]( )?[\+\-]?( )?[\d\.]+ /',
|
|
[$this, 'match'], $formula);
|
|
// Variables
|
|
if (count($this->variables) > 0) {
|
|
$valid_variables = implode("|", $this->valid_variables);
|
|
$formula = preg_replace_callback('/[\+\-\*\/\^ ]('.$valid_variables.')[ \+\-\*\/\^]/',
|
|
[$this, 'match1'], $formula);
|
|
}
|
|
} else {
|
|
// Functions
|
|
$valid_functions = implode("|", $this->valid_functions);
|
|
$formula = preg_replace_callback('/('.$valid_functions.')( )?[\+\-]?( )?[\d\.eEINF]+ /',
|
|
[$this, 'match2'], $formula);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param float $result
|
|
* @param integer $precision
|
|
*
|
|
* @return float
|
|
*/
|
|
private function round_($result, $precision) {
|
|
if (abs($result) === INF) {
|
|
return $result;
|
|
}
|
|
$str = strtolower((string) $result);
|
|
if (strpos($str, 'e') !== false) {
|
|
$r = explode('e', $str);
|
|
$left = round((float) $r[0], (int) $precision);
|
|
$right = $r[1];
|
|
return (float) ($left.'e'.$right);
|
|
}
|
|
return round($result, (int) $precision);
|
|
}
|
|
|
|
/**
|
|
* Parses and evaluates a given formula.
|
|
*
|
|
* @return array Returns an array [0 => v1, 1 => v2], where
|
|
* v1 is 'done' or 'error', and
|
|
* v2 is a computed result or error message, respectively.
|
|
*/
|
|
public function getResult()
|
|
{
|
|
// Check that the formula is not empty
|
|
if (!isset($this->formula[0])) {
|
|
$this->correct = false;
|
|
$this->error_type = 2;
|
|
}
|
|
|
|
// Check set variables
|
|
for ($i=0; $i<=count($this->valid_variables)-1; $i++) {
|
|
if (strlen($this->valid_variables[$i]) != 1
|
|
|| !preg_match('/^([a-z])$/', $this->valid_variables[$i]) === 1
|
|
|| $this->valid_variables[$i] == "e") {
|
|
$this->correct = false;
|
|
$this->error_type = 4;
|
|
}
|
|
}
|
|
|
|
$this->prepare($this->formula);
|
|
$this->group($this->formula);
|
|
|
|
$open_parentheses_count = substr_count($this->formula, '(');
|
|
$close_parentheses_count = substr_count($this->formula, ')');
|
|
|
|
// Check for an equality of opening and closing parentheses
|
|
if ($open_parentheses_count != $close_parentheses_count) {
|
|
$this->correct = false;
|
|
$this->error_type = 3;
|
|
}
|
|
|
|
// Check the syntax is correct when using parentheses
|
|
if (preg_match('/(\)( )?[^\)\+\-\*\/\^ ])|(\(( )*?\))|([^nstgp\(\+\-\*\/\^ ]( )?\()/',
|
|
$this->formula)) {
|
|
$this->correct = false;
|
|
}
|
|
|
|
$processing_formula = $this->formula;
|
|
|
|
// Begin a general parse
|
|
while ((strstr($processing_formula, '(') || strstr($processing_formula, ')'))
|
|
&& $this->correct) {
|
|
$start_cursor_pos = 0;
|
|
$end_cursor_pos = 0;
|
|
|
|
$this->group($processing_formula, '');
|
|
|
|
$temp = $processing_formula;
|
|
|
|
while (strstr($temp, '(')) {
|
|
$strlen_temp = strlen($temp);
|
|
for ($i=0; $i<=$strlen_temp-1; $i++) {
|
|
if (isset($temp[$i]) && $temp[$i] == '(') {
|
|
$start_cursor_pos = $start_cursor_pos + $i+1;
|
|
$temp = substr($temp, $i+1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$strlen_temp = strlen($temp);
|
|
for ($i=0; $i<=$strlen_temp-1; $i++) {
|
|
if (isset($temp[$i]) && $temp[$i] == ')') {
|
|
$end_cursor_pos = $strlen_temp - $i;
|
|
$temp = substr($temp, 0, $i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
$length = strlen($processing_formula);
|
|
|
|
if (!empty($temp)) {
|
|
$temp = $this->parse($temp);
|
|
$checkExp = $this->checkExp($processing_formula, $length, $end_cursor_pos, $temp);
|
|
if (isset($checkExp->result)) {
|
|
$temp = $checkExp->result;
|
|
$end_cursor_pos = $checkExp->cursor;
|
|
}
|
|
}
|
|
|
|
$processing_formula = substr($processing_formula, 0, $start_cursor_pos-1)
|
|
.$temp.substr($processing_formula, $length - $end_cursor_pos+1);
|
|
}
|
|
|
|
if ($this->correct) {
|
|
$this->formula = $processing_formula;
|
|
$result = $this->parse($this->formula);
|
|
}
|
|
|
|
if ($this->correct) {
|
|
return ['done', $this->round_($result, $this->precision)];
|
|
} else {
|
|
return ['error', $this->errorMsg()];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get result, only value
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getResultValue()
|
|
{
|
|
$result = $this->getResult();
|
|
|
|
return ($result[0] == 'done') ? $result[1] : 0;
|
|
}
|
|
}
|