Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [8.2, 8.1, 8.0]
dependency-version: [prefer-lowest, prefer-stable]
php: [8.5, 8.4, 8.3, 8.2, 8.1]
dependency-version: [prefer-stable]
os: [ubuntu-latest, windows-latest]

name: ${{ matrix.os }} - PHP${{ matrix.php }} - ${{ matrix.dependency-version }}

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -29,7 +29,7 @@ jobs:
- name: Install dependencies
run: |
composer install --no-interaction
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction

- name: Execute tests
run: vendor/bin/phpunit
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
vendor/
.idea/
composer.lock
.phpunit.cache
.phpunit.result.cache
.vscode
.php-cs-fixer.cache
Expand Down
24 changes: 12 additions & 12 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

$config = new PhpCsFixer\Config();
$config->setUnsupportedPhpVersionAllowed(true);

$config
->setRiskyAllowed(true)
Expand Down Expand Up @@ -41,7 +42,7 @@
// Comments with annotation should be docblock when used on structural elements.
'comment_to_phpdoc' => true,
// Remove extra spaces in a nullable typehint.
'compact_nullable_typehint' => true,
'compact_nullable_type_declaration' => true,
// Concatenation should be spaced according configuration.
'concat_space' => ['spacing'=>'one'],
// The PHP constants `true`, `false`, and `null` MUST be written using the correct casing.
Expand Down Expand Up @@ -69,7 +70,7 @@
// Replace core functions calls returning constants with the constants.
'function_to_constant' => true,
// Ensure single space between function's argument and its typehint.
'function_typehint_space' => true,
'type_declaration_spaces' => true,
// Renames PHPDoc tags.
'general_phpdoc_tag_rename' => true,
// Function `implode` must be called with 2 arguments in the documented order.
Expand Down Expand Up @@ -111,9 +112,9 @@
// Add leading `\` before function invocation to speed up resolving.
'native_function_invocation' => ['include'=>['@all','trans']],
// Native type hints for functions should use the correct case.
'native_function_type_declaration_casing' => true,
// All instances created with new keyword must be followed by braces.
'new_with_braces' => true,
'native_type_declaration_casing' => true,
// All instances created with new keyword must be followed by parentheses.
'new_with_parentheses' => true,
// Master functions shall be used instead of aliases.
'no_alias_functions' => true,
// Master language constructs shall be used instead of aliases.
Expand Down Expand Up @@ -148,8 +149,6 @@
'no_short_bool_cast' => true,
// When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis.
'no_spaces_after_function_name' => true,
// There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis.
'no_spaces_inside_parenthesis' => true,
// Removes `@param`, `@return` and `@var` tags that don't provide any useful information.
'no_superfluous_phpdoc_tags' => true,
// Remove trailing whitespace at the end of non-blank lines.
Expand All @@ -158,8 +157,8 @@
'no_trailing_whitespace_in_comment' => true,
// Removes unneeded parentheses around control statements.
'no_unneeded_control_parentheses' => true,
// Removes unneeded curly braces that are superfluous and aren't part of a control structure's body.
'no_unneeded_curly_braces' => true,
// Removes unneeded braces that are superfluous and aren't part of a control structure's body.
'no_unneeded_braces' => true,
// A `final` class must not have `final` methods and `private` methods must not be `final`.
'no_unneeded_final_method' => true,
// In function arguments there must not be arguments with default values before non-default ones.
Expand Down Expand Up @@ -229,7 +228,7 @@
// A PHP file without end tag must always end with a single empty line feed.
'single_blank_line_at_eof' => true,
// There should be exactly one blank line before a namespace declaration.
'single_blank_line_before_namespace' => true,
'blank_lines_before_namespace' => ['max_line_breaks' => 2, 'min_line_breaks' => 2],
// There MUST NOT be more than one property or constant declared per statement.
'single_class_element_per_statement' => true,
// There MUST be one use keyword per declaration.
Expand All @@ -242,6 +241,8 @@
'single_quote' => true,
// Each trait `use` must be done as single statement.
'single_trait_insert_per_statement' => true,
// There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis.
'spaces_inside_parentheses' => false,
// Replace all `<>` with `!=`.
'standardize_not_equals' => true,
// Lambdas not (indirect) referencing `$this` must be declared `static`.
Expand All @@ -264,8 +265,7 @@
'trim_array_spaces' => true,
// Unary operators should be placed adjacent to their operands.
'unary_operator_spaces' => true,
// Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility.
'visibility_required' => true,
'modifier_keywords' => ['elements' => ['const', 'method', 'property']],
// Add `void` return type to functions with missing or empty return statements, but priority is given to `@return` annotations. Requires PHP >= 7.1.
'void_return' => true,
// In array declaration, there MUST be a whitespace after each comma.
Expand Down
94 changes: 74 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Features:
* Built in support for +, -, *, /, % and power (^) operators
* Paratheses () and arrays [] are fully supported
* Parentheses () and arrays [] are fully supported
* Logical operators (==, !=, <, <, >=, <=, &&, ||, !)
* Built in support for most PHP math functions
* Support for BCMath Arbitrary Precision Math
Expand All @@ -16,7 +16,8 @@
* Dynamic variable resolution (delayed computation)
* 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
* Exceptions on divide or modulo by zero, or treat as zero
* Custom handling of non-numeric values reaching an arithmetic operator
* Unary Plus and Minus (e.g. +3 or -sin(12))
* Pi ($pi) and Euler's number ($e) support to 11 decimal places
* Easily extensible
Expand All @@ -40,21 +41,35 @@ Default functions:
* abs
* acos (arccos)
* acosh
* arccos
* arccosec
* arccot
* arccotan
* arccsc (arccosec)
* arcctg (arccot, arccotan)
* arcsec
* arccsc (arccosec)
* arcsin
* arctan
* arctg
* array
* asin (arcsin)
* atan (atn, arctan, arctg)
* atan2
* atanh
* atn
* avg
* bindec
* ceil
* cos
* cosec
* cosec (csc)
* cosh
* cot
* cotan
* cotg
* csc
* ctg (cot, cotan, cotg, ctn)
* ctn
* decbin
* dechex
* decoct
Expand All @@ -67,6 +82,8 @@ Default functions:
* hypot
* if
* intdiv
* lg
* ln
* log (ln)
* log10 (lg)
* log1p
Expand All @@ -84,6 +101,8 @@ Default functions:
* sqrt
* tan (tn, tg)
* tanh
* tg
* tn

