From cab8e2d38ae1c8c7fb75022f7d9b0539a0a86d4e Mon Sep 17 00:00:00 2001 From: Alexander Kiryukhin Date: Fri, 15 May 2020 21:51:23 +0300 Subject: Massive refactoring More clean structure Parsing without regular expressions --- src/NXP/Classes/Tokenizer.php | 313 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 src/NXP/Classes/Tokenizer.php (limited to 'src/NXP/Classes/Tokenizer.php') diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php new file mode 100644 index 0000000..869c720 --- /dev/null +++ b/src/NXP/Classes/Tokenizer.php @@ -0,0 +1,313 @@ + + */ +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 = []; + + /** + * @var bool + */ + private $inSingleQuotedString = false; + + /** + * @var bool + */ + private $inDoubleQuotedString = false; + + /** + * Tokenizer constructor. + * @param string $input + * @param Operator[] $operators + */ + public function __construct(string $input, array $operators) + { + $this->input = $input; + $this->operators = $operators; + } + + public function tokenize() + { + foreach (mb_str_split($this->input, 1) as $ch) { + switch (true) { + case $this->inSingleQuotedString: + 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 === "\"") { + $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": + $this->tokens[] = new Token(Token::Space, ""); + continue 2; + case $this->isNumber($ch): + if ($this->stringBuffer != "") { + $this->stringBuffer .= $ch; + continue 2; + } + $this->numberBuffer .= $ch; + $this->allowNegative = false; + break; + case strtolower($ch) === "e": + if ($this->numberBuffer != "" && strpos($this->numberBuffer, ".") !== false) { + $this->numberBuffer .= "e"; + $this->allowNegative = true; + break; + } + case $this->isAlpha($ch): + if ($this->numberBuffer != "") { + $this->emptyNumberBufferAsLiteral(); + $this->tokens[] = new Token(Token::Operator, "*"); + } + $this->allowNegative = false; + $this->stringBuffer .= $ch; + break; + case $ch == "\"": + $this->inDoubleQuotedString = true; + continue 2; + 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 != "") { + $this->tokens[] = new Token(Token::Function, $this->stringBuffer); + $this->stringBuffer = ""; + } elseif ($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: + if ($this->allowNegative && $ch == "-") { + $this->allowNegative = false; + $this->numberBuffer .= "-"; + continue 2; + } + $this->emptyNumberBufferAsLiteral(); + $this->emptyStrBufferAsVariable(); + if (count($this->tokens) > 0) { + if ($this->tokens[count($this->tokens) - 1]->type === Token::Operator) { + $this->tokens[count($this->tokens) - 1]->value .= $ch; + } else { + $this->tokens[] = new Token(Token::Operator, $ch); + } + } else { + $this->tokens[] = new Token(Token::Operator, $ch); + } + $this->allowNegative = true; + } + } + $this->emptyNumberBufferAsLiteral(); + $this->emptyStrBufferAsVariable(); + return $this; + } + + private function isNumber($ch) + { + return $ch >= '0' && $ch <= '9'; + } + + private function isAlpha($ch) + { + return $ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch == '_'; + } + + private function emptyNumberBufferAsLiteral() + { + if ($this->numberBuffer != "") { + $this->tokens[] = new Token(Token::Literal, $this->numberBuffer); + $this->numberBuffer = ""; + } + } + + private function isDot($ch) + { + return $ch == '.'; + } + + private function isLP($ch) + { + return $ch == '('; + } + + private function isRP($ch) + { + return $ch == ')'; + } + + private function emptyStrBufferAsVariable() + { + if ($this->stringBuffer != "") { + $this->tokens[] = new Token(Token::Variable, $this->stringBuffer); + $this->stringBuffer = ""; + } + } + + private function isComma($ch) + { + return $ch == ','; + } + + /** + * @return Token[] Array of tokens in revers polish notation + * @throws IncorrectBracketsException + * @throws UnknownOperatorException + */ + public function buildReversePolishNotation() + { + $tokens = []; + /** @var SplStack $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) { + throw new IncorrectBracketsException(); + } + $tokens[] = $stack->pop(); + } + break; + case Token::Operator: + if (!array_key_exists($token->value, $this->operators)) { + throw new UnknownOperatorException(); + } + $op1 = $this->operators[$token->value]; + while ($stack->count() > 0 && $stack->top()->type === Token::Operator) { + if (!array_key_exists($stack->top()->value, $this->operators)) { + throw new UnknownOperatorException(); + } + $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) { + break; + } + $tokens[] = $ctoken; + } catch (RuntimeException $e) { + throw new IncorrectBracketsException(); + } + } + if ($stack->count() > 0 && $stack->top()->type == Token::Function) { + $tokens[] = $stack->pop(); + } + break; + case Token::Space: + //do nothing + } + } + while ($stack->count() !== 0) { + if ($stack->top()->type === Token::LeftParenthesis || $stack->top()->type === Token::RightParenthesis) { + throw new IncorrectBracketsException(); + } + if ($stack->top()->type === Token::Space) { + $stack->pop(); + continue; + } + $tokens[] = $stack->pop(); + } + return $tokens; + } +} + -- cgit v1.2.3 From b74742641f9c5dfb6077745c3c175bca6234d8e0 Mon Sep 17 00:00:00 2001 From: Alexander Kiryukhin Date: Fri, 15 May 2020 22:02:52 +0300 Subject: 7.1 downgrade --- src/NXP/Classes/Tokenizer.php | 15 ++------------- src/NXP/MathExecutor.php | 2 -- 2 files changed, 2 insertions(+), 15 deletions(-) (limited to 'src/NXP/Classes/Tokenizer.php') diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php index 869c720..b72b869 100644 --- a/src/NXP/Classes/Tokenizer.php +++ b/src/NXP/Classes/Tokenizer.php @@ -10,18 +10,6 @@ namespace NXP\Classes; -use NXP\Classes\Token\AbstractOperator; -use NXP\Classes\Token\InterfaceOperator; -use NXP\Classes\Token\InterfaceToken; -use NXP\Classes\Token\TokenComma; -use NXP\Classes\Token\TokenFunction; -use NXP\Classes\Token\TokenLeftBracket; -use NXP\Classes\Token\TokenMinus; -use NXP\Classes\Token\TokenNumber; -use NXP\Classes\Token\TokenRightBracket; -use NXP\Classes\Token\TokenStringDoubleQuoted; -use NXP\Classes\Token\TokenStringSingleQuoted; -use NXP\Classes\Token\TokenVariable; use NXP\Exception\IncorrectBracketsException; use NXP\Exception\UnknownOperatorException; use RuntimeException; @@ -80,7 +68,7 @@ class Tokenizer public function tokenize() { - foreach (mb_str_split($this->input, 1) as $ch) { + foreach (str_split($this->input, 1) as $ch) { switch (true) { case $this->inSingleQuotedString: if ($ch === "'") { @@ -111,6 +99,7 @@ class Tokenizer $this->numberBuffer .= $ch; $this->allowNegative = false; break; + /** @noinspection PhpMissingBreakStatementInspection */ case strtolower($ch) === "e": if ($this->numberBuffer != "" && strpos($this->numberBuffer, ".") !== false) { $this->numberBuffer .= "e"; diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 8d3e890..a0a3f9b 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -14,8 +14,6 @@ namespace NXP; use NXP\Classes\Calculator; use NXP\Classes\CustomFunction; use NXP\Classes\Operator; -use NXP\Classes\Token\AbstractOperator; -use NXP\Classes\TokenFactory; use NXP\Classes\Tokenizer; use NXP\Exception\DivisionByZeroException; use ReflectionException; -- cgit v1.2.3 From 1bb9f61423c1eed320e95a1288a146ece410e1f9 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Tue, 19 May 2020 21:52:26 -0400 Subject: typed parameters and return types --- src/NXP/Classes/Calculator.php | 2 +- src/NXP/Classes/CustomFunction.php | 4 ++-- src/NXP/Classes/Operator.php | 2 +- src/NXP/Classes/Tokenizer.php | 20 ++++++++++---------- src/NXP/MathExecutor.php | 18 +++++++++--------- 5 files changed, 23 insertions(+), 23 deletions(-) (limited to 'src/NXP/Classes/Tokenizer.php') diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php index 0db49e1..c6ccff1 100644 --- a/src/NXP/Classes/Calculator.php +++ b/src/NXP/Classes/Calculator.php @@ -49,7 +49,7 @@ class Calculator * @throws IncorrectExpressionException * @throws UnknownVariableException */ - public function calculate($tokens, $variables) + public function calculate(array $tokens, array $variables) { /** @var Token[] $stack */ $stack = []; diff --git a/src/NXP/Classes/CustomFunction.php b/src/NXP/Classes/CustomFunction.php index 944f7d9..636435c 100644 --- a/src/NXP/Classes/CustomFunction.php +++ b/src/NXP/Classes/CustomFunction.php @@ -32,7 +32,7 @@ class CustomFunction * @param int $places * @throws ReflectionException */ - public function __construct(string $name, callable $function, $places = null) + public function __construct(string $name, callable $function, int $places = null) { $this->name = $name; $this->function = $function; @@ -44,7 +44,7 @@ class CustomFunction } } - public function execute(&$stack) + public function execute(array &$stack) : Token { if (count($stack) < $this->places) { throw new IncorrectExpressionException(); diff --git a/src/NXP/Classes/Operator.php b/src/NXP/Classes/Operator.php index 53550a9..c3762ea 100644 --- a/src/NXP/Classes/Operator.php +++ b/src/NXP/Classes/Operator.php @@ -52,7 +52,7 @@ class Operator $this->places = $reflection->getNumberOfParameters(); } - public function execute(&$stack) + public function execute(array &$stack) : Token { if (count($stack) < $this->places) { throw new IncorrectExpressionException(); diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php index b72b869..caf395f 100644 --- a/src/NXP/Classes/Tokenizer.php +++ b/src/NXP/Classes/Tokenizer.php @@ -66,7 +66,7 @@ class Tokenizer $this->operators = $operators; } - public function tokenize() + public function tokenize() : self { foreach (str_split($this->input, 1) as $ch) { switch (true) { @@ -173,17 +173,17 @@ class Tokenizer return $this; } - private function isNumber($ch) + private function isNumber(string $ch) : bool { return $ch >= '0' && $ch <= '9'; } - private function isAlpha($ch) + private function isAlpha(string $ch) : bool { return $ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch == '_'; } - private function emptyNumberBufferAsLiteral() + private function emptyNumberBufferAsLiteral() : void { if ($this->numberBuffer != "") { $this->tokens[] = new Token(Token::Literal, $this->numberBuffer); @@ -191,22 +191,22 @@ class Tokenizer } } - private function isDot($ch) + private function isDot(string $ch) : bool { return $ch == '.'; } - private function isLP($ch) + private function isLP(string $ch) : bool { return $ch == '('; } - private function isRP($ch) + private function isRP(string $ch) : bool { return $ch == ')'; } - private function emptyStrBufferAsVariable() + private function emptyStrBufferAsVariable() : void { if ($this->stringBuffer != "") { $this->tokens[] = new Token(Token::Variable, $this->stringBuffer); @@ -214,7 +214,7 @@ class Tokenizer } } - private function isComma($ch) + private function isComma(string $ch) : bool { return $ch == ','; } @@ -224,7 +224,7 @@ class Tokenizer * @throws IncorrectBracketsException * @throws UnknownOperatorException */ - public function buildReversePolishNotation() + public function buildReversePolishNotation() : array { $tokens = []; /** @var SplStack $stack */ diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index a0a3f9b..53d8dfd 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -58,7 +58,7 @@ class MathExecutor * Set default operands and functions * @throws ReflectionException */ - protected function addDefaults() + protected function addDefaults() : void { foreach ($this->defaultOperators() as $name => $operator) { list($callable, $priority, $isRightAssoc) = $operator; @@ -75,7 +75,7 @@ class MathExecutor * * @return array of class names */ - protected function defaultOperators() + protected function defaultOperators() : array { return [ '+' => [ @@ -102,7 +102,7 @@ class MathExecutor '/' => [ function ($a, $b) { if ($b == 0) { - throw new DivisionByZeroException(); + throw new DivisionByZeroException(); } return $a / $b; }, @@ -181,7 +181,7 @@ class MathExecutor * @param Operator $operator * @return MathExecutor */ - public function addOperator(Operator $operator) + public function addOperator(Operator $operator) : self { $this->operators[$operator->operator] = $operator; return $this; @@ -193,7 +193,7 @@ class MathExecutor * * @return array */ - protected function defaultFunctions() + protected function defaultFunctions() : array { return [ 'abs' => function ($arg) { @@ -341,9 +341,9 @@ class MathExecutor * @throws Exception\UnknownOperatorException * @throws Exception\UnknownVariableException */ - public function execute($expression) + public function execute(string $expression) { - $cachekey = (string)$expression; + $cachekey = $expression; if (!array_key_exists($cachekey, $this->cache)) { $tokens = (new Tokenizer($expression, $this->operators))->tokenize()->buildReversePolishNotation(); $this->cache[$cachekey] = $tokens; @@ -363,7 +363,7 @@ class MathExecutor * @return MathExecutor * @throws ReflectionException */ - public function addFunction($name, $function = null, $places = null) + public function addFunction(string $name, callable $function = null, int $places = null) : self { $this->functions[$name] = new CustomFunction($name, $function, $places); return $this; @@ -374,7 +374,7 @@ class MathExecutor * * @return array */ - protected function defaultVars() + protected function defaultVars() : array { return [ 'pi' => 3.14159265359, -- cgit v1.2.3