From aa8ffe19f2eb6f194b3e6ee1aac4c2706659e6b9 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Thu, 4 Jun 2020 11:43:16 -0400 Subject: [PATCH 1/6] Variable fixes (#67) * Reproduce if throws UnknownOperatorException * Fix variable detection * Adding IncorrectNumberOfFunctionParametersException * Removing tabs * Better exception message text --- src/NXP/Classes/Calculator.php | 2 +- src/NXP/Classes/CustomFunction.php | 5 +- src/NXP/Classes/Operator.php | 1 - src/NXP/Classes/Tokenizer.php | 16 +++-- ...ectNumberOfFunctionParametersException.php | 16 +++++ tests/MathTest.php | 68 +++++++++++++++++++ 6 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 src/NXP/Exception/IncorrectNumberOfFunctionParametersException.php diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php index 94c6933..bcb4b4e 100644 --- a/src/NXP/Classes/Calculator.php +++ b/src/NXP/Classes/Calculator.php @@ -77,7 +77,7 @@ class Calculator } $result = array_pop($stack); if ($result === null || !empty($stack)) { - throw new IncorrectExpressionException(); + throw new IncorrectExpressionException('Stack must be empty'); } return $result->value; } diff --git a/src/NXP/Classes/CustomFunction.php b/src/NXP/Classes/CustomFunction.php index 06e21ef..f127ad2 100644 --- a/src/NXP/Classes/CustomFunction.php +++ b/src/NXP/Classes/CustomFunction.php @@ -3,7 +3,7 @@ namespace NXP\Classes; -use NXP\Exception\IncorrectExpressionException; +use NXP\Exception\IncorrectNumberOfFunctionParametersException; use ReflectionException; use ReflectionFunction; @@ -30,6 +30,7 @@ class CustomFunction * @param callable $function * @param int $places * @throws ReflectionException + * @throws IncorrectNumberOfFunctionParametersException */ public function __construct(string $name, callable $function, ?int $places = null) { @@ -46,7 +47,7 @@ class CustomFunction public function execute(array &$stack) : Token { if (count($stack) < $this->places) { - throw new IncorrectExpressionException(); + throw new IncorrectNumberOfFunctionParametersException($this->name); } $args = []; for ($i = 0; $i < $this->places; $i++) { diff --git a/src/NXP/Classes/Operator.php b/src/NXP/Classes/Operator.php index 86df549..00485bc 100644 --- a/src/NXP/Classes/Operator.php +++ b/src/NXP/Classes/Operator.php @@ -46,7 +46,6 @@ class Operator $this->isRightAssoc = $isRightAssoc; $this->priority = $priority; $this->function = $function; - $this->function = $function; $reflection = new ReflectionFunction($function); $this->places = $reflection->getNumberOfParameters(); } diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php index 6b14677..203724a 100644 --- a/src/NXP/Classes/Tokenizer.php +++ b/src/NXP/Classes/Tokenizer.php @@ -157,14 +157,16 @@ class Tokenizer } $this->emptyNumberBufferAsLiteral(); $this->emptyStrBufferAsVariable(); - if (count($this->tokens) > 0) { - if ($this->tokens[count($this->tokens) - 1]->type === Token::Operator) { - $this->tokens[count($this->tokens) - 1]->value .= $ch; + if ($ch != '$') { + if (count($this->tokens) > 0) { + if ($this->tokens[count($this->tokens) - 1]->type === Token::Operator) { + $this->tokens[count($this->tokens) - 1]->value .= $ch; + } else { + $this->tokens[] = new Token(Token::Operator, $ch); + } } else { $this->tokens[] = new Token(Token::Operator, $ch); } - } else { - $this->tokens[] = new Token(Token::Operator, $ch); } $this->allowNegative = true; } @@ -251,12 +253,12 @@ class Tokenizer break; case Token::Operator: if (!array_key_exists($token->value, $this->operators)) { - throw new UnknownOperatorException(); + throw new UnknownOperatorException($token->value); } $op1 = $this->operators[$token->value]; while ($stack->count() > 0 && $stack->top()->type === Token::Operator) { if (!array_key_exists($stack->top()->value, $this->operators)) { - throw new UnknownOperatorException(); + throw new UnknownOperatorException($stack->top()->value); } $op2 = $this->operators[$stack->top()->value]; if ($op2->priority >= $op1->priority) { diff --git a/src/NXP/Exception/IncorrectNumberOfFunctionParametersException.php b/src/NXP/Exception/IncorrectNumberOfFunctionParametersException.php new file mode 100644 index 0000000..4c66bdf --- /dev/null +++ b/src/NXP/Exception/IncorrectNumberOfFunctionParametersException.php @@ -0,0 +1,16 @@ +expectException(DivisionByZeroException::class); $calculator->execute('10 / 0'); + $calculator->setVar('one', 1)->setVar('zero', 0); + $this->assertEquals(0.0, $calculator->execute('$one / $zero')); + } + + public function testVariableIncorrectExpressionException() + { + $calculator = new MathExecutor(); + $calculator->setVar('four', 4); + $this->assertEquals(4, $calculator->execute('$four')); + $this->expectException(IncorrectExpressionException::class); + $this->assertEquals(0.0, $calculator->execute('$')); + $this->assertEquals(0.0, $calculator->execute('$ + $four')); } public function testExponentiation() @@ -289,6 +303,60 @@ class MathTest extends TestCase $this->assertEquals(cos(2), $calculator->execute( 'if(cos(2), cos(2), 0)' )); + $trx_amount = 100000; + $calculator->setVar('trx_amount', $trx_amount); + $this->assertEquals($trx_amount, $calculator->execute('$trx_amount')); + $this->assertEquals($trx_amount * 0.03, $calculator->execute( + 'if($trx_amount < 40000, $trx_amount * 0.06, $trx_amount * 0.03)' + )); + $this->assertEquals($trx_amount * 0.03, $calculator->execute( + 'if($trx_amount < 40000, $trx_amount * 0.06, if($trx_amount < 60000, $trx_amount * 0.05, $trx_amount * 0.03))' + )); + $trx_amount = 39000; + $calculator->setVar('trx_amount', $trx_amount); + $this->assertEquals($trx_amount * 0.06, $calculator->execute( + 'if($trx_amount < 40000, $trx_amount * 0.06, if($trx_amount < 60000, $trx_amount * 0.05, $trx_amount * 0.03))' + )); + $trx_amount = 59000; + $calculator->setVar('trx_amount', $trx_amount); + $this->assertEquals($trx_amount * 0.05, $calculator->execute( + 'if($trx_amount < 40000, $trx_amount * 0.06, if($trx_amount < 60000, $trx_amount * 0.05, $trx_amount * 0.03))' + )); + $this->expectException(IncorrectNumberOfFunctionParametersException::class); + $this->assertEquals(0.0, $calculator->execute( + 'if($trx_amount < 40000, $trx_amount * 0.06)' + )); + } + + public function testVariables() + { + $calculator = new MathExecutor(); + $this->assertEquals(3.14159265359, $calculator->execute('$pi')); + $this->assertEquals(2.71828182846, $calculator->execute('$e')); + $calculator->setVars([ + 'trx_amount' => 100000, + 'ten' => 10, + 'nine' => 9, + 'eight' => 8, + 'seven' => 7, + 'six' => 6, + 'five' => 5, + 'four' => 4, + 'three' => 3, + 'two' => 2, + 'one' => 1, + 'zero' => 0, + ]); + $this->assertEquals(100000, $calculator->execute('$trx_amount')); + $this->assertEquals(10 - 9, $calculator->execute('$ten - $nine')); + $this->assertEquals(9 - 10, $calculator->execute('$nine - $ten')); + $this->assertEquals(10 + 9, $calculator->execute('$ten + $nine')); + $this->assertEquals(10 * 9, $calculator->execute('$ten * $nine')); + $this->assertEquals(10 / 9, $calculator->execute('$ten / $nine')); + $this->assertEquals(10 / (9 / 5), $calculator->execute('$ten / ($nine / $five)')); + + $this->expectException(UnknownVariableException::class); + $this->assertEquals(0.0, $calculator->execute('$unsetVariable')); } public function testEvaluateFunctionParameters() From c1e07f254a4e952868be8240080661628aef8e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Sun, 26 Jul 2020 04:27:26 +0200 Subject: [PATCH 2/6] Handler for not found variables (#68) * Added handler to define not found variables Added support for string variables Fixed strings and ints comparison error * Check if variables have scalar types (int, float, string and bool) Better $onVarNotFound logic --- src/NXP/Classes/Calculator.php | 14 ++++-- src/NXP/Exception/MathExecutorException.php | 2 +- src/NXP/MathExecutor.php | 48 ++++++++++++++++----- tests/MathTest.php | 31 +++++++++++++ 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php index bcb4b4e..49142df 100644 --- a/src/NXP/Classes/Calculator.php +++ b/src/NXP/Classes/Calculator.php @@ -49,7 +49,7 @@ class Calculator * @throws IncorrectExpressionException * @throws UnknownVariableException */ - public function calculate(array $tokens, array $variables) + public function calculate(array $tokens, array $variables, callable $onVarNotFound = null) { /** @var Token[] $stack */ $stack = []; @@ -58,10 +58,18 @@ class Calculator $stack[] = $token; } elseif ($token->type === Token::Variable) { $variable = $token->value; - if (!array_key_exists($variable, $variables)) { + + $value = null; + if (array_key_exists($variable, $variables)) { + $value = $variables[$variable]; + } elseif ($onVarNotFound) { + $value = call_user_func($onVarNotFound, $variable); + } + + if (!isset($value)) { throw new UnknownVariableException($variable); } - $value = $variables[$variable]; + $stack[] = new Token(Token::Literal, $value); } elseif ($token->type === Token::Function) { if (!array_key_exists($token->value, $this->functions)) { diff --git a/src/NXP/Exception/MathExecutorException.php b/src/NXP/Exception/MathExecutorException.php index 0e3ea84..4405f85 100644 --- a/src/NXP/Exception/MathExecutorException.php +++ b/src/NXP/Exception/MathExecutorException.php @@ -14,6 +14,6 @@ namespace NXP\Exception; /** * @author Vitaliy Zhuk */ -abstract class MathExecutorException extends \Exception +class MathExecutorException extends \Exception { } diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 8f876dc..d23ba80 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -15,9 +15,9 @@ use NXP\Classes\Calculator; use NXP\Classes\CustomFunction; use NXP\Classes\Operator; use NXP\Classes\Tokenizer; -use NXP\Exception\MathExecutorException; use NXP\Exception\DivisionByZeroException; use NXP\Exception\UnknownVariableException; +use NXP\Exception\MathExecutorException; use ReflectionException; /** @@ -33,6 +33,11 @@ class MathExecutor */ private $variables = []; + /** + * @var callable + */ + private $onVarNotFound = null; + /** * @var Operator[] */ @@ -134,14 +139,22 @@ class MathExecutor ], '==' => [ function ($a, $b) { - return $a == $b; + if (is_string($a) || is_string($b)) { + return strcmp($a, $b) == 0; + } else { + return $a == $b; + } }, 140, false ], '!=' => [ function ($a, $b) { - return $a != $b; + if (is_string($a) || is_string($b)) { + return strcmp($a, $b) != 0; + } else { + return $a != $b; + } }, 140, false @@ -353,7 +366,7 @@ class MathExecutor $tokens = $this->cache[$cachekey]; } $calculator = new Calculator($this->functions, $this->operators); - return $calculator->calculate($tokens, $this->variables); + return $calculator->calculate($tokens, $this->variables, $this->onVarNotFound); } /** @@ -415,13 +428,13 @@ class MathExecutor * @param string $variable * @param integer|float $value * @return MathExecutor - * @throws MathExecutorException */ public function setVar(string $variable, $value) : self { - if (!is_numeric($value)) { - throw new MathExecutorException("Variable ({$variable}) value must be a number ({$value}) type ({gettype($value)})"); + if (!is_scalar($value)) { + throw new MathExecutorException("Variable ({$variable}) value must be a scalar type ({gettype($value)})"); } + $this->variables[$variable] = $value; return $this; } @@ -445,6 +458,20 @@ class MathExecutor return $this; } + /** + * Define a method that will be invoked when a variable is not found. + * The first parameter will be the variable name, and the returned value will be used as the variable value. + * + * @param callable $handler + * + * @return MathExecutor + */ + public function setVarNotFoundHandler(callable $handler): self + { + $this->onVarNotFound = $handler; + return $this; + } + /** * Remove variable from executor * @@ -458,12 +485,13 @@ class MathExecutor } /** - * Remove all variables + * Remove all variables and the variable not found handler * @return MathExecutor */ public function removeVars() : self { $this->variables = []; + $this->onVarNotFound = null; return $this; } @@ -474,7 +502,7 @@ class MathExecutor */ public function getOperators() { - return $this->tokenFactory->getOperators(); + return $this->operators; } /** @@ -485,7 +513,7 @@ class MathExecutor */ public function getFunctions() : array { - return $this->tokenFactory->getFunctions(); + return $this->functions; } /** diff --git a/tests/MathTest.php b/tests/MathTest.php index ee9c6bd..d76a57c 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -409,6 +409,37 @@ class MathTest extends TestCase $this->assertEquals(1, $calculator->execute('(-4 + 5)')); } + public function testStringComparison() + { + $calculator = new MathExecutor(); + $this->assertEquals(true, $calculator->execute('"a" == \'a\'')); + $this->assertEquals(true, $calculator->execute('"hello world" == "hello world"')); + $this->assertEquals(false, $calculator->execute('"hello world" == "hola mundo"')); + $this->assertEquals(true, $calculator->execute('"hello world" != "hola mundo"')); + } + + public function testVarStringComparison() + { + $calculator = new MathExecutor(); + $calculator->setVar('var', 0); + $this->assertEquals($calculator->execute('0 == "a"'), $calculator->execute('var == "a"')); + } + + public function testOnVarNotFound() + { + $calculator = new MathExecutor(); + $calculator->setVarNotFoundHandler( + function ($varName) { + if ($varName == 'undefined') { + return 3; + } else { + return null; + } + } + ); + $this->assertEquals(15, $calculator->execute('5 * undefined')); + } + public function testMinusZero() { $calculator = new MathExecutor(); From b38893d672f479314e8b9f254457b8b9755c781f Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Sun, 26 Jul 2020 22:14:51 -0400 Subject: [PATCH 3/6] Release prep (#69) * String comparison unit tests * getVars and getFunctions sanity checks * Add dynamic variable documentation --- README.md | 16 ++++++++++++++++ tests/MathTest.php | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98740d6..69651d5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ * Conditional If logic * Support for user defined operators * Support for user defined functions +* Dynamic variable resolution (delayed computation) * Unlimited variable name lengths * String support, as function parameters or as evaluated as a number by PHP * Exceptions on divide by zero, or treat as zero @@ -128,6 +129,21 @@ $executor->setVar('var1', 0.15)->setVar('var2', 0.22); echo $executor->execute("$var1 + $var2"); ``` + +You can dynamically define variables at run time. If a variable has a high computation cost, but might not be used, then you can define an undefined variable handler. It will only get called when the variable is used, rather than having to always set it initially. + +```php +$calculator = new MathExecutor(); +$calculator->setVarNotFoundHandler( + function ($varName) { + if ($varName == 'trans') { + return transmogrify(); + } + return null; + } +); +``` + ## Division By Zero Support: Division by zero throws a `\NXP\Exception\DivisionByZeroException` by default ```php diff --git a/tests/MathTest.php b/tests/MathTest.php index d76a57c..2669ccd 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -416,13 +416,21 @@ class MathTest extends TestCase $this->assertEquals(true, $calculator->execute('"hello world" == "hello world"')); $this->assertEquals(false, $calculator->execute('"hello world" == "hola mundo"')); $this->assertEquals(true, $calculator->execute('"hello world" != "hola mundo"')); + $this->assertEquals(true, $calculator->execute('"a" < "b"')); + $this->assertEquals(false, $calculator->execute('"a" > "b"')); + $this->assertEquals(true, $calculator->execute('"a" <= "b"')); + $this->assertEquals(false, $calculator->execute('"a" >= "b"')); + $this->assertEquals(true, $calculator->execute('"A" != "a"')); } public function testVarStringComparison() { $calculator = new MathExecutor(); - $calculator->setVar('var', 0); - $this->assertEquals($calculator->execute('0 == "a"'), $calculator->execute('var == "a"')); + $calculator->setVar('var', 97); + $this->assertEquals(false, $calculator->execute('97 == "a"')); + $this->assertEquals(false, $calculator->execute('$var == "a"')); + $calculator->setVar('var', 'a'); + $this->assertEquals(true, $calculator->execute('$var == "a"')); } public function testOnVarNotFound() @@ -432,9 +440,8 @@ class MathTest extends TestCase function ($varName) { if ($varName == 'undefined') { return 3; - } else { - return null; } + return null; } ); $this->assertEquals(15, $calculator->execute('5 * undefined')); @@ -446,4 +453,28 @@ class MathTest extends TestCase $this->assertEquals(1, $calculator->execute('1 - 0')); $this->assertEquals(1, $calculator->execute('1-0')); } + + public function testGetFunctionsReturnsArray() + { + $calculator = new MathExecutor(); + $this->assertIsArray($calculator->getFunctions()); + } + + public function testGetFunctionsReturnsFunctions() + { + $calculator = new MathExecutor(); + $this->assertGreaterThan(40, count($calculator->getFunctions())); + } + + public function testGetVarsReturnsArray() + { + $calculator = new MathExecutor(); + $this->assertIsArray($calculator->getVars()); + } + + public function testGetVarsReturnsCount() + { + $calculator = new MathExecutor(); + $this->assertGreaterThan(1, count($calculator->getVars())); + } } From 44d72cc252974ac36cc138591aac785022760a50 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Mon, 27 Jul 2020 12:25:59 -0400 Subject: [PATCH 4/6] Better setVar error message (#70) Additional unit tests Readme update --- README.md | 6 +++-- src/NXP/MathExecutor.php | 3 ++- tests/MathTest.php | 57 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 69651d5..f88e5e3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MathExecutor [![Tests](https://github.com/neonxp/MathExecutor/workflows/Tests/badge.svg)](https://github.com/neonxp/MathExecutor/actions?query=workflow%3ATests) [![Latest Packagist release](https://img.shields.io/packagist/v/nxp/math-executor.svg)](https://packagist.org/packages/nxp/math-executor) +# MathExecutor [![Tests](https://github.com/neonxp/MathExecutor/workflows/Tests/badge.svg)](https://github.com/neonxp/MathExecutor/actions?query=workflow%3ATests) # A simple and extensible math expressions calculator @@ -115,6 +115,8 @@ You can think of the **if** function as prototyped like: function if($condition, $returnIfTrue, $returnIfFalse) ``` ## Variables: +Variables can be prefixed with the dollar sign ($) for PHP compatibility, but is not required. + Default variables: ``` @@ -127,7 +129,7 @@ You can add your own variables to executor: ```php $executor->setVar('var1', 0.15)->setVar('var2', 0.22); -echo $executor->execute("$var1 + $var2"); +echo $executor->execute("$var1 + var2"); ``` You can dynamically define variables at run time. If a variable has a high computation cost, but might not be used, then you can define an undefined variable handler. It will only get called when the variable is used, rather than having to always set it initially. diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index d23ba80..434246a 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -432,7 +432,8 @@ class MathExecutor public function setVar(string $variable, $value) : self { if (!is_scalar($value)) { - throw new MathExecutorException("Variable ({$variable}) value must be a scalar type ({gettype($value)})"); + $type = gettype($value); + throw new MathExecutorException("Variable ({$variable}) type ({$type}) is not scalar"); } $this->variables[$variable] = $value; diff --git a/tests/MathTest.php b/tests/MathTest.php index 2669ccd..8f5665b 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -15,6 +15,7 @@ use Exception; use NXP\Exception\DivisionByZeroException; use NXP\Exception\IncorrectExpressionException; use NXP\Exception\IncorrectNumberOfFunctionParametersException; +use NXP\Exception\MathExecutorException; use NXP\Exception\UnknownFunctionException; use NXP\Exception\UnknownVariableException; use NXP\MathExecutor; @@ -332,9 +333,11 @@ class MathTest extends TestCase { $calculator = new MathExecutor(); $this->assertEquals(3.14159265359, $calculator->execute('$pi')); + $this->assertEquals(3.14159265359, $calculator->execute('pi')); $this->assertEquals(2.71828182846, $calculator->execute('$e')); + $this->assertEquals(2.71828182846, $calculator->execute('e')); $calculator->setVars([ - 'trx_amount' => 100000, + 'trx_amount' => 100000.01, 'ten' => 10, 'nine' => 9, 'eight' => 8, @@ -347,7 +350,7 @@ class MathTest extends TestCase 'one' => 1, 'zero' => 0, ]); - $this->assertEquals(100000, $calculator->execute('$trx_amount')); + $this->assertEquals(100000.01, $calculator->execute('$trx_amount')); $this->assertEquals(10 - 9, $calculator->execute('$ten - $nine')); $this->assertEquals(9 - 10, $calculator->execute('$nine - $ten')); $this->assertEquals(10 + 9, $calculator->execute('$ten + $nine')); @@ -355,8 +358,14 @@ class MathTest extends TestCase $this->assertEquals(10 / 9, $calculator->execute('$ten / $nine')); $this->assertEquals(10 / (9 / 5), $calculator->execute('$ten / ($nine / $five)')); - $this->expectException(UnknownVariableException::class); - $this->assertEquals(0.0, $calculator->execute('$unsetVariable')); + // test variables without leading $ + $this->assertEquals(100000.01, $calculator->execute('trx_amount')); + $this->assertEquals(10 - 9, $calculator->execute('ten - nine')); + $this->assertEquals(9 - 10, $calculator->execute('nine - ten')); + $this->assertEquals(10 + 9, $calculator->execute('ten + nine')); + $this->assertEquals(10 * 9, $calculator->execute('ten * nine')); + $this->assertEquals(10 / 9, $calculator->execute('ten / nine')); + $this->assertEquals(10 / (9 / 5), $calculator->execute('ten / (nine / five)')); } public function testEvaluateFunctionParameters() @@ -477,4 +486,44 @@ class MathTest extends TestCase $calculator = new MathExecutor(); $this->assertGreaterThan(1, count($calculator->getVars())); } + + public function testUndefinedVarThrowsExecption() + { + $calculator = new MathExecutor(); + $this->assertGreaterThan(1, count($calculator->getVars())); + $this->expectException(UnknownVariableException::class); + $calculator->execute('5 * undefined'); + } + + public function testSetVarsAcceptsAllScalars() + { + $calculator = new MathExecutor(); + $calculator->setVar('boolTrue', true); + $calculator->setVar('boolFalse', false); + $calculator->setVar('int', 1); + $calculator->setVar('float', 1.1); + $calculator->setVar('string', 'string'); + $this->assertEquals(7, count($calculator->getVars())); + } + + public function testSetVarsDoesNoAcceptObject() + { + $calculator = new MathExecutor(); + $this->expectException(MathExecutorException::class); + $calculator->setVar('object', $this); + } + + public function testSetVarsDoesNotAcceptNull() + { + $calculator = new MathExecutor(); + $this->expectException(MathExecutorException::class); + $calculator->setVar('null', null); + } + + public function testSetVarsDoesNotAcceptResource() + { + $calculator = new MathExecutor(); + $this->expectException(MathExecutorException::class); + $calculator->setVar('resource', tmpfile()); + } } From f8faf3fa8d21ebd6e6f8c73f022eb64e407dc6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Tue, 15 Sep 2020 02:47:26 +0200 Subject: [PATCH 5/6] Improved support for null variables (#72) * Added handler to define not found variables Added support for string variables Fixed strings and ints comparison error * Check if variables have scalar types (int, float, string and bool) Better $onVarNotFound logic * Better support for null variables * Better support for null variables * Better support for null variables --- src/NXP/Classes/Calculator.php | 4 +--- src/NXP/MathExecutor.php | 2 +- tests/MathTest.php | 42 +++++++++++++++++----------------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php index 49142df..4785a8f 100644 --- a/src/NXP/Classes/Calculator.php +++ b/src/NXP/Classes/Calculator.php @@ -64,9 +64,7 @@ class Calculator $value = $variables[$variable]; } elseif ($onVarNotFound) { $value = call_user_func($onVarNotFound, $variable); - } - - if (!isset($value)) { + } else { throw new UnknownVariableException($variable); } diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 434246a..f45335c 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -416,7 +416,7 @@ class MathExecutor */ public function getVar(string $variable) { - if (!isset($this->variables[$variable])) { + if (!array_key_exists($variable, $this->variables)) { throw new UnknownVariableException("Variable ({$variable}) not set"); } return $this->variables[$variable]; diff --git a/tests/MathTest.php b/tests/MathTest.php index 8f5665b..c519c5c 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -237,8 +237,8 @@ class MathTest extends TestCase $calculator = new MathExecutor(); $this->expectException(DivisionByZeroException::class); $calculator->execute('10 / 0'); - $calculator->setVar('one', 1)->setVar('zero', 0); - $this->assertEquals(0.0, $calculator->execute('$one / $zero')); + $calculator->setVar('one', 1)->setVar('zero', 0); + $this->assertEquals(0.0, $calculator->execute('$one / $zero')); } public function testVariableIncorrectExpressionException() @@ -337,19 +337,19 @@ class MathTest extends TestCase $this->assertEquals(2.71828182846, $calculator->execute('$e')); $this->assertEquals(2.71828182846, $calculator->execute('e')); $calculator->setVars([ - 'trx_amount' => 100000.01, - 'ten' => 10, - 'nine' => 9, - 'eight' => 8, - 'seven' => 7, - 'six' => 6, - 'five' => 5, - 'four' => 4, - 'three' => 3, - 'two' => 2, - 'one' => 1, - 'zero' => 0, - ]); + 'trx_amount' => 100000.01, + 'ten' => 10, + 'nine' => 9, + 'eight' => 8, + 'seven' => 7, + 'six' => 6, + 'five' => 5, + 'four' => 4, + 'three' => 3, + 'two' => 2, + 'one' => 1, + 'zero' => 0, + ]); $this->assertEquals(100000.01, $calculator->execute('$trx_amount')); $this->assertEquals(10 - 9, $calculator->execute('$ten - $nine')); $this->assertEquals(9 - 10, $calculator->execute('$nine - $ten')); @@ -374,8 +374,8 @@ class MathTest extends TestCase $calculator->addFunction( 'round', function ($value, $decimals) { - return round($value, $decimals); - } + return round($value, $decimals); + } ); $expression = 'round(100 * 1.111111, 2)'; $phpResult = 0; @@ -403,9 +403,9 @@ class MathTest extends TestCase $calculator->addFunction( 'test', function ($arg) use ($testString) { - $this->assertEquals($testString, $arg); - return 0; - } + $this->assertEquals($testString, $arg); + return 0; + } ); $calculator->execute('test("' . $testString . '")'); // single quotes $calculator->execute("test('" . $testString . "')"); // double quotes @@ -526,4 +526,4 @@ class MathTest extends TestCase $this->expectException(MathExecutorException::class); $calculator->setVar('resource', tmpfile()); } -} +} \ No newline at end of file From 8aa6674831c7c2329d06b8dc9db7801cb4997695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Mar=C3=ADn?= Date: Wed, 16 Sep 2020 03:08:14 +0200 Subject: [PATCH 6/6] Allow null values in `setVar` method (#73) * Added handler to define not found variables Added support for string variables Fixed strings and ints comparison error * Check if variables have scalar types (int, float, string and bool) Better $onVarNotFound logic * Better support for null variables * Better support for null variables * Better support for null variables * Allow null values in `setVar` method --- src/NXP/MathExecutor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index f45335c..b7abcad 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -431,7 +431,7 @@ class MathExecutor */ public function setVar(string $variable, $value) : self { - if (!is_scalar($value)) { + if (!is_scalar($value) && $value !== null) { $type = gettype($value); throw new MathExecutorException("Variable ({$variable}) type ({$type}) is not scalar"); }