Merge pull request #63 from phpfui/neonxp-ng
Update parameters and add back functions
This commit is contained in:
commit
913cf0a1e8
7 changed files with 157 additions and 41 deletions
16
README.md
16
README.md
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<Token> $stack */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue