From e03df64281a1c33639b13c9f490d4452c1a1784d Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Thu, 10 Jan 2019 20:29:20 -0500 Subject: Fixed function argument ordering and default parameter type for addFunction Updated unit tests Fixed docs --- README.md | 8 ++++---- src/NXP/Classes/Token/TokenFunction.php | 2 +- src/NXP/MathExecutor.php | 2 +- tests/MathTest.php | 8 ++++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b86f9c6..0c87108 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,11 @@ Default functions: Add custom function to executor: ```php -$executor->addFunction('abs', function($arg) { - return abs($arg); -}, 1); +$executor->addFunction('abs', function($arg) {return abs($arg);}); ``` +Default parameters are not currently supported. + ## Operators: Default operators: `+ - * / ^` @@ -113,7 +113,7 @@ Default variables: ``` $pi = 3.14159265359 -$e = 2.71828182846 +$e = 2.71828182846 ``` You can add your own variables to executor: diff --git a/src/NXP/Classes/Token/TokenFunction.php b/src/NXP/Classes/Token/TokenFunction.php index b2866c3..d61bb5d 100644 --- a/src/NXP/Classes/Token/TokenFunction.php +++ b/src/NXP/Classes/Token/TokenFunction.php @@ -32,7 +32,7 @@ class TokenFunction extends AbstractContainerToken implements InterfaceFunction $args = []; list($places, $function) = $this->value; for ($i = 0; $i < $places; $i++) { - array_push($args, array_pop($stack)->getValue()); + $args[] = array_shift($stack)->getValue(); } $result = call_user_func_array($function, $args); diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 29c6a64..6325d35 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -174,7 +174,7 @@ class MathExecutor * @return MathExecutor * @throws \ReflectionException */ - public function addFunction($name, $function = null, $places = 1) + public function addFunction($name, $function = null, $places = null) { $this->tokenFactory->addFunction($name, $function, $places); diff --git a/tests/MathTest.php b/tests/MathTest.php index 55e0799..d1c9604 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -146,6 +146,14 @@ class MathTest extends \PHPUnit_Framework_TestCase $this->assertEquals($calculator->execute('round(100/30)'), $phpResult); } + public function testFunctionsWithQuotes() + { + $calculator = new MathExecutor(); + $calculator->addFunction('concat', function($first, $second){return $first.$second;}); + $this->assertEquals('testing', $calculator->execute('concat("test", "ing")')); + $this->assertEquals('testing', $calculator->execute("concat('test', 'ing')")); + } + public function testQuotes() { $calculator = new MathExecutor(); -- cgit v1.2.3 From a4849cbcc5402f3dd00965abe44df3239341bae8 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Fri, 16 Aug 2019 14:01:53 -0400 Subject: Subtraction fix (#46) (#7) * Updated unit tests * Fixed docs * Better unary minus support --- .gitignore | 3 ++- README.md | 10 ++++++++-- phpunit.xml.dist | 1 - src/NXP/Classes/Lexer.php | 20 ++++++++++++++++++-- tests/MathTest.php | 23 ++++++++++++++++++++++- 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 83436db..c3b38ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vendor/ .idea/ -composer.lock \ No newline at end of file +composer.lock +.phpunit.result.cache diff --git a/README.md b/README.md index 81d9475..7238a0b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ $executor->addFunction('abs', function($arg) {return abs($arg);}); ``` Function default parameters are not supported at this time. +Default parameters are not currently supported. + ## Operators: Default operators: `+ - * / ^` @@ -112,7 +114,7 @@ Default variables: ``` $pi = 3.14159265359 -$e = 2.71828182846 +$e = 2.71828182846 ``` You can add your own variables to executor: @@ -138,7 +140,7 @@ try { ``` ## Unary Minus Operator: -Negative numbers are supported via the unary minus operator, but need to have a space before the minus sign. `-1+ -3` is legal, while `-1+-3` will produce an error due to the way the parser works. Positive numbers are not explicitly supported as unsigned numbers are assumed positive. +Negative numbers are supported via the unary minus operator. Positive numbers are not explicitly supported as unsigned numbers are assumed positive. ## String Support: Expressions can contain double or single quoted strings that are evaluated the same way as PHP evalutes strings as numbers. You can also pass strings to functions. @@ -156,3 +158,7 @@ You can add operators, functions and variables with the public methods in MathEx This will allow you to remove functions and operators if needed, or implement different types more simply. Also note that you can replace an existing default operator by adding a new operator with the same regular expression string. For example if you just need to redefine TokenPlus, you can just add a new operator with the same regex string, in this case '\\+'. + +## Future Enhancements + +At some point this package will be upgraded to a currently supported version of PHP. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bfe1846..d515f51 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" bootstrap="./tests/bootstrap.php" > diff --git a/src/NXP/Classes/Lexer.php b/src/NXP/Classes/Lexer.php index 898d8c1..73c25ac 100644 --- a/src/NXP/Classes/Lexer.php +++ b/src/NXP/Classes/Lexer.php @@ -15,11 +15,12 @@ use NXP\Classes\Token\InterfaceOperator; use NXP\Classes\Token\TokenComma; use NXP\Classes\Token\TokenFunction; use NXP\Classes\Token\TokenLeftBracket; +use NXP\Classes\Token\TokenMinus; use NXP\Classes\Token\TokenNumber; use NXP\Classes\Token\TokenRightBracket; +use NXP\Classes\Token\TokenStringDoubleQuoted; use NXP\Classes\Token\TokenStringSingleQuoted; use NXP\Classes\Token\TokenVariable; -use NXP\Classes\Token\TokenStringDoubleQuoted; use NXP\Exception\IncorrectBracketsException; use NXP\Exception\IncorrectExpressionException; @@ -66,6 +67,7 @@ class Lexer { $output = []; $stack = []; + $lastToken = null; foreach ($tokensStream as $token) { if ($token instanceof TokenStringDoubleQuoted) { @@ -73,7 +75,20 @@ class Lexer } elseif ($token instanceof TokenStringSingleQuoted) { $output[] = $token; } elseif ($token instanceof TokenNumber) { - $output[] = $token; + // if the number starts with a minus sign, it could be a negative number, or it could be an operator grabbed by the greedy regex + // if previous token is an operator, then it negative, otherwise remove the minus sign and put a negative operator on the stack + if ($lastToken !== null) { + $value = (int)$token->getValue(); + if ($value < 0 && ! ($lastToken instanceof AbstractOperator)) { + $token = new TokenNumber(abs($value)); + $output[] = $token; + $output[] = new TokenMinus('-'); + } else { + $output[] = $token; + } + } else { + $output[] = $token; + } } elseif ($token instanceof TokenVariable) { $output[] = $token; } elseif ($token instanceof TokenFunction) { @@ -118,6 +133,7 @@ class Lexer $output[] = array_pop($stack); } } + $lastToken = $token; } while (!empty($stack)) { $token = array_pop($stack); diff --git a/tests/MathTest.php b/tests/MathTest.php index 3cf4dbd..7f5ce9b 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -41,6 +41,15 @@ class MathTest extends \PHPUnit_Framework_TestCase public function providerExpressions() { return [ + ['-5'], + ['-5+10'], + ['4-5'], + ['4 -5'], + ['(4*2)-5'], + ['(4*2) - 5'], + ['4*-5'], + ['4 * -5'], + ['0.1 + 0.2'], ['1 + 2'], @@ -80,7 +89,6 @@ class MathTest extends \PHPUnit_Framework_TestCase ['1 + 2 * 3 / (3 * min(1, 5) * 2 + 1)'], ['1 + 2 * 3 / (3 / min(1, 5) / 2 + 1)'], - ['sin(10) * cos(50) / min(10, 20/2)'], ['sin(10) * cos(50) / min(10, (20/2))'], ['sin(10) * cos(50) / min(10, (max(10,20)/2))'], @@ -97,6 +105,11 @@ class MathTest extends \PHPUnit_Framework_TestCase ['-1- -2'], ['-1/-2'], ['-1*-2'], + + ['(1+2+3+4-5)*7/100'], + ['(1+2+3+4- 5)*7/100'], + ['( 1 + 2 + 3 + 4 - 5 ) * 7 / 100'], + ]; } @@ -166,6 +179,14 @@ class MathTest extends \PHPUnit_Framework_TestCase $this->assertEquals($phpResult, $calculator->execute($expression)); } + public function testFunctionsWithQuotes() + { + $calculator = new MathExecutor(); + $calculator->addFunction('concat', function($first, $second){return $first.$second;}); + $this->assertEquals('testing', $calculator->execute('concat("test", "ing")')); + $this->assertEquals('testing', $calculator->execute("concat('test', 'ing')")); + } + public function testQuotes() { $calculator = new MathExecutor(); -- cgit v1.2.3 -- cgit v1.2.3 From 7988714aaac569b7cc39c3ad1831f12d54054fd7 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Wed, 30 Oct 2019 21:57:59 -0400 Subject: Upgrading PHPUnit and dropping support for 5.6 and 7.0 --- .travis.yml | 1 - README.md | 6 ------ composer.json | 4 ++-- tests/MathTest.php | 2 +- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 551e6ed..3a63f2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 5.6 - 7.1 - 7.2 - 7.3 diff --git a/README.md b/README.md index 7238a0b..58c5422 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,6 @@ $executor->addFunction('abs', function($arg) {return abs($arg);}); ``` Function default parameters are not supported at this time. -Default parameters are not currently supported. - ## Operators: Default operators: `+ - * / ^` @@ -158,7 +156,3 @@ You can add operators, functions and variables with the public methods in MathEx This will allow you to remove functions and operators if needed, or implement different types more simply. Also note that you can replace an existing default operator by adding a new operator with the same regular expression string. For example if you just need to redefine TokenPlus, you can just add a new operator with the same regex string, in this case '\\+'. - -## Future Enhancements - -At some point this package will be upgraded to a currently supported version of PHP. diff --git a/composer.json b/composer.json index dc35c2f..4c57b47 100644 --- a/composer.json +++ b/composer.json @@ -12,10 +12,10 @@ } ], "require": { - "php": ">=5.6" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "~5.0" + "phpunit/phpunit": "~7.0" }, "autoload": { "psr-0": {"NXP": "src/"} diff --git a/tests/MathTest.php b/tests/MathTest.php index 7f5ce9b..e289016 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -21,7 +21,7 @@ use NXP\Exception\UnknownOperatorException; use NXP\Exception\UnknownTokenException; use NXP\Exception\UnknownVariableException; -class MathTest extends \PHPUnit_Framework_TestCase +class MathTest extends \PHPUnit\Framework\TestCase { /** * @dataProvider providerExpressions -- cgit v1.2.3 From 84f3f967b71fc39bf61be13e46c89158ad0a34a9 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Wed, 27 Nov 2019 11:39:51 -0500 Subject: Update README.md and more function support --- README.md | 67 ++++++++++++++--- src/NXP/Classes/Token/TokenNotEqual.php | 53 ++++++++++++++ src/NXP/Classes/Token/TokenUnequal.php | 53 -------------- src/NXP/MathExecutor.php | 123 ++++++++++++++++++++++++++++---- tests/MathTest.php | 38 +++++++++- 5 files changed, 254 insertions(+), 80 deletions(-) create mode 100644 src/NXP/Classes/Token/TokenNotEqual.php delete mode 100644 src/NXP/Classes/Token/TokenUnequal.php diff --git a/README.md b/README.md index 7238a0b..7e0573b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ ## Features: * Built in support for +, -, *, / and power (^) operators plus () +* Logical operators (==, !=, <, <, >=, <=, &&, ||) +* Built in support for most PHP math functions +* Conditional If logic * Support for user defined operators * Support for user defined functions * Unlimited variable name lengths @@ -20,24 +23,54 @@ composer require "nxp/math-executor" ## Sample usage: ```php -require "vendor/autoload.php"; +require 'vendor/autoload.php'; $executor = new \NXP\MathExecutor(); -echo $executor->execute("1 + 2 * (2 - (4+10))^2 + sin(10)"); +echo $executor->execute('1 + 2 * (2 - (4+10))^2 + sin(10)'); ``` ## Functions: Default functions: -* sin -* cos -* tn -* asin +* abs * acos -* atn -* min -* max +* acosh +* asin +* atan (atn) +* atan2 +* atanh * avg +* bindec +* ceil +* cos +* cosh +* decbin +* dechex +* decoct +* deg2rad +* exp +* expm1 +* floor +* fmod +* hexdec +* hypot +* if +* intdiv +* log +* log10 +* log1p +* max +* min +* octdec +* pi +* pow +* rad2deg +* round +* sin +* sinh +* sqrt +* tan (tn) +* tanh Add custom function to executor: ```php @@ -75,7 +108,7 @@ class ModulusToken extends AbstractOperator */ public function getPriority() { - return 3; + return 180; } /** @@ -109,6 +142,18 @@ And adding to executor: $executor->addOperator('MyNamespace\ModulusToken'); ``` +## Logical operators: +Logical operators (==, !=, <, <, >=, <=, &&, ||) are supported, but logically they can only return true (1) or false (0). In order to leverage them, use the built in **if** function: + +``` +if($a > $b, $a - $b, $b - $a) +``` + +You can think of the **if** function as prototyped like: + +``` +function if($condition, $returnIfTrue, $returnIfFalse) +``` ## Variables: Default variables: @@ -161,4 +206,4 @@ Also note that you can replace an existing default operator by adding a new oper ## Future Enhancements -At some point this package will be upgraded to a currently supported version of PHP. +This package will continue to track currently supported versions of PHP. We recommend you keep PHP up-to-date. Currently the code will run under 5.6, but don't expect 5.6 support going forward. diff --git a/src/NXP/Classes/Token/TokenNotEqual.php b/src/NXP/Classes/Token/TokenNotEqual.php new file mode 100644 index 0000000..21c9454 --- /dev/null +++ b/src/NXP/Classes/Token/TokenNotEqual.php @@ -0,0 +1,53 @@ +getValue() != $op2->getValue(); + + return new TokenNumber($result); + } +} diff --git a/src/NXP/Classes/Token/TokenUnequal.php b/src/NXP/Classes/Token/TokenUnequal.php deleted file mode 100644 index c8c5d1a..0000000 --- a/src/NXP/Classes/Token/TokenUnequal.php +++ /dev/null @@ -1,53 +0,0 @@ -getValue() != $op2->getValue(); - - return new TokenNumber($result); - } -} diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 2a5f9fb..5de7dc5 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -267,7 +267,7 @@ class MathExecutor 'NXP\Classes\Token\TokenAnd', 'NXP\Classes\Token\TokenOr', 'NXP\Classes\Token\TokenEqual', - 'NXP\Classes\Token\TokenUnequal', + 'NXP\Classes\Token\TokenNotEqual', 'NXP\Classes\Token\TokenGreaterThanOrEqual', 'NXP\Classes\Token\TokenGreaterThan', 'NXP\Classes\Token\TokenLessThanOrEqual', @@ -278,33 +278,75 @@ class MathExecutor protected function defaultFunctions() { return [ - 'sin' => function ($arg) { - return sin($arg); + 'abs' => function ($arg) { + return abs($arg); }, - 'cos' => function ($arg) { - return cos($arg); + 'acos' => function ($arg) { + return acos($arg); }, - 'tn' => function ($arg) { - return tan($arg); + 'acosh' => function ($arg) { + return acosh($arg); }, 'asin' => function ($arg) { return asin($arg); }, - 'acos' => function ($arg) { - return acos($arg); - }, - 'atn' => function ($arg) { + 'atan' => function ($arg) { return atan($arg); }, - 'min' => function ($arg1, $arg2) { - return min($arg1, $arg2); + 'atan2' => function ($arg1, $arg2) { + return atan2($arg1, $arg2); }, - 'max' => function ($arg1, $arg2) { - return max($arg1, $arg2); + 'atanh' => function ($arg) { + return atanh($arg); + }, + 'atn' => function ($arg) { + return atan($arg); }, 'avg' => function ($arg1, $arg2) { return ($arg1 + $arg2) / 2; }, + 'bindec' => function ($arg) { + return bindec($arg); + }, + 'ceil' => function ($arg) { + return ceil($arg); + }, + 'cos' => function ($arg) { + return cos($arg); + }, + 'cosh' => function ($arg) { + return cosh($arg); + }, + 'decbin' => function ($arg) { + return decbin($arg); + }, + 'dechex' => function ($arg) { + return dechex($arg); + }, + 'decoct' => function ($arg) { + return decoct($arg); + }, + 'deg2rad' => function ($arg) { + return deg2rad($arg); + }, + 'exp' => function ($arg) { + return exp($arg); + }, + 'expm1' => function ($arg) { + return expm1($arg); + }, + 'floor' => function ($arg) { + return floor($arg); + }, + 'fmod' => function ($arg1, $arg2) { + return fmod($arg1, $arg2); + }, + 'hexdec' => function ($arg) { + return hexdec($arg); + }, + 'hypot' => function ($arg1, $arg2) { + return hypot($arg1, $arg2); + }, 'if' => function ($expr, $trueval, $falseval) { if ($expr === true || $expr === false) { $exres = $expr; @@ -316,6 +358,57 @@ class MathExecutor } else { return $this->execute($falseval); } + }, + 'intdiv' => function ($arg1, $arg2) { + return intdiv($arg1, $arg2); + }, + 'log' => function ($arg) { + return log($arg); + }, + 'log10' => function ($arg) { + return log10($arg); + }, + 'log1p' => function ($arg) { + return log1p($arg); + }, + 'max' => function ($arg1, $arg2) { + return max($arg1, $arg2); + }, + 'min' => function ($arg1, $arg2) { + return min($arg1, $arg2); + }, + 'octdec' => function ($arg) { + return octdec($arg); + }, + 'pi' => function () { + return pi(); + }, + 'pow' => function ($arg1, $arg2) { + return pow($arg1, $arg2); + }, + 'rad2deg' => function ($arg) { + return rad2deg($arg); + }, + 'round' => function ($arg) { + return round($arg); + }, + 'sin' => function ($arg) { + return sin($arg); + }, + 'sinh' => function ($arg) { + return sinh($arg); + }, + 'sqrt' => function ($arg) { + return sqrt($arg); + }, + 'tan' => function ($arg) { + return tan($arg); + }, + 'tanh' => function ($arg) { + return tanh($arg); + }, + 'tn' => function ($arg) { + return tan($arg); } ]; } diff --git a/tests/MathTest.php b/tests/MathTest.php index 90cdccf..27817db 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -50,7 +50,43 @@ class MathTest extends \PHPUnit\Framework\TestCase ['4*-5'], ['4 * -5'], - [cos(2)], + ['abs(1.5)'], + ['acos(0.15)'], + ['acosh(1.5)'], + ['asin(0.15)'], + ['atan(0.15)'], + ['atan2(1.5, 3.5)'], + ['atanh(0.15)'], + ['bindec("10101")'], + ['ceil(1.5)'], + ['cos(1.5)'], + ['cosh(1.5)'], + ['decbin("15")'], + ['dechex("15")'], + ['decoct("15")'], + ['deg2rad(1.5)'], + ['exp(1.5)'], + ['expm1(1.5)'], + ['floor(1.5)'], + ['fmod(1.5, 3.5)'], + ['hexdec("abcdef")'], + ['hypot(1.5, 3.5)'], + ['intdiv(10, 2)'], + ['log(1.5)'], + ['log10(1.5)'], + ['log1p(1.5)'], + ['max(1.5, 3.5)'], + ['min(1.5, 3.5)'], + ['octdec(1.5)'], + ['pi()'], + ['pow(1.5, 3.5)'], + ['rad2deg(1.5)'], + ['round(1.5)'], + ['sin(1.5)'], + ['sinh(1.5)'], + ['sqrt(1.5)'], + ['tan(1.5)'], + ['tanh(1.5)'], ['0.1 + 0.2'], ['1 + 2'], -- cgit v1.2.3 From 2db48eff956f5a63513685cb5b576b588c698912 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Sat, 30 Nov 2019 18:41:36 -0500 Subject: PHP 7.4 support --- .travis.yml | 1 + README.md | 2 -- src/NXP/MathExecutor.php | 4 ++-- tests/MathTest.php | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a63f2f..a471c3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4 before_script: - wget http://getcomposer.org/composer.phar diff --git a/README.md b/README.md index 7e0573b..18da2fd 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,6 @@ $executor->addFunction('abs', function($arg) {return abs($arg);}); ``` Function default parameters are not supported at this time. -Default parameters are not currently supported. - ## Operators: Default operators: `+ - * / ^` diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index 5de7dc5..9d3f5e8 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -365,7 +365,7 @@ class MathExecutor 'log' => function ($arg) { return log($arg); }, - 'log10' => function ($arg) { + 'log10' => function ($arg) { return log10($arg); }, 'log1p' => function ($arg) { @@ -401,7 +401,7 @@ class MathExecutor 'sqrt' => function ($arg) { return sqrt($arg); }, - 'tan' => function ($arg) { + 'tan' => function ($arg) { return tan($arg); }, 'tanh' => function ($arg) { diff --git a/tests/MathTest.php b/tests/MathTest.php index 27817db..391408e 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -73,11 +73,11 @@ class MathTest extends \PHPUnit\Framework\TestCase ['hypot(1.5, 3.5)'], ['intdiv(10, 2)'], ['log(1.5)'], - ['log10(1.5)'], + ['log10(1.5)'], ['log1p(1.5)'], ['max(1.5, 3.5)'], ['min(1.5, 3.5)'], - ['octdec(1.5)'], + ['octdec("15")'], ['pi()'], ['pow(1.5, 3.5)'], ['rad2deg(1.5)'], @@ -85,7 +85,7 @@ class MathTest extends \PHPUnit\Framework\TestCase ['sin(1.5)'], ['sinh(1.5)'], ['sqrt(1.5)'], - ['tan(1.5)'], + ['tan(1.5)'], ['tanh(1.5)'], ['0.1 + 0.2'], -- cgit v1.2.3