Add custom function to executor:
```php
Expand All @@ -92,20 +111,20 @@ $executor->addFunction('concat', function($arg1, $arg2) {return $arg1 . $arg2;})
Optional parameters:
```php
$executor->addFunction('round', function($num, int $precision = 0) {return round($num, $precision);});
$executor->calculate('round(17.119)'); // 17
$executor->calculate('round(17.119, 2)'); // 17.12
$executor->execute('round(17.119)'); // 17
$executor->execute('round(17.119, 2)'); // 17.12
```
Variable number of parameters:
```php
$executor->addFunction('avarage', function(...$args) {return array_sum($args) / count($args);});
$executor->calculate('avarage(1,3)'); // 2
$executor->calculate('avarage(1, 3, 4, 8)'); // 4
$executor->addFunction('average', function(...$args) {return array_sum($args) / count($args);});
$executor->execute('average(1,3)'); // 2
$executor->execute('average(1, 3, 4, 8)'); // 4
```

## Operators:
Default operators: `+ - * / % ^`

Add custom operator to executor:
Add custom float modulo operator to executor:

```php
use NXP\Classes\Operator;
Expand All @@ -114,13 +133,9 @@ $executor->addOperator(new Operator(
'%', // Operator sign
false, // Is right associated operator
180, // Operator priority
function (&$stack)
function ($op1, $op2)
{
$op2 = array_pop($stack);
$op1 = array_pop($stack);
$result = $op1->getValue() % $op2->getValue();

return $result;
return fmod($op1, $op2);
}
));
```
Expand Down Expand Up @@ -180,8 +195,7 @@ $executor->setVarValidationHandler(function (string $name, $variable) {
You can dynamically define variables at run time. If a variable has a high computation cost, but might not be used, then you can define an undefined variable handler. It will only get called when the variable is used, rather than having to always set it initially.

```php
$calculator = new MathExecutor();
$calculator->setVarNotFoundHandler(
$executor->setVarNotFoundHandler(
function ($varName) {
if ($varName == 'trans') {
return transmogrify();
Expand All @@ -196,21 +210,21 @@ By default, `MathExecutor` uses PHP floating point math, but if you need a fixed
`WARNING`: Functions may return a PHP floating point number. By doing the basic math functions on the results, you will get back a fixed number of decimal points. Use a plus sign in front of any stand alone function to return the proper number of decimal places.

## Division By Zero Support:
Division by zero throws a `\NXP\Exception\DivisionByZeroException` by default
Division and modulo by zero throw a `\NXP\Exception\DivisionByZeroException` by default
```php
try {
echo $executor->execute('1/0');
} catch (DivisionByZeroException $e) {
echo $e->getMessage();
}
```
Or call setDivisionByZeroIsZero
Or call setDivisionByZeroIsZero, which covers both `/` and `%`
```php
echo $executor->setDivisionByZeroIsZero()->execute('1/0');
```
If you want another behavior, you can override division operator:
```php
$executor->addOperator("/", false, 180, function($a, $b) {
$executor->addOperator(new Operator("/", false, 180, function($a, $b) {
if ($b == 0) {
return null;
}
Expand All @@ -219,6 +233,46 @@ $executor->addOperator("/", false, 180, function($a, $b) {
echo $executor->execute('1/0');
```

## Non-Numeric Value Support:
Arithmetic and ordering operators expect numbers. When a value that is not a number reaches one of them, it is passed to
PHP as-is, which raises a `\TypeError` for the arithmetic operators (`'N/A' / 2`) and compares as a string for the
ordering ones (`'N/A' > 1` is `true`). Call **setNonNumericHandler()** to decide what such a value means instead:

```php
$executor->setNonNumericHandler(
function ($value, string $operator) {
// 'N/A' ratings count as zero in every calculation
return 0;
}
);
$executor->setVar('rating', 'N/A');
echo $executor->execute('rating / 2'); // 0
```

The handler receives the offending value and the name of the operator (`'+'`, `'/'`, `'uNeg'`, ...), so it can react
differently per operator, and whatever it returns is used in place of the original value. Throwing from it turns the
`\TypeError` into an error of your own:

```php
$executor->setNonNumericHandler(
function ($value, string $operator) {
throw new MathExecutorException("Value ({$value}) is not a number, required by operator ({$operator})");
}
);
```

It is called for the operators that require a number (`+`, `-`, `*`, `/`, `%`, `^`, unary `-` and unary `+`, `>`, `>=`,
`<` and `<=`), including the ones redefined by `setDivisionByZeroIsZero()` and `useBCMath()`. Without a handler nothing
changes, which is the default.

These are never affected:
* Values that are numeric (`'3'` included), `null`, boolean or array. Arrays are a supported variable type, so
`[1, 2] + [3, 4]` keeps its PHP meaning.
* The operators with defined string or boolean semantics: `==`, `!=`, `&&`, `||` and `!`.
* An ordering operator comparing two non-numeric values, which stays a string comparison, consistent with `==` and
`!=`. So `'apple' < 'banana'` is still `true`, while `rating > 1` uses the handler, because the other side is a
number and PHP would otherwise compare that number as a string.

## String Support:
Expressions can contain double or single quoted strings that are evaluated the same way as PHP evaluates strings as numbers. You can also pass strings to functions.

Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@

},
"require": {
"php": ">=8.0"
"php": ">=8.0 <8.6"
},
"require-dev": {
"phpunit/phpunit": ">=9.0",
"friendsofphp/php-cs-fixer": "^3.8",
"phpstan/phpstan": "^1.9"
"phpunit/phpunit": ">=10.0",
"friendsofphp/php-cs-fixer": "*",
"phpstan/phpstan": "*"
},
"autoload": {
"psr-4": {
Expand Down
35 changes: 17 additions & 18 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
convertDeprecationsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="./tests/bootstrap.php"
>

<testsuites>
<testsuite name="Math Executor tests">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
colors="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="./tests/bootstrap.php"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
backupStaticProperties="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd">
<testsuites>
<testsuite name="Math Executor tests">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
1 change: 1 addition & 0 deletions src/NXP/Classes/Calculator.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* This file is part of the MathExecutor package
*
Expand Down
2 changes: 1 addition & 1 deletion src/NXP/Classes/Operator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class Operator
{
/**
* @var callable(\SplStack)
* @var callable
*/
public $function;

Expand Down
Loading
Loading