aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier Marín <javier@marinros.com>2022-05-09 21:13:30 +0300
committerGitHub <noreply@github.com>2022-05-09 21:13:30 +0300
commit645f1dfbc6310185b73852c5008ef321b66a0f18 (patch)
tree2efdfcf4aabeb3c4bc75a80aac7e54dbe68aebe5
parentb7b46bfc476ea0d22e0e92144f68aa81d390fff0 (diff)
Two more tests + some code refactoring (#104)
* test: add testNullReturnType and testUnsupportedOperands * refactor: fix PhpDoc comments and use PHP 7.4 arrow functions * refactor: fix PHP-CS-Fixer issues * test: run testUnsupportedOperands() only on PHP8+ Co-authored-by: Javier Marín <contacto@ideatic.net>
-rw-r--r--src/NXP/Classes/Calculator.php15
-rw-r--r--src/NXP/Classes/CustomFunction.php2
-rw-r--r--src/NXP/Classes/Token.php2
-rw-r--r--src/NXP/Classes/Tokenizer.php2
-rw-r--r--src/NXP/MathExecutor.php397
-rw-r--r--tests/MathTest.php35
6 files changed, 133 insertions, 320 deletions
diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php
index 2bb5b4d..10adc24 100644
--- a/src/NXP/Classes/Calculator.php
+++ b/src/NXP/Classes/Calculator.php
@@ -20,10 +20,18 @@ use NXP\Exception\UnknownVariableException;
*/
class Calculator
{
+ /** @var array<string, CustomFunction> */
private array $functions = [];
+ /** @var array<Operator> */
private array $operators = [];
+ /**
+ * @todo PHP8: Use constructor property promotion -> public function __construct(private array $functions, private array $operators)
+ *
+ * @param array<string, CustomFunction> $functions
+ * @param array<Operator> $operators
+ */
public function __construct(array $functions, array $operators)
{
$this->functions = $functions;
@@ -32,10 +40,13 @@ class Calculator
/**
* Calculate array of tokens in reverse polish notation
- * @param Token[] $tokens
+ *
+ * @param Token[] $tokens
* @param array<string, float|string> $variables
- * @throws IncorrectExpressionException
+ *
* @throws UnknownVariableException
+ * @throws IncorrectExpressionException
+ * @return int|float|string|null
*/
public function calculate(array $tokens, array $variables, ?callable $onVarNotFound = null)
{
diff --git a/src/NXP/Classes/CustomFunction.php b/src/NXP/Classes/CustomFunction.php
index bc0f4e7..843cf82 100644
--- a/src/NXP/Classes/CustomFunction.php
+++ b/src/NXP/Classes/CustomFunction.php
@@ -19,7 +19,7 @@ class CustomFunction
/**
* CustomFunction constructor.
- * @param int $places
+ *
* @throws ReflectionException
* @throws IncorrectNumberOfFunctionParametersException
*/
diff --git a/src/NXP/Classes/Token.php b/src/NXP/Classes/Token.php
index b2a0a64..41e4cba 100644
--- a/src/NXP/Classes/Token.php
+++ b/src/NXP/Classes/Token.php
@@ -24,12 +24,14 @@ class Token
public string $type = self::Literal;
+
public $value;
public ?string $name;
/**
* Token constructor.
+ *
*/
public function __construct(string $type, $value, ?string $name = null)
{
diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php
index 1bd6d6d..32404a2 100644
--- a/src/NXP/Classes/Tokenizer.php
+++ b/src/NXP/Classes/Tokenizer.php
@@ -20,6 +20,7 @@ use SplStack;
*/
class Tokenizer
{
+ /** @var array<Token> */
public array $tokens = [];
private string $input = '';
@@ -30,6 +31,7 @@ class Tokenizer
private bool $allowNegative = true;
+ /** @var array<Operator> */
private array $operators = [];
private bool $inSingleQuotedString = false;
diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php
index f980270..d2bcfe6 100644
--- a/src/NXP/MathExecutor.php
+++ b/src/NXP/MathExecutor.php
@@ -90,7 +90,7 @@ class MathExecutor
* @throws Exception\IncorrectExpressionException
* @throws Exception\UnknownOperatorException
* @throws UnknownVariableException
- * @return number
+ * @return int|float|string|null
*/
public function execute(string $expression, bool $cache = true)
{
@@ -114,9 +114,11 @@ class MathExecutor
/**
* Add function to executor
*
- * @param string $name Name of function
- * @param callable $function Function
- * @param int $places Count of arguments
+ * @param string $name Name of function
+ * @param callable|null $function Function
+ * @param int|null $places Count of arguments
+ *
+ * @throws Exception\IncorrectNumberOfFunctionParametersException
* @throws ReflectionException
* @return MathExecutor
*/
@@ -159,7 +161,6 @@ class MathExecutor
/**
* Add variable to executor. To set a custom validator use setVarValidationHandler.
*
- * @param $value
* @throws MathExecutorException if the value is invalid based on the default or custom validator
* @return MathExecutor
*/
@@ -281,18 +282,10 @@ class MathExecutor
/**
* Set division by zero returns zero instead of throwing DivisionByZeroException
- *
- * @return MathExecutor
*/
public function setDivisionByZeroIsZero() : self
{
- $this->addOperator(new Operator('/', false, 180, static function($a, $b) {
- if (0 == $b) {
- return 0;
- }
-
- return $a / $b;
- }));
+ $this->addOperator(new Operator('/', false, 180, static fn($a, $b) => 0 == $b ? 0 : $a / $b));
return $this;
}
@@ -341,43 +334,15 @@ class MathExecutor
protected function defaultOperators() : array
{
return [
- '+' => [
- static function($a, $b) {
- return $a + $b;
- },
- 170,
- false
- ],
- '-' => [
- static function($a, $b) {
- return $a - $b;
- },
- 170,
- false
- ],
- 'uPos' => [ // unary positive token
- static function($a) {
- return $a;
- },
- 200,
- false
- ],
- 'uNeg' => [ // unary minus token
- static function($a) {
- return 0 - $a;
- },
- 200,
- false
- ],
- '*' => [
- static function($a, $b) {
- return $a * $b;
- },
- 180,
- false
- ],
+ '+' => [static fn($a, $b) => $a + $b, 170, false],
+ '-' => [static fn($a, $b) => $a - $b, 170, false],
+ // unary positive token
+ 'uPos' => [static fn($a) => $a, 200, false],
+ // unary minus token
+ 'uNeg' => [static fn($a) => 0 - $a, 200, false],
+ '*' => [static fn($a, $b) => $a * $b, 180, false],
'/' => [
- static function($a, $b) {
+ static function($a, $b) { /** @todo PHP8: Use throw as expression -> static fn($a, $b) => 0 == $b ? throw new DivisionByZeroException() : $a / $b */
if (0 == $b) {
throw new DivisionByZeroException();
}
@@ -387,79 +352,15 @@ class MathExecutor
180,
false
],
- '^' => [
- static function($a, $b) {
- return \pow($a, $b);
- },
- 220,
- true
- ],
- '&&' => [
- static function($a, $b) {
- return $a && $b;
- },
- 100,
- false
- ],
- '||' => [
- static function($a, $b) {
- return $a || $b;
- },
- 90,
- false
- ],
- '==' => [
- static function($a, $b) {
- if (\is_string($a) || \is_string($b)) {
- return 0 == \strcmp($a, $b);
- }
-
- return $a == $b;
-
- },
- 140,
- false
- ],
- '!=' => [
- static function($a, $b) {
- if (\is_string($a) || \is_string($b)) {
- return 0 != \strcmp($a, $b);
- }
-
- return $a != $b;
-
- },
- 140,
- false
- ],
- '>=' => [
- static function($a, $b) {
- return $a >= $b;
- },
- 150,
- false
- ],
- '>' => [
- static function($a, $b) {
- return $a > $b;
- },
- 150,
- false
- ],
- '<=' => [
- static function($a, $b) {
- return $a <= $b;
- },
- 150,
- false
- ],
- '<' => [
- static function($a, $b) {
- return $a < $b;
- },
- 150,
- false
- ],
+ '^' => [static fn($a, $b) => \pow($a, $b), 220, true],
+ '&&' => [static fn($a, $b) => $a && $b, 100, false],
+ '||' => [static fn($a, $b) => $a || $b, 90, false],
+ '==' => [static fn($a, $b) => \is_string($a) || \is_string($b) ? 0 == \strcmp($a, $b) : $a == $b, 140, false],
+ '!=' => [static fn($a, $b) => \is_string($a) || \is_string($b) ? 0 != \strcmp($a, $b) : $a != $b, 140, false],
+ '>=' => [static fn($a, $b) => $a >= $b, 150, false],
+ '>' => [static fn($a, $b) => $a > $b, 150, false],
+ '<=' => [static fn($a, $b) => $a <= $b, 150, false],
+ '<' => [static fn($a, $b) => $a < $b, 150, false],
];
}
@@ -472,126 +373,46 @@ class MathExecutor
protected function defaultFunctions() : array
{
return [
- 'abs' => static function($arg) {
- return \abs($arg);
- },
- 'acos' => static function($arg) {
- return \acos($arg);
- },
- 'acosh' => static function($arg) {
- return \acosh($arg);
- },
- 'arcsin' => static function($arg) {
- return \asin($arg);
- },
- 'arcctg' => static function($arg) {
- return M_PI / 2 - \atan($arg);
- },
- 'arccot' => static function($arg) {
- return M_PI / 2 - \atan($arg);
- },
- 'arccotan' => static function($arg) {
- return M_PI / 2 - \atan($arg);
- },
- 'arcsec' => static function($arg) {
- return \acos(1 / $arg);
- },
- 'arccosec' => static function($arg) {
- return \asin(1 / $arg);
- },
- 'arccsc' => static function($arg) {
- return \asin(1 / $arg);
- },
- 'arccos' => static function($arg) {
- return \acos($arg);
- },
- 'arctan' => static function($arg) {
- return \atan($arg);
- },
- 'arctg' => static function($arg) {
- return \atan($arg);
- },
- 'asin' => static function($arg) {
- return \asin($arg);
- },
- 'atan' => static function($arg) {
- return \atan($arg);
- },
- 'atan2' => static function($arg1, $arg2) {
- return \atan2($arg1, $arg2);
- },
- 'atanh' => static function($arg) {
- return \atanh($arg);
- },
- 'atn' => static function($arg) {
- return \atan($arg);
- },
- 'avg' => static function($arg1, $arg2) {
- return ($arg1 + $arg2) / 2;
- },
- 'bindec' => static function($arg) {
- return \bindec($arg);
- },
- 'ceil' => static function($arg) {
- return \ceil($arg);
- },
- 'cos' => static function($arg) {
- return \cos($arg);
- },
- 'cosec' => static function($arg) {
- return 1 / \sin($arg);
- },
- 'csc' => static function($arg) {
- return 1 / \sin($arg);
- },
- 'cosh' => static function($arg) {
- return \cosh($arg);
- },
- 'ctg' => static function($arg) {
- return \cos($arg) / \sin($arg);
- },
- 'cot' => static function($arg) {
- return \cos($arg) / \sin($arg);
- },
- 'cotan' => static function($arg) {
- return \cos($arg) / \sin($arg);
- },
- 'cotg' => static function($arg) {
- return \cos($arg) / \sin($arg);
- },
- 'ctn' => static function($arg) {
- return \cos($arg) / \sin($arg);
- },
- 'decbin' => static function($arg) {
- return \decbin($arg);
- },
- 'dechex' => static function($arg) {
- return \dechex($arg);
- },
- 'decoct' => static function($arg) {
- return \decoct($arg);
- },
- 'deg2rad' => static function($arg) {
- return \deg2rad($arg);
- },
- 'exp' => static function($arg) {
- return \exp($arg);
- },
- 'expm1' => static function($arg) {
- return \expm1($arg);
- },
- 'floor' => static function($arg) {
- return \floor($arg);
- },
- 'fmod' => static function($arg1, $arg2) {
- return \fmod($arg1, $arg2);
- },
- 'hexdec' => static function($arg) {
- return \hexdec($arg);
- },
- 'hypot' => static function($arg1, $arg2) {
- return \hypot($arg1, $arg2);
- },
+ 'abs' => static fn($arg) => \abs($arg),
+ 'acos' => static fn($arg) => \acos($arg),
+ 'acosh' => static fn($arg) => \acosh($arg),
+ 'arcsin' => static fn($arg) => \asin($arg),
+ 'arcctg' => static fn($arg) => M_PI / 2 - \atan($arg),
+ 'arccot' => static fn($arg) => M_PI / 2 - \atan($arg),
+ 'arccotan' => static fn($arg) => M_PI / 2 - \atan($arg),
+ 'arcsec' => static fn($arg) => \acos(1 / $arg),
+ 'arccosec' => static fn($arg) => \asin(1 / $arg),
+ 'arccsc' => static fn($arg) => \asin(1 / $arg),
+ 'arccos' => static fn($arg) => \acos($arg),
+ 'arctan' => static fn($arg) => \atan($arg),
+ 'arctg' => static fn($arg) => \atan($arg),
+ 'asin' => static fn($arg) => \asin($arg),
+ 'atan' => static fn($arg) => \atan($arg),
+ 'atan2' => static fn($arg1, $arg2) => \atan2($arg1, $arg2),
+ 'atanh' => static fn($arg) => \atanh($arg),
+ 'atn' => static fn($arg) => \atan($arg),
+ 'avg' => static fn($arg1, $arg2) => ($arg1 + $arg2) / 2,
+ 'bindec' => static fn($arg) => \bindec($arg),
+ 'ceil' => static fn($arg) => \ceil($arg),
+ 'cos' => static fn($arg) => \cos($arg),
+ 'cosec' => static fn($arg) => 1 / \sin($arg),
+ 'csc' => static fn($arg) => 1 / \sin($arg),
+ 'cosh' => static fn($arg) => \cosh($arg),
+ 'ctg' => static fn($arg) => \cos($arg) / \sin($arg),
+ 'cot' => static fn($arg) => \cos($arg) / \sin($arg),
+ 'cotan' => static fn($arg) => \cos($arg) / \sin($arg),
+ 'cotg' => static fn($arg) => \cos($arg) / \sin($arg),
+ 'ctn' => static fn($arg) => \cos($arg) / \sin($arg),
+ 'decbin' => static fn($arg) => \decbin($arg),
+ 'dechex' => static fn($arg) => \dechex($arg),
+ 'decoct' => static fn($arg) => \decoct($arg),
+ 'deg2rad' => static fn($arg) => \deg2rad($arg),
+ 'exp' => static fn($arg) => \exp($arg),
+ 'expm1' => static fn($arg) => \expm1($arg),
+ 'floor' => static fn($arg) => \floor($arg),
+ 'fmod' => static fn($arg1, $arg2) => \fmod($arg1, $arg2),
+ 'hexdec' => static fn($arg) => \hexdec($arg),
+ 'hypot' => static fn($arg1, $arg2) => \hypot($arg1, $arg2),
'if' => function($expr, $trueval, $falseval) {
if (true === $expr || false === $expr) {
$exres = $expr;
@@ -603,72 +424,29 @@ class MathExecutor
return $this->execute($trueval);
}
- return $this->execute($falseval);
-
- },
- 'intdiv' => static function($arg1, $arg2) {
- return \intdiv($arg1, $arg2);
- },
- 'ln' => static function($arg) {
- return \log($arg);
- },
- 'lg' => static function($arg) {
- return \log10($arg);
- },
- 'log' => static function($arg) {
- return \log($arg);
- },
- 'log10' => static function($arg) {
- return \log10($arg);
- },
- 'log1p' => static function($arg) {
- return \log1p($arg);
- },
- 'max' => static function($arg1, $arg2) {
- return \max($arg1, $arg2);
- },
- 'min' => static function($arg1, $arg2) {
- return \min($arg1, $arg2);
- },
- 'octdec' => static function($arg) {
- return \octdec($arg);
- },
- 'pi' => static function() {
- return M_PI;
- },
- 'pow' => static function($arg1, $arg2) {
- return $arg1 ** $arg2;
- },
- 'rad2deg' => static function($arg) {
- return \rad2deg($arg);
- },
- 'round' => static function($arg) {
- return \round($arg);
- },
- 'sin' => static function($arg) {
- return \sin($arg);
- },
- 'sinh' => static function($arg) {
- return \sinh($arg);
- },
- 'sec' => static function($arg) {
- return 1 / \cos($arg);
- },
- 'sqrt' => static function($arg) {
- return \sqrt($arg);
- },
- 'tan' => static function($arg) {
- return \tan($arg);
- },
- 'tanh' => static function($arg) {
- return \tanh($arg);
- },
- 'tn' => static function($arg) {
- return \tan($arg);
- },
- 'tg' => static function($arg) {
- return \tan($arg);
- }
+ return $this->execute($falseval);
+ },
+ 'intdiv' => static fn($arg1, $arg2) => \intdiv($arg1, $arg2),
+ 'ln' => static fn($arg) => \log($arg),
+ 'lg' => static fn($arg) => \log10($arg),
+ 'log' => static fn($arg) => \log($arg),
+ 'log10' => static fn($arg) => \log10($arg),
+ 'log1p' => static fn($arg) => \log1p($arg),
+ 'max' => static fn($arg1, $arg2) => \max($arg1, $arg2),
+ 'min' => static fn($arg1, $arg2) => \min($arg1, $arg2),
+ 'octdec' => static fn($arg) => \octdec($arg),
+ 'pi' => static fn() => M_PI,
+ 'pow' => static fn($arg1, $arg2) => $arg1 ** $arg2,
+ 'rad2deg' => static fn($arg) => \rad2deg($arg),
+ 'round' => static fn($arg) => \round($arg),
+ 'sin' => static fn($arg) => \sin($arg),
+ 'sinh' => static fn($arg) => \sinh($arg),
+ 'sec' => static fn($arg) => 1 / \cos($arg),
+ 'sqrt' => static fn($arg) => \sqrt($arg),
+ 'tan' => static fn($arg) => \tan($arg),
+ 'tanh' => static fn($arg) => \tanh($arg),
+ 'tn' => static fn($arg) => \tan($arg),
+ 'tg' => static fn($arg) => \tan($arg)
];
}
@@ -687,7 +465,6 @@ class MathExecutor
/**
* Default variable validation, ensures that the value is a scalar.
- * @param $value
* @throws MathExecutorException if the value is not a scalar
*/
protected function defaultVarValidation(string $variable, $value) : void
diff --git a/tests/MathTest.php b/tests/MathTest.php
index 9567dd2..f82452b 100644
--- a/tests/MathTest.php
+++ b/tests/MathTest.php
@@ -497,7 +497,6 @@ class MathTest extends TestCase
if ('undefined' == $varName) {
return 3;
}
-
}
);
$this->assertEquals(15, $calculator->execute('5 * undefined'));
@@ -527,6 +526,13 @@ class MathTest extends TestCase
$this->assertEquals(1.5e+9, $calculator->execute('1.5e+9'));
}
+ public function testNullReturnType() : void
+ {
+ $calculator = new MathExecutor();
+ $calculator->setVar('nullValue', null);
+ $this->assertEquals(null, $calculator->execute('nullValue'));
+ }
+
public function testGetFunctionsReturnsArray() : void
{
$calculator = new MathExecutor();
@@ -568,7 +574,7 @@ class MathTest extends TestCase
$calculator->setVar('null', null);
$calculator->setVar('float', 1.1);
$calculator->setVar('string', 'string');
- $this->assertEquals(8, \count($calculator->getVars()));
+ $this->assertCount(8, $calculator->getVars());
$this->assertEquals(true, $calculator->getVar('boolTrue'));
$this->assertEquals(false, $calculator->getVar('boolFalse'));
$this->assertEquals(1, $calculator->getVar('int'));
@@ -719,19 +725,34 @@ class MathTest extends TestCase
$this->assertEquals(256, $calculator->execute('2 ^ 8')); // second arg $cache is true by default
$this->assertIsArray($calculator->getCache());
- $this->assertEquals(1, \count($calculator->getCache()));
+ $this->assertCount(1, $calculator->getCache());
$this->assertEquals(512, $calculator->execute('2 ^ 9', true));
- $this->assertEquals(2, \count($calculator->getCache()));
+ $this->assertCount(2, $calculator->getCache());
$this->assertEquals(1024, $calculator->execute('2 ^ 10', false));
- $this->assertEquals(2, \count($calculator->getCache()));
+ $this->assertCount(2, $calculator->getCache());
$calculator->clearCache();
$this->assertIsArray($calculator->getCache());
- $this->assertEquals(0, \count($calculator->getCache()));
+ $this->assertCount(0, $calculator->getCache());
$this->assertEquals(2048, $calculator->execute('2 ^ 11', false));
- $this->assertEquals(0, \count($calculator->getCache()));
+ $this->assertCount(0, $calculator->getCache());
+ }
+
+ public function testUnsupportedOperands() : void
+ {
+ if (\version_compare(PHP_VERSION, '8') >= 0) {
+ $calculator = new MathExecutor();
+
+ $calculator->setVar('stringVar', 'string');
+ $calculator->setVar('intVar', 1);
+
+ $this->expectException(\TypeError::class);
+ $calculator->execute('stringVar + intVar');
+ } else {
+ $this->expectNotToPerformAssertions();
+ }
}
}