aboutsummaryrefslogtreecommitdiff
path: root/src/NXP/MathExecutor.php
diff options
context:
space:
mode:
authorNeonXP <frei@neonxp.info>2013-09-06 05:42:09 +0400
committerNeonXP <frei@neonxp.info>2013-09-06 05:42:09 +0400
commit9cdc34290a84093b1c4640118289a7cf56d55125 (patch)
tree2187fa94cc9f152c9196e7bd4fe23a2c81f60ffd /src/NXP/MathExecutor.php
parentf172123a0dccdca7651c7ad552175924a16b9458 (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.php351
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;
}