diff options
author | Bruce Wells <brucekwells@gmail.com> | 2022-04-27 00:31:50 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-27 00:31:50 +0300 |
commit | b7b46bfc476ea0d22e0e92144f68aa81d390fff0 (patch) | |
tree | b89329b67b35a90a59373acfd25f2774b82008a6 /src | |
parent | c396a882ffa5f7467947a6a19c2435a7b4cbad22 (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.php | 42 | ||||
-rw-r--r-- | src/NXP/Classes/CustomFunction.php | 24 | ||||
-rw-r--r-- | src/NXP/Classes/Operator.php | 33 | ||||
-rw-r--r-- | src/NXP/Classes/Token.php | 41 | ||||
-rw-r--r-- | src/NXP/Classes/Tokenizer.php | 252 | ||||
-rw-r--r-- | src/NXP/MathExecutor.php | 846 |
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"); + } } } |