diff options
author | NeonXP <frei@neonxp.info> | 2013-09-06 05:42:09 +0400 |
---|---|---|
committer | NeonXP <frei@neonxp.info> | 2013-09-06 05:42:09 +0400 |
commit | 9cdc34290a84093b1c4640118289a7cf56d55125 (patch) | |
tree | 2187fa94cc9f152c9196e7bd4fe23a2c81f60ffd /src | |
parent | f172123a0dccdca7651c7ad552175924a16b9458 (diff) |
Mass refactoring
Some changes:
+ Added support of functions with multiple arguments
+ Added some default function (min, max, avg). just example of multiple arguments :)
- Removed variables support (I think they pointless)
~ All tokens now in individual classes
~ Parsing based on regular expressions
~ Fix negative numbers
~ Fix grouping with brackets
Diffstat (limited to 'src')
24 files changed, 901 insertions, 671 deletions
diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php new file mode 100644 index 0000000..e74b7ab --- /dev/null +++ b/src/NXP/Classes/Calculator.php @@ -0,0 +1,46 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes; + +use NXP\Classes\Token\InterfaceOperator; +use NXP\Classes\Token\TokenFunction; +use NXP\Classes\Token\TokenNumber; +use NXP\Exception\IncorrectExpressionException; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class Calculator +{ + /** + * @param array $tokens Tokens in reverse polish notation + * @return number + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function calculate($tokens) + { + $stack = array(); + foreach ($tokens as $token) { + if ($token instanceof TokenNumber) { + array_push($stack, $token); + } + if ($token instanceof InterfaceOperator || $token instanceof TokenFunction) { + array_push($stack, $token->execute($stack)); + } + } + $result = array_pop($stack); + if (!empty($stack)) { + throw new IncorrectExpressionException(); + } + + return $result->getValue(); + } +} diff --git a/src/NXP/Classes/Func.php b/src/NXP/Classes/Func.php deleted file mode 100644 index e8c0fa2..0000000 --- a/src/NXP/Classes/Func.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -/** - * This file is part of the MathExecutor package - * - * (c) Alexander Kiryukhin - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code - */ - -namespace NXP\Classes; - -class Func -{ - /** - * @var string - */ - private $name; - - /** - * @var callable - */ - private $callback; - - /** - * @param $name - * @param $callback - */ - public function __construct($name, $callback) - { - $this->name = $name; - $this->callback = $callback; - } - - public function getName() - { - return $this->name; - } - - public function getCallback() - { - return $this->callback; - } -} diff --git a/src/NXP/Classes/Lexer.php b/src/NXP/Classes/Lexer.php new file mode 100644 index 0000000..676962d --- /dev/null +++ b/src/NXP/Classes/Lexer.php @@ -0,0 +1,121 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes; +use NXP\Classes\Token\AbstractOperator; +use NXP\Classes\Token\InterfaceOperator; +use NXP\Classes\Token\TokenComma; +use NXP\Classes\Token\TokenFunction; +use NXP\Classes\Token\TokenLeftBracket; +use NXP\Classes\Token\TokenNumber; +use NXP\Classes\Token\TokenRightBracket; +use NXP\Exception\IncorrectBracketsException; +use NXP\Exception\IncorrectExpressionException; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class Lexer +{ + /** + * @var TokenFactory + */ + private $tokenFactory; + + public function __construct($tokenFactory) + { + $this->tokenFactory = $tokenFactory; + } + + /** + * @param string $input Source string of equation + * @return array Tokens stream + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function stringToTokensStream($input) + { + $matches = array(); + preg_match_all($this->tokenFactory->getTokenParserRegex(), $input, $matches); + $tokensStream = array_map( + function ($token) { + return $this->tokenFactory->createToken($token); + }, + $matches[0] + ); + + return $tokensStream; + } + + /** + * @param array $tokensStream Tokens stream + * @return array Array of tokens in revers polish notation + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function buildReversePolishNotation($tokensStream) + { + $output = array(); + $stack = array(); + + foreach ($tokensStream as $token) { + if ($token instanceof TokenNumber) { + $output[] = $token; + } + if ($token instanceof TokenFunction) { + array_push($stack, $token); + } + if ($token instanceof TokenLeftBracket) { + array_push($stack, $token); + } + if ($token instanceof TokenComma) { + while (($current = array_pop($stack)) && (!$current instanceof TokenLeftBracket)) { + $output[] = $current; + if (empty($stack)) { + throw new IncorrectExpressionException(); + } + } + } + if ($token instanceof TokenRightBracket) { + while (($current = array_pop($stack)) && (!$current instanceof TokenLeftBracket)) { + $output[] = $current; + } + if (!empty($stack) && ($stack[count($stack)-1] instanceof TokenFunction)) { + $output[] = array_pop($stack); + } + } + + if ($token instanceof AbstractOperator) { + while ( + count($stack) > 0 && + ($stack[count($stack)-1] instanceof InterfaceOperator) && + ( + $token->getAssociation() == AbstractOperator::LEFT_ASSOC && + $token->getPriority() <= $stack[count($stack)-1]->getPriority() + ) || ( + $token->getAssociation() == AbstractOperator::RIGHT_ASSOC && + $token->getPriority() < $stack[count($stack)-1]->getPriority() + ) + ) { + $output[] = array_pop($stack); + } + + array_push($stack, $token); + } + } + while (!empty($stack)) { + $token = array_pop($stack); + if ($token instanceof TokenLeftBracket || $token instanceof TokenRightBracket) { + throw new IncorrectBracketsException(); + } + $output[] = $token; + } + + return $output; + } +} diff --git a/src/NXP/Classes/Operand.php b/src/NXP/Classes/Operand.php deleted file mode 100644 index fae0c69..0000000 --- a/src/NXP/Classes/Operand.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php - -/** - * This file is part of the MathExecutor package - * - * (c) Alexander Kiryukhin - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code - */ - -namespace NXP\Classes; - -class Operand -{ - const LEFT_ASSOCIATED = 'LEFT_ASSOCIATED'; - const RIGHT_ASSOCIATED = 'RIGHT_ASSOCIATED'; - const ASSOCIATED = 'ASSOCIATED'; - - const UNARY = 'UNARY'; - const BINARY = 'BINARY'; - - /** - * @var string - */ - private $symbol; - - /** - * @var int - */ - private $priority; - - /** - * @var string - */ - private $association; - - /** - * @var string - */ - private $type; - - /** - * @var callable - */ - private $callback; - - /** - * @param $symbol - * @param $priority - * @param $association - * @param $type - * @param $callback - */ - public function __construct($symbol, $priority, $association, $type, $callback) - { - $this->association = $association; - $this->symbol = $symbol; - $this->type = $type; - $this->priority = $priority; - $this->callback = $callback; - } - - public function getAssociation() - { - return $this->association; - } - - public function getSymbol() - { - return $this->symbol; - } - - public function getType() - { - return $this->type; - } - - public function getCallback() - { - return $this->callback; - } - - public function getPriority() - { - return $this->priority; - } - -} diff --git a/src/NXP/Classes/Token.php b/src/NXP/Classes/Token.php deleted file mode 100644 index bbcd5bc..0000000 --- a/src/NXP/Classes/Token.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php - -/** - * This file is part of the MathExecutor package - * - * (c) Alexander Kiryukhin - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code - */ - -namespace NXP\Classes; - -class Token -{ - const NOTHING = 'NOTHING'; - const STRING = 'STRING'; - const NUMBER = 'NUMBER'; - const OPERATOR = 'OPERATOR'; - const LEFT_BRACKET = 'LEFT_BRACKET'; - const RIGHT_BRACKET = 'RIGHT_BRACKET'; - const FUNC = 'FUNC'; - - /** - * @var string - */ - protected $value; - - /** - * @var string - */ - protected $type; - - public function __construct($type, $value) - { - $this->type = $type; - $this->value = $value; - } - - /** - * @return string - */ - public function getType() - { - return $this->type; - } - - /** - * @return string - */ - public function getValue() - { - return $this->value; - } - -} diff --git a/src/NXP/Classes/Token/AbstractContainerToken.php b/src/NXP/Classes/Token/AbstractContainerToken.php new file mode 100644 index 0000000..12d49d2 --- /dev/null +++ b/src/NXP/Classes/Token/AbstractContainerToken.php @@ -0,0 +1,46 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +abstract class AbstractContainerToken implements InterfaceToken +{ + /** + * @var string + */ + protected $value; + + /** + * @param string $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @param string $value + */ + public function setValue($value) + { + $this->value = $value; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } +} diff --git a/src/NXP/Classes/Token/AbstractOperator.php b/src/NXP/Classes/Token/AbstractOperator.php new file mode 100644 index 0000000..6cdfe99 --- /dev/null +++ b/src/NXP/Classes/Token/AbstractOperator.php @@ -0,0 +1,20 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +abstract class AbstractOperator implements InterfaceToken, InterfaceOperator +{ + const RIGHT_ASSOC = 'RIGHT'; + const LEFT_ASSOC = 'LEFT'; +} diff --git a/src/NXP/Classes/Token/InterfaceFunction.php b/src/NXP/Classes/Token/InterfaceFunction.php new file mode 100644 index 0000000..be867b0 --- /dev/null +++ b/src/NXP/Classes/Token/InterfaceFunction.php @@ -0,0 +1,23 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +interface InterfaceFunction +{ + /** + * @param array $stack + * @return $this + */ + public function execute(&$stack); +} diff --git a/src/NXP/Classes/Token/InterfaceOperator.php b/src/NXP/Classes/Token/InterfaceOperator.php new file mode 100644 index 0000000..da6cdf0 --- /dev/null +++ b/src/NXP/Classes/Token/InterfaceOperator.php @@ -0,0 +1,33 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +interface InterfaceOperator +{ + /** + * @return int + */ + public function getPriority(); + + /** + * @return string + */ + public function getAssociation(); + + /** + * @param array $stack + * @return TokenNumber + */ + public function execute(&$stack); +} diff --git a/src/NXP/Classes/Token/InterfaceToken.php b/src/NXP/Classes/Token/InterfaceToken.php new file mode 100644 index 0000000..86fec91 --- /dev/null +++ b/src/NXP/Classes/Token/InterfaceToken.php @@ -0,0 +1,22 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +interface InterfaceToken +{ + /** + * @return string + */ + public static function getRegex(); +} diff --git a/src/NXP/Classes/Token/TokenComma.php b/src/NXP/Classes/Token/TokenComma.php new file mode 100644 index 0000000..f590e32 --- /dev/null +++ b/src/NXP/Classes/Token/TokenComma.php @@ -0,0 +1,25 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class TokenComma implements InterfaceToken +{ + /** + * @return string + */ + public static function getRegex() + { + return '\,'; + } +} diff --git a/src/NXP/Classes/Token/TokenDegree.php b/src/NXP/Classes/Token/TokenDegree.php new file mode 100644 index 0000000..8488dcd --- /dev/null +++ b/src/NXP/Classes/Token/TokenDegree.php @@ -0,0 +1,54 @@ +<?php +/** +* This file is part of the MathExecutor package +* +* (c) Alexander Kiryukhin +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code +*/ + +namespace NXP\Classes\Token; + +/** +* @author Alexander Kiryukhin <alexander@symdev.org> +*/ +class TokenDegree extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\^'; + } + + /** + * @return int + */ + public function getPriority() + { + return 3; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::RIGHT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * @return $this + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + $result = $op1->getValue() ^ $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenDivision.php b/src/NXP/Classes/Token/TokenDivision.php new file mode 100644 index 0000000..479a4ec --- /dev/null +++ b/src/NXP/Classes/Token/TokenDivision.php @@ -0,0 +1,54 @@ +<?php +/** +* This file is part of the MathExecutor package +* +* (c) Alexander Kiryukhin +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code +*/ + +namespace NXP\Classes\Token; + +/** +* @author Alexander Kiryukhin <alexander@symdev.org> +*/ +class TokenDivision extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\/'; + } + + /** + * @return int + */ + public function getPriority() + { + return 2; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * @return $this + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + $result = $op1->getValue() / $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenFunction.php b/src/NXP/Classes/Token/TokenFunction.php new file mode 100644 index 0000000..23f64bd --- /dev/null +++ b/src/NXP/Classes/Token/TokenFunction.php @@ -0,0 +1,41 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class TokenFunction extends AbstractContainerToken implements InterfaceFunction +{ + /** + * @return string + */ + public static function getRegex() + { + return '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; + } + + /** + * @param array $stack + * @return $this + */ + public function execute(&$stack) + { + $args = array(); + list($places, $function) = $this->value; + for ($i = 0; $i < $places; $i++) { + array_push($args, array_pop($stack)->getValue()); + } + $result = call_user_func_array($function, $args); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenLeftBracket.php b/src/NXP/Classes/Token/TokenLeftBracket.php new file mode 100644 index 0000000..0cfdc1a --- /dev/null +++ b/src/NXP/Classes/Token/TokenLeftBracket.php @@ -0,0 +1,25 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class TokenLeftBracket implements InterfaceToken +{ + /** + * @return string + */ + public static function getRegex() + { + return '\('; + } +} diff --git a/src/NXP/Classes/Token/TokenMinus.php b/src/NXP/Classes/Token/TokenMinus.php new file mode 100644 index 0000000..0463d4c --- /dev/null +++ b/src/NXP/Classes/Token/TokenMinus.php @@ -0,0 +1,54 @@ +<?php +/** +* This file is part of the MathExecutor package +* +* (c) Alexander Kiryukhin +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code +*/ + +namespace NXP\Classes\Token; + +/** +* @author Alexander Kiryukhin <alexander@symdev.org> +*/ +class TokenMinus extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\-'; + } + + /** + * @return int + */ + public function getPriority() + { + return 1; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * @return $this + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + $result = $op1->getValue() - $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenMultiply.php b/src/NXP/Classes/Token/TokenMultiply.php new file mode 100644 index 0000000..e6fd960 --- /dev/null +++ b/src/NXP/Classes/Token/TokenMultiply.php @@ -0,0 +1,54 @@ +<?php +/** +* This file is part of the MathExecutor package +* +* (c) Alexander Kiryukhin +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code +*/ + +namespace NXP\Classes\Token; + +/** +* @author Alexander Kiryukhin <alexander@symdev.org> +*/ +class TokenMultiply extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\*'; + } + + /** + * @return int + */ + public function getPriority() + { + return 2; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * @return $this + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + $result = $op1->getValue() * $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenNumber.php b/src/NXP/Classes/Token/TokenNumber.php new file mode 100644 index 0000000..763ca04 --- /dev/null +++ b/src/NXP/Classes/Token/TokenNumber.php @@ -0,0 +1,25 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class TokenNumber extends AbstractContainerToken +{ + /** + * @return string + */ + public static function getRegex() + { + return '\-?\d+\.?\d*'; + } +} diff --git a/src/NXP/Classes/Token/TokenPlus.php b/src/NXP/Classes/Token/TokenPlus.php new file mode 100644 index 0000000..f9562e7 --- /dev/null +++ b/src/NXP/Classes/Token/TokenPlus.php @@ -0,0 +1,54 @@ +<?php +/** +* This file is part of the MathExecutor package +* +* (c) Alexander Kiryukhin +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code +*/ + +namespace NXP\Classes\Token; + +/** +* @author Alexander Kiryukhin <alexander@symdev.org> +*/ +class TokenPlus extends AbstractOperator +{ + /** + * @return string + */ + public static function getRegex() + { + return '\+'; + } + + /** + * @return int + */ + public function getPriority() + { + return 1; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param InterfaceToken[] $stack + * @return $this + */ + public function execute(&$stack) + { + $op2 = array_pop($stack); + $op1 = array_pop($stack); + $result = $op1->getValue() + $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenRightBracket.php b/src/NXP/Classes/Token/TokenRightBracket.php new file mode 100644 index 0000000..c68473a --- /dev/null +++ b/src/NXP/Classes/Token/TokenRightBracket.php @@ -0,0 +1,25 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes\Token; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class TokenRightBracket implements InterfaceToken +{ + /** + * @return string + */ + public static function getRegex() + { + return '\)'; + } +} diff --git a/src/NXP/Classes/TokenFactory.php b/src/NXP/Classes/TokenFactory.php new file mode 100644 index 0000000..924203d --- /dev/null +++ b/src/NXP/Classes/TokenFactory.php @@ -0,0 +1,125 @@ +<?php +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Classes; + +use NXP\Classes\Token\InterfaceToken; +use NXP\Classes\Token\TokenComma; +use NXP\Classes\Token\TokenFunction; +use NXP\Classes\Token\TokenLeftBracket; +use NXP\Classes\Token\TokenNumber; +use NXP\Classes\Token\TokenRightBracket; +use NXP\Exception\UnknownFunctionException; +use NXP\Exception\UnknownOperatorException; +use NXP\Exception\UnknownTokenException; + +/** + * @author Alexander Kiryukhin <alexander@symdev.org> + */ +class TokenFactory +{ + /** + * Available operators + * + * @var array + */ + protected $operators = array(); + + /** + * Available functions + * + * @var array + */ + protected $functions = array(); + + /** + * @param $name + * @param $function + * @param $places + */ + public function addFunction($name, $function, $places = 1) + { + $this->functions[$name] = array($places, $function); + } + + public function addOperator($operatorClass) + { + $class = new \ReflectionClass($operatorClass); + + if (!in_array('NXP\Classes\Token\InterfaceToken', $class->getInterfaceNames())) { + throw new UnknownOperatorException; + } + + $this->operators[] = $operatorClass; + $this->operators = array_unique($this->operators); + } + + /** + * @return string + */ + public function getTokenParserRegex() + { + $operatorsRegex = ''; + foreach ($this->operators as $operator) { + $operatorsRegex .= $operator::getRegex(); + } + + return sprintf( + '/(%s)|([%s])|(%s)|([%s%s%s])/i', + TokenNumber::getRegex(), + $operatorsRegex, + TokenFunction::getRegex(), + TokenLeftBracket::getRegex(), + TokenRightBracket::getRegex(), + TokenComma::getRegex() + ); + } + + /** + * @param string $token + * @return InterfaceToken + * @throws UnknownTokenException + */ + public function createToken($token) + { + if (is_numeric($token)) { + return new TokenNumber($token); + } + + if ($token == '(') { + return new TokenLeftBracket(); + } + + if ($token == ')') { + return new TokenRightBracket(); + } + + if ($token == ',') { + return new TokenComma(); + } + + foreach ($this->operators as $operator) { + $regex = sprintf('/%s/i', $operator::getRegex()); + if (preg_match($regex, $token)) { + return new $operator; + } + } + + $regex = sprintf('/%s/i', TokenFunction::getRegex()); + if (preg_match($regex, $token)) { + if (isset($this->functions[$token])) { + return new TokenFunction($this->functions[$token]); + } else { + throw new UnknownFunctionException(); + } + } + throw new UnknownTokenException(); + } +} diff --git a/src/NXP/Classes/TokenParser.php b/src/NXP/Classes/TokenParser.php deleted file mode 100644 index f498184..0000000 --- a/src/NXP/Classes/TokenParser.php +++ /dev/null @@ -1,165 +0,0 @@ -<?php - -/** - * This file is part of the MathExecutor package - * - * (c) Alexander Kiryukhin - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code - */ - -namespace NXP\Classes; - -class TokenParser -{ - const DIGIT = 'DIGIT'; - const CHAR = 'CHAR'; - const SPECIAL_CHAR = 'SPECIAL_CHAR'; - const LEFT_BRACKET = 'LEFT_BRACKET'; - const RIGHT_BRACKET = 'RIGHT_BRACKET'; - const SPACE = 'SPACE'; - - private $terms = array( - self::DIGIT => '[0-9\.]', - self::CHAR => '[a-z_]', - self::SPECIAL_CHAR => '[\!\@\#\$\%\^\&\*\/\|\-\+\=\~]', - self::LEFT_BRACKET => '\(', - self::RIGHT_BRACKET => '\)', - self::SPACE => '\s' - ); - - const ERROR_STATE = 'ERROR_STATE'; - - private $transitions = array( - Token::NOTHING => array( - self::DIGIT => Token::NUMBER, - self::CHAR => Token::STRING, - self::SPECIAL_CHAR => Token::OPERATOR, - self::LEFT_BRACKET => Token::LEFT_BRACKET, - self::RIGHT_BRACKET => Token::RIGHT_BRACKET, - self::SPACE => Token::NOTHING - ), - Token::STRING => array( - self::DIGIT => Token::STRING, - self::CHAR => Token::STRING, - self::SPECIAL_CHAR => Token::OPERATOR, - self::LEFT_BRACKET => Token::LEFT_BRACKET, - self::RIGHT_BRACKET => Token::RIGHT_BRACKET, - self::SPACE => Token::NOTHING - ), - Token::NUMBER => array( - self::DIGIT => Token::NUMBER, - self::CHAR => self::ERROR_STATE, - self::SPECIAL_CHAR => Token::OPERATOR, - self::LEFT_BRACKET => Token::LEFT_BRACKET, - self::RIGHT_BRACKET => Token::RIGHT_BRACKET, - self::SPACE => Token::NOTHING - ), - Token::OPERATOR => array( - self::DIGIT => Token::NUMBER, - self::CHAR => Token::STRING, - self::SPECIAL_CHAR => Token::OPERATOR, - self::LEFT_BRACKET => Token::LEFT_BRACKET, - self::RIGHT_BRACKET => Token::RIGHT_BRACKET, - self::SPACE => Token::NOTHING - ), - self::ERROR_STATE => array( - self::DIGIT => self::ERROR_STATE, - self::CHAR => self::ERROR_STATE, - self::SPECIAL_CHAR => self::ERROR_STATE, - self::LEFT_BRACKET => self::ERROR_STATE, - self::RIGHT_BRACKET => self::ERROR_STATE, - self::SPACE => self::ERROR_STATE - ), - Token::LEFT_BRACKET => array( - self::DIGIT => Token::NUMBER, - self::CHAR => Token::STRING, - self::SPECIAL_CHAR => Token::OPERATOR, - self::LEFT_BRACKET => Token::LEFT_BRACKET, - self::RIGHT_BRACKET => Token::RIGHT_BRACKET, - self::SPACE => Token::NOTHING - ), - Token::RIGHT_BRACKET => array( - self::DIGIT => Token::NUMBER, - self::CHAR => Token::STRING, - self::SPECIAL_CHAR => Token::OPERATOR, - self::LEFT_BRACKET => Token::LEFT_BRACKET, - self::RIGHT_BRACKET => Token::RIGHT_BRACKET, - self::SPACE => Token::NOTHING - ), - ); - - private $accumulator = ''; - - private $state = Token::NOTHING; - - private $queue = null; - - public function __construct() - { - $this->queue = new \SplQueue(); - } - - /** - * Tokenize math expression - * @param $expression - * @return \SplQueue - * @throws \Exception - */ - public function tokenize($expression) - { - $oldState = null; - for ($i=0; $i<strlen($expression); $i++) { - $char = substr($expression, $i, 1); - $class = $this->getSymbolType($char); - $oldState = $this->state; - $this->state = $this->transitions[$this->state][$class]; - if ($this->state == self::ERROR_STATE) { - throw new \Exception("Parse expression error at $i column (symbol '$char')"); - } - $this->addToQueue($oldState); - $this->accumulator .= $char; - } - if (!empty($this->accumulator)) { - $token = new Token($this->state, $this->accumulator); - $this->queue->push($token); - } - - return $this->queue; - } - - /** - * @param $symbol - * @return string - * @throws \Exception - */ - private function getSymbolType($symbol) - { - foreach ($this->terms as $class => $regex) { - if (preg_match("/$regex/i", $symbol)) { - return $class; - } - } - - throw new \Exception("Unknown char '$symbol'"); - } - - /** - * @param $oldState - */ - private function addToQueue($oldState) - { - if ($oldState == Token::NOTHING) { - $this->accumulator = ''; - - return; - } - - if (($this->state != $oldState) || ($oldState == Token::LEFT_BRACKET) || ($oldState == Token::RIGHT_BRACKET)) { - $token = new Token($oldState, $this->accumulator); - $this->queue->push($token); - $this->accumulator = ''; - } - } -} diff --git a/src/NXP/Exception/IncorrectBracketsException.php b/src/NXP/Exception/IncorrectBracketsException.php new file mode 100644 index 0000000..68d67c6 --- /dev/null +++ b/src/NXP/Exception/IncorrectBracketsException.php @@ -0,0 +1,19 @@ +<?php + +/** + * This file is part of the MathExecutor package + * + * (c) Alexander Kiryukhin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code + */ + +namespace NXP\Exception; + +/** + * @author V@author Alexander Kiryukhin <alexander@symdev.org> + */ +class IncorrectBracketsException extends \Exception +{ +} diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 482b4b7..a93a305 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -11,14 +11,10 @@ namespace NXP; -use NXP\Classes\Func; -use NXP\Classes\Operand; +use NXP\Classes\Calculator; +use NXP\Classes\Lexer; use NXP\Classes\Token; -use NXP\Classes\TokenParser; -use NXP\Exception\IncorrectExpressionException; -use NXP\Exception\UnknownFunctionException; -use NXP\Exception\UnknownOperatorException; -use NXP\Exception\UnknownTokenException; +use NXP\Classes\TokenFactory; /** * Class MathExecutor @@ -27,35 +23,9 @@ use NXP\Exception\UnknownTokenException; class MathExecutor { /** - * Available operators - * - * @var array + * @var TokenFactory */ - private $operators = array(); - - /** - * Available functions - * - * @var array - */ - private $functions = array(); - - /** - * Available variables - * - * @var array - */ - private $variables = array(); - - /** - * @var \SplStack - */ - private $stack; - - /** - * @var \SplQueue - */ - private $queue; + private $tokenFactory; /** * Base math operators @@ -67,10 +37,6 @@ class MathExecutor public function __clone() { - $this->variables = array(); - $this->operators = array(); - $this->functions = array(); - $this->addDefaults(); } @@ -79,29 +45,35 @@ class MathExecutor */ protected function addDefaults() { - $this->addOperator(new Operand('+', 1, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1+$op2; })); - $this->addOperator(new Operand('-', 1, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1-$op2; })); - $this->addOperator(new Operand('*', 2, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1*$op2; })); - $this->addOperator(new Operand('/', 2, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1/$op2; })); - $this->addOperator(new Operand('^', 3, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return pow($op1,$op2); })); + $this->tokenFactory = new TokenFactory(); + + $this->tokenFactory->addOperator('NXP\Classes\Token\TokenPlus'); + $this->tokenFactory->addOperator('NXP\Classes\Token\TokenMinus'); + $this->tokenFactory->addOperator('NXP\Classes\Token\TokenMultiply'); + $this->tokenFactory->addOperator('NXP\Classes\Token\TokenDivision'); + $this->tokenFactory->addOperator('NXP\Classes\Token\TokenDegree'); + + $this->tokenFactory->addFunction('sin', 'sin'); + $this->tokenFactory->addFunction('cos', 'cos'); + $this->tokenFactory->addFunction('tn', 'tan'); + $this->tokenFactory->addFunction('asin', 'asin'); + $this->tokenFactory->addFunction('acos', 'acos'); + $this->tokenFactory->addFunction('atn', 'atan'); + $this->tokenFactory->addFunction('min', 'min', 2); + $this->tokenFactory->addFunction('max', 'max', 2); + $this->tokenFactory->addFunction('avg', function($arg1, $arg2) { return ($arg1 + $arg2) / 2; }, 2); - $this->addFunction(new Func('sin', function ($arg) { return sin($arg); })); - $this->addFunction(new Func('cos', function ($arg) { return cos($arg); })); - $this->addFunction(new Func('tn', function ($arg) { return tan($arg); })); - $this->addFunction(new Func('asin', function ($arg) { return asin($arg); })); - $this->addFunction(new Func('acos', function ($arg) { return acos($arg); })); - $this->addFunction(new Func('atn', function ($arg) { return atan($arg); })); } /** * Add operator to executor * - * @param Operand $operator + * @param string $operatorClass Class of operator token * @return MathExecutor */ - public function addOperator(Operand $operator) + public function addOperator($operatorClass) { - $this->operators[$operator->getSymbol()] = $operator; + $this->tokenFactory->addOperator($operatorClass); return $this; } @@ -109,79 +81,14 @@ class MathExecutor /** * Add function to executor * - * @param string $name - * @param callable $function + * @param string $name Name of function + * @param callable $function Function + * @param int $places Count of arguments * @return MathExecutor */ - public function addFunction($name, callable $function = null) + public function addFunction($name, callable $function = null, $places = 1) { - if ($name instanceof Func) { - $this->functions[$name->getName()] = $name->getCallback(); - } else { - $this->functions[$name] = $function; - } - - return $this; - } - - /** - * Add variable to executor - * - * @param string $variable - * @param integer|float $value - * @throws \Exception - * @return MathExecutor - */ - public function setVar($variable, $value) - { - if (!is_numeric($value)) { - throw new \Exception("Variable value must be a number"); - } - - $this->variables[$variable] = $value; - - return $this; - } - - /** - * Add variables to executor - * - * @param array $variables - * @param bool $clear Clear previous variables - * @return MathExecutor - */ - public function setVars(array $variables, $clear = true) - { - if ($clear) { - $this->removeVars(); - } - - foreach ($variables as $name => $value) { - $this->setVar($name, $value); - } - - return $this; - } - - /** - * Remove variable from executor - * - * @param string $variable - * @return MathExecutor - */ - public function removeVar($variable) - { - unset ($this->variables[$variable]); - - return $this; - } - - /** - * Remove all variables - */ - public function removeVars() - { - $this->variables = array(); + $this->tokenFactory->addFunction($name, $function, $places); return $this; } @@ -194,199 +101,11 @@ class MathExecutor */ public function execute($expression) { - $reversePolishNotation = $this->convertToReversePolishNotation($expression); - $result = $this->calculateReversePolishNotation($reversePolishNotation); - - return $result; - } - - /** - * Convert expression from normal expression form to RPN - * - * @param $expression - * @return \SplQueue - * @throws \Exception - */ - private function convertToReversePolishNotation($expression) - { - $this->stack = new \SplStack(); - $this->queue = new \SplQueue(); - - $tokenParser = new TokenParser(); - $input = $tokenParser->tokenize($expression); - - foreach ($input as $token) { - $this->categorizeToken($token); - } - - while (!$this->stack->isEmpty()) { - $token = $this->stack->pop(); - - if ($token->getType() != Token::OPERATOR) { - throw new \Exception('Opening bracket without closing bracket'); - } - - $this->queue->push($token); - } - - return $this->queue; - } - - /** - * @param Token $token - * @throws \Exception - */ - private function categorizeToken(Token $token) - { - switch ($token->getType()) { - case Token::NUMBER : - $this->queue->push($token); - break; - - case Token::STRING: - if (array_key_exists($token->getValue(), $this->variables)) { - $this->queue->push(new Token(Token::NUMBER, $this->variables[$token->getValue()])); - } else { - $this->stack->push($token); - } - break; - - case Token::LEFT_BRACKET: - $this->stack->push($token); - break; - - case Token::RIGHT_BRACKET: - $previousToken = $this->stack->pop(); - while (!$this->stack->isEmpty() && ($previousToken->getType() != Token::LEFT_BRACKET)) { - $this->queue->push($previousToken); - $previousToken = $this->stack->pop(); - } - if ((!$this->stack->isEmpty()) && ($this->stack->top()->getType() == Token::STRING)) { - $funcName = $this->stack->pop()->getValue(); - if (!array_key_exists($funcName, $this->functions)) { - throw new UnknownFunctionException(sprintf( - 'Unknown function: "%s".', - $funcName - )); - } - $this->queue->push(new Token(Token::FUNC, $funcName)); - } - break; - - case Token::OPERATOR: - if (!array_key_exists($token->getValue(), $this->operators)) { - throw new UnknownOperatorException(sprintf( - 'Unknown operator: "%s".', - $token->getValue() - )); - } - - $this->proceedOperator($token); - $this->stack->push($token); - break; - - default: - throw new UnknownTokenException(sprintf( - 'Unknown token: "%s".', - $token->getValue() - )); - } - } - - /** - * @param $token - * @throws \Exception - */ - private function proceedOperator(Token $token) - { - if (!array_key_exists($token->getValue(), $this->operators)) { - throw new UnknownOperatorException(sprintf( - 'Unknown operator: "%s".', - $token->getValue() - )); - } - - /** @var Operand $operator */ - $operator = $this->operators[$token->getValue()]; - - while (!$this->stack->isEmpty()) { - $top = $this->stack->top(); - - if ($top->getType() == Token::OPERATOR) { - /** @var Operand $operator */ - $operator = $this->operators[$top->getValue()]; - $priority = $operator->getPriority(); - if ( $operator->getAssociation() == Operand::RIGHT_ASSOCIATED) { - if (($priority > $operator->getPriority())) { - $this->queue->push($this->stack->pop()); - } else { - return; - } - } else { - if (($priority >= $operator->getPriority())) { - $this->queue->push($this->stack->pop()); - } else { - return; - } - } - } elseif ($top->getType() == Token::STRING) { - $this->queue->push($this->stack->pop()); - } else { - return; - } - } - } - - /** - * @param \SplQueue $expression - * @return mixed - * @throws \Exception - */ - private function calculateReversePolishNotation(\SplQueue $expression) - { - $this->stack = new \SplStack(); - /** @var Token $token */ - foreach ($expression as $token) { - switch ($token->getType()) { - case Token::NUMBER : - $this->stack->push($token); - break; - - case Token::OPERATOR: - /** @var Operand $operator */ - $operator = $this->operators[$token->getValue()]; - if ($operator->getType() == Operand::BINARY) { - $arg2 = $this->stack->pop()->getValue(); - $arg1 = $this->stack->pop()->getValue(); - } else { - $arg2 = null; - $arg1 = $this->stack->pop()->getValue(); - } - $callback = $operator->getCallback(); - - $this->stack->push(new Token(Token::NUMBER, (call_user_func($callback, $arg1, $arg2)))); - break; - - case Token::FUNC: - /** @var Func $function */ - $callback = $this->functions[$token->getValue()]; - $arg = $this->stack->pop()->getValue(); - $this->stack->push(new Token(Token::NUMBER, (call_user_func($callback, $arg)))); - break; - - default: - throw new UnknownTokenException(sprintf( - 'Unknown token: "%s".', - $token->getValue() - )); - } - } - - $result = $this->stack->pop()->getValue(); - - if (!$this->stack->isEmpty()) { - throw new IncorrectExpressionException('Incorrect expression.'); - } + $lexer = new Lexer($this->tokenFactory); + $tokensStream = $lexer->stringToTokensStream($expression); + $tokens = $lexer->buildReversePolishNotation($tokensStream); + $calculator = new Calculator(); + $result = $calculator->calculate($tokens); return $result; } |