aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Wells <bruce.wells@simparel.com>2019-11-27 19:19:42 +0300
committerBruce Wells <bruce.wells@simparel.com>2019-11-27 19:19:42 +0300
commit44a13487b5a89951d244ab7a5e723cf7ec893a54 (patch)
tree88cafa1f3fe84e1db662470851a68947973cadda
parentadf43bc705fd2d839a639c162f53407434867206 (diff)
parentf975f0bfbc6ac28f0a868b2c237cca071c37c39e (diff)
Merge branch 'master' of https://github.com/neonxp/MathExecutor
# Conflicts: # .gitignore # tests/MathTest.php
-rw-r--r--.gitignore1
-rw-r--r--README.md6
-rw-r--r--src/NXP/Classes/Token/TokenAnd.php53
-rw-r--r--src/NXP/Classes/Token/TokenDegree.php2
-rw-r--r--src/NXP/Classes/Token/TokenDivision.php2
-rw-r--r--src/NXP/Classes/Token/TokenEqual.php53
-rw-r--r--src/NXP/Classes/Token/TokenGreaterThan.php53
-rw-r--r--src/NXP/Classes/Token/TokenGreaterThanOrEqual.php53
-rw-r--r--src/NXP/Classes/Token/TokenLessThan.php53
-rw-r--r--src/NXP/Classes/Token/TokenLessThanOrEqual.php53
-rw-r--r--src/NXP/Classes/Token/TokenMinus.php2
-rw-r--r--src/NXP/Classes/Token/TokenMultiply.php2
-rw-r--r--src/NXP/Classes/Token/TokenOr.php53
-rw-r--r--src/NXP/Classes/Token/TokenPlus.php2
-rw-r--r--src/NXP/Classes/Token/TokenUnequal.php53
-rw-r--r--src/NXP/Classes/TokenFactory.php10
-rw-r--r--src/NXP/MathExecutor.php27
-rw-r--r--tests/MathTest.php61
18 files changed, 526 insertions, 13 deletions
diff --git a/.gitignore b/.gitignore
index c3b38ab..7256f68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ vendor/
.idea/
composer.lock
.phpunit.result.cache
+.vscode
diff --git a/README.md b/README.md
index 58c5422..7238a0b 100644
--- a/README.md
+++ b/README.md
@@ -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();