diff options
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | code-of-conduct.ru.md | 5 | ||||
-rw-r--r-- | src/NXP/Classes/Calculator.php | 20 | ||||
-rw-r--r-- | src/NXP/Classes/Lexer.php | 9 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenComma.php | 30 | ||||
-rw-r--r-- | src/NXP/Classes/Token/TokenFunction.php | 3 | ||||
-rw-r--r-- | tests/MathTest.php | 48 |
7 files changed, 86 insertions, 34 deletions
@@ -1,6 +1,6 @@ # MathExecutor [![Stories in Ready](https://badge.waffle.io/NeonXP/MathExecutor.png?label=ready&title=Ready)](https://waffle.io/NeonXP/MathExecutor) [![Build Status](https://travis-ci.org/NeonXP/MathExecutor.png?branch=master)](https://travis-ci.org/NeonXP/MathExecutor) -A simple math expressions calculator +# A simple and extensible math expressions calculator ## Features: * Built in support for +, -, *, / and power (^) operators plus () @@ -11,7 +11,7 @@ A simple math expressions calculator * Exceptions on divide by zero, or treat as zero * Unary Minus (e.g. -3) * Pi ($pi) and Euler's number ($e) support to 11 decimal places -* Easily extendable +* Easily extensible ## Install via Composer: ``` @@ -43,6 +43,7 @@ Add custom function to executor: ```php $executor->addFunction('abs', function($arg) {return abs($arg);}); ``` +Function default parameters are not supported at this time. Default parameters are not currently supported. diff --git a/code-of-conduct.ru.md b/code-of-conduct.ru.md new file mode 100644 index 0000000..04a566f --- /dev/null +++ b/code-of-conduct.ru.md @@ -0,0 +1,5 @@ +# Кодекс поведения + +Нам всё равно, кто ты в реальной жизни, просто будь профессионалом и всё будет хорошо. + +Если ты будешь вести себя как сволочь - не удивляйся, что и к тебе будут относиться так же. diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php index 1ceac84..980a52b 100644 --- a/src/NXP/Classes/Calculator.php +++ b/src/NXP/Classes/Calculator.php @@ -36,25 +36,17 @@ class Calculator { $stack = []; foreach ($tokens as $token) { - if ($token instanceof TokenNumber) { - array_push($stack, $token); - } - if ($token instanceof TokenStringDoubleQuoted) { - array_push($stack, $token); - } - if ($token instanceof TokenStringSingleQuoted) { - array_push($stack, $token); - } - if ($token instanceof TokenVariable) { + if ($token instanceof TokenNumber || $token instanceof TokenStringDoubleQuoted || $token instanceof TokenStringSingleQuoted) { + $stack[] = $token; + } else if ($token instanceof TokenVariable) { $variable = $token->getValue(); if (!array_key_exists($variable, $variables)) { throw new UnknownVariableException($variable); } $value = $variables[$variable]; - array_push($stack, new TokenNumber($value)); - } - if ($token instanceof InterfaceOperator || $token instanceof TokenFunction) { - array_push($stack, $token->execute($stack)); + $stack[] = new TokenNumber($value); + } else if ($token instanceof InterfaceOperator || $token instanceof TokenFunction) { + $stack[] = $token->execute($stack); } } $result = array_pop($stack); diff --git a/src/NXP/Classes/Lexer.php b/src/NXP/Classes/Lexer.php index a511c9b..898d8c1 100644 --- a/src/NXP/Classes/Lexer.php +++ b/src/NXP/Classes/Lexer.php @@ -77,7 +77,7 @@ class Lexer } elseif ($token instanceof TokenVariable) { $output[] = $token; } elseif ($token instanceof TokenFunction) { - array_push($stack, $token); + $stack[] = $token; } elseif ($token instanceof AbstractOperator) { // While we have something on the stack while (($count = count($stack)) > 0 @@ -104,9 +104,12 @@ class Lexer $output[] = array_pop($stack); } - array_push($stack, $token); + // Comma operators do nothing really, don't put them on the stack + if (! ($token instanceof TokenComma)) { + $stack[] = $token; + } } elseif ($token instanceof TokenLeftBracket) { - array_push($stack, $token); + $stack[] = $token; } elseif ($token instanceof TokenRightBracket) { while (($current = array_pop($stack)) && (!($current instanceof TokenLeftBracket))) { $output[] = $current; diff --git a/src/NXP/Classes/Token/TokenComma.php b/src/NXP/Classes/Token/TokenComma.php index 6a45595..f6fc068 100644 --- a/src/NXP/Classes/Token/TokenComma.php +++ b/src/NXP/Classes/Token/TokenComma.php @@ -13,7 +13,7 @@ namespace NXP\Classes\Token; /** * @author Alexander Kiryukhin <a.kiryukhin@mail.ru> */ -class TokenComma implements InterfaceToken +class TokenComma extends AbstractOperator { /** * @return string @@ -22,4 +22,32 @@ class TokenComma implements InterfaceToken { return '\,'; } + + /** + * Comma operator is lowest priority + * + * @return int + */ + public function getPriority() + { + return 0; + } + + /** + * @return string + */ + public function getAssociation() + { + return self::LEFT_ASSOC; + } + + /** + * @param array $stack + * @return TokenNumber + */ + public function execute(&$stack) + { + // Comma operators don't do anything, stack has already executed + } + } diff --git a/src/NXP/Classes/Token/TokenFunction.php b/src/NXP/Classes/Token/TokenFunction.php index d61bb5d..432f107 100644 --- a/src/NXP/Classes/Token/TokenFunction.php +++ b/src/NXP/Classes/Token/TokenFunction.php @@ -32,8 +32,9 @@ class TokenFunction extends AbstractContainerToken implements InterfaceFunction $args = []; list($places, $function) = $this->value; for ($i = 0; $i < $places; $i++) { - $args[] = array_shift($stack)->getValue(); + array_unshift($args, array_pop($stack)->getValue()); } + $result = call_user_func_array($function, $args); return new TokenNumber($result); diff --git a/tests/MathTest.php b/tests/MathTest.php index d1c9604..eba32c0 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -32,7 +32,7 @@ class MathTest extends \PHPUnit_Framework_TestCase /** @var float $phpResult */ eval('$phpResult = ' . $expression . ';'); - $this->assertEquals($calculator->execute($expression), $phpResult, "Expression was: ${expression}"); + $this->assertEquals($phpResult, $calculator->execute($expression), "Expression was: ${expression}"); } /** @@ -117,7 +117,7 @@ class MathTest extends \PHPUnit_Framework_TestCase public function testZeroDivision() { $calculator = new MathExecutor(); - $this->assertEquals($calculator->execute('10 / 0'), 0); + $this->assertEquals(0, $calculator->execute('10 / 0')); } public function testZeroDivisionException() @@ -131,19 +131,39 @@ class MathTest extends \PHPUnit_Framework_TestCase public function testExponentiation() { $calculator = new MathExecutor(); - $this->assertEquals($calculator->execute('10 ^ 2'), 100); + $this->assertEquals(100, $calculator->execute('10 ^ 2')); + } + + public function testFunctionParameterOrder() + { + $calculator = new MathExecutor(); + + $calculator->addFunction('concat', function ($arg1, $arg2) {return $arg1.$arg2;}); + $this->assertEquals('testing', $calculator->execute('concat("test","ing")')); + $this->assertEquals('testing', $calculator->execute("concat('test','ing')")); } public function testFunction() { $calculator = new MathExecutor(); + $calculator->addFunction('round', function ($arg) {return round($arg);}); + $this->assertEquals(round(100/30), $calculator->execute('round(100/30)')); + } - $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); + public function testEvaluateFunctionParameters() + { + $calculator = new MathExecutor(); + $calculator->addFunction('round', function ($value, $decimals) + { + return round($value, $decimals); + } + ); + $expression = 'round(100 * 1.111111, 2)'; + eval('$phpResult = ' . $expression . ';'); + $this->assertEquals($phpResult, $calculator->execute($expression)); + $expression = 'round((100*0.04)+(((100*1.02)+0.5)*1.28),2)'; + eval('$phpResult = ' . $expression . ';'); + $this->assertEquals($phpResult, $calculator->execute($expression)); } public function testFunctionsWithQuotes() @@ -158,11 +178,13 @@ class MathTest extends \PHPUnit_Framework_TestCase { $calculator = new MathExecutor(); $testString = "some, long. arg; with: different-separators!"; - $calculator->addFunction('test', function ($arg) use ($testString) { - $this->assertEquals($arg, $testString); + $calculator->addFunction('test', function ($arg) use ($testString) + { + $this->assertEquals($testString, $arg); return 0; - }, 1); + } + ); $calculator->execute('test("' . $testString . '")'); // single quotes $calculator->execute("test('" . $testString . "')"); // double quotes } -}
\ No newline at end of file +} |