From f0d4562b9ef3bbc64cb8353f0597e639420ede6b Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Tue, 30 Oct 2018 16:16:01 -0400 Subject: Division By Zero Exception support Updated the documentation. Unit tests for strings. DivisionByZeroException support. --- README.md | 57 ++++++++++++++++-------- src/NXP/Classes/Token/AbstractOperator.php | 30 +++++++++++++ src/NXP/Classes/Token/TokenDivision.php | 4 ++ src/NXP/Classes/TokenFactory.php | 33 +++++++++++++- src/NXP/MathExecutor.php | 23 ++++++++++ tests/MathTest.php | 69 +++++++++++++++++------------- 6 files changed, 167 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 2fdb5c1..f5bf318 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,37 @@ -[![Stories in Ready](https://badge.waffle.io/NeonXP/MathExecutor.png?label=ready&title=Ready)](https://waffle.io/NeonXP/MathExecutor) -# MathExecutor +# MathExecutor [![Stories in Ready](https://badge.waffle.io/NeonXP/MathExecutor.png?label=ready&title=Ready)](https://waffle.io/NeonXP/MathExecutor) [![Build Status](https://travis-ci.org/NeonXP/MathExecutor.png?branch=master)](https://travis-ci.org/NeonXP/MathExecutor) -[![Build Status](https://travis-ci.org/NeonXP/MathExecutor.png?branch=master)](https://travis-ci.org/NeonXP/MathExecutor) +A simple math expressions calculator -Simple math expressions calculator - -## Install via Composer +## Features: +* Built in support for +, -, *, / and power (^) operators +* Support for user defined operators +* Support for user defined functions +* Unlimited varable length +* String support, as function parameters or as evaluated by PHP +* Exceptions on divide by zero, or treat as zero +* Unary Minus +## Install via Composer: Stable branch ``` composer require "nxp/math-executor" "dev-master" ``` -Dev branch +Dev branch (currently unsupported) ``` composer require "nxp/math-executor" "dev-dev" ``` ## Sample usage: - ```php require "vendor/autoload.php"; -$calculator = new \NXP\MathExecutor(); +$executor = new \NXP\MathExecutor(); -print $calculator->execute("1 + 2 * (2 - (4+10))^2 + sin(10)"); +echo $executor->execute("1 + 2 * (2 - (4+10))^2 + sin(10)"); ``` ## Functions: - Default functions: * sin * cos @@ -48,13 +51,10 @@ $executor->addFunction('abs', function($arg) { ``` ## Operators: - Default operators: `+ - * / ^` Add custom operator to executor: -MyNamespace/ModulusToken.php: - ```php addOperator('MyNamespace\ModulusToken'); ``` ## Variables: - Default variables: ``` @@ -124,9 +123,31 @@ $e = 2.71828182846 You can add own variable to executor: ```php -$executor->setVars(array( +$executor->setVars([ 'var1' => 0.15, 'var2' => 0.22 -)); +]); + +echo $executor->execute("$var1 + $var2"); +``` +## Division By Zero Support: +By default, the result of division by zero is zero and no error is generated. You have the option to thow a \NXP\Exception\DivisionByZeroException by by calling setDivisionByZeroException. -$executor->execute("$var1 + $var2"); +```php +$executor->setDivisionByZeroException(); +try { + echo $executor->execute('1/0'); +} catch (\NXP\Exception\DivisionByZeroException $e) { + echo $e->getMessage(); +} +``` + +## Unary Minus Operator: +Negative numbers are supported via the unary minus operator, but need to have a space before the minus sign. `-1+ -3` is legal, while '`-1+-3` will produce an error due to the way the parser works. Positive numbers are not explicitly supported as unsigned numbers are assumed positive. + +## String Support: +Expressions can contain double or single quoted strings that are evaluated the same way as PHP. You can also pass strings to functions. + +```php +echo $executor->execute("1 + '2.5' * '.5' + myFunction('category')"); +``` diff --git a/src/NXP/Classes/Token/AbstractOperator.php b/src/NXP/Classes/Token/AbstractOperator.php index 6cdfe99..87634d5 100644 --- a/src/NXP/Classes/Token/AbstractOperator.php +++ b/src/NXP/Classes/Token/AbstractOperator.php @@ -17,4 +17,34 @@ abstract class AbstractOperator implements InterfaceToken, InterfaceOperator { const RIGHT_ASSOC = 'RIGHT'; const LEFT_ASSOC = 'LEFT'; + + /** + * Divide by zero reporting + * + * @var bool + */ + private $divideByZeroReporting = false; + + /** + * Set division by zero exception reporting + * + * @param bool $exception default true + * + * @return $this + */ + public function setDivisionByZeroException($exception = true) + { + $this->divideByZeroReporting = $exception; + return $this; + } + + /** + * Get division by zero exception status + * + * @return bool + */ + public function getDivisionByZeroException() + { + return $this->divideByZeroReporting; + } } diff --git a/src/NXP/Classes/Token/TokenDivision.php b/src/NXP/Classes/Token/TokenDivision.php index 5bbc35e..9166d34 100644 --- a/src/NXP/Classes/Token/TokenDivision.php +++ b/src/NXP/Classes/Token/TokenDivision.php @@ -59,6 +59,10 @@ class TokenDivision extends AbstractOperator throw new IncorrectExpressionException("Division requires two operators"); } + if ($this->getDivisionByZeroException() && $op2->getValue() == 0) { + throw new DivisionByZeroException(); + } + $result = $op2->getValue() != 0 ? $op1->getValue() / $op2->getValue() : 0; return new TokenNumber($result); diff --git a/src/NXP/Classes/TokenFactory.php b/src/NXP/Classes/TokenFactory.php index d70dd55..6956f31 100644 --- a/src/NXP/Classes/TokenFactory.php +++ b/src/NXP/Classes/TokenFactory.php @@ -34,6 +34,13 @@ class TokenFactory */ protected $operators = []; + /** + * Divide by zero reporting + * + * @var bool + */ + protected $divideByZeroReporting = false; + /** * Available functions * @@ -90,6 +97,29 @@ class TokenFactory return $this->operators; } + /** + * Set division by zero exception reporting + * + * @param bool $exception default true + * + * @return TokenFactory + */ + public function setDivisionByZeroException($exception = true) + { + $this->divideByZeroReporting = $exception; + return $this; + } + + /** + * Get division by zero exception status + * + * @return bool + */ + public function getDivisionByZeroException() + { + return $this->divideByZeroReporting; + } + /** * @return string */ @@ -143,7 +173,8 @@ class TokenFactory foreach ($this->operators as $operator) { $regex = sprintf('/%s/i', $operator::getRegex()); if (preg_match($regex, $token)) { - return new $operator; + $op = new $operator; + return $op->setDivisionByZeroException($this->getDivisionByZeroException()); } } diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index d62d07a..fbf6ab1 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -218,6 +218,29 @@ class MathExecutor return $this->tokenFactory->getFunctions(); } + /** + * Set division by zero exception reporting + * + * @param bool $exception default true + * + * @return MathExecutor + */ + public function setDivisionByZeroException($exception = true) + { + $this->tokenFactory->setDivisionByZeroException($exception); + return $this; + } + + /** + * Get division by zero exception status + * + * @return bool + */ + public function getDivisionByZeroException() + { + return $this->tokenFactory->getDivisionByZeroException(); + } + /** * Execute expression * diff --git a/tests/MathTest.php b/tests/MathTest.php index c8b8a03..c5dd60d 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -35,36 +35,6 @@ class MathTest extends \PHPUnit_Framework_TestCase $this->assertEquals($calculator->execute($expression), $phpResult); } - public function testUnknownFunctionException() - { - $calculator = new MathExecutor(); - $this->expectException(UnknownFunctionException::class); - $calculator->execute('1 * fred("wilma") + 3'); - } - - public function testIncorrectExpressionException() - { - $calculator = new MathExecutor(); - $this->expectException(IncorrectExpressionException::class); - $calculator->execute('1 * + '); - } - - public function testZeroDivision() - { - $calculator = new MathExecutor(); - $this->assertEquals($calculator->execute('1 / 0'), 0); - - // future version with allow for optional exceptions on divide by zero - // $this->expectException(DivisionByZeroException::class); - // $calculator->execute('1 / 0'); - } - - public function testExponentiation() - { - $calculator = new MathExecutor(); - $this->assertEquals($calculator->execute('10 ^ 2'), 100); - } - /** * Expressions data provider */ @@ -94,6 +64,7 @@ class MathTest extends \PHPUnit_Framework_TestCase ['(2- 2)*2'], ['2 + 2*2'], ['2+ 2*2'], + ['2+2*2'], ['(2+2)*2'], ['(2 + 2)*-2'], ['(2+-2)*2'], @@ -103,6 +74,10 @@ class MathTest extends \PHPUnit_Framework_TestCase ['100500 * 3.5E5'], ['100500 * 3.5E-5'], + ['1 + "2" / 3'], + ["1.5 + '2.5' / 4"], + ['1.5 + "2.5" * ".5"'], + ['-1 + -2'], ['-1+-2'], ['-1- -2'], @@ -111,6 +86,40 @@ class MathTest extends \PHPUnit_Framework_TestCase ]; } + public function testUnknownFunctionException() + { + $calculator = new MathExecutor(); + $this->expectException(UnknownFunctionException::class); + $calculator->execute('1 * fred("wilma") + 3'); + } + + public function testIncorrectExpressionException() + { + $calculator = new MathExecutor(); + $this->expectException(IncorrectExpressionException::class); + $calculator->execute('1 * + '); + } + + public function testZeroDivision() + { + $calculator = new MathExecutor(); + $this->assertEquals($calculator->execute('10 / 0'), 0); + } + + public function testZeroDivisionException() + { + $calculator = new MathExecutor(); + $calculator->setDivisionByZeroException(); + $this->expectException(DivisionByZeroException::class); + $calculator->execute('10 / 0'); + } + + public function testExponentiation() + { + $calculator = new MathExecutor(); + $this->assertEquals($calculator->execute('10 ^ 2'), 100); + } + public function testFunction() { $calculator = new MathExecutor(); -- cgit v1.2.3