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/NXP/MathExecutor.php | |
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/NXP/MathExecutor.php')
-rw-r--r-- | src/NXP/MathExecutor.php | 351 |
1 files changed, 35 insertions, 316 deletions
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; } |