diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenAnd.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenDegree.php | 2 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenDivision.php | 2 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenEqual.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenGreaterThan.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenGreaterThanOrEqual.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenLessThan.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenLessThanOrEqual.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenMinus.php | 2 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenMultiply.php | 2 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenOr.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenPlus.php | 2 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenUnequal.php | 53 | ||||
-rw-r--r-- | src/NXP/Classes/TokenFactory.php | 10 | ||||
-rw-r--r-- | src/NXP/MathExecutor.php | 27 | ||||
-rw-r--r-- | tests/MathTest.php | 61 |
18 files changed, 526 insertions, 13 deletions
@@ -2,3 +2,4 @@ vendor/ .idea/ composer.lock .phpunit.result.cache +.vscode @@ -45,6 +45,8 @@ $executor->addFunction('abs', function($arg) {return abs($arg);}); ``` Function default parameters are not supported at this time. +Default parameters are not currently supported. + ## Operators: Default operators: `+ - * / ^` @@ -156,3 +158,7 @@ You can add operators, functions and variables with the public methods in MathEx This will allow you to remove functions and operators if needed, or implement different types more simply. Also note that you can replace an existing default operator by adding a new operator with the same regular expression string. For example if you just need to redefine TokenPlus, you can just add a new operator with the same regex string, in this case '\\+'. + +## Future Enhancements + +At some point this package will be upgraded to a currently supported version of PHP. diff --git a/src/NXP/Classes/Token/TokenAnd.php b/src/NXP/Classes/Token/TokenAnd.php new file mode 100644 index 0000000..dab4497 --- /dev/null +++ b/src/NXP/Classes/Token/TokenAnd.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenAnd extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '&&'; + } + + /** + * @return int + */ + public function getPriority() + { + return 100; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException("&& requires two operators"); + } + + $result = $op1->getValue() && $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenDegree.php b/src/NXP/Classes/Token/TokenDegree.php index 273183a..0d22f91 100644 --- a/src/NXP/Classes/Token/TokenDegree.php +++ b/src/NXP/Classes/Token/TokenDegree.php @@ -30,7 +30,7 @@ class TokenDegree extends AbstractOperator */ public function getPriority() { - return 3; + return 220; } /** diff --git a/src/NXP/Classes/Token/TokenDivision.php b/src/NXP/Classes/Token/TokenDivision.php index 4a8f8ce..328833b 100644 --- a/src/NXP/Classes/Token/TokenDivision.php +++ b/src/NXP/Classes/Token/TokenDivision.php @@ -31,7 +31,7 @@ class TokenDivision extends AbstractOperator */ public function getPriority() { - return 2; + return 180; } /** diff --git a/src/NXP/Classes/Token/TokenEqual.php b/src/NXP/Classes/Token/TokenEqual.php new file mode 100644 index 0000000..b0ac31e --- /dev/null +++ b/src/NXP/Classes/Token/TokenEqual.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenEqual extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\=\='; + } + + /** + * @return int + */ + public function getPriority() + { + return 140; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException("== requires two operators"); + } + + $result = $op1->getValue() == $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenGreaterThan.php b/src/NXP/Classes/Token/TokenGreaterThan.php new file mode 100644 index 0000000..51a5aca --- /dev/null +++ b/src/NXP/Classes/Token/TokenGreaterThan.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenGreaterThan extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '>'; + } + + /** + * @return int + */ + public function getPriority() + { + return 150; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException("> requires two operators"); + } + + $result = $op1->getValue() > $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenGreaterThanOrEqual.php b/src/NXP/Classes/Token/TokenGreaterThanOrEqual.php new file mode 100644 index 0000000..aa4425f --- /dev/null +++ b/src/NXP/Classes/Token/TokenGreaterThanOrEqual.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenGreaterThanOrEqual extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '>\='; + } + + /** + * @return int + */ + public function getPriority() + { + return 150; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException(">= requires two operators"); + } + + $result = $op1->getValue() >= $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenLessThan.php b/src/NXP/Classes/Token/TokenLessThan.php new file mode 100644 index 0000000..d289028 --- /dev/null +++ b/src/NXP/Classes/Token/TokenLessThan.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenLessThan extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '<'; + } + + /** + * @return int + */ + public function getPriority() + { + return 150; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException("< requires two operators"); + } + + $result = $op1->getValue() < $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenLessThanOrEqual.php b/src/NXP/Classes/Token/TokenLessThanOrEqual.php new file mode 100644 index 0000000..5a2fba3 --- /dev/null +++ b/src/NXP/Classes/Token/TokenLessThanOrEqual.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenLessThanOrEqual extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '<\='; + } + + /** + * @return int + */ + public function getPriority() + { + return 150; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException("<= requires two operators"); + } + + $result = $op1->getValue() <= $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenMinus.php b/src/NXP/Classes/Token/TokenMinus.php index ad0b8e5..d8ac079 100644 --- a/src/NXP/Classes/Token/TokenMinus.php +++ b/src/NXP/Classes/Token/TokenMinus.php @@ -30,7 +30,7 @@ class TokenMinus extends AbstractOperator */ public function getPriority() { - return 1; + return 170; } /** diff --git a/src/NXP/Classes/Token/TokenMultiply.php b/src/NXP/Classes/Token/TokenMultiply.php index 0b1ecfb..762fb48 100644 --- a/src/NXP/Classes/Token/TokenMultiply.php +++ b/src/NXP/Classes/Token/TokenMultiply.php @@ -30,7 +30,7 @@ class TokenMultiply extends AbstractOperator */ public function getPriority() { - return 2; + return 180; } /** diff --git a/src/NXP/Classes/Token/TokenOr.php b/src/NXP/Classes/Token/TokenOr.php new file mode 100644 index 0000000..86a4e53 --- /dev/null +++ b/src/NXP/Classes/Token/TokenOr.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenOr extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\|\|'; + } + + /** + * @return int + */ + public function getPriority() + { + return 90; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException("|| requires two operators"); + } + + $result = $op1->getValue() || $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenPlus.php b/src/NXP/Classes/Token/TokenPlus.php index a6e5036..494f786 100644 --- a/src/NXP/Classes/Token/TokenPlus.php +++ b/src/NXP/Classes/Token/TokenPlus.php @@ -30,7 +30,7 @@ class TokenPlus extends AbstractOperator */ public function getPriority() { - return 1; + return 170; } /** diff --git a/src/NXP/Classes/Token/TokenUnequal.php b/src/NXP/Classes/Token/TokenUnequal.php new file mode 100644 index 0000000..c8c5d1a --- /dev/null +++ b/src/NXP/Classes/Token/TokenUnequal.php @@ -0,0 +1,53 @@ +<?php + +namespace NXP\Classes\Token; + +use NXP\Exception\IncorrectExpressionException; + +class TokenUnequal extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\!\='; + } + + /** + * @return int + */ + public function getPriority() + { + return 140; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * + * @return $this + * + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + + if ($op1 === null || $op2 === null) { + throw new IncorrectExpressionException("!= requires two operators"); + } + + $result = $op1->getValue() != $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/TokenFactory.php b/src/NXP/Classes/TokenFactory.php index 74b5789..5aa634a 100644 --- a/src/NXP/Classes/TokenFactory.php +++ b/src/NXP/Classes/TokenFactory.php @@ -133,21 +133,21 @@ class TokenFactory { $operatorsRegex = ''; foreach ($this->operators as $operator) { - $operatorsRegex .= $operator::getRegex(); + $operatorsRegex .= '|(' . $operator::getRegex() . ')'; } - - return sprintf( - '/(%s)|(%s)|(%s)|([%s])|(%s)|(%s)|([%s%s%s])/i', + $s = sprintf( + '/(%s)|(%s)|(%s)|(%s)|(%s)|([%s%s%s])', TokenNumber::getRegex(), TokenStringDoubleQuoted::getRegex(), TokenStringSingleQuoted::getRegex(), - $operatorsRegex, TokenFunction::getRegex(), TokenVariable::getRegex(), TokenLeftBracket::getRegex(), TokenRightBracket::getRegex(), TokenComma::getRegex() ); + $s .= $operatorsRegex . '/i'; + return $s; } /** diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 6325d35..2a5f9fb 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -223,13 +223,14 @@ class MathExecutor */ public function execute($expression) { - if (!array_key_exists($expression, $this->cache)) { + $cachekey = (string)$expression; + if (!array_key_exists($cachekey, $this->cache)) { $lexer = new Lexer($this->tokenFactory); $tokensStream = $lexer->stringToTokensStream($expression); $tokens = $lexer->buildReversePolishNotation($tokensStream); - $this->cache[$expression] = $tokens; + $this->cache[$cachekey] = $tokens; } else { - $tokens = $this->cache[$expression]; + $tokens = $this->cache[$cachekey]; } $calculator = new Calculator(); $result = $calculator->calculate($tokens, $this->variables); @@ -263,6 +264,14 @@ class MathExecutor 'NXP\Classes\Token\TokenMultiply', 'NXP\Classes\Token\TokenDivision', 'NXP\Classes\Token\TokenDegree', + 'NXP\Classes\Token\TokenAnd', + 'NXP\Classes\Token\TokenOr', + 'NXP\Classes\Token\TokenEqual', + 'NXP\Classes\Token\TokenUnequal', + 'NXP\Classes\Token\TokenGreaterThanOrEqual', + 'NXP\Classes\Token\TokenGreaterThan', + 'NXP\Classes\Token\TokenLessThanOrEqual', + 'NXP\Classes\Token\TokenLessThan', ]; } @@ -296,6 +305,18 @@ class MathExecutor 'avg' => function ($arg1, $arg2) { return ($arg1 + $arg2) / 2; }, + 'if' => function ($expr, $trueval, $falseval) { + if ($expr === true || $expr === false) { + $exres = $expr; + } else { + $exres = $this->execute($expr); + } + if ($exres) { + return $this->execute($trueval); + } else { + return $this->execute($falseval); + } + } ]; } diff --git a/tests/MathTest.php b/tests/MathTest.php index e289016..90cdccf 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -50,6 +50,8 @@ class MathTest extends \PHPUnit\Framework\TestCase ['4*-5'], ['4 * -5'], + [cos(2)], + ['0.1 + 0.2'], ['1 + 2'], @@ -63,6 +65,10 @@ class MathTest extends \PHPUnit\Framework\TestCase ['1 / 2'], ['2 * 2 + 3 * 3'], + ['2 * 2 / 3 * 3'], + ['2 / 2 / 3 / 3'], + ['2 / 2 * 3 / 3'], + ['2 / 2 * 3 * 3'], ['1 + 0.6 - 3 * 2 / 50'], @@ -89,6 +95,8 @@ class MathTest extends \PHPUnit\Framework\TestCase ['1 + 2 * 3 / (3 * min(1, 5) * 2 + 1)'], ['1 + 2 * 3 / (3 / min(1, 5) / 2 + 1)'], + ['(1 + 2) * 3 / (3 / min(1, 5) / 2 + 1)'], + ['sin(10) * cos(50) / min(10, 20/2)'], ['sin(10) * cos(50) / min(10, (20/2))'], ['sin(10) * cos(50) / min(10, (max(10,20)/2))'], @@ -110,6 +118,38 @@ class MathTest extends \PHPUnit\Framework\TestCase ['(1+2+3+4- 5)*7/100'], ['( 1 + 2 + 3 + 4 - 5 ) * 7 / 100'], + ['1 && 0'], + ['1 && 0 && 1'], + ['1 || 0'], + ['1 && 0 || 1'], + + ['5 == 3'], + ['5 == 5'], + ['5 != 3'], + ['5 != 5'], + ['5 > 3'], + ['3 > 5'], + ['3 >= 5'], + ['3 >= 3'], + ['3 < 5'], + ['5 < 3'], + ['3 <= 5'], + ['5 <= 5'], + ['10 < 9 || 4 > (2+1)'], + ['10 < 9 || 4 > (2+1) && 5 == 5 || 4 != 6 || 3 >= 4 || 3 <= 7'], + + ['1 + 5 == 3 + 1'], + ['1 + 5 == 5 + 1'], + ['1 + 5 != 3 + 1'], + ['1 + 5 != 5 + 1'], + ['1 + 5 > 3 + 1'], + ['1 + 3 > 5 + 1'], + ['1 + 3 >= 5 + 1'], + ['1 + 3 >= 3 + 1'], + ['1 + 3 < 5 + 1'], + ['1 + 5 < 3 + 1'], + ['1 + 3 <= 5 + 1'], + ['1 + 5 <= 5 + 1'], ]; } @@ -163,6 +203,27 @@ class MathTest extends \PHPUnit\Framework\TestCase $this->assertEquals(round(100/30), $calculator->execute('round(100/30)')); } + public function testFunctionIf() + { + $calculator = new MathExecutor(); + $this->assertEquals(30, $calculator->execute( + 'if(100 > 99, 30, 0)')); + $this->assertEquals(0, $calculator->execute( + 'if(100 < 99, 30, 0)')); + $this->assertEquals(30, $calculator->execute( + 'if(98 < 99 && sin(1) < 1, 30, 0)')); + $this->assertEquals(40, $calculator->execute( + 'if(98 < 99 && sin(1) < 1, max(30, 40), 0)')); + $this->assertEquals(40, $calculator->execute( + 'if(98 < 99 && sin(1) < 1, if(10 > 5, max(30, 40), 1), 0)')); + $this->assertEquals(20, $calculator->execute( + 'if(98 < 99 && sin(1) > 1, if(10 > 5, max(30, 40), 1), if(4 <= 4, 20, 21))')); + $this->assertEquals(cos(2), $calculator->execute( + 'if(98 < 99 && sin(1) >= 1, max(30, 40), cos(2))')); + $this->assertEquals(cos(2), $calculator->execute( + 'if(cos(2), cos(2), 0)')); + } + public function testEvaluateFunctionParameters() { $calculator = new MathExecutor(); |