diff --git a/README.md b/README.md index c665575..65b9dc2 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ $executor->addOperator(new Operator( $op2 = array_pop($stack); $op1 = array_pop($stack); $result = $op1->getValue() % $op2->getValue(); - + return $result; } )); @@ -124,15 +124,12 @@ $e = 2.71828182846 You can add your own variables to executor: ```php -$executor->setVars([ - 'var1' => 0.15, - 'var2' => 0.22 -]); +$executor->setVar('var1', 0.15)->setVar('var2', 0.22); echo $executor->execute("$var1 + $var2"); ``` ## Division By Zero Support: -Division by zero throws a `\NXP\Exception\DivisionByZeroException` +Division by zero throws a `\NXP\Exception\DivisionByZeroException` by default ```php try { echo $executor->execute('1/0'); @@ -140,12 +137,15 @@ try { echo $e->getMessage(); } ``` -If you want another behavior, you should override division operator: - +Or call setDivisionByZeroIsZero +```php +echo $executor->setDivisionByZeroIsZero()->execute('1/0'); +``` +If you want another behavior, you can override division operator: ```php $executor->addOperator("/", false, 180, function($a, $b) { if ($b == 0) { - return 0; + return null; } return $a / $b; }); diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php index 0db49e1..c6ccff1 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($tokens, $variables) + public function calculate(array $tokens, array $variables) { /** @var Token[] $stack */ $stack = []; diff --git a/src/NXP/Classes/CustomFunction.php b/src/NXP/Classes/CustomFunction.php index 944f7d9..bf36405 100644 --- a/src/NXP/Classes/CustomFunction.php +++ b/src/NXP/Classes/CustomFunction.php @@ -32,7 +32,7 @@ class CustomFunction * @param int $places * @throws ReflectionException */ - public function __construct(string $name, callable $function, $places = null) + public function __construct(string $name, callable $function, ?int $places = null) { $this->name = $name; $this->function = $function; @@ -44,7 +44,7 @@ class CustomFunction } } - public function execute(&$stack) + public function execute(array &$stack) : Token { if (count($stack) < $this->places) { throw new IncorrectExpressionException(); diff --git a/src/NXP/Classes/Operator.php b/src/NXP/Classes/Operator.php index 53550a9..c3762ea 100644 --- a/src/NXP/Classes/Operator.php +++ b/src/NXP/Classes/Operator.php @@ -52,7 +52,7 @@ class Operator $this->places = $reflection->getNumberOfParameters(); } - public function execute(&$stack) + public function execute(array &$stack) : Token { if (count($stack) < $this->places) { throw new IncorrectExpressionException(); diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php index b72b869..caf395f 100644 --- a/src/NXP/Classes/Tokenizer.php +++ b/src/NXP/Classes/Tokenizer.php @@ -66,7 +66,7 @@ class Tokenizer $this->operators = $operators; } - public function tokenize() + public function tokenize() : self { foreach (str_split($this->input, 1) as $ch) { switch (true) { @@ -173,17 +173,17 @@ class Tokenizer return $this; } - private function isNumber($ch) + private function isNumber(string $ch) : bool { return $ch >= '0' && $ch <= '9'; } - private function isAlpha($ch) + private function isAlpha(string $ch) : bool { return $ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch == '_'; } - private function emptyNumberBufferAsLiteral() + private function emptyNumberBufferAsLiteral() : void { if ($this->numberBuffer != "") { $this->tokens[] = new Token(Token::Literal, $this->numberBuffer); @@ -191,22 +191,22 @@ class Tokenizer } } - private function isDot($ch) + private function isDot(string $ch) : bool { return $ch == '.'; } - private function isLP($ch) + private function isLP(string $ch) : bool { return $ch == '('; } - private function isRP($ch) + private function isRP(string $ch) : bool { return $ch == ')'; } - private function emptyStrBufferAsVariable() + private function emptyStrBufferAsVariable() : void { if ($this->stringBuffer != "") { $this->tokens[] = new Token(Token::Variable, $this->stringBuffer); @@ -214,7 +214,7 @@ class Tokenizer } } - private function isComma($ch) + private function isComma(string $ch) : bool { return $ch == ','; } @@ -224,7 +224,7 @@ class Tokenizer * @throws IncorrectBracketsException * @throws UnknownOperatorException */ - public function buildReversePolishNotation() + public function buildReversePolishNotation() : array { $tokens = []; /** @var SplStack $stack */ diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index a0a3f9b..5fdc78f 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -15,6 +15,7 @@ use NXP\Classes\Calculator; use NXP\Classes\CustomFunction; use NXP\Classes\Operator; use NXP\Classes\Tokenizer; +use NXP\MathExecutorException; use NXP\Exception\DivisionByZeroException; use ReflectionException; @@ -34,12 +35,12 @@ class MathExecutor /** * @var Operator[] */ - public $operators = []; + private $operators = []; /** * @var CustomFunction[] */ - public $functions = []; + private $functions = []; /** * @var array @@ -58,10 +59,10 @@ class MathExecutor * Set default operands and functions * @throws ReflectionException */ - protected function addDefaults() + protected function addDefaults() : void { foreach ($this->defaultOperators() as $name => $operator) { - list($callable, $priority, $isRightAssoc) = $operator; + [$callable, $priority, $isRightAssoc] = $operator; $this->addOperator(new Operator($name, $isRightAssoc, $priority, $callable)); } foreach ($this->defaultFunctions() as $name => $callable) { @@ -75,7 +76,7 @@ class MathExecutor * * @return array of class names */ - protected function defaultOperators() + protected function defaultOperators() : array { return [ '+' => [ @@ -181,7 +182,7 @@ class MathExecutor * @param Operator $operator * @return MathExecutor */ - public function addOperator(Operator $operator) + public function addOperator(Operator $operator) : self { $this->operators[$operator->operator] = $operator; return $this; @@ -193,7 +194,7 @@ class MathExecutor * * @return array */ - protected function defaultFunctions() + protected function defaultFunctions() : array { return [ 'abs' => function ($arg) { @@ -341,9 +342,9 @@ class MathExecutor * @throws Exception\UnknownOperatorException * @throws Exception\UnknownVariableException */ - public function execute($expression) + public function execute(string $expression) { - $cachekey = (string)$expression; + $cachekey = $expression; if (!array_key_exists($cachekey, $this->cache)) { $tokens = (new Tokenizer($expression, $this->operators))->tokenize()->buildReversePolishNotation(); $this->cache[$cachekey] = $tokens; @@ -363,7 +364,7 @@ class MathExecutor * @return MathExecutor * @throws ReflectionException */ - public function addFunction($name, $function = null, $places = null) + public function addFunction(string $name, ?callable $function = null, ?int $places = null) : self { $this->functions[$name] = new CustomFunction($name, $function, $places); return $this; @@ -374,7 +375,7 @@ class MathExecutor * * @return array */ - protected function defaultVars() + protected function defaultVars() : array { return [ 'pi' => 3.14159265359, @@ -382,6 +383,126 @@ class MathExecutor ]; } + /** + * Get all vars + * + * @return array + */ + public function getVars() : array + { + return $this->variables; + } + + /** + * Get a specific var + * + * @param string $variable + * @return integer|float + * @throws UnknownVariableException + */ + public function getVar(string $variable) + { + if (!isset($this->variables[$variable])) { + throw new UnknownVariableException("Variable ({$variable}) not set"); + } + return $this->variables[$variable]; + } + + /** + * Add variable to executor + * + * @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)})"); + } + $this->variables[$variable] = $value; + return $this; + } + + /** + * Add variables to executor + * + * @param array $variables + * @param bool $clear Clear previous variables + * @return MathExecutor + * @throws \Exception + */ + public function setVars(array $variables, bool $clear = true) : self + { + if ($clear) { + $this->removeVars(); + } + foreach ($variables as $name => $value) { + $this->setVar($name, $value); + } + return $this; + } + + /** + * Remove variable from executor + * + * @param string $variable + * @return MathExecutor + */ + public function removeVar(string $variable) : self + { + unset ($this->variables[$variable]); + return $this; + } + + /** + * Remove all variables + * @return MathExecutor + */ + public function removeVars() : self + { + $this->variables = []; + return $this; + } + + /** + * Get all registered operators to executor + * + * @return array of operator class names + */ + public function getOperators() + { + return $this->tokenFactory->getOperators(); + } + + /** + * Get all registered functions + * + * @return array containing callback and places indexed by + * function name + */ + public function getFunctions() : array + { + return $this->tokenFactory->getFunctions(); + } + + /** + * Set division by zero returns zero instead of throwing DivisionByZeroException + * + * @return MathExecutor + */ + public function setDivisionByZeroIsZero() : self + { + $this->addOperator(new Operator("/", false, 180, function ($a, $b) { + if ($b == 0) { + return 0; + } + return $a / $b; + })); + return $this; + } + public function __clone() { $this->addDefaults(); diff --git a/tests/MathTest.php b/tests/MathTest.php index 958f14c..39ac649 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -203,7 +203,7 @@ class MathTest extends TestCase ['(-3 * -1)'], ['1 + (-3 * -1)'], ['1 + ( -3 * 1)'], - ['1 + (3 * -1)'], + ['1 + (3 *-1)'], ['1 - 0'], ['1-0'], ]; @@ -226,12 +226,7 @@ class MathTest extends TestCase public function testZeroDivision() { $calculator = new MathExecutor(); - $calculator->addOperator(new Operator("/", false, 180, function ($a, $b) { - if ($b == 0) { - return 0; - } - return $a / $b; - })); + $calculator->setDivisionByZeroIsZero(); $this->assertEquals(0, $calculator->execute('10 / 0')); }