diff options
Diffstat (limited to 'src/NXP/Classes')
-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 |
5 files changed, 198 insertions, 194 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; + } } |