Merge pull request #63 from phpfui/neonxp-ng

Update parameters and add back functions
This commit is contained in:
Alexander Kiryukhin 2020-05-20 22:26:59 +03:00 committed by GitHub
commit 913cf0a1e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 41 deletions

View file

@ -95,7 +95,7 @@ $executor->addOperator(new Operator(
$op2 = array_pop($stack); $op2 = array_pop($stack);
$op1 = array_pop($stack); $op1 = array_pop($stack);
$result = $op1->getValue() % $op2->getValue(); $result = $op1->getValue() % $op2->getValue();
return $result; return $result;
} }
)); ));
@ -124,15 +124,12 @@ $e = 2.71828182846
You can add your own variables to executor: You can add your own variables to executor:
```php ```php
$executor->setVars([ $executor->setVar('var1', 0.15)->setVar('var2', 0.22);
'var1' => 0.15,
'var2' => 0.22
]);
echo $executor->execute("$var1 + $var2"); echo $executor->execute("$var1 + $var2");
``` ```
## Division By Zero Support: ## Division By Zero Support:
Division by zero throws a `\NXP\Exception\DivisionByZeroException` Division by zero throws a `\NXP\Exception\DivisionByZeroException` by default
```php ```php
try { try {
echo $executor->execute('1/0'); echo $executor->execute('1/0');
@ -140,12 +137,15 @@ try {
echo $e->getMessage(); 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 ```php
$executor->addOperator("/", false, 180, function($a, $b) { $executor->addOperator("/", false, 180, function($a, $b) {
if ($b == 0) { if ($b == 0) {
return 0; return null;
} }
return $a / $b; return $a / $b;
}); });

View file

@ -49,7 +49,7 @@ class Calculator
* @throws IncorrectExpressionException * @throws IncorrectExpressionException
* @throws UnknownVariableException * @throws UnknownVariableException
*/ */
public function calculate($tokens, $variables) public function calculate(array $tokens, array $variables)
{ {
/** @var Token[] $stack */ /** @var Token[] $stack */
$stack = []; $stack = [];

View file

@ -32,7 +32,7 @@ class CustomFunction
* @param int $places * @param int $places
* @throws ReflectionException * @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->name = $name;
$this->function = $function; $this->function = $function;
@ -44,7 +44,7 @@ class CustomFunction
} }
} }
public function execute(&$stack) public function execute(array &$stack) : Token
{ {
if (count($stack) < $this->places) { if (count($stack) < $this->places) {
throw new IncorrectExpressionException(); throw new IncorrectExpressionException();

View file

@ -52,7 +52,7 @@ class Operator
$this->places = $reflection->getNumberOfParameters(); $this->places = $reflection->getNumberOfParameters();
} }
public function execute(&$stack) public function execute(array &$stack) : Token
{ {
if (count($stack) < $this->places) { if (count($stack) < $this->places) {
throw new IncorrectExpressionException(); throw new IncorrectExpressionException();

View file

@ -66,7 +66,7 @@ class Tokenizer
$this->operators = $operators; $this->operators = $operators;
} }
public function tokenize() public function tokenize() : self
{ {
foreach (str_split($this->input, 1) as $ch) { foreach (str_split($this->input, 1) as $ch) {
switch (true) { switch (true) {
@ -173,17 +173,17 @@ class Tokenizer
return $this; return $this;
} }
private function isNumber($ch) private function isNumber(string $ch) : bool
{ {
return $ch >= '0' && $ch <= '9'; 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 == '_'; return $ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch == '_';
} }
private function emptyNumberBufferAsLiteral() private function emptyNumberBufferAsLiteral() : void
{ {
if ($this->numberBuffer != "") { if ($this->numberBuffer != "") {
$this->tokens[] = new Token(Token::Literal, $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 == '.'; return $ch == '.';
} }
private function isLP($ch) private function isLP(string $ch) : bool
{ {
return $ch == '('; return $ch == '(';
} }
private function isRP($ch) private function isRP(string $ch) : bool
{ {
return $ch == ')'; return $ch == ')';
} }
private function emptyStrBufferAsVariable() private function emptyStrBufferAsVariable() : void
{ {
if ($this->stringBuffer != "") { if ($this->stringBuffer != "") {
$this->tokens[] = new Token(Token::Variable, $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 == ','; return $ch == ',';
} }
@ -224,7 +224,7 @@ class Tokenizer
* @throws IncorrectBracketsException * @throws IncorrectBracketsException
* @throws UnknownOperatorException * @throws UnknownOperatorException
*/ */
public function buildReversePolishNotation() public function buildReversePolishNotation() : array
{ {
$tokens = []; $tokens = [];
/** @var SplStack<Token> $stack */ /** @var SplStack<Token> $stack */

View file

@ -15,6 +15,7 @@ use NXP\Classes\Calculator;
use NXP\Classes\CustomFunction; use NXP\Classes\CustomFunction;
use NXP\Classes\Operator; use NXP\Classes\Operator;
use NXP\Classes\Tokenizer; use NXP\Classes\Tokenizer;
use NXP\MathExecutorException;
use NXP\Exception\DivisionByZeroException; use NXP\Exception\DivisionByZeroException;
use ReflectionException; use ReflectionException;
@ -34,12 +35,12 @@ class MathExecutor
/** /**
* @var Operator[] * @var Operator[]
*/ */
public $operators = []; private $operators = [];
/** /**
* @var CustomFunction[] * @var CustomFunction[]
*/ */
public $functions = []; private $functions = [];
/** /**
* @var array * @var array
@ -58,10 +59,10 @@ class MathExecutor
* Set default operands and functions * Set default operands and functions
* @throws ReflectionException * @throws ReflectionException
*/ */
protected function addDefaults() protected function addDefaults() : void
{ {
foreach ($this->defaultOperators() as $name => $operator) { foreach ($this->defaultOperators() as $name => $operator) {
list($callable, $priority, $isRightAssoc) = $operator; [$callable, $priority, $isRightAssoc] = $operator;
$this->addOperator(new Operator($name, $isRightAssoc, $priority, $callable)); $this->addOperator(new Operator($name, $isRightAssoc, $priority, $callable));
} }
foreach ($this->defaultFunctions() as $name => $callable) { foreach ($this->defaultFunctions() as $name => $callable) {
@ -75,7 +76,7 @@ class MathExecutor
* *
* @return array of class names * @return array of class names
*/ */
protected function defaultOperators() protected function defaultOperators() : array
{ {
return [ return [
'+' => [ '+' => [
@ -181,7 +182,7 @@ class MathExecutor
* @param Operator $operator * @param Operator $operator
* @return MathExecutor * @return MathExecutor
*/ */
public function addOperator(Operator $operator) public function addOperator(Operator $operator) : self
{ {
$this->operators[$operator->operator] = $operator; $this->operators[$operator->operator] = $operator;
return $this; return $this;
@ -193,7 +194,7 @@ class MathExecutor
* *
* @return array * @return array
*/ */
protected function defaultFunctions() protected function defaultFunctions() : array
{ {
return [ return [
'abs' => function ($arg) { 'abs' => function ($arg) {
@ -341,9 +342,9 @@ class MathExecutor
* @throws Exception\UnknownOperatorException * @throws Exception\UnknownOperatorException
* @throws Exception\UnknownVariableException * @throws Exception\UnknownVariableException
*/ */
public function execute($expression) public function execute(string $expression)
{ {
$cachekey = (string)$expression; $cachekey = $expression;
if (!array_key_exists($cachekey, $this->cache)) { if (!array_key_exists($cachekey, $this->cache)) {
$tokens = (new Tokenizer($expression, $this->operators))->tokenize()->buildReversePolishNotation(); $tokens = (new Tokenizer($expression, $this->operators))->tokenize()->buildReversePolishNotation();
$this->cache[$cachekey] = $tokens; $this->cache[$cachekey] = $tokens;
@ -363,7 +364,7 @@ class MathExecutor
* @return MathExecutor * @return MathExecutor
* @throws ReflectionException * @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); $this->functions[$name] = new CustomFunction($name, $function, $places);
return $this; return $this;
@ -374,7 +375,7 @@ class MathExecutor
* *
* @return array * @return array
*/ */
protected function defaultVars() protected function defaultVars() : array
{ {
return [ return [
'pi' => 3.14159265359, '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() public function __clone()
{ {
$this->addDefaults(); $this->addDefaults();

View file

@ -203,7 +203,7 @@ class MathTest extends TestCase
['(-3 * -1)'], ['(-3 * -1)'],
['1 + (-3 * -1)'], ['1 + (-3 * -1)'],
['1 + ( -3 * 1)'], ['1 + ( -3 * 1)'],
['1 + (3 * -1)'], ['1 + (3 *-1)'],
['1 - 0'], ['1 - 0'],
['1-0'], ['1-0'],
]; ];
@ -226,12 +226,7 @@ class MathTest extends TestCase
public function testZeroDivision() public function testZeroDivision()
{ {
$calculator = new MathExecutor(); $calculator = new MathExecutor();
$calculator->addOperator(new Operator("/", false, 180, function ($a, $b) { $calculator->setDivisionByZeroIsZero();
if ($b == 0) {
return 0;
}
return $a / $b;
}));
$this->assertEquals(0, $calculator->execute('10 / 0')); $this->assertEquals(0, $calculator->execute('10 / 0'));
} }