diff --git a/.travis.yml b/.travis.yml index 842031b..47d1020 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: php php: - - 5.3 - - 5.4 + - 5.6 + - 7.2 before_script: - wget http://getcomposer.org/composer.phar - - php composer.phar install \ No newline at end of file + - php composer.phar install diff --git a/README.md b/README.md index 8c611b8..2fdb5c1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,15 @@ Simple math expressions calculator ## Install via Composer -All instructions to install here: https://packagist.org/packages/nxp/math-executor +Stable branch +``` +composer require "nxp/math-executor" "dev-master" +``` + +Dev branch +``` +composer require "nxp/math-executor" "dev-dev" +``` ## Sample usage: diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php index 41e0d9f..bfa7015 100644 --- a/src/NXP/Classes/Calculator.php +++ b/src/NXP/Classes/Calculator.php @@ -13,6 +13,7 @@ namespace NXP\Classes; use NXP\Classes\Token\InterfaceOperator; use NXP\Classes\Token\TokenFunction; use NXP\Classes\Token\TokenNumber; +use NXP\Classes\Token\TokenString; use NXP\Classes\Token\TokenVariable; use NXP\Exception\IncorrectExpressionException; use NXP\Exception\UnknownVariableException; @@ -32,15 +33,18 @@ class Calculator */ public function calculate($tokens, $variables) { - $stack = array(); + $stack = []; foreach ($tokens as $token) { if ($token instanceof TokenNumber) { array_push($stack, $token); } + if ($token instanceof TokenString) { + array_push($stack, $token); + } if ($token instanceof TokenVariable) { $variable = $token->getValue(); if (!array_key_exists($variable, $variables)) { - throw new UnknownVariableException(); + throw new UnknownVariableException($variable); } $value = $variables[$variable]; array_push($stack, new TokenNumber($value)); diff --git a/src/NXP/Classes/Lexer.php b/src/NXP/Classes/Lexer.php index e541732..82b2c53 100644 --- a/src/NXP/Classes/Lexer.php +++ b/src/NXP/Classes/Lexer.php @@ -17,6 +17,7 @@ use NXP\Classes\Token\TokenLeftBracket; use NXP\Classes\Token\TokenNumber; use NXP\Classes\Token\TokenRightBracket; use NXP\Classes\Token\TokenVariable; +use NXP\Classes\Token\TokenString; use NXP\Exception\IncorrectBracketsException; use NXP\Exception\IncorrectExpressionException; @@ -42,7 +43,7 @@ class Lexer */ public function stringToTokensStream($input) { - $matches = array(); + $matches = []; preg_match_all($this->tokenFactory->getTokenParserRegex(), $input, $matches); $tokenFactory = $this->tokenFactory; $tokensStream = array_map( @@ -62,10 +63,13 @@ class Lexer */ public function buildReversePolishNotation($tokensStream) { - $output = array(); - $stack = array(); + $output = []; + $stack = []; foreach ($tokensStream as $token) { + if ($token instanceof TokenString) { + $output[] = $token; + } if ($token instanceof TokenNumber) { $output[] = $token; } diff --git a/src/NXP/Classes/Token/TokenFunction.php b/src/NXP/Classes/Token/TokenFunction.php index 23f64bd..2ed8ace 100644 --- a/src/NXP/Classes/Token/TokenFunction.php +++ b/src/NXP/Classes/Token/TokenFunction.php @@ -29,7 +29,7 @@ class TokenFunction extends AbstractContainerToken implements InterfaceFunction */ public function execute(&$stack) { - $args = array(); + $args = []; list($places, $function) = $this->value; for ($i = 0; $i < $places; $i++) { array_push($args, array_pop($stack)->getValue()); diff --git a/src/NXP/Classes/Token/TokenString.php b/src/NXP/Classes/Token/TokenString.php new file mode 100644 index 0000000..cab0711 --- /dev/null +++ b/src/NXP/Classes/Token/TokenString.php @@ -0,0 +1,25 @@ + + */ +class TokenString extends AbstractContainerToken +{ + /** + * @return string + */ + public static function getRegex() + { + return '"([^"]|"")*"'; + } +} diff --git a/src/NXP/Classes/TokenFactory.php b/src/NXP/Classes/TokenFactory.php index 19ba1cf..d70dd55 100644 --- a/src/NXP/Classes/TokenFactory.php +++ b/src/NXP/Classes/TokenFactory.php @@ -17,6 +17,7 @@ use NXP\Classes\Token\TokenLeftBracket; use NXP\Classes\Token\TokenNumber; use NXP\Classes\Token\TokenRightBracket; use NXP\Classes\Token\TokenVariable; +use NXP\Classes\Token\TokenString; use NXP\Exception\UnknownFunctionException; use NXP\Exception\UnknownOperatorException; use NXP\Exception\UnknownTokenException; @@ -31,24 +32,35 @@ class TokenFactory * * @var array */ - protected $operators = array(); + protected $operators = []; /** * Available functions * * @var array */ - protected $functions = array(); + protected $functions = []; /** * Add function - * @param $name - * @param $function - * @param $places + * @param string $name + * @param callable $function + * @param int $places */ - public function addFunction($name, $function, $places = 1) + public function addFunction($name, callable $function, $places = 1) { - $this->functions[$name] = array($places, $function); + $this->functions[$name] = [$places, $function]; + } + + /** + * get functions + * + * @return array containing callback and places indexed by + * function name + */ + public function getFunctions() + { + return $this->functions; } /** @@ -61,7 +73,7 @@ class TokenFactory $class = new \ReflectionClass($operatorClass); if (!in_array('NXP\Classes\Token\InterfaceToken', $class->getInterfaceNames())) { - throw new UnknownOperatorException; + throw new UnknownOperatorException($operatorClass); } $this->operators[] = $operatorClass; @@ -69,13 +81,13 @@ class TokenFactory } /** - * Add variable - * @param string $name - * @param mixed $value + * Get registered operators + * + * @return array of operator class names */ - public function addVariable($name, $value) + public function getOperators() { - + return $this->operators; } /** @@ -89,8 +101,9 @@ class TokenFactory } return sprintf( - '/(%s)|([%s])|(%s)|(%s)|([%s%s%s])/i', + '/(%s)|(%s)|([%s])|(%s)|(%s)|([%s%s%s])/i', TokenNumber::getRegex(), + TokenString::getRegex(), $operatorsRegex, TokenFunction::getRegex(), TokenVariable::getRegex(), @@ -119,6 +132,10 @@ class TokenFactory return new TokenRightBracket(); } + if ($token[0] == '"') { + return new TokenString(str_replace('"', '', $token)); + } + if ($token == ',') { return new TokenComma(); } @@ -140,10 +157,10 @@ class TokenFactory if (isset($this->functions[$token])) { return new TokenFunction($this->functions[$token]); } else { - throw new UnknownFunctionException(); + throw new UnknownFunctionException($token); } } - throw new UnknownTokenException(); + throw new UnknownTokenException($token); } } diff --git a/src/NXP/Exception/IncorrectBracketsException.php b/src/NXP/Exception/IncorrectBracketsException.php index e2aaed9..b3e8315 100644 --- a/src/NXP/Exception/IncorrectBracketsException.php +++ b/src/NXP/Exception/IncorrectBracketsException.php @@ -14,6 +14,6 @@ namespace NXP\Exception; /** * @author Alexander Kiryukhin */ -class IncorrectBracketsException extends \Exception +class IncorrectBracketsException extends MathExecutorException { } diff --git a/src/NXP/Exception/IncorrectExpressionException.php b/src/NXP/Exception/IncorrectExpressionException.php index ad5bc42..6f579c1 100644 --- a/src/NXP/Exception/IncorrectExpressionException.php +++ b/src/NXP/Exception/IncorrectExpressionException.php @@ -14,6 +14,6 @@ namespace NXP\Exception; /** * @author Vitaliy Zhuk */ -class IncorrectExpressionException extends \Exception +class IncorrectExpressionException extends MathExecutorException { } diff --git a/src/NXP/Exception/UnknownFunctionException.php b/src/NXP/Exception/UnknownFunctionException.php index 5bb3658..e8ff0c0 100644 --- a/src/NXP/Exception/UnknownFunctionException.php +++ b/src/NXP/Exception/UnknownFunctionException.php @@ -14,6 +14,6 @@ namespace NXP\Exception; /** * @author Vitaliy Zhuk */ -class UnknownFunctionException extends \Exception +class UnknownFunctionException extends MathExecutorException { } diff --git a/src/NXP/Exception/UnknownOperatorException.php b/src/NXP/Exception/UnknownOperatorException.php index b6617c3..acb4b98 100644 --- a/src/NXP/Exception/UnknownOperatorException.php +++ b/src/NXP/Exception/UnknownOperatorException.php @@ -14,6 +14,6 @@ namespace NXP\Exception; /** * @author Vitaliy Zhuk */ -class UnknownOperatorException extends \Exception +class UnknownOperatorException extends MathExecutorException { } diff --git a/src/NXP/Exception/UnknownTokenException.php b/src/NXP/Exception/UnknownTokenException.php index b8a593f..bba28a4 100644 --- a/src/NXP/Exception/UnknownTokenException.php +++ b/src/NXP/Exception/UnknownTokenException.php @@ -14,6 +14,6 @@ namespace NXP\Exception; /** * @author Vitaliy Zhuk */ -class UnknownTokenException extends \Exception +class UnknownTokenException extends MathExecutorException { } diff --git a/src/NXP/Exception/UnknownVariableException.php b/src/NXP/Exception/UnknownVariableException.php index 8de9493..8c215db 100644 --- a/src/NXP/Exception/UnknownVariableException.php +++ b/src/NXP/Exception/UnknownVariableException.php @@ -14,6 +14,6 @@ namespace NXP\Exception; /** * @author Alexander Kiryukhin */ -class UnknownVariableException extends \Exception +class UnknownVariableException extends MathExecutorException { } diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 5cb4bca..d62d07a 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -15,6 +15,7 @@ use NXP\Classes\Calculator; use NXP\Classes\Lexer; use NXP\Classes\Token; use NXP\Classes\TokenFactory; +use NXP\Exception\UnknownVariableException; /** * Class MathExecutor @@ -27,7 +28,7 @@ class MathExecutor * * @var array */ - private $variables = array(); + private $variables = []; /** * @var TokenFactory @@ -37,7 +38,7 @@ class MathExecutor /** * @var array */ - private $cache = array(); + private $cache = []; /** * Base math operators @@ -75,10 +76,36 @@ class MathExecutor $this->tokenFactory->addFunction('max', 'max', 2); $this->tokenFactory->addFunction('avg', function($arg1, $arg2) { return ($arg1 + $arg2) / 2; }, 2); - $this->setVars(array( + $this->setVars([ 'pi' => 3.14159265359, 'e' => 2.71828182846 - )); + ]); + } + + /** + * Get all vars + * + * @return array + */ + public function getVars() + { + return $this->variables; + } + + /** + * Get a specific var + * + * @param string $variable + * @return integer|float + * @throws UnknownVariableException + */ + public function getVar($variable) + { + if (! isset($this->variables[$variable])) { + throw new UnknownVariableException("Variable ({$variable}) not set"); + } + + return $this->variables[$variable]; } /** @@ -86,11 +113,14 @@ class MathExecutor * * @param string $variable * @param integer|float $value - * @throws \Exception * @return MathExecutor */ public function setVar($variable, $value) { + if (!is_numeric($value)) { + throw new \Exception("Variable ({$variable}) value must be a number ({$value}) type ({gettype($value)})"); + } + $this->variables[$variable] = $value; return $this; @@ -134,7 +164,7 @@ class MathExecutor */ public function removeVars() { - $this->variables = array(); + $this->variables = []; return $this; } @@ -152,6 +182,16 @@ class MathExecutor return $this; } + /** + * Get all registered operators to executor + * + * @return array of operator class names + */ + public function getOperators() + { + return $this->tokenFactory->getOperators(); + } + /** * Add function to executor * @@ -160,13 +200,24 @@ class MathExecutor * @param int $places Count of arguments * @return MathExecutor */ - public function addFunction($name, callable $function = null, $places = 1) + public function addFunction($name, $function = null, $places = 1) { $this->tokenFactory->addFunction($name, $function, $places); return $this; } + /** + * Get all registered functions + * + * @return array containing callback and places indexed by + * function name + */ + public function getFunctions() + { + return $this->tokenFactory->getFunctions(); + } + /** * Execute expression * diff --git a/tests/MathTest.php b/tests/MathTest.php index a83a0d4..db6e486 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -27,7 +27,7 @@ class MathTest extends \PHPUnit_Framework_TestCase $this->assertEquals($calculator->execute($expression), $phpResult); } - public function testZeroDevision() + public function testZeroDivision() { $calculator = new MathExecutor(); $this->assertEquals($calculator->execute('1 / 0'), 0); @@ -44,34 +44,44 @@ class MathTest extends \PHPUnit_Framework_TestCase */ public function providerExpressions() { - return array( - array('0.1 + 0.2'), - array('1 + 2'), + return [ + ['0.1 + 0.2'], + ['1 + 2'], - array('0.1 - 0.2'), - array('1 - 2'), + ['0.1 - 0.2'], + ['1 - 2'], - array('0.1 * 2'), - array('1 * 2'), + ['0.1 * 2'], + ['1 * 2'], - array('0.1 / 0.2'), - array('1 / 2'), + ['0.1 / 0.2'], + ['1 / 2'], - array('2 * 2 + 3 * 3'), + ['2 * 2 + 3 * 3'], - array('1 + 0.6 - 3 * 2 / 50'), + ['1 + 0.6 - 3 * 2 / 50'], - array('(5 + 3) * -1'), + ['(5 + 3) * -1'], - array('2+2*2'), - array('(2+2)*2'), - array('(2+2)*-2'), - array('(2+-2)*2'), + ['2+2*2'], + ['(2+2)*2'], + ['(2+2)*-2'], + ['(2+-2)*2'], - array('sin(10) * cos(50) / min(10, 20/2)'), + ['sin(10) * cos(50) / min(10, 20/2)'], - array('100500 * 3.5E5'), - array('100500 * 3.5E-5') - ); + ['100500 * 3.5E5'], + ['100500 * 3.5E-5'], + ]; } -} + + public function testFunction() + { + $calculator = new MathExecutor(); + + $calculator->addFunction('round', function ($arg) { return round($arg); }, 1); + /** @var float $phpResult */ + eval('$phpResult = round(100/30);'); + $this->assertEquals($calculator->execute('round(100/30)'), $phpResult); + } +} \ No newline at end of file