# Conflicts:
#	.gitignore
#	tests/MathTest.php
This commit is contained in:
Bruce Wells 2019-11-27 11:19:42 -05:00
commit 44a13487b5
18 changed files with 526 additions and 13 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ vendor/
.idea/ .idea/
composer.lock composer.lock
.phpunit.result.cache .phpunit.result.cache
.vscode

View file

@ -45,6 +45,8 @@ $executor->addFunction('abs', function($arg) {return abs($arg);});
``` ```
Function default parameters are not supported at this time. Function default parameters are not supported at this time.
Default parameters are not currently supported.
## Operators: ## Operators:
Default 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. 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 '\\+'. 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.

View file

@ -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);
}
}

View file

@ -30,7 +30,7 @@ class TokenDegree extends AbstractOperator
*/ */
public function getPriority() public function getPriority()
{ {
return 3; return 220;
} }
/** /**

View file

@ -31,7 +31,7 @@ class TokenDivision extends AbstractOperator
*/ */
public function getPriority() public function getPriority()
{ {
return 2; return 180;
} }
/** /**

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -30,7 +30,7 @@ class TokenMinus extends AbstractOperator
*/ */
public function getPriority() public function getPriority()
{ {
return 1; return 170;
} }
/** /**

View file

@ -30,7 +30,7 @@ class TokenMultiply extends AbstractOperator
*/ */
public function getPriority() public function getPriority()
{ {
return 2; return 180;
} }
/** /**

View file

@ -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);
}
}

View file

@ -30,7 +30,7 @@ class TokenPlus extends AbstractOperator
*/ */
public function getPriority() public function getPriority()
{ {
return 1; return 170;
} }
/** /**

View file

@ -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);
}
}

View file

@ -133,21 +133,21 @@ class TokenFactory
{ {
$operatorsRegex = ''; $operatorsRegex = '';
foreach ($this->operators as $operator) { foreach ($this->operators as $operator) {
$operatorsRegex .= $operator::getRegex(); $operatorsRegex .= '|(' . $operator::getRegex() . ')';
} }
$s = sprintf(
return sprintf( '/(%s)|(%s)|(%s)|(%s)|(%s)|([%s%s%s])',
'/(%s)|(%s)|(%s)|([%s])|(%s)|(%s)|([%s%s%s])/i',
TokenNumber::getRegex(), TokenNumber::getRegex(),
TokenStringDoubleQuoted::getRegex(), TokenStringDoubleQuoted::getRegex(),
TokenStringSingleQuoted::getRegex(), TokenStringSingleQuoted::getRegex(),
$operatorsRegex,
TokenFunction::getRegex(), TokenFunction::getRegex(),
TokenVariable::getRegex(), TokenVariable::getRegex(),
TokenLeftBracket::getRegex(), TokenLeftBracket::getRegex(),
TokenRightBracket::getRegex(), TokenRightBracket::getRegex(),
TokenComma::getRegex() TokenComma::getRegex()
); );
$s .= $operatorsRegex . '/i';
return $s;
} }
/** /**

View file

@ -223,13 +223,14 @@ class MathExecutor
*/ */
public function execute($expression) 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); $lexer = new Lexer($this->tokenFactory);
$tokensStream = $lexer->stringToTokensStream($expression); $tokensStream = $lexer->stringToTokensStream($expression);
$tokens = $lexer->buildReversePolishNotation($tokensStream); $tokens = $lexer->buildReversePolishNotation($tokensStream);
$this->cache[$expression] = $tokens; $this->cache[$cachekey] = $tokens;
} else { } else {
$tokens = $this->cache[$expression]; $tokens = $this->cache[$cachekey];
} }
$calculator = new Calculator(); $calculator = new Calculator();
$result = $calculator->calculate($tokens, $this->variables); $result = $calculator->calculate($tokens, $this->variables);
@ -263,6 +264,14 @@ class MathExecutor
'NXP\Classes\Token\TokenMultiply', 'NXP\Classes\Token\TokenMultiply',
'NXP\Classes\Token\TokenDivision', 'NXP\Classes\Token\TokenDivision',
'NXP\Classes\Token\TokenDegree', '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) { 'avg' => function ($arg1, $arg2) {
return ($arg1 + $arg2) / 2; 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);
}
}
]; ];
} }

View file

@ -50,6 +50,8 @@ class MathTest extends \PHPUnit\Framework\TestCase
['4*-5'], ['4*-5'],
['4 * -5'], ['4 * -5'],
[cos(2)],
['0.1 + 0.2'], ['0.1 + 0.2'],
['1 + 2'], ['1 + 2'],
@ -63,6 +65,10 @@ class MathTest extends \PHPUnit\Framework\TestCase
['1 / 2'], ['1 / 2'],
['2 * 2 + 3 * 3'], ['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'], ['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)'], ['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, (20/2))'], ['sin(10) * cos(50) / min(10, (20/2))'],
['sin(10) * cos(50) / min(10, (max(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 + 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)')); $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() public function testEvaluateFunctionParameters()
{ {
$calculator = new MathExecutor(); $calculator = new MathExecutor();