Merge pull request #1 from NeonXP/master

Merge from upstream master
This commit is contained in:
Bruce Wells 2018-09-12 14:21:25 -04:00 committed by GitHub
commit 12d41b160b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 180 additions and 61 deletions

View file

@ -1,8 +1,8 @@
language: php language: php
php: php:
- 5.3 - 5.6
- 5.4 - 7.2
before_script: before_script:
- wget http://getcomposer.org/composer.phar - wget http://getcomposer.org/composer.phar

View file

@ -7,7 +7,15 @@ Simple math expressions calculator
## Install via Composer ## 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: ## Sample usage:

View file

@ -13,6 +13,7 @@ namespace NXP\Classes;
use NXP\Classes\Token\InterfaceOperator; use NXP\Classes\Token\InterfaceOperator;
use NXP\Classes\Token\TokenFunction; use NXP\Classes\Token\TokenFunction;
use NXP\Classes\Token\TokenNumber; use NXP\Classes\Token\TokenNumber;
use NXP\Classes\Token\TokenString;
use NXP\Classes\Token\TokenVariable; use NXP\Classes\Token\TokenVariable;
use NXP\Exception\IncorrectExpressionException; use NXP\Exception\IncorrectExpressionException;
use NXP\Exception\UnknownVariableException; use NXP\Exception\UnknownVariableException;
@ -32,15 +33,18 @@ class Calculator
*/ */
public function calculate($tokens, $variables) public function calculate($tokens, $variables)
{ {
$stack = array(); $stack = [];
foreach ($tokens as $token) { foreach ($tokens as $token) {
if ($token instanceof TokenNumber) { if ($token instanceof TokenNumber) {
array_push($stack, $token); array_push($stack, $token);
} }
if ($token instanceof TokenString) {
array_push($stack, $token);
}
if ($token instanceof TokenVariable) { if ($token instanceof TokenVariable) {
$variable = $token->getValue(); $variable = $token->getValue();
if (!array_key_exists($variable, $variables)) { if (!array_key_exists($variable, $variables)) {
throw new UnknownVariableException(); throw new UnknownVariableException($variable);
} }
$value = $variables[$variable]; $value = $variables[$variable];
array_push($stack, new TokenNumber($value)); array_push($stack, new TokenNumber($value));

View file

@ -17,6 +17,7 @@ use NXP\Classes\Token\TokenLeftBracket;
use NXP\Classes\Token\TokenNumber; use NXP\Classes\Token\TokenNumber;
use NXP\Classes\Token\TokenRightBracket; use NXP\Classes\Token\TokenRightBracket;
use NXP\Classes\Token\TokenVariable; use NXP\Classes\Token\TokenVariable;
use NXP\Classes\Token\TokenString;
use NXP\Exception\IncorrectBracketsException; use NXP\Exception\IncorrectBracketsException;
use NXP\Exception\IncorrectExpressionException; use NXP\Exception\IncorrectExpressionException;
@ -42,7 +43,7 @@ class Lexer
*/ */
public function stringToTokensStream($input) public function stringToTokensStream($input)
{ {
$matches = array(); $matches = [];
preg_match_all($this->tokenFactory->getTokenParserRegex(), $input, $matches); preg_match_all($this->tokenFactory->getTokenParserRegex(), $input, $matches);
$tokenFactory = $this->tokenFactory; $tokenFactory = $this->tokenFactory;
$tokensStream = array_map( $tokensStream = array_map(
@ -62,10 +63,13 @@ class Lexer
*/ */
public function buildReversePolishNotation($tokensStream) public function buildReversePolishNotation($tokensStream)
{ {
$output = array(); $output = [];
$stack = array(); $stack = [];
foreach ($tokensStream as $token) { foreach ($tokensStream as $token) {
if ($token instanceof TokenString) {
$output[] = $token;
}
if ($token instanceof TokenNumber) { if ($token instanceof TokenNumber) {
$output[] = $token; $output[] = $token;
} }

View file

@ -29,7 +29,7 @@ class TokenFunction extends AbstractContainerToken implements InterfaceFunction
*/ */
public function execute(&$stack) public function execute(&$stack)
{ {
$args = array(); $args = [];
list($places, $function) = $this->value; list($places, $function) = $this->value;
for ($i = 0; $i < $places; $i++) { for ($i = 0; $i < $places; $i++) {
array_push($args, array_pop($stack)->getValue()); array_push($args, array_pop($stack)->getValue());

View file

@ -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 Bruce Wells <brucekwells@gmail.com>
*/
class TokenString extends AbstractContainerToken
{
/**
* @return string
*/
public static function getRegex()
{
return '"([^"]|"")*"';
}
}

View file

@ -17,6 +17,7 @@ use NXP\Classes\Token\TokenLeftBracket;
use NXP\Classes\Token\TokenNumber; use NXP\Classes\Token\TokenNumber;
use NXP\Classes\Token\TokenRightBracket; use NXP\Classes\Token\TokenRightBracket;
use NXP\Classes\Token\TokenVariable; use NXP\Classes\Token\TokenVariable;
use NXP\Classes\Token\TokenString;
use NXP\Exception\UnknownFunctionException; use NXP\Exception\UnknownFunctionException;
use NXP\Exception\UnknownOperatorException; use NXP\Exception\UnknownOperatorException;
use NXP\Exception\UnknownTokenException; use NXP\Exception\UnknownTokenException;
@ -31,24 +32,35 @@ class TokenFactory
* *
* @var array * @var array
*/ */
protected $operators = array(); protected $operators = [];
/** /**
* Available functions * Available functions
* *
* @var array * @var array
*/ */
protected $functions = array(); protected $functions = [];
/** /**
* Add function * Add function
* @param $name * @param string $name
* @param $function * @param callable $function
* @param $places * @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); $class = new \ReflectionClass($operatorClass);
if (!in_array('NXP\Classes\Token\InterfaceToken', $class->getInterfaceNames())) { if (!in_array('NXP\Classes\Token\InterfaceToken', $class->getInterfaceNames())) {
throw new UnknownOperatorException; throw new UnknownOperatorException($operatorClass);
} }
$this->operators[] = $operatorClass; $this->operators[] = $operatorClass;
@ -69,13 +81,13 @@ class TokenFactory
} }
/** /**
* Add variable * Get registered operators
* @param string $name *
* @param mixed $value * @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( return sprintf(
'/(%s)|([%s])|(%s)|(%s)|([%s%s%s])/i', '/(%s)|(%s)|([%s])|(%s)|(%s)|([%s%s%s])/i',
TokenNumber::getRegex(), TokenNumber::getRegex(),
TokenString::getRegex(),
$operatorsRegex, $operatorsRegex,
TokenFunction::getRegex(), TokenFunction::getRegex(),
TokenVariable::getRegex(), TokenVariable::getRegex(),
@ -119,6 +132,10 @@ class TokenFactory
return new TokenRightBracket(); return new TokenRightBracket();
} }
if ($token[0] == '"') {
return new TokenString(str_replace('"', '', $token));
}
if ($token == ',') { if ($token == ',') {
return new TokenComma(); return new TokenComma();
} }
@ -140,10 +157,10 @@ class TokenFactory
if (isset($this->functions[$token])) { if (isset($this->functions[$token])) {
return new TokenFunction($this->functions[$token]); return new TokenFunction($this->functions[$token]);
} else { } else {
throw new UnknownFunctionException(); throw new UnknownFunctionException($token);
} }
} }
throw new UnknownTokenException(); throw new UnknownTokenException($token);
} }
} }

View file

@ -14,6 +14,6 @@ namespace NXP\Exception;
/** /**
* @author Alexander Kiryukhin <alexander@symdev.org> * @author Alexander Kiryukhin <alexander@symdev.org>
*/ */
class IncorrectBracketsException extends \Exception class IncorrectBracketsException extends MathExecutorException
{ {
} }

View file

@ -14,6 +14,6 @@ namespace NXP\Exception;
/** /**
* @author Vitaliy Zhuk <zhuk2205@gmail.com> * @author Vitaliy Zhuk <zhuk2205@gmail.com>
*/ */
class IncorrectExpressionException extends \Exception class IncorrectExpressionException extends MathExecutorException
{ {
} }

View file

@ -14,6 +14,6 @@ namespace NXP\Exception;
/** /**
* @author Vitaliy Zhuk <zhuk2205@gmail.com> * @author Vitaliy Zhuk <zhuk2205@gmail.com>
*/ */
class UnknownFunctionException extends \Exception class UnknownFunctionException extends MathExecutorException
{ {
} }

View file

@ -14,6 +14,6 @@ namespace NXP\Exception;
/** /**
* @author Vitaliy Zhuk <zhuk2205@gmail.com> * @author Vitaliy Zhuk <zhuk2205@gmail.com>
*/ */
class UnknownOperatorException extends \Exception class UnknownOperatorException extends MathExecutorException
{ {
} }

View file

@ -14,6 +14,6 @@ namespace NXP\Exception;
/** /**
* @author Vitaliy Zhuk <zhuk2205@gmail.com> * @author Vitaliy Zhuk <zhuk2205@gmail.com>
*/ */
class UnknownTokenException extends \Exception class UnknownTokenException extends MathExecutorException
{ {
} }

View file

@ -14,6 +14,6 @@ namespace NXP\Exception;
/** /**
* @author Alexander Kiryukhin <alexander@symdev.org> * @author Alexander Kiryukhin <alexander@symdev.org>
*/ */
class UnknownVariableException extends \Exception class UnknownVariableException extends MathExecutorException
{ {
} }

View file

@ -15,6 +15,7 @@ use NXP\Classes\Calculator;
use NXP\Classes\Lexer; use NXP\Classes\Lexer;
use NXP\Classes\Token; use NXP\Classes\Token;
use NXP\Classes\TokenFactory; use NXP\Classes\TokenFactory;
use NXP\Exception\UnknownVariableException;
/** /**
* Class MathExecutor * Class MathExecutor
@ -27,7 +28,7 @@ class MathExecutor
* *
* @var array * @var array
*/ */
private $variables = array(); private $variables = [];
/** /**
* @var TokenFactory * @var TokenFactory
@ -37,7 +38,7 @@ class MathExecutor
/** /**
* @var array * @var array
*/ */
private $cache = array(); private $cache = [];
/** /**
* Base math operators * Base math operators
@ -75,10 +76,36 @@ class MathExecutor
$this->tokenFactory->addFunction('max', 'max', 2); $this->tokenFactory->addFunction('max', 'max', 2);
$this->tokenFactory->addFunction('avg', function($arg1, $arg2) { return ($arg1 + $arg2) / 2; }, 2); $this->tokenFactory->addFunction('avg', function($arg1, $arg2) { return ($arg1 + $arg2) / 2; }, 2);
$this->setVars(array( $this->setVars([
'pi' => 3.14159265359, 'pi' => 3.14159265359,
'e' => 2.71828182846 '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 string $variable
* @param integer|float $value * @param integer|float $value
* @throws \Exception
* @return MathExecutor * @return MathExecutor
*/ */
public function setVar($variable, $value) 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; $this->variables[$variable] = $value;
return $this; return $this;
@ -134,7 +164,7 @@ class MathExecutor
*/ */
public function removeVars() public function removeVars()
{ {
$this->variables = array(); $this->variables = [];
return $this; return $this;
} }
@ -152,6 +182,16 @@ class MathExecutor
return $this; 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 * Add function to executor
* *
@ -160,13 +200,24 @@ class MathExecutor
* @param int $places Count of arguments * @param int $places Count of arguments
* @return MathExecutor * @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); $this->tokenFactory->addFunction($name, $function, $places);
return $this; 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 * Execute expression
* *

View file

@ -27,7 +27,7 @@ class MathTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($calculator->execute($expression), $phpResult); $this->assertEquals($calculator->execute($expression), $phpResult);
} }
public function testZeroDevision() public function testZeroDivision()
{ {
$calculator = new MathExecutor(); $calculator = new MathExecutor();
$this->assertEquals($calculator->execute('1 / 0'), 0); $this->assertEquals($calculator->execute('1 / 0'), 0);
@ -44,34 +44,44 @@ class MathTest extends \PHPUnit_Framework_TestCase
*/ */
public function providerExpressions() public function providerExpressions()
{ {
return array( return [
array('0.1 + 0.2'), ['0.1 + 0.2'],
array('1 + 2'), ['1 + 2'],
array('0.1 - 0.2'), ['0.1 - 0.2'],
array('1 - 2'), ['1 - 2'],
array('0.1 * 2'), ['0.1 * 2'],
array('1 * 2'), ['1 * 2'],
array('0.1 / 0.2'), ['0.1 / 0.2'],
array('1 / 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'), ['2+2*2'],
array('(2+2)*2'), ['(2+2)*2'],
array('(2+2)*-2'), ['(2+2)*-2'],
array('(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'), ['100500 * 3.5E5'],
array('100500 * 3.5E-5') ['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);
} }
} }