aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzhukv <zhuk2205@gmail.com>2013-08-03 14:47:47 +0400
committerzhukv <zhuk2205@gmail.com>2013-08-03 14:47:47 +0400
commiteb9c3651614dd5e5aef067880092e9f622c264df (patch)
treee093d7928420255e986acb44d4ab34634f601c52 /src
parent253fb694a3fcafa3f9ea6da6681f0b176cdec1f4 (diff)
Fix to PSR standart, fix tokenizer, fix function executor.
Diffstat (limited to 'src')
-rw-r--r--src/NXP/Classes/Func.php45
-rw-r--r--src/NXP/Classes/Operand.php89
-rw-r--r--src/NXP/Classes/Token.php56
-rw-r--r--src/NXP/Classes/TokenParser.php165
-rw-r--r--src/NXP/Exception/IncorrectExpressionException.php19
-rw-r--r--src/NXP/Exception/MathExecutorException.php19
-rw-r--r--src/NXP/Exception/UnknownFunctionException.php19
-rw-r--r--src/NXP/Exception/UnknownOperatorException.php19
-rw-r--r--src/NXP/Exception/UnknownTokenException.php19
-rw-r--r--src/NXP/MathExecutor.php393
10 files changed, 843 insertions, 0 deletions
diff --git a/src/NXP/Classes/Func.php b/src/NXP/Classes/Func.php
new file mode 100644
index 0000000..e8c0fa2
--- /dev/null
+++ b/src/NXP/Classes/Func.php
@@ -0,0 +1,45 @@
+<?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/Operand.php b/src/NXP/Classes/Operand.php
new file mode 100644
index 0000000..fae0c69
--- /dev/null
+++ b/src/NXP/Classes/Operand.php
@@ -0,0 +1,89 @@
+<?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
new file mode 100644
index 0000000..bbcd5bc
--- /dev/null
+++ b/src/NXP/Classes/Token.php
@@ -0,0 +1,56 @@
+<?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/TokenParser.php b/src/NXP/Classes/TokenParser.php
new file mode 100644
index 0000000..f498184
--- /dev/null
+++ b/src/NXP/Classes/TokenParser.php
@@ -0,0 +1,165 @@
+<?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/IncorrectExpressionException.php b/src/NXP/Exception/IncorrectExpressionException.php
new file mode 100644
index 0000000..ad5bc42
--- /dev/null
+++ b/src/NXP/Exception/IncorrectExpressionException.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 Vitaliy Zhuk <zhuk2205@gmail.com>
+ */
+class IncorrectExpressionException extends \Exception
+{
+}
diff --git a/src/NXP/Exception/MathExecutorException.php b/src/NXP/Exception/MathExecutorException.php
new file mode 100644
index 0000000..0e3ea84
--- /dev/null
+++ b/src/NXP/Exception/MathExecutorException.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 Vitaliy Zhuk <zhuk2205@gmail.com>
+ */
+abstract class MathExecutorException extends \Exception
+{
+}
diff --git a/src/NXP/Exception/UnknownFunctionException.php b/src/NXP/Exception/UnknownFunctionException.php
new file mode 100644
index 0000000..5bb3658
--- /dev/null
+++ b/src/NXP/Exception/UnknownFunctionException.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 Vitaliy Zhuk <zhuk2205@gmail.com>
+ */
+class UnknownFunctionException extends \Exception
+{
+}
diff --git a/src/NXP/Exception/UnknownOperatorException.php b/src/NXP/Exception/UnknownOperatorException.php
new file mode 100644
index 0000000..b6617c3
--- /dev/null
+++ b/src/NXP/Exception/UnknownOperatorException.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 Vitaliy Zhuk <zhuk2205@gmail.com>
+ */
+class UnknownOperatorException extends \Exception
+{
+}
diff --git a/src/NXP/Exception/UnknownTokenException.php b/src/NXP/Exception/UnknownTokenException.php
new file mode 100644
index 0000000..b8a593f
--- /dev/null
+++ b/src/NXP/Exception/UnknownTokenException.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 Vitaliy Zhuk <zhuk2205@gmail.com>
+ */
+class UnknownTokenException extends \Exception
+{
+}
diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php
new file mode 100644
index 0000000..482b4b7
--- /dev/null
+++ b/src/NXP/MathExecutor.php
@@ -0,0 +1,393 @@
+<?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;
+
+use NXP\Classes\Func;
+use NXP\Classes\Operand;
+use NXP\Classes\Token;
+use NXP\Classes\TokenParser;
+use NXP\Exception\IncorrectExpressionException;
+use NXP\Exception\UnknownFunctionException;
+use NXP\Exception\UnknownOperatorException;
+use NXP\Exception\UnknownTokenException;
+
+/**
+ * Class MathExecutor
+ * @package NXP
+ */
+class MathExecutor
+{
+ /**
+ * Available operators
+ *
+ * @var array
+ */
+ 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;
+
+ /**
+ * Base math operators
+ */
+ public function __construct()
+ {
+ $this->addDefaults();
+ }
+
+ public function __clone()
+ {
+ $this->variables = array();
+ $this->operators = array();
+ $this->functions = array();
+
+ $this->addDefaults();
+ }
+
+ /**
+ * Set default operands and functions
+ */
+ 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->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
+ * @return MathExecutor
+ */
+ public function addOperator(Operand $operator)
+ {
+ $this->operators[$operator->getSymbol()] = $operator;
+
+ return $this;
+ }
+
+ /**
+ * Add function to executor
+ *
+ * @param string $name
+ * @param callable $function
+ * @return MathExecutor
+ */
+ public function addFunction($name, callable $function = null)
+ {
+ 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();
+
+ return $this;
+ }
+
+ /**
+ * Execute expression
+ *
+ * @param $expression
+ * @return int|float
+ */
+ 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.');
+ }
+
+ return $result;
+ }
+}