aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBruce Wells <brucekwells@gmail.com>2022-04-27 00:31:50 +0300
committerGitHub <noreply@github.com>2022-04-27 00:31:50 +0300
commitb7b46bfc476ea0d22e0e92144f68aa81d390fff0 (patch)
treeb89329b67b35a90a59373acfd25f2774b82008a6 /src
parentc396a882ffa5f7467947a6a19c2435a7b4cbad22 (diff)
Phpcs fixer (#103)
* Configuring PHP CS Fixer Dropping PHP 7,3 support * Fixing merge issue
Diffstat (limited to 'src')
-rw-r--r--src/NXP/Classes/Calculator.php42
-rw-r--r--src/NXP/Classes/CustomFunction.php24
-rw-r--r--src/NXP/Classes/Operator.php33
-rw-r--r--src/NXP/Classes/Token.php41
-rw-r--r--src/NXP/Classes/Tokenizer.php252
-rw-r--r--src/NXP/MathExecutor.php846
6 files changed, 628 insertions, 610 deletions
diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php
index a8f16af..2bb5b4d 100644
--- a/src/NXP/Classes/Calculator.php
+++ b/src/NXP/Classes/Calculator.php
@@ -20,21 +20,10 @@ use NXP\Exception\UnknownVariableException;
*/
class Calculator
{
- /**
- * @var CustomFunction[]
- */
- private $functions;
+ private array $functions = [];
- /**
- * @var Operator[]
- */
- private $operators;
+ private array $operators = [];
- /**
- * Calculator constructor.
- * @param CustomFunction[] $functions
- * @param Operator[] $operators
- */
public function __construct(array $functions, array $operators)
{
$this->functions = $functions;
@@ -45,46 +34,49 @@ class Calculator
* Calculate array of tokens in reverse polish notation
* @param Token[] $tokens
* @param array<string, float|string> $variables
- * @return mixed
* @throws IncorrectExpressionException
* @throws UnknownVariableException
*/
- public function calculate(array $tokens, array $variables, callable $onVarNotFound = null)
+ public function calculate(array $tokens, array $variables, ?callable $onVarNotFound = null)
{
/** @var Token[] $stack */
$stack = [];
+
foreach ($tokens as $token) {
- if ($token->type === Token::Literal || $token->type === Token::String) {
+ if (Token::Literal === $token->type || Token::String === $token->type) {
$stack[] = $token;
- } elseif ($token->type === Token::Variable) {
+ } elseif (Token::Variable === $token->type) {
$variable = $token->value;
$value = null;
- if (array_key_exists($variable, $variables)) {
+
+ if (\array_key_exists($variable, $variables)) {
$value = $variables[$variable];
} elseif ($onVarNotFound) {
- $value = call_user_func($onVarNotFound, $variable);
+ $value = \call_user_func($onVarNotFound, $variable);
} else {
throw new UnknownVariableException($variable);
}
$stack[] = new Token(Token::Literal, $value, $variable);
- } elseif ($token->type === Token::Function) {
- if (!array_key_exists($token->value, $this->functions)) {
+ } elseif (Token::Function === $token->type) {
+ if (! \array_key_exists($token->value, $this->functions)) {
throw new UnknownFunctionException($token->value);
}
$stack[] = $this->functions[$token->value]->execute($stack);
- } elseif ($token->type === Token::Operator) {
- if (!array_key_exists($token->value, $this->operators)) {
+ } elseif (Token::Operator === $token->type) {
+ if (! \array_key_exists($token->value, $this->operators)) {
throw new UnknownOperatorException($token->value);
}
$stack[] = $this->operators[$token->value]->execute($stack);
}
}
- $result = array_pop($stack);
- if ($result === null || !empty($stack)) {
+ $result = \array_pop($stack);
+
+ if (null === $result || ! empty($stack)) {
throw new IncorrectExpressionException('Stack must be empty');
}
+
return $result->value;
}
}
diff --git a/src/NXP/Classes/CustomFunction.php b/src/NXP/Classes/CustomFunction.php
index 225495f..bc0f4e7 100644
--- a/src/NXP/Classes/CustomFunction.php
+++ b/src/NXP/Classes/CustomFunction.php
@@ -8,25 +8,17 @@ use ReflectionFunction;
class CustomFunction
{
- /**
- * @var string
- */
- public $name;
+ public string $name = '';
/**
* @var callable $function
*/
public $function;
- /**
- * @var int
- */
- public $places;
+ public int $places = 0;
/**
* CustomFunction constructor.
- * @param string $name
- * @param callable $function
* @param int $places
* @throws ReflectionException
* @throws IncorrectNumberOfFunctionParametersException
@@ -35,7 +27,8 @@ class CustomFunction
{
$this->name = $name;
$this->function = $function;
- if ($places === null) {
+
+ if (null === $places) {
$reflection = new ReflectionFunction($function);
$this->places = $reflection->getNumberOfParameters();
} else {
@@ -48,17 +41,18 @@ class CustomFunction
*
* @throws IncorrectNumberOfFunctionParametersException
*/
- public function execute(array &$stack): Token
+ public function execute(array &$stack) : Token
{
- if (count($stack) < $this->places) {
+ if (\count($stack) < $this->places) {
throw new IncorrectNumberOfFunctionParametersException($this->name);
}
$args = [];
+
for ($i = 0; $i < $this->places; $i++) {
- array_unshift($args, array_pop($stack)->value);
+ \array_unshift($args, \array_pop($stack)->value);
}
- $result = call_user_func_array($this->function, $args);
+ $result = \call_user_func_array($this->function, $args);
return new Token(Token::Literal, $result);
}
diff --git a/src/NXP/Classes/Operator.php b/src/NXP/Classes/Operator.php
index 1b1acc4..7dee06d 100644
--- a/src/NXP/Classes/Operator.php
+++ b/src/NXP/Classes/Operator.php
@@ -7,37 +7,21 @@ use ReflectionFunction;
class Operator
{
- /**
- * @var string
- */
- public $operator;
+ public string $operator = '';
- /**
- * @var bool
- */
- public $isRightAssoc;
+ public bool $isRightAssoc = false;
- /**
- * @var int
- */
- public $priority;
+ public int $priority = 0;
/**
* @var callable(\SplStack)
*/
public $function;
- /**
- * @var int
- */
- public $places;
+ public int $places = 0;
/**
* Operator constructor.
- * @param string $operator
- * @param bool $isRightAssoc
- * @param int $priority
- * @param callable $function
*/
public function __construct(string $operator, bool $isRightAssoc, int $priority, callable $function)
{
@@ -54,17 +38,18 @@ class Operator
*
* @throws IncorrectExpressionException
*/
- public function execute(array &$stack): Token
+ public function execute(array &$stack) : Token
{
- if (count($stack) < $this->places) {
+ if (\count($stack) < $this->places) {
throw new IncorrectExpressionException();
}
$args = [];
+
for ($i = 0; $i < $this->places; $i++) {
- array_unshift($args, array_pop($stack)->value);
+ \array_unshift($args, \array_pop($stack)->value);
}
- $result = call_user_func_array($this->function, $args);
+ $result = \call_user_func_array($this->function, $args);
return new Token(Token::Literal, $result);
}
diff --git a/src/NXP/Classes/Token.php b/src/NXP/Classes/Token.php
index 924771e..b2a0a64 100644
--- a/src/NXP/Classes/Token.php
+++ b/src/NXP/Classes/Token.php
@@ -4,31 +4,34 @@ namespace NXP\Classes;
class Token
{
- public const Literal = "literal";
- public const Variable = "variable";
- public const Operator = "operator";
- public const LeftParenthesis = "LP";
- public const RightParenthesis = "RP";
- public const Function = "function";
- public const ParamSeparator = "separator";
- public const String = "string";
- public const Space = "space";
-
- /** @var self::* */
- public $type = self::Literal;
-
- /** @var float|string */
+ public const Literal = 'literal';
+
+ public const Variable = 'variable';
+
+ public const Operator = 'operator';
+
+ public const LeftParenthesis = 'LP';
+
+ public const RightParenthesis = 'RP';
+
+ public const Function = 'function';
+
+ public const ParamSeparator = 'separator';
+
+ public const String = 'string';
+
+ public const Space = 'space';
+
+ public string $type = self::Literal;
+
public $value;
- /** @var string */
- public $name;
+ public ?string $name;
/**
* Token constructor.
- * @param self::* $type
- * @param float|string $value
*/
- public function __construct(string $type, $value, string $name = null)
+ public function __construct(string $type, $value, ?string $name = null)
{
$this->type = $type;
$this->value = $value;
diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php
index 087a78d..1bd6d6d 100644
--- a/src/NXP/Classes/Tokenizer.php
+++ b/src/NXP/Classes/Tokenizer.php
@@ -20,44 +20,24 @@ use SplStack;
*/
class Tokenizer
{
- /**
- * @var Token[]
- */
- public $tokens = [];
- /**
- * @var string
- */
- private $input = '';
- /**
- * @var string
- */
- private $numberBuffer = '';
- /**
- * @var string
- */
- private $stringBuffer = '';
- /**
- * @var bool
- */
- private $allowNegative = true;
- /**
- * @var Operator[]
- */
- private $operators = [];
+ public array $tokens = [];
- /**
- * @var bool
- */
- private $inSingleQuotedString = false;
+ private string $input = '';
- /**
- * @var bool
- */
- private $inDoubleQuotedString = false;
+ private string $numberBuffer = '';
+
+ private string $stringBuffer = '';
+
+ private bool $allowNegative = true;
+
+ private array $operators = [];
+
+ private bool $inSingleQuotedString = false;
+
+ private bool $inDoubleQuotedString = false;
/**
* Tokenizer constructor.
- * @param string $input
* @param Operator[] $operators
*/
public function __construct(string $input, array $operators)
@@ -66,109 +46,137 @@ class Tokenizer
$this->operators = $operators;
}
- public function tokenize(): self
+ public function tokenize() : self
{
- foreach (str_split($this->input, 1) as $ch) {
+ foreach (\str_split($this->input, 1) as $ch) {
switch (true) {
case $this->inSingleQuotedString:
- if ($ch === "'") {
+ if ("'" === $ch) {
$this->tokens[] = new Token(Token::String, $this->stringBuffer);
$this->inSingleQuotedString = false;
$this->stringBuffer = '';
+
continue 2;
}
$this->stringBuffer .= $ch;
+
continue 2;
+
case $this->inDoubleQuotedString:
- if ($ch === '"') {
+ if ('"' === $ch) {
$this->tokens[] = new Token(Token::String, $this->stringBuffer);
$this->inDoubleQuotedString = false;
$this->stringBuffer = '';
+
continue 2;
}
$this->stringBuffer .= $ch;
+
continue 2;
- case $ch == ' ' || $ch == "\n" || $ch == "\r" || $ch == "\t":
+
+ case ' ' == $ch || "\n" == $ch || "\r" == $ch || "\t" == $ch:
$this->tokens[] = new Token(Token::Space, '');
+
continue 2;
+
case $this->isNumber($ch):
- if ($this->stringBuffer != '') {
+ if ('' != $this->stringBuffer) {
$this->stringBuffer .= $ch;
+
continue 2;
}
$this->numberBuffer .= $ch;
$this->allowNegative = false;
+
break;
/** @noinspection PhpMissingBreakStatementInspection */
- case strtolower($ch) === 'e':
- if (strlen($this->numberBuffer) && strpos($this->numberBuffer, '.') !== false) {
+ case 'e' === \strtolower($ch):
+ if (\strlen($this->numberBuffer) && false !== \strpos($this->numberBuffer, '.')) {
$this->numberBuffer .= 'e';
$this->allowNegative = false;
+
break;
}
// no break
+ // Intentionally fall through
case $this->isAlpha($ch):
- if (strlen($this->numberBuffer)) {
+ if (\strlen($this->numberBuffer)) {
$this->emptyNumberBufferAsLiteral();
$this->tokens[] = new Token(Token::Operator, '*');
}
$this->allowNegative = false;
$this->stringBuffer .= $ch;
+
break;
- case $ch == '"':
+
+ case '"' == $ch:
$this->inDoubleQuotedString = true;
+
continue 2;
- case $ch == "'":
+
+ case "'" == $ch:
$this->inSingleQuotedString = true;
+
continue 2;
case $this->isDot($ch):
$this->numberBuffer .= $ch;
$this->allowNegative = false;
+
break;
+
case $this->isLP($ch):
- if ($this->stringBuffer != '') {
+ if ('' != $this->stringBuffer) {
$this->tokens[] = new Token(Token::Function, $this->stringBuffer);
$this->stringBuffer = '';
- } elseif (strlen($this->numberBuffer)) {
+ } elseif (\strlen($this->numberBuffer)) {
$this->emptyNumberBufferAsLiteral();
$this->tokens[] = new Token(Token::Operator, '*');
}
$this->allowNegative = true;
$this->tokens[] = new Token(Token::LeftParenthesis, '');
+
break;
+
case $this->isRP($ch):
$this->emptyNumberBufferAsLiteral();
$this->emptyStrBufferAsVariable();
$this->allowNegative = false;
$this->tokens[] = new Token(Token::RightParenthesis, '');
+
break;
+
case $this->isComma($ch):
$this->emptyNumberBufferAsLiteral();
$this->emptyStrBufferAsVariable();
$this->allowNegative = true;
$this->tokens[] = new Token(Token::ParamSeparator, '');
+
break;
+
default:
// special case for unary operations
- if ($ch == '-' || $ch == '+') {
+ if ('-' == $ch || '+' == $ch) {
if ($this->allowNegative) {
$this->allowNegative = false;
- $this->tokens[] = new Token(Token::Operator, $ch == '-' ? 'uNeg' : 'uPos');
+ $this->tokens[] = new Token(Token::Operator, '-' == $ch ? 'uNeg' : 'uPos');
+
continue 2;
}
// could be in exponent, in which case negative should be added to the numberBuffer
- if ($this->numberBuffer && $this->numberBuffer[strlen($this->numberBuffer) - 1] == 'e') {
+ if ($this->numberBuffer && 'e' == $this->numberBuffer[\strlen($this->numberBuffer) - 1]) {
$this->numberBuffer .= $ch;
+
continue 2;
}
}
$this->emptyNumberBufferAsLiteral();
$this->emptyStrBufferAsVariable();
- if ($ch != '$') {
- if (count($this->tokens) > 0) {
- if ($this->tokens[count($this->tokens) - 1]->type === Token::Operator) {
- $this->tokens[count($this->tokens) - 1]->value .= $ch;
+
+ if ('$' != $ch) {
+ if (\count($this->tokens) > 0) {
+ if (Token::Operator === $this->tokens[\count($this->tokens) - 1]->type) {
+ $this->tokens[\count($this->tokens) - 1]->value .= $ch;
} else {
$this->tokens[] = new Token(Token::Operator, $ch);
}
@@ -181,107 +189,76 @@ class Tokenizer
}
$this->emptyNumberBufferAsLiteral();
$this->emptyStrBufferAsVariable();
- return $this;
- }
-
- private function isNumber(string $ch): bool
- {
- return $ch >= '0' && $ch <= '9';
- }
-
- private function isAlpha(string $ch): bool
- {
- return $ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch == '_';
- }
-
- private function emptyNumberBufferAsLiteral(): void
- {
- if (strlen($this->numberBuffer)) {
- $this->tokens[] = new Token(Token::Literal, $this->numberBuffer);
- $this->numberBuffer = '';
- }
- }
-
- private function isDot(string $ch): bool
- {
- return $ch == '.';
- }
-
- private function isLP(string $ch): bool
- {
- return $ch == '(';
- }
-
- private function isRP(string $ch): bool
- {
- return $ch == ')';
- }
- private function emptyStrBufferAsVariable(): void
- {
- if ($this->stringBuffer != '') {
- $this->tokens[] = new Token(Token::Variable, $this->stringBuffer);
- $this->stringBuffer = '';
- }
- }
-
- private function isComma(string $ch): bool
- {
- return $ch == ',';
+ return $this;
}
/**
- * @return Token[] Array of tokens in revers polish notation
* @throws IncorrectBracketsException
* @throws UnknownOperatorException
+ * @return Token[] Array of tokens in revers polish notation
*/
- public function buildReversePolishNotation(): array
+ public function buildReversePolishNotation() : array
{
$tokens = [];
/** @var SplStack<Token> $stack */
$stack = new SplStack();
+
foreach ($this->tokens as $token) {
switch ($token->type) {
case Token::Literal:
case Token::Variable:
case Token::String:
$tokens[] = $token;
+
break;
+
case Token::Function:
case Token::LeftParenthesis:
$stack->push($token);
+
break;
+
case Token::ParamSeparator:
- while ($stack->top()->type !== Token::LeftParenthesis) {
- if ($stack->count() === 0) {
+ while (Token::LeftParenthesis !== $stack->top()->type) {
+ if (0 === $stack->count()) {
throw new IncorrectBracketsException();
}
$tokens[] = $stack->pop();
}
+
break;
+
case Token::Operator:
- if (!array_key_exists($token->value, $this->operators)) {
+ if (! \array_key_exists($token->value, $this->operators)) {
throw new UnknownOperatorException($token->value);
}
$op1 = $this->operators[$token->value];
- while ($stack->count() > 0 && $stack->top()->type === Token::Operator) {
- if (!array_key_exists($stack->top()->value, $this->operators)) {
+
+ while ($stack->count() > 0 && Token::Operator === $stack->top()->type) {
+ if (! \array_key_exists($stack->top()->value, $this->operators)) {
throw new UnknownOperatorException($stack->top()->value);
}
$op2 = $this->operators[$stack->top()->value];
+
if ($op2->priority >= $op1->priority) {
$tokens[] = $stack->pop();
+
continue;
}
+
break;
}
$stack->push($token);
+
break;
+
case Token::RightParenthesis:
while (true) {
try {
$ctoken = $stack->pop();
- if ($ctoken->type === Token::LeftParenthesis) {
+
+ if (Token::LeftParenthesis === $ctoken->type) {
break;
}
$tokens[] = $ctoken;
@@ -289,24 +266,77 @@ class Tokenizer
throw new IncorrectBracketsException();
}
}
- if ($stack->count() > 0 && $stack->top()->type == Token::Function) {
+
+ if ($stack->count() > 0 && Token::Function == $stack->top()->type) {
$tokens[] = $stack->pop();
}
+
break;
+
case Token::Space:
//do nothing
}
}
- while ($stack->count() !== 0) {
- if ($stack->top()->type === Token::LeftParenthesis || $stack->top()->type === Token::RightParenthesis) {
+
+ while (0 !== $stack->count()) {
+ if (Token::LeftParenthesis === $stack->top()->type || Token::RightParenthesis === $stack->top()->type) {
throw new IncorrectBracketsException();
}
- if ($stack->top()->type === Token::Space) {
+
+ if (Token::Space === $stack->top()->type) {
$stack->pop();
+
continue;
}
$tokens[] = $stack->pop();
}
+
return $tokens;
}
+
+ private function isNumber(string $ch) : bool
+ {
+ return $ch >= '0' && $ch <= '9';
+ }
+
+ private function isAlpha(string $ch) : bool
+ {
+ return $ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || '_' == $ch;
+ }
+
+ private function emptyNumberBufferAsLiteral() : void
+ {
+ if (\strlen($this->numberBuffer)) {
+ $this->tokens[] = new Token(Token::Literal, $this->numberBuffer);
+ $this->numberBuffer = '';
+ }
+ }
+
+ private function isDot(string $ch) : bool
+ {
+ return '.' == $ch;
+ }
+
+ private function isLP(string $ch) : bool
+ {
+ return '(' == $ch;
+ }
+
+ private function isRP(string $ch) : bool
+ {
+ return ')' == $ch;
+ }
+
+ private function emptyStrBufferAsVariable() : void
+ {
+ if ('' != $this->stringBuffer) {
+ $this->tokens[] = new Token(Token::Variable, $this->stringBuffer);
+ $this->stringBuffer = '';
+ }
+ }
+
+ private function isComma(string $ch) : bool
+ {
+ return ',' == $ch;
+ }
}
diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php
index ab550fa..f980270 100644
--- a/src/NXP/MathExecutor.php
+++ b/src/NXP/MathExecutor.php
@@ -31,7 +31,7 @@ class MathExecutor
*
* @var array<string, float|string>
*/
- protected $variables = [];
+ protected array $variables = [];
/**
* @var callable|null
@@ -46,17 +46,17 @@ class MathExecutor
/**
* @var Operator[]
*/
- protected $operators = [];
+ protected array $operators = [];
/**
* @var array<string, CustomFunction>
*/
- protected $functions = [];
+ protected array $functions = [];
/**
* @var array<string, Token[]>
*/
- protected $cache = [];
+ protected array $cache = [];
/**
* Base math operators
@@ -66,386 +66,39 @@ class MathExecutor
$this->addDefaults();
}
- /**
- * Set default operands and functions
- * @throws ReflectionException
- */
- protected function addDefaults(): void
- {
- foreach ($this->defaultOperators() as $name => $operator) {
- [$callable, $priority, $isRightAssoc] = $operator;
- $this->addOperator(new Operator($name, $isRightAssoc, $priority, $callable));
- }
- foreach ($this->defaultFunctions() as $name => $callable) {
- $this->addFunction($name, $callable);
- }
-
- $this->onVarValidation = [$this, 'defaultVarValidation'];
- $this->variables = $this->defaultVars();
- }
-
- /**
- * Get the default operators
- *
- * @return array<string, array{callable, int, bool}>
- */
- protected function defaultOperators(): array
+ public function __clone()
{
- return [
- '+' => [
- function ($a, $b) {
- return $a + $b;
- },
- 170,
- false
- ],
- '-' => [
- function ($a, $b) {
- return $a - $b;
- },
- 170,
- false
- ],
- 'uPos' => [ // unary positive token
- function ($a) {
- return $a;
- },
- 200,
- false
- ],
- 'uNeg' => [ // unary minus token
- function ($a) {
- return 0 - $a;
- },
- 200,
- false
- ],
- '*' => [
- function ($a, $b) {
- return $a * $b;
- },
- 180,
- false
- ],
- '/' => [
- function ($a, $b) {
- if ($b == 0) {
- throw new DivisionByZeroException();
- }
- return $a / $b;
- },
- 180,
- false
- ],
- '^' => [
- function ($a, $b) {
- return pow($a, $b);
- },
- 220,
- true
- ],
- '&&' => [
- function ($a, $b) {
- return $a && $b;
- },
- 100,
- false
- ],
- '||' => [
- function ($a, $b) {
- return $a || $b;
- },
- 90,
- false
- ],
- '==' => [
- function ($a, $b) {
- if (is_string($a) || is_string($b)) {
- return strcmp($a, $b) == 0;
- } else {
- return $a == $b;
- }
- },
- 140,
- false
- ],
- '!=' => [
- function ($a, $b) {
- if (is_string($a) || is_string($b)) {
- return strcmp($a, $b) != 0;
- } else {
- return $a != $b;
- }
- },
- 140,
- false
- ],
- '>=' => [
- function ($a, $b) {
- return $a >= $b;
- },
- 150,
- false
- ],
- '>' => [
- function ($a, $b) {
- return $a > $b;
- },
- 150,
- false
- ],
- '<=' => [
- function ($a, $b) {
- return $a <= $b;
- },
- 150,
- false
- ],
- '<' => [
- function ($a, $b) {
- return $a < $b;
- },
- 150,
- false
- ],
- ];
+ $this->addDefaults();
}
/**
* Add operator to executor
*
- * @param Operator $operator
* @return MathExecutor
*/
- public function addOperator(Operator $operator): self
+ public function addOperator(Operator $operator) : self
{
$this->operators[$operator->operator] = $operator;
- return $this;
- }
- /**
- * Gets the default functions as an array. Key is function name
- * and value is the function as a closure.
- *
- * @return array<callable>
- */
- protected function defaultFunctions(): array
- {
- return [
- 'abs' => function ($arg) {
- return abs($arg);
- },
- 'acos' => function ($arg) {
- return acos($arg);
- },
- 'acosh' => function ($arg) {
- return acosh($arg);
- },
- 'arcsin' => function ($arg) {
- return asin($arg);
- },
- 'arcctg' => function ($arg) {
- return M_PI/2 - atan($arg);
- },
- 'arccot' => function ($arg) {
- return M_PI/2 - atan($arg);
- },
- 'arccotan' => function ($arg) {
- return M_PI/2 - atan($arg);
- },
- 'arcsec' => function ($arg) {
- return acos(1/$arg);
- },
- 'arccosec' => function ($arg) {
- return asin(1/$arg);
- },
- 'arccsc' => function ($arg) {
- return asin(1/$arg);
- },
- 'arccos' => function ($arg) {
- return acos($arg);
- },
- 'arctan' => function ($arg) {
- return atan($arg);
- },
- 'arctg' => function ($arg) {
- return atan($arg);
- },
- 'asin' => function ($arg) {
- return asin($arg);
- },
- 'atan' => function ($arg) {
- return atan($arg);
- },
- 'atan2' => function ($arg1, $arg2) {
- return atan2($arg1, $arg2);
- },
- 'atanh' => function ($arg) {
- return atanh($arg);
- },
- 'atn' => function ($arg) {
- return atan($arg);
- },
- 'avg' => function ($arg1, $arg2) {
- return ($arg1 + $arg2) / 2;
- },
- 'bindec' => function ($arg) {
- return bindec($arg);
- },
- 'ceil' => function ($arg) {
- return ceil($arg);
- },
- 'cos' => function ($arg) {
- return cos($arg);
- },
- 'cosec' => function ($arg) {
- return 1 / sin($arg);
- },
- 'csc' => function ($arg) {
- return 1 / sin($arg);
- },
- 'cosh' => function ($arg) {
- return cosh($arg);
- },
- 'ctg' => function ($arg) {
- return cos($arg) / sin($arg);
- },
- 'cot' => function ($arg) {
- return cos($arg) / sin($arg);
- },
- 'cotan' => function ($arg) {
- return cos($arg) / sin($arg);
- },
- 'cotg' => function ($arg) {
- return cos($arg) / sin($arg);
- },
- 'ctn' => function ($arg) {
- return cos($arg) / sin($arg);
- },
- 'decbin' => function ($arg) {
- return decbin($arg);
- },
- 'dechex' => function ($arg) {
- return dechex($arg);
- },
- 'decoct' => function ($arg) {
- return decoct($arg);
- },
- 'deg2rad' => function ($arg) {
- return deg2rad($arg);
- },
- 'exp' => function ($arg) {
- return exp($arg);
- },
- 'expm1' => function ($arg) {
- return expm1($arg);
- },
- 'floor' => function ($arg) {
- return floor($arg);
- },
- 'fmod' => function ($arg1, $arg2) {
- return fmod($arg1, $arg2);
- },
- 'hexdec' => function ($arg) {
- return hexdec($arg);
- },
- 'hypot' => function ($arg1, $arg2) {
- return hypot($arg1, $arg2);
- },
- 'if' => function ($expr, $trueval, $falseval) {
- if ($expr === true || $expr === false) {
- $exres = $expr;
- } else {
- $exres = $this->execute($expr);
- }
- if ($exres) {
- return $this->execute($trueval);
- } else {
- return $this->execute($falseval);
- }
- },
- 'intdiv' => function ($arg1, $arg2) {
- return intdiv($arg1, $arg2);
- },
- 'ln' => function ($arg) {
- return log($arg);
- },
- 'lg' => function ($arg) {
- return log10($arg);
- },
- 'log' => function ($arg) {
- return log($arg);
- },
- 'log10' => function ($arg) {
- return log10($arg);
- },
- 'log1p' => function ($arg) {
- return log1p($arg);
- },
- 'max' => function ($arg1, $arg2) {
- return max($arg1, $arg2);
- },
- 'min' => function ($arg1, $arg2) {
- return min($arg1, $arg2);
- },
- 'octdec' => function ($arg) {
- return octdec($arg);
- },
- 'pi' => function () {
- return pi();
- },
- 'pow' => function ($arg1, $arg2) {
- return $arg1 ** $arg2;
- },
- 'rad2deg' => function ($arg) {
- return rad2deg($arg);
- },
- 'round' => function ($arg) {
- return round($arg);
- },
- 'sin' => function ($arg) {
- return sin($arg);
- },
- 'sinh' => function ($arg) {
- return sinh($arg);
- },
- 'sec' => function ($arg) {
- return 1 / cos($arg);
- },
- 'sqrt' => function ($arg) {
- return sqrt($arg);
- },
- 'tan' => function ($arg) {
- return tan($arg);
- },
- 'tanh' => function ($arg) {
- return tanh($arg);
- },
- 'tn' => function ($arg) {
- return tan($arg);
- },
- 'tg' => function ($arg) {
- return tan($arg);
- }
- ];
+ return $this;
}
/**
* Execute expression
*
- * @param string $expression
- * @param bool $cache
- * @return number
* @throws Exception\IncorrectBracketsException
* @throws Exception\IncorrectExpressionException
* @throws Exception\UnknownOperatorException
* @throws UnknownVariableException
+ * @return number
*/
public function execute(string $expression, bool $cache = true)
{
$cacheKey = $expression;
- if (!array_key_exists($cacheKey, $this->cache)) {
+
+ if (! \array_key_exists($cacheKey, $this->cache)) {
$tokens = (new Tokenizer($expression, $this->operators))->tokenize()->buildReversePolishNotation();
+
if ($cache) {
$this->cache[$cacheKey] = $tokens;
}
@@ -454,6 +107,7 @@ class MathExecutor
}
$calculator = new Calculator($this->functions, $this->operators);
+
return $calculator->calculate($tokens, $this->variables, $this->onVarNotFound);
}
@@ -463,26 +117,14 @@ class MathExecutor
* @param string $name Name of function
* @param callable $function Function
* @param int $places Count of arguments
- * @return MathExecutor
* @throws ReflectionException
+ * @return MathExecutor
*/
- public function addFunction(string $name, ?callable $function = null, ?int $places = null): self
+ public function addFunction(string $name, ?callable $function = null, ?int $places = null) : self
{
$this->functions[$name] = new CustomFunction($name, $function, $places);
- return $this;
- }
- /**
- * Returns the default variables names as key/value pairs
- *
- * @return array<string, float>
- */
- protected function defaultVars(): array
- {
- return [
- 'pi' => 3.14159265359,
- 'e' => 2.71828182846
- ];
+ return $this;
}
/**
@@ -490,7 +132,7 @@ class MathExecutor
*
* @return array<string, float|string>
*/
- public function getVars(): array
+ public function getVars() : array
{
return $this->variables;
}
@@ -498,61 +140,47 @@ class MathExecutor
/**
* Get a specific var
*
- * @param string $variable
- * @return integer|float
* @throws UnknownVariableException if VarNotFoundHandler is not set
+ * @return int|float
*/
public function getVar(string $variable)
{
- if (!array_key_exists($variable, $this->variables)) {
+ if (! \array_key_exists($variable, $this->variables)) {
if ($this->onVarNotFound) {
- return call_user_func($this->onVarNotFound, $variable);
+ return \call_user_func($this->onVarNotFound, $variable);
}
+
throw new UnknownVariableException("Variable ({$variable}) not set");
}
+
return $this->variables[$variable];
}
/**
* Add variable to executor. To set a custom validator use setVarValidationHandler.
*
- * @param string $variable
* @param $value
- * @return MathExecutor
* @throws MathExecutorException if the value is invalid based on the default or custom validator
+ * @return MathExecutor
*/
- public function setVar(string $variable, $value): self
+ public function setVar(string $variable, $value) : self
{
if ($this->onVarValidation) {
- call_user_func($this->onVarValidation, $variable, $value);
+ \call_user_func($this->onVarValidation, $variable, $value);
}
$this->variables[$variable] = $value;
- return $this;
- }
- /**
- * Default variable validation, ensures that the value is a scalar.
- * @param string $variable
- * @param $value
- * @throws MathExecutorException if the value is not a scalar
- */
- protected function defaultVarValidation(string $variable, $value): void
- {
- if (!is_scalar($value) && $value !== null) {
- $type = gettype($value);
- throw new MathExecutorException("Variable ({$variable}) type ({$type}) is not scalar");
- }
+ return $this;
}
/**
* Test to see if a variable exists
*
- * @param string $variable
*/
- public function varExists(string $variable): bool
+ public function varExists(string $variable) : bool
{
- return array_key_exists($variable, $this->variables);
+ return \array_key_exists($variable, $this->variables);
}
/**
@@ -560,17 +188,19 @@ class MathExecutor
*
* @param array<string, float|int|string> $variables
* @param bool $clear Clear previous variables
- * @return MathExecutor
* @throws \Exception
+ * @return MathExecutor
*/
- public function setVars(array $variables, bool $clear = true): self
+ public function setVars(array $variables, bool $clear = true) : self
{
if ($clear) {
$this->removeVars();
}
+
foreach ($variables as $name => $value) {
$this->setVar($name, $value);
}
+
return $this;
}
@@ -578,13 +208,13 @@ class MathExecutor
* Define a method that will be invoked when a variable is not found.
* The first parameter will be the variable name, and the returned value will be used as the variable value.
*
- * @param callable $handler
*
* @return MathExecutor
*/
- public function setVarNotFoundHandler(callable $handler): self
+ public function setVarNotFoundHandler(callable $handler) : self
{
$this->onVarNotFound = $handler;
+
return $this;
}
@@ -597,21 +227,22 @@ class MathExecutor
*
* @return MathExecutor
*/
- public function setVarValidationHandler(?callable $handler): self
+ public function setVarValidationHandler(?callable $handler) : self
{
$this->onVarValidation = $handler;
+
return $this;
}
/**
* Remove variable from executor
*
- * @param string $variable
* @return MathExecutor
*/
- public function removeVar(string $variable): self
+ public function removeVar(string $variable) : self
{
unset($this->variables[$variable]);
+
return $this;
}
@@ -619,10 +250,11 @@ class MathExecutor
* Remove all variables and the variable not found handler
* @return MathExecutor
*/
- public function removeVars(): self
+ public function removeVars() : self
{
$this->variables = [];
$this->onVarNotFound = null;
+
return $this;
}
@@ -642,7 +274,7 @@ class MathExecutor
* @return array<string, CustomFunction> containing callback and places indexed by
* function name
*/
- public function getFunctions(): array
+ public function getFunctions() : array
{
return $this->functions;
}
@@ -652,14 +284,16 @@ class MathExecutor
*
* @return MathExecutor
*/
- public function setDivisionByZeroIsZero(): self
+ public function setDivisionByZeroIsZero() : self
{
- $this->addOperator(new Operator("/", false, 180, function ($a, $b) {
- if ($b == 0) {
+ $this->addOperator(new Operator('/', false, 180, static function($a, $b) {
+ if (0 == $b) {
return 0;
}
+
return $a / $b;
}));
+
return $this;
}
@@ -667,7 +301,7 @@ class MathExecutor
* Get cache array with tokens
* @return array<string, Token[]>
*/
- public function getCache(): array
+ public function getCache() : array
{
return $this->cache;
}
@@ -675,13 +309,393 @@ class MathExecutor
/**
* Clear token's cache
*/
- public function clearCache(): void
+ public function clearCache() : void
{
$this->cache = [];
}
- public function __clone()
+ /**
+ * Set default operands and functions
+ * @throws ReflectionException
+ */
+ protected function addDefaults() : void
{
- $this->addDefaults();
+ foreach ($this->defaultOperators() as $name => $operator) {
+ [$callable, $priority, $isRightAssoc] = $operator;
+ $this->addOperator(new Operator($name, $isRightAssoc, $priority, $callable));
+ }
+
+ foreach ($this->defaultFunctions() as $name => $callable) {
+ $this->addFunction($name, $callable);
+ }
+
+ $this->onVarValidation = [$this, 'defaultVarValidation'];
+ $this->variables = $this->defaultVars();
+ }
+
+ /**
+ * Get the default operators
+ *
+ * @return array<string, array{callable, int, bool}>
+ */
+ protected function defaultOperators() : array
+ {
+ return [
+ '+' => [
+ static function($a, $b) {
+ return $a + $b;
+ },
+ 170,
+ false
+ ],
+ '-' => [
+ static function($a, $b) {
+ return $a - $b;
+ },
+ 170,
+ false
+ ],
+ 'uPos' => [ // unary positive token
+ static function($a) {
+ return $a;
+ },
+ 200,
+ false
+ ],
+ 'uNeg' => [ // unary minus token
+ static function($a) {
+ return 0 - $a;
+ },
+ 200,
+ false
+ ],
+ '*' => [
+ static function($a, $b) {
+ return $a * $b;
+ },
+ 180,
+ false
+ ],
+ '/' => [
+ static function($a, $b) {
+ if (0 == $b) {
+ throw new DivisionByZeroException();
+ }
+
+ return $a / $b;
+ },
+ 180,
+ false
+ ],
+ '^' => [
+ static function($a, $b) {
+ return \pow($a, $b);
+ },
+ 220,
+ true
+ ],
+ '&&' => [
+ static function($a, $b) {
+ return $a && $b;
+ },
+ 100,
+ false
+ ],
+ '||' => [
+ static function($a, $b) {
+ return $a || $b;
+ },
+ 90,
+ false
+ ],
+ '==' => [
+ static function($a, $b) {
+ if (\is_string($a) || \is_string($b)) {
+ return 0 == \strcmp($a, $b);
+ }
+
+ return $a == $b;
+
+ },
+ 140,
+ false
+ ],
+ '!=' => [
+ static function($a, $b) {
+ if (\is_string($a) || \is_string($b)) {
+ return 0 != \strcmp($a, $b);
+ }
+
+ return $a != $b;
+
+ },
+ 140,
+ false
+ ],
+ '>=' => [
+ static function($a, $b) {
+ return $a >= $b;
+ },
+ 150,
+ false
+ ],
+ '>' => [
+ static function($a, $b) {
+ return $a > $b;
+ },
+ 150,
+ false
+ ],
+ '<=' => [
+ static function($a, $b) {
+ return $a <= $b;
+ },
+ 150,
+ false
+ ],
+ '<' => [
+ static function($a, $b) {
+ return $a < $b;
+ },
+ 150,
+ false
+ ],
+ ];
+ }
+
+ /**
+ * Gets the default functions as an array. Key is function name
+ * and value is the function as a closure.
+ *
+ * @return array<callable>
+ */
+ protected function defaultFunctions() : array
+ {
+ return [
+ 'abs' => static function($arg) {
+ return \abs($arg);
+ },
+ 'acos' => static function($arg) {
+ return \acos($arg);
+ },
+ 'acosh' => static function($arg) {
+ return \acosh($arg);
+ },
+ 'arcsin' => static function($arg) {
+ return \asin($arg);
+ },
+ 'arcctg' => static function($arg) {
+ return M_PI / 2 - \atan($arg);
+ },
+ 'arccot' => static function($arg) {
+ return M_PI / 2 - \atan($arg);
+ },
+ 'arccotan' => static function($arg) {
+ return M_PI / 2 - \atan($arg);
+ },
+ 'arcsec' => static function($arg) {
+ return \acos(1 / $arg);
+ },
+ 'arccosec' => static function($arg) {
+ return \asin(1 / $arg);
+ },
+ 'arccsc' => static function($arg) {
+ return \asin(1 / $arg);
+ },
+ 'arccos' => static function($arg) {
+ return \acos($arg);
+ },
+ 'arctan' => static function($arg) {
+ return \atan($arg);
+ },
+ 'arctg' => static function($arg) {
+ return \atan($arg);
+ },
+ 'asin' => static function($arg) {
+ return \asin($arg);
+ },
+ 'atan' => static function($arg) {
+ return \atan($arg);
+ },
+ 'atan2' => static function($arg1, $arg2) {
+ return \atan2($arg1, $arg2);
+ },
+ 'atanh' => static function($arg) {
+ return \atanh($arg);
+ },
+ 'atn' => static function($arg) {
+ return \atan($arg);
+ },
+ 'avg' => static function($arg1, $arg2) {
+ return ($arg1 + $arg2) / 2;
+ },
+ 'bindec' => static function($arg) {
+ return \bindec($arg);
+ },
+ 'ceil' => static function($arg) {
+ return \ceil($arg);
+ },
+ 'cos' => static function($arg) {
+ return \cos($arg);
+ },
+ 'cosec' => static function($arg) {
+ return 1 / \sin($arg);
+ },
+ 'csc' => static function($arg) {
+ return 1 / \sin($arg);
+ },
+ 'cosh' => static function($arg) {
+ return \cosh($arg);
+ },
+ 'ctg' => static function($arg) {
+ return \cos($arg) / \sin($arg);
+ },
+ 'cot' => static function($arg) {
+ return \cos($arg) / \sin($arg);
+ },
+ 'cotan' => static function($arg) {
+ return \cos($arg) / \sin($arg);
+ },
+ 'cotg' => static function($arg) {
+ return \cos($arg) / \sin($arg);
+ },
+ 'ctn' => static function($arg) {
+ return \cos($arg) / \sin($arg);
+ },
+ 'decbin' => static function($arg) {
+ return \decbin($arg);
+ },
+ 'dechex' => static function($arg) {
+ return \dechex($arg);
+ },
+ 'decoct' => static function($arg) {
+ return \decoct($arg);
+ },
+ 'deg2rad' => static function($arg) {
+ return \deg2rad($arg);
+ },
+ 'exp' => static function($arg) {
+ return \exp($arg);
+ },
+ 'expm1' => static function($arg) {
+ return \expm1($arg);
+ },
+ 'floor' => static function($arg) {
+ return \floor($arg);
+ },
+ 'fmod' => static function($arg1, $arg2) {
+ return \fmod($arg1, $arg2);
+ },
+ 'hexdec' => static function($arg) {
+ return \hexdec($arg);
+ },
+ 'hypot' => static function($arg1, $arg2) {
+ return \hypot($arg1, $arg2);
+ },
+ 'if' => function($expr, $trueval, $falseval) {
+ if (true === $expr || false === $expr) {
+ $exres = $expr;
+ } else {
+ $exres = $this->execute($expr);
+ }
+
+ if ($exres) {
+ return $this->execute($trueval);
+ }
+
+ return $this->execute($falseval);
+
+ },
+ 'intdiv' => static function($arg1, $arg2) {
+ return \intdiv($arg1, $arg2);
+ },
+ 'ln' => static function($arg) {
+ return \log($arg);
+ },
+ 'lg' => static function($arg) {
+ return \log10($arg);
+ },
+ 'log' => static function($arg) {
+ return \log($arg);
+ },
+ 'log10' => static function($arg) {
+ return \log10($arg);
+ },
+ 'log1p' => static function($arg) {
+ return \log1p($arg);
+ },
+ 'max' => static function($arg1, $arg2) {
+ return \max($arg1, $arg2);
+ },
+ 'min' => static function($arg1, $arg2) {
+ return \min($arg1, $arg2);
+ },
+ 'octdec' => static function($arg) {
+ return \octdec($arg);
+ },
+ 'pi' => static function() {
+ return M_PI;
+ },
+ 'pow' => static function($arg1, $arg2) {
+ return $arg1 ** $arg2;
+ },
+ 'rad2deg' => static function($arg) {
+ return \rad2deg($arg);
+ },
+ 'round' => static function($arg) {
+ return \round($arg);
+ },
+ 'sin' => static function($arg) {
+ return \sin($arg);
+ },
+ 'sinh' => static function($arg) {
+ return \sinh($arg);
+ },
+ 'sec' => static function($arg) {
+ return 1 / \cos($arg);
+ },
+ 'sqrt' => static function($arg) {
+ return \sqrt($arg);
+ },
+ 'tan' => static function($arg) {
+ return \tan($arg);
+ },
+ 'tanh' => static function($arg) {
+ return \tanh($arg);
+ },
+ 'tn' => static function($arg) {
+ return \tan($arg);
+ },
+ 'tg' => static function($arg) {
+ return \tan($arg);
+ }
+ ];
+ }
+
+ /**
+ * Returns the default variables names as key/value pairs
+ *
+ * @return array<string, float>
+ */
+ protected function defaultVars() : array
+ {
+ return [
+ 'pi' => 3.14159265359,
+ 'e' => 2.71828182846
+ ];
+ }
+
+ /**
+ * Default variable validation, ensures that the value is a scalar.
+ * @param $value
+ * @throws MathExecutorException if the value is not a scalar
+ */
+ protected function defaultVarValidation(string $variable, $value) : void
+ {
+ if (! \is_scalar($value) && null !== $value) {
+ $type = \gettype($value);
+
+ throw new MathExecutorException("Variable ({$variable}) type ({$type}) is not scalar");
+ }
}
}