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
18
README.md
18
README.md
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue