-- cgit v1.2.3 From 5bdc27efdb50434a6f44a01e158d5e13eff3a020 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Mon, 26 Nov 2018 09:25:33 -0500 Subject: Fixed function in () block issue --- README.md | 7 +- src/NXP/Classes/Lexer.php | 263 +++++++++++++++++++++++----------------------- tests/MathTest.php | 14 +++ 3 files changed, 151 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index f5bf318..c15dccb 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,19 @@ A simple math expressions calculator ## Features: -* Built in support for +, -, *, / and power (^) operators +* Built in support for +, -, *, / and power (^) operators plus () * Support for user defined operators * Support for user defined functions -* Unlimited varable length +* Unlimited varable name lengths * String support, as function parameters or as evaluated by PHP * Exceptions on divide by zero, or treat as zero * Unary Minus +* Pi ($pi) and Euler's number ($e) support to 11 decimal places ## Install via Composer: Stable branch ``` -composer require "nxp/math-executor" "dev-master" +composer require "nxp/math-executor" ``` Dev branch (currently unsupported) diff --git a/src/NXP/Classes/Lexer.php b/src/NXP/Classes/Lexer.php index 82b2c53..36eb43c 100644 --- a/src/NXP/Classes/Lexer.php +++ b/src/NXP/Classes/Lexer.php @@ -1,130 +1,133 @@ - - */ -class Lexer -{ - /** - * @var TokenFactory - */ - private $tokenFactory; - - public function __construct($tokenFactory) - { - $this->tokenFactory = $tokenFactory; - } - - /** - * @param string $input Source string of equation - * @return array Tokens stream - * @throws \NXP\Exception\IncorrectExpressionException - */ - public function stringToTokensStream($input) - { - $matches = []; - preg_match_all($this->tokenFactory->getTokenParserRegex(), $input, $matches); - $tokenFactory = $this->tokenFactory; - $tokensStream = array_map( - function ($token) use ($tokenFactory) { - return $tokenFactory->createToken($token); - }, - $matches[0] - ); - - return $tokensStream; - } - - /** - * @param array $tokensStream Tokens stream - * @return array Array of tokens in revers polish notation - * @throws \NXP\Exception\IncorrectExpressionException - */ - public function buildReversePolishNotation($tokensStream) - { - $output = []; - $stack = []; - - foreach ($tokensStream as $token) { - if ($token instanceof TokenString) { - $output[] = $token; - } - if ($token instanceof TokenNumber) { - $output[] = $token; - } - if ($token instanceof TokenVariable) { - $output[] = $token; - } - if ($token instanceof TokenFunction) { - array_push($stack, $token); - } - if ($token instanceof TokenLeftBracket) { - array_push($stack, $token); - } - if ($token instanceof TokenComma) { - while (($current = array_pop($stack)) && (!$current instanceof TokenLeftBracket)) { - $output[] = $current; - if (empty($stack)) { - throw new IncorrectExpressionException(); - } - } - } - if ($token instanceof TokenRightBracket) { - while (($current = array_pop($stack)) && (!$current instanceof TokenLeftBracket)) { - $output[] = $current; - } - if (!empty($stack) && ($stack[count($stack)-1] instanceof TokenFunction)) { - $output[] = array_pop($stack); - } - } - - if ($token instanceof AbstractOperator) { - while ( - count($stack) > 0 && - ($stack[count($stack)-1] instanceof InterfaceOperator) && - (( - $token->getAssociation() == AbstractOperator::LEFT_ASSOC && - $token->getPriority() <= $stack[count($stack)-1]->getPriority() - ) || ( - $token->getAssociation() == AbstractOperator::RIGHT_ASSOC && - $token->getPriority() < $stack[count($stack)-1]->getPriority() - )) - ) { - $output[] = array_pop($stack); - } - - array_push($stack, $token); - } - } - while (!empty($stack)) { - $token = array_pop($stack); - if ($token instanceof TokenLeftBracket || $token instanceof TokenRightBracket) { - throw new IncorrectBracketsException(); - } - $output[] = $token; - } - - return $output; - } -} + + */ +class Lexer +{ + /** + * @var TokenFactory + */ + private $tokenFactory; + + public function __construct($tokenFactory) + { + $this->tokenFactory = $tokenFactory; + } + + /** + * @param string $input Source string of equation + * @return array Tokens stream + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function stringToTokensStream($input) + { + $matches = []; + preg_match_all($this->tokenFactory->getTokenParserRegex(), $input, $matches); + $tokenFactory = $this->tokenFactory; + $tokensStream = array_map( + function ($token) use ($tokenFactory) { + return $tokenFactory->createToken($token); + }, + $matches[0] + ); + + return $tokensStream; + } + + /** + * @param array $tokensStream Tokens stream + * @return array Array of tokens in revers polish notation + * @throws \NXP\Exception\IncorrectExpressionException + */ + public function buildReversePolishNotation($tokensStream) + { + $output = []; + $stack = []; + + foreach ($tokensStream as $token) { + if ($token instanceof TokenString) { + $output[] = $token; + } + elseif ($token instanceof TokenNumber) { + $output[] = $token; + } + elseif ($token instanceof TokenVariable) { + $output[] = $token; + } + elseif ($token instanceof TokenFunction) { + array_push($stack, $token); + } + elseif ($token instanceof AbstractOperator) { + // While we have something on the stack + while (($count = count($stack)) > 0 + && ( + // If it is a function + ($stack[$count-1] instanceof TokenFunction) + + || + // Or the operator at the top of the operator stack + // has (left associative and equal precedence) + // or has greater precedence + (($stack[$count-1] instanceof InterfaceOperator) && + ( + ($stack[$count-1]->getAssociation() == AbstractOperator::LEFT_ASSOC && + $token->getPriority() == $stack[$count-1]->getPriority()) + || + ($stack[$count-1]->getPriority() > $token->getPriority()) + ) + ) + ) + + // And not a left bracket + && ( ! ($stack[$count-1] instanceof TokenLeftBracket)) ) { + $output[] = array_pop($stack); + } + + array_push($stack, $token); + } + elseif ($token instanceof TokenLeftBracket) { + array_push($stack, $token); + } + elseif ($token instanceof TokenRightBracket) { + while (($current = array_pop($stack)) && ( ! ($current instanceof TokenLeftBracket))) { + $output[] = $current; + } + if (!empty($stack) && ($stack[count($stack)-1] instanceof TokenFunction)) { + $output[] = array_pop($stack); + } + } + } + while (!empty($stack)) { + $token = array_pop($stack); + if ($token instanceof TokenLeftBracket || $token instanceof TokenRightBracket) { + throw new IncorrectBracketsException(); + } + $output[] = $token; + } + + return $output; + } +} diff --git a/tests/MathTest.php b/tests/MathTest.php index c5dd60d..9acdb63 100644 --- a/tests/MathTest.php +++ b/tests/MathTest.php @@ -69,7 +69,21 @@ class MathTest extends \PHPUnit_Framework_TestCase ['(2 + 2)*-2'], ['(2+-2)*2'], + ['1 + 2 * 3 / (min(1, 5) + 2 + 1)'], + ['1 + 2 * 3 / (min(1, 5) - 2 + 5)'], + ['1 + 2 * 3 / (min(1, 5) * 2 + 1)'], + ['1 + 2 * 3 / (min(1, 5) / 2 + 1)'], + ['1 + 2 * 3 / (min(1, 5) / 2 * 1)'], + ['1 + 2 * 3 / (min(1, 5) / 2 / 1)'], + ['1 + 2 * 3 / (3 + min(1, 5) + 2 + 1)'], + ['1 + 2 * 3 / (3 - min(1, 5) - 2 + 1)'], + ['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))'], ['100500 * 3.5E5'], ['100500 * 3.5E-5'], -- cgit v1.2.3 From 83b9b6a3f2c621a8bb7a12358d589404d64c3509 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Wed, 19 Dec 2018 14:46:30 -0500 Subject: Fixing typos in and clarifying documentation. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c15dccb..39490a4 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ A simple math expressions calculator * Built in support for +, -, *, / and power (^) operators plus () * Support for user defined operators * Support for user defined functions -* Unlimited varable name lengths -* String support, as function parameters or as evaluated by PHP +* Unlimited variable name lengths +* String support, as function parameters or as evaluated as a number by PHP * Exceptions on divide by zero, or treat as zero -* Unary Minus +* Unary Minus (e.g. -3) * Pi ($pi) and Euler's number ($e) support to 11 decimal places ## Install via Composer: @@ -121,7 +121,7 @@ $pi = 3.14159265359 $e = 2.71828182846 ``` -You can add own variable to executor: +You can add your own variables to executor: ```php $executor->setVars([ @@ -132,7 +132,7 @@ $executor->setVars([ echo $executor->execute("$var1 + $var2"); ``` ## Division By Zero Support: -By default, the result of division by zero is zero and no error is generated. You have the option to thow a \NXP\Exception\DivisionByZeroException by by calling setDivisionByZeroException. +By default, the result of division by zero is zero and no error is generated. You have the option to thow a \NXP\Exception\DivisionByZeroException by calling setDivisionByZeroException. ```php $executor->setDivisionByZeroException(); @@ -147,7 +147,7 @@ try { 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. ## String Support: -Expressions can contain double or single quoted strings that are evaluated the same way as PHP. You can also pass strings to functions. +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. ```php echo $executor->execute("1 + '2.5' * '.5' + myFunction('category')"); -- cgit v1.2.3 From 0729b6b9bc0e3cebb868f889e88676b95c121a50 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Thu, 10 Jan 2019 12:45:54 -0500 Subject: Syncing from origin (#3) * Documentation fixes (#34) Fixing typos in and clarifying documentation. * MathExecutor allow override default operators, functions and vars (#36) --- src/NXP/Classes/TokenFactory.php | 11 +++-- src/NXP/MathExecutor.php | 99 ++++++++++++++++++++++++++++------------ 2 files changed, 78 insertions(+), 32 deletions(-) diff --git a/src/NXP/Classes/TokenFactory.php b/src/NXP/Classes/TokenFactory.php index 6956f31..4ed55af 100644 --- a/src/NXP/Classes/TokenFactory.php +++ b/src/NXP/Classes/TokenFactory.php @@ -50,12 +50,17 @@ class TokenFactory /** * Add function - * @param string $name + * @param string $name * @param callable $function - * @param int $places + * @param int $places + * @throws \ReflectionException */ - public function addFunction($name, callable $function, $places = 1) + public function addFunction($name, callable $function, $places = null) { + if ($places === null) { + $reflector = new \ReflectionFunction($function); + $places = $reflector->getNumberOfParameters(); + } $this->functions[$name] = [$places, $function]; } diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php index fbf6ab1..706f8af 100644 --- a/src/NXP/MathExecutor.php +++ b/src/NXP/MathExecutor.php @@ -53,35 +53,6 @@ class MathExecutor $this->addDefaults(); } - /** - * Set default operands and functions - */ - protected function addDefaults() - { - $this->tokenFactory = new TokenFactory(); - - $this->tokenFactory->addOperator('NXP\Classes\Token\TokenPlus'); - $this->tokenFactory->addOperator('NXP\Classes\Token\TokenMinus'); - $this->tokenFactory->addOperator('NXP\Classes\Token\TokenMultiply'); - $this->tokenFactory->addOperator('NXP\Classes\Token\TokenDivision'); - $this->tokenFactory->addOperator('NXP\Classes\Token\TokenDegree'); - - $this->tokenFactory->addFunction('sin', 'sin'); - $this->tokenFactory->addFunction('cos', 'cos'); - $this->tokenFactory->addFunction('tn', 'tan'); - $this->tokenFactory->addFunction('asin', 'asin'); - $this->tokenFactory->addFunction('acos', 'acos'); - $this->tokenFactory->addFunction('atn', 'atan'); - $this->tokenFactory->addFunction('min', 'min', 2); - $this->tokenFactory->addFunction('max', 'max', 2); - $this->tokenFactory->addFunction('avg', function($arg1, $arg2) { return ($arg1 + $arg2) / 2; }, 2); - - $this->setVars([ - 'pi' => 3.14159265359, - 'e' => 2.71828182846 - ]); - } - /** * Get all vars * @@ -262,4 +233,74 @@ class MathExecutor return $result; } + + /** + * Set default operands and functions + */ + protected function addDefaults() + { + $this->tokenFactory = new TokenFactory(); + + foreach ($this->defaultOperators() as $operatorClass) { + $this->tokenFactory->addOperator($operatorClass); + } + + foreach ($this->defaultFunctions() as $name => $callable) { + $this->tokenFactory->addFunction($name, $callable); + } + + $this->setVars($this->defaultVars()); + } + + protected function defaultOperators() + { + return [ + 'NXP\Classes\Token\TokenPlus', + 'NXP\Classes\Token\TokenMinus', + 'NXP\Classes\Token\TokenMultiply', + 'NXP\Classes\Token\TokenDivision', + 'NXP\Classes\Token\TokenDegree', + ]; + } + + protected function defaultFunctions() + { + return [ + 'sin' => function ($arg) { + return sin($arg); + }, + 'cos' => function ($arg) { + return cos($arg); + }, + 'tn' => function ($arg) { + return tan($arg); + }, + 'asin' => function ($arg) { + return asin($arg); + }, + 'acos' => function ($arg) { + return acos($arg); + }, + 'atn' => function ($arg) { + return atan($arg); + }, + 'min' => function ($arg1, $arg2) { + return min($arg1, $arg2); + }, + 'max' => function ($arg1, $arg2) { + return max($arg1, $arg2); + }, + 'avg' => function ($arg1, $arg2) { + return ($arg1 + $arg2) / 2; + }, + ]; + } + + protected function defaultVars() + { + return [ + 'pi' => 3.14159265359, + 'e' => 2.71828182846 + ]; + } } -- cgit v1.2.3 From ba505031a329eaf632d1af646308482f9db57b68 Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Thu, 10 Jan 2019 13:47:27 -0500 Subject: Syncing to origin (#4) * Documentation fixes (#34) Fixing typos in and clarifying documentation. * MathExecutor allow override default operators, functions and vars (#36) * Added simple coc (#37) * Added simple coc * Fix * Replaceable operators (#38) * Updated from NeonXP/MathExecutor * Fixed function in () block issue * Fixing typos in and clarifying documentation. * Syncing from origin (#3) * Documentation fixes (#34) Fixing typos in and clarifying documentation. * MathExecutor allow override default operators, functions and vars (#36) * Allow for operators to be replaced based on regex expression --- README.md | 12 ++++++++++++ code-of-conduct.md | 9 +++++++++ src/NXP/Classes/TokenFactory.php | 3 +-- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 code-of-conduct.md diff --git a/README.md b/README.md index 39490a4..6579a23 100644 --- a/README.md +++ b/README.md @@ -11,6 +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 ## Install via Composer: Stable branch @@ -152,3 +153,14 @@ Expressions can contain double or single quoted strings that are evaluated the s ```php echo $executor->execute("1 + '2.5' * '.5' + myFunction('category')"); ``` + +## Extending MathExecutor +You can add operators, functions and variables with the public methods in MathExecutor, but if you need to do more serious modifications to base behaviours, the easiest way to extend MathExecutor is to redefine the following methods in your derived class: +* defaultOperators +* defaultFunctions +* defaultVars + +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 '\+'. + diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 0000000..83e0f4e --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,9 @@ +# Code of conduct + +We don't care who you are IRL. Be professional and responsible. + +If you are a good person, we are happy for you. + +If you are an asshole to us, we will be assholes in relation to you. + +> Do to no one what you yourself dislike \ No newline at end of file diff --git a/src/NXP/Classes/TokenFactory.php b/src/NXP/Classes/TokenFactory.php index 4ed55af..778cb59 100644 --- a/src/NXP/Classes/TokenFactory.php +++ b/src/NXP/Classes/TokenFactory.php @@ -88,8 +88,7 @@ class TokenFactory throw new UnknownOperatorException($operatorClass); } - $this->operators[] = $operatorClass; - $this->operators = array_unique($this->operators); + $this->operators[$operatorClass::getRegex()] = $operatorClass; } /** -- cgit v1.2.3