aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/NXP/Classes/Calculator.php2
-rw-r--r--src/NXP/Classes/CustomFunction.php36
-rw-r--r--src/NXP/Classes/Token.php2
-rw-r--r--src/NXP/Classes/Tokenizer.php34
-rw-r--r--src/NXP/Exception/IncorrectFunctionParameterException.php16
-rw-r--r--src/NXP/MathExecutor.php13
-rw-r--r--tests/MathTest.php41
7 files changed, 116 insertions, 28 deletions
diff --git a/src/NXP/Classes/Calculator.php b/src/NXP/Classes/Calculator.php
index 10adc24..6b63503 100644
--- a/src/NXP/Classes/Calculator.php
+++ b/src/NXP/Classes/Calculator.php
@@ -74,7 +74,7 @@ class Calculator
if (! \array_key_exists($token->value, $this->functions)) {
throw new UnknownFunctionException($token->value);
}
- $stack[] = $this->functions[$token->value]->execute($stack);
+ $stack[] = $this->functions[$token->value]->execute($stack, $token->paramCount);
} elseif (Token::Operator === $token->type) {
if (! \array_key_exists($token->value, $this->operators)) {
throw new UnknownOperatorException($token->value);
diff --git a/src/NXP/Classes/CustomFunction.php b/src/NXP/Classes/CustomFunction.php
index 843cf82..1ebdd2c 100644
--- a/src/NXP/Classes/CustomFunction.php
+++ b/src/NXP/Classes/CustomFunction.php
@@ -2,6 +2,7 @@
namespace NXP\Classes;
+use NXP\Exception\IncorrectFunctionParameterException;
use NXP\Exception\IncorrectNumberOfFunctionParametersException;
use ReflectionException;
use ReflectionFunction;
@@ -15,41 +16,48 @@ class CustomFunction
*/
public $function;
- public int $places = 0;
+ private ReflectionFunction $reflectionFunction;
/**
* CustomFunction constructor.
*
* @throws ReflectionException
- * @throws IncorrectNumberOfFunctionParametersException
*/
- public function __construct(string $name, callable $function, ?int $places = null)
+ public function __construct(string $name, callable $function)
{
$this->name = $name;
$this->function = $function;
+ $this->reflectionFunction = new ReflectionFunction($function);
- if (null === $places) {
- $reflection = new ReflectionFunction($function);
- $this->places = $reflection->getNumberOfParameters();
- } else {
- $this->places = $places;
- }
}
/**
* @param array<Token> $stack
*
- * @throws IncorrectNumberOfFunctionParametersException
+ * @throws IncorrectNumberOfFunctionParametersException|IncorrectFunctionParameterException
*/
- public function execute(array &$stack) : Token
+ public function execute(array &$stack, int $paramCountInStack) : Token
{
- if (\count($stack) < $this->places) {
+ if ($paramCountInStack < $this->reflectionFunction->getNumberOfRequiredParameters()) {
throw new IncorrectNumberOfFunctionParametersException($this->name);
}
$args = [];
- for ($i = 0; $i < $this->places; $i++) {
- \array_unshift($args, \array_pop($stack)->value);
+ if ($paramCountInStack > 0) {
+ $reflectionParameters = $this->reflectionFunction->getParameters();
+
+ for ($i = 0; $i < $paramCountInStack; $i++) {
+ $value = \array_pop($stack)->value;
+ $valueType = \gettype($value);
+ $reflectionParameter = $reflectionParameters[\min(\count($reflectionParameters) - 1, $i)];
+ //TODO to support type check for union types (php >= 8.0) and intersection types (php >= 8.1), we should increase min php level in composer.json
+ // For now, only support basic types. @see testFunctionParameterTypes
+ if ($reflectionParameter->hasType() && $reflectionParameter->getType()->getName() !== $valueType){
+ throw new IncorrectFunctionParameterException();
+ }
+
+ \array_unshift($args, $value);
+ }
}
$result = \call_user_func_array($this->function, $args);
diff --git a/src/NXP/Classes/Token.php b/src/NXP/Classes/Token.php
index e75b17e..7532e77 100644
--- a/src/NXP/Classes/Token.php
+++ b/src/NXP/Classes/Token.php
@@ -28,6 +28,8 @@ class Token
public ?string $name;
+ public ?int $paramCount = null;//to store function parameter count in stack
+
/**
* Token constructor.
*
diff --git a/src/NXP/Classes/Tokenizer.php b/src/NXP/Classes/Tokenizer.php
index 32404a2..23e1cc9 100644
--- a/src/NXP/Classes/Tokenizer.php
+++ b/src/NXP/Classes/Tokenizer.php
@@ -20,7 +20,7 @@ use SplStack;
*/
class Tokenizer
{
- /** @var array<Token> */
+ /** @var array<Token> */
public array $tokens = [];
private string $input = '';
@@ -31,7 +31,7 @@ class Tokenizer
private bool $allowNegative = true;
- /** @var array<Operator> */
+ /** @var array<Operator> */
private array $operators = [];
private bool $inSingleQuotedString = false;
@@ -99,8 +99,8 @@ class Tokenizer
break;
}
- // no break
- // Intentionally fall through
+ // no break
+ // Intentionally fall through
case $this->isAlpha($ch):
if (\strlen($this->numberBuffer)) {
$this->emptyNumberBufferAsLiteral();
@@ -196,8 +196,8 @@ class Tokenizer
}
/**
- * @throws IncorrectBracketsException
* @throws UnknownOperatorException
+ * @throws IncorrectBracketsException
* @return Token[] Array of tokens in revers polish notation
*/
public function buildReversePolishNotation() : array
@@ -205,6 +205,10 @@ class Tokenizer
$tokens = [];
/** @var SplStack<Token> $stack */
$stack = new SplStack();
+ /**
+ * @var SplStack<int> $paramCounter
+ */
+ $paramCounter = new SplStack();
foreach ($this->tokens as $token) {
switch ($token->type) {
@@ -213,9 +217,21 @@ class Tokenizer
case Token::String:
$tokens[] = $token;
+ if ($paramCounter->count() > 0 && 0 === $paramCounter->top()) {
+ $paramCounter->push($paramCounter->pop() + 1);
+ }
+
break;
case Token::Function:
+ if ($paramCounter->count() > 0 && 0 === $paramCounter->top()) {
+ $paramCounter->push($paramCounter->pop() + 1);
+ }
+ $stack->push($token);
+ $paramCounter->push(0);
+
+ break;
+
case Token::LeftParenthesis:
$stack->push($token);
@@ -228,6 +244,7 @@ class Tokenizer
}
$tokens[] = $stack->pop();
}
+ $paramCounter->push($paramCounter->pop() + 1);
break;
@@ -270,7 +287,12 @@ class Tokenizer
}
if ($stack->count() > 0 && Token::Function == $stack->top()->type) {
- $tokens[] = $stack->pop();
+ /**
+ * @var Token $f
+ */
+ $f = $stack->pop();
+ $f->paramCount = $paramCounter->pop();
+ $tokens[] = $f;
}
break;
diff --git a/src/NXP/Exception/IncorrectFunctionParameterException.php b/src/NXP/Exception/IncorrectFunctionParameterException.php
new file mode 100644
index 0000000..b378718
--- /dev/null
+++ b/src/NXP/Exception/IncorrectFunctionParameterException.php
@@ -0,0 +1,16 @@
+<?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\Exception;
+
+class IncorrectFunctionParameterException extends MathExecutorException
+{
+}
diff --git a/src/NXP/MathExecutor.php b/src/NXP/MathExecutor.php
index d2bcfe6..b3e539b 100644
--- a/src/NXP/MathExecutor.php
+++ b/src/NXP/MathExecutor.php
@@ -116,15 +116,14 @@ class MathExecutor
*
* @param string $name Name of function
* @param callable|null $function Function
- * @param int|null $places Count of arguments
*
- * @throws Exception\IncorrectNumberOfFunctionParametersException
* @throws ReflectionException
+ * @throws Exception\IncorrectNumberOfFunctionParametersException
* @return MathExecutor
*/
- public function addFunction(string $name, ?callable $function = null, ?int $places = null) : self
+ public function addFunction(string $name, ?callable $function = null) : self
{
- $this->functions[$name] = new CustomFunction($name, $function, $places);
+ $this->functions[$name] = new CustomFunction($name, $function);
return $this;
}
@@ -432,13 +431,13 @@ class MathExecutor
'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),
+ 'max' => static fn($arg1, $arg2, ...$args) => \max($arg1, $arg2, ...$args),
+ 'min' => static fn($arg1, $arg2, ...$args) => \min($arg1, $arg2, ...$args),
'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),
+ 'round' => static fn($num, int $precision = 0) => \round($num, $precision),
'sin' => static fn($arg) => \sin($arg),
'sinh' => static fn($arg) => \sinh($arg),
'sec' => static fn($arg) => 1 / \cos($arg),
diff --git a/tests/MathTest.php b/tests/MathTest.php
index f82452b..e616721 100644
--- a/tests/MathTest.php
+++ b/tests/MathTest.php
@@ -14,6 +14,7 @@ namespace NXP\Tests;
use Exception;
use NXP\Exception\DivisionByZeroException;
use NXP\Exception\IncorrectExpressionException;
+use NXP\Exception\IncorrectFunctionParameterException;
use NXP\Exception\IncorrectNumberOfFunctionParametersException;
use NXP\Exception\MathExecutorException;
use NXP\Exception\UnknownFunctionException;
@@ -243,6 +244,7 @@ class MathTest extends TestCase
['-(4*-2)-5'],
['-(-4*2) - 5'],
['-4*-5'],
+ ['max(1,2,4.9,3)']
];
}
@@ -323,6 +325,45 @@ class MathTest extends TestCase
$this->assertEquals(\round(100 / 30), $calculator->execute('round(100/30)'));
}
+ public function testFunctionUnlimitedParameters() : void
+ {
+ $calculator = new MathExecutor();
+ $calculator->addFunction('max', static function($arg1, $arg2, ...$args) {
+ return \max($arg1, $arg2, ...$args);
+ });
+ $this->assertEquals(\max(4, 6, 8.1, 2, 7), $calculator->execute('max(4,6,8.1,2,7)'));
+ }
+
+ public function testFunctionOptionalParameters() : void
+ {
+ $calculator = new MathExecutor();
+ $calculator->addFunction('round', static function($num, $precision = 0) {
+ return \round($num, $precision);
+ });
+ $this->assertEquals(\round(11.176), $calculator->execute('round(11.176)'));
+ $this->assertEquals(\round(11.176, 2), $calculator->execute('round(11.176,2)'));
+ }
+
+ public function testFunctionParameterTypes() : void
+ {
+ $calculator = new MathExecutor();
+ $this->expectException(IncorrectFunctionParameterException::class);
+ $calculator->addFunction('myfunc', static function(string $name, int $age) {
+ return $name . $age;
+ });
+ $calculator->execute('myfunc(22, "John Doe")');
+ }
+
+ public function testFunctionIncorrectNumberOfParameters() : void
+ {
+ $calculator = new MathExecutor();
+ $this->expectException(IncorrectNumberOfFunctionParametersException::class);
+ $calculator->addFunction('myfunc', static function($arg1, $arg2) {
+ return $arg1 + $arg2;
+ });
+ $calculator->execute('myfunc(1)');
+ }
+
public function testFunctionIf() : void
{
$calculator = new MathExecutor();