Fix to PSR standart, fix tokenizer, fix function executor.
This commit is contained in:
parent
253fb694a3
commit
eb9c365161
19 changed files with 476 additions and 165 deletions
9
.travis.yml
Normal file
9
.travis.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
language: php
|
||||||
|
|
||||||
|
php:
|
||||||
|
- 5.3
|
||||||
|
- 5.4
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- wget http://getcomposer.org/composer.phar
|
||||||
|
- php composer.phar install
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) Alexander Kiryukhin
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -1,64 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Author: Alexander "NeonXP" Kiryukhin
|
|
||||||
* Date: 14.03.13
|
|
||||||
* Time: 3:41
|
|
||||||
*/
|
|
||||||
namespace NXP\Tests;
|
|
||||||
|
|
||||||
use \NXP\MathExecutor;
|
|
||||||
|
|
||||||
class MathTest extends \PHPUnit_Framework_TestCase {
|
|
||||||
public function setup()
|
|
||||||
{
|
|
||||||
require '../MathExecutor.php';
|
|
||||||
require '../Classes/Func.php';
|
|
||||||
require '../Classes/Operand.php';
|
|
||||||
require '../Classes/Token.php';
|
|
||||||
require '../Classes/TokenParser.php';
|
|
||||||
}
|
|
||||||
public function testCalculating()
|
|
||||||
{
|
|
||||||
$calculator = new MathExecutor();
|
|
||||||
for ($i = 1; $i <= 10; $i++) {
|
|
||||||
$expression = $this->generateExpression();
|
|
||||||
print "Test #$i. Expression: '$expression'\t";
|
|
||||||
|
|
||||||
eval('$result1 = ' . $expression . ';');
|
|
||||||
print "PHP result: $result1 \t";
|
|
||||||
$result2 = $calculator->execute($expression);
|
|
||||||
print "NXP Math Executor result: $result2\n";
|
|
||||||
$this->assertEquals($result1, $result2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateExpression()
|
|
||||||
{
|
|
||||||
$operators = [ '+', '-', '*', '/' ];
|
|
||||||
$number = true;
|
|
||||||
$expression = '';
|
|
||||||
$brackets = 0;
|
|
||||||
for ($i = 1; $i < rand(1,10)*2; $i++) {
|
|
||||||
if ($number) {
|
|
||||||
$expression .= rand(1,100)*0.5;
|
|
||||||
} else {
|
|
||||||
$expression .= $operators[rand(0,3)];
|
|
||||||
}
|
|
||||||
$number = !$number;
|
|
||||||
$rand = rand(1,5);
|
|
||||||
if (($rand == 1) && ($number)) {
|
|
||||||
$expression .= '(';
|
|
||||||
$brackets++;
|
|
||||||
} elseif (($rand == 2) && (!$number) && ($brackets > 0)) {
|
|
||||||
$expression .= ')';
|
|
||||||
$brackets--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($number) {
|
|
||||||
$expression .= rand(1,100)*0.5;
|
|
||||||
}
|
|
||||||
$expression .= str_repeat(')', $brackets);
|
|
||||||
|
|
||||||
return $expression;
|
|
||||||
}
|
|
||||||
}
|
|
50
README.md
50
README.md
|
@ -2,13 +2,49 @@
|
||||||
|
|
||||||
Simple math expressions calculator
|
Simple math expressions calculator
|
||||||
|
|
||||||
## Sample usage:
|
|
||||||
|
|
||||||
<?php
|
|
||||||
require "vendor/autoload.php";
|
|
||||||
$calculator = new \NXP\MathExecutor();
|
|
||||||
print $calculator->execute("1 + 2 * (2 - (4+10))^2");
|
|
||||||
|
|
||||||
## Install via Composer
|
## Install via Composer
|
||||||
|
|
||||||
All instructions to install here: https://packagist.org/packages/nxp/math-executor
|
All instructions to install here: https://packagist.org/packages/nxp/math-executor
|
||||||
|
|
||||||
|
## Sample usage:
|
||||||
|
|
||||||
|
```php
|
||||||
|
require "vendor/autoload.php";
|
||||||
|
|
||||||
|
$calculator = new \NXP\MathExecutor();
|
||||||
|
|
||||||
|
print $calculator->execute("1 + 2 * (2 - (4+10))^2");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions:
|
||||||
|
|
||||||
|
Default functions:
|
||||||
|
* sin
|
||||||
|
* cos
|
||||||
|
* tn
|
||||||
|
* asin
|
||||||
|
* asoc
|
||||||
|
* atn
|
||||||
|
|
||||||
|
Add custom function to executor:
|
||||||
|
```php
|
||||||
|
$executor->addFunction('abs', function($arg) {
|
||||||
|
return abs($arg);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Operators:
|
||||||
|
|
||||||
|
Default operators: `+ - * / ^`
|
||||||
|
|
||||||
|
## Variables:
|
||||||
|
|
||||||
|
You can add own variable to executor:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$executor->setVars(array(
|
||||||
|
'var1' => 0.15,
|
||||||
|
'var2' => 0.22
|
||||||
|
));
|
||||||
|
|
||||||
|
$executor->execute("var1 + var2");
|
|
@ -12,6 +12,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-0": {"NXP": "."}
|
"psr-0": {"NXP": "src/"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
phpunit.xml.dist
Normal file
20
phpunit.xml.dist
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
syntaxCheck="false"
|
||||||
|
bootstrap="./tests/bootstrap.php"
|
||||||
|
>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Math Executor tests">
|
||||||
|
<directory>./tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
</phpunit>
|
|
@ -1,14 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author: Alexander "NeonXP" Kiryukhin
|
* This file is part of the MathExecutor package
|
||||||
* Date: 17.03.13
|
*
|
||||||
* Time: 4:30
|
* (c) Alexander Kiryukhin
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace NXP\Classes;
|
namespace NXP\Classes;
|
||||||
|
|
||||||
|
class Func
|
||||||
class Func {
|
{
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +27,7 @@ class Func {
|
||||||
* @param $name
|
* @param $name
|
||||||
* @param $callback
|
* @param $callback
|
||||||
*/
|
*/
|
||||||
function __construct($name, $callback)
|
public function __construct($name, $callback)
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->callback = $callback;
|
$this->callback = $callback;
|
|
@ -1,14 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author: Alexander "NeonXP" Kiryukhin
|
* This file is part of the MathExecutor package
|
||||||
* Date: 17.03.13
|
*
|
||||||
* Time: 4:27
|
* (c) Alexander Kiryukhin
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace NXP\Classes;
|
namespace NXP\Classes;
|
||||||
|
|
||||||
|
class Operand
|
||||||
class Operand {
|
{
|
||||||
const LEFT_ASSOCIATED = 'LEFT_ASSOCIATED';
|
const LEFT_ASSOCIATED = 'LEFT_ASSOCIATED';
|
||||||
const RIGHT_ASSOCIATED = 'RIGHT_ASSOCIATED';
|
const RIGHT_ASSOCIATED = 'RIGHT_ASSOCIATED';
|
||||||
const ASSOCIATED = 'ASSOCIATED';
|
const ASSOCIATED = 'ASSOCIATED';
|
||||||
|
@ -48,7 +52,7 @@ class Operand {
|
||||||
* @param $type
|
* @param $type
|
||||||
* @param $callback
|
* @param $callback
|
||||||
*/
|
*/
|
||||||
function __construct($symbol, $priority, $association, $type, $callback)
|
public function __construct($symbol, $priority, $association, $type, $callback)
|
||||||
{
|
{
|
||||||
$this->association = $association;
|
$this->association = $association;
|
||||||
$this->symbol = $symbol;
|
$this->symbol = $symbol;
|
|
@ -1,15 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author: Alexander "NeonXP" Kiryukhin
|
* This file is part of the MathExecutor package
|
||||||
* Date: 17.03.13
|
*
|
||||||
* Time: 3:23
|
* (c) Alexander Kiryukhin
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace NXP\Classes;
|
namespace NXP\Classes;
|
||||||
|
|
||||||
|
class Token
|
||||||
class Token {
|
{
|
||||||
|
|
||||||
const NOTHING = 'NOTHING';
|
const NOTHING = 'NOTHING';
|
||||||
const STRING = 'STRING';
|
const STRING = 'STRING';
|
||||||
const NUMBER = 'NUMBER';
|
const NUMBER = 'NUMBER';
|
||||||
|
@ -28,7 +31,7 @@ class Token {
|
||||||
*/
|
*/
|
||||||
protected $type;
|
protected $type;
|
||||||
|
|
||||||
function __construct($type, $value)
|
public function __construct($type, $value)
|
||||||
{
|
{
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
$this->value = $value;
|
$this->value = $value;
|
|
@ -1,14 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author: Alexander "NeonXP" Kiryukhin
|
* This file is part of the MathExecutor package
|
||||||
* Date: 17.03.13
|
*
|
||||||
* Time: 2:45
|
* (c) Alexander Kiryukhin
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace NXP\Classes;
|
namespace NXP\Classes;
|
||||||
|
|
||||||
|
class TokenParser
|
||||||
class TokenParser {
|
{
|
||||||
const DIGIT = 'DIGIT';
|
const DIGIT = 'DIGIT';
|
||||||
const CHAR = 'CHAR';
|
const CHAR = 'CHAR';
|
||||||
const SPECIAL_CHAR = 'SPECIAL_CHAR';
|
const SPECIAL_CHAR = 'SPECIAL_CHAR';
|
||||||
|
@ -16,75 +20,75 @@ class TokenParser {
|
||||||
const RIGHT_BRACKET = 'RIGHT_BRACKET';
|
const RIGHT_BRACKET = 'RIGHT_BRACKET';
|
||||||
const SPACE = 'SPACE';
|
const SPACE = 'SPACE';
|
||||||
|
|
||||||
private $terms = [
|
private $terms = array(
|
||||||
self::DIGIT => '[0-9\.]',
|
self::DIGIT => '[0-9\.]',
|
||||||
self::CHAR => '[a-z]',
|
self::CHAR => '[a-z_]',
|
||||||
self::SPECIAL_CHAR => '[\!\@\#\$\%\^\&\*\/\|\-\+\=\~]',
|
self::SPECIAL_CHAR => '[\!\@\#\$\%\^\&\*\/\|\-\+\=\~]',
|
||||||
self::LEFT_BRACKET => '\(',
|
self::LEFT_BRACKET => '\(',
|
||||||
self::RIGHT_BRACKET => '\)',
|
self::RIGHT_BRACKET => '\)',
|
||||||
self::SPACE => '\s'
|
self::SPACE => '\s'
|
||||||
];
|
);
|
||||||
|
|
||||||
const ERROR_STATE = 'ERROR_STATE';
|
const ERROR_STATE = 'ERROR_STATE';
|
||||||
|
|
||||||
private $transitions = [
|
private $transitions = array(
|
||||||
Token::NOTHING => [
|
Token::NOTHING => array(
|
||||||
self::DIGIT => Token::NUMBER,
|
self::DIGIT => Token::NUMBER,
|
||||||
self::CHAR => Token::STRING,
|
self::CHAR => Token::STRING,
|
||||||
self::SPECIAL_CHAR => Token::OPERATOR,
|
self::SPECIAL_CHAR => Token::OPERATOR,
|
||||||
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
||||||
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
||||||
self::SPACE => Token::NOTHING
|
self::SPACE => Token::NOTHING
|
||||||
],
|
),
|
||||||
Token::STRING => [
|
Token::STRING => array(
|
||||||
self::DIGIT => Token::STRING,
|
self::DIGIT => Token::STRING,
|
||||||
self::CHAR => Token::STRING,
|
self::CHAR => Token::STRING,
|
||||||
self::SPECIAL_CHAR => Token::OPERATOR,
|
self::SPECIAL_CHAR => Token::OPERATOR,
|
||||||
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
||||||
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
||||||
self::SPACE => Token::NOTHING
|
self::SPACE => Token::NOTHING
|
||||||
],
|
),
|
||||||
Token::NUMBER => [
|
Token::NUMBER => array(
|
||||||
self::DIGIT => Token::NUMBER,
|
self::DIGIT => Token::NUMBER,
|
||||||
self::CHAR => self::ERROR_STATE,
|
self::CHAR => self::ERROR_STATE,
|
||||||
self::SPECIAL_CHAR => Token::OPERATOR,
|
self::SPECIAL_CHAR => Token::OPERATOR,
|
||||||
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
||||||
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
||||||
self::SPACE => Token::NOTHING
|
self::SPACE => Token::NOTHING
|
||||||
],
|
),
|
||||||
Token::OPERATOR => [
|
Token::OPERATOR => array(
|
||||||
self::DIGIT => Token::NUMBER,
|
self::DIGIT => Token::NUMBER,
|
||||||
self::CHAR => Token::STRING,
|
self::CHAR => Token::STRING,
|
||||||
self::SPECIAL_CHAR => Token::OPERATOR,
|
self::SPECIAL_CHAR => Token::OPERATOR,
|
||||||
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
||||||
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
||||||
self::SPACE => Token::NOTHING
|
self::SPACE => Token::NOTHING
|
||||||
],
|
),
|
||||||
self::ERROR_STATE => [
|
self::ERROR_STATE => array(
|
||||||
self::DIGIT => self::ERROR_STATE,
|
self::DIGIT => self::ERROR_STATE,
|
||||||
self::CHAR => self::ERROR_STATE,
|
self::CHAR => self::ERROR_STATE,
|
||||||
self::SPECIAL_CHAR => self::ERROR_STATE,
|
self::SPECIAL_CHAR => self::ERROR_STATE,
|
||||||
self::LEFT_BRACKET => self::ERROR_STATE,
|
self::LEFT_BRACKET => self::ERROR_STATE,
|
||||||
self::RIGHT_BRACKET => self::ERROR_STATE,
|
self::RIGHT_BRACKET => self::ERROR_STATE,
|
||||||
self::SPACE => self::ERROR_STATE
|
self::SPACE => self::ERROR_STATE
|
||||||
],
|
),
|
||||||
Token::LEFT_BRACKET => [
|
Token::LEFT_BRACKET => array(
|
||||||
self::DIGIT => Token::NUMBER,
|
self::DIGIT => Token::NUMBER,
|
||||||
self::CHAR => Token::STRING,
|
self::CHAR => Token::STRING,
|
||||||
self::SPECIAL_CHAR => Token::OPERATOR,
|
self::SPECIAL_CHAR => Token::OPERATOR,
|
||||||
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
||||||
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
||||||
self::SPACE => Token::NOTHING
|
self::SPACE => Token::NOTHING
|
||||||
],
|
),
|
||||||
Token::RIGHT_BRACKET => [
|
Token::RIGHT_BRACKET => array(
|
||||||
self::DIGIT => Token::NUMBER,
|
self::DIGIT => Token::NUMBER,
|
||||||
self::CHAR => Token::STRING,
|
self::CHAR => Token::STRING,
|
||||||
self::SPECIAL_CHAR => Token::OPERATOR,
|
self::SPECIAL_CHAR => Token::OPERATOR,
|
||||||
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
self::LEFT_BRACKET => Token::LEFT_BRACKET,
|
||||||
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
self::RIGHT_BRACKET => Token::RIGHT_BRACKET,
|
||||||
self::SPACE => Token::NOTHING
|
self::SPACE => Token::NOTHING
|
||||||
],
|
),
|
||||||
];
|
);
|
||||||
|
|
||||||
private $accumulator = '';
|
private $accumulator = '';
|
||||||
|
|
||||||
|
@ -92,7 +96,7 @@ class TokenParser {
|
||||||
|
|
||||||
private $queue = null;
|
private $queue = null;
|
||||||
|
|
||||||
function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->queue = new \SplQueue();
|
$this->queue = new \SplQueue();
|
||||||
}
|
}
|
||||||
|
@ -148,8 +152,10 @@ class TokenParser {
|
||||||
{
|
{
|
||||||
if ($oldState == Token::NOTHING) {
|
if ($oldState == Token::NOTHING) {
|
||||||
$this->accumulator = '';
|
$this->accumulator = '';
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($this->state != $oldState) || ($oldState == Token::LEFT_BRACKET) || ($oldState == Token::RIGHT_BRACKET)) {
|
if (($this->state != $oldState) || ($oldState == Token::LEFT_BRACKET) || ($oldState == Token::RIGHT_BRACKET)) {
|
||||||
$token = new Token($oldState, $this->accumulator);
|
$token = new Token($oldState, $this->accumulator);
|
||||||
$this->queue->push($token);
|
$this->queue->push($token);
|
19
src/NXP/Exception/IncorrectExpressionException.php
Normal file
19
src/NXP/Exception/IncorrectExpressionException.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vitaliy Zhuk <zhuk2205@gmail.com>
|
||||||
|
*/
|
||||||
|
class IncorrectExpressionException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
19
src/NXP/Exception/MathExecutorException.php
Normal file
19
src/NXP/Exception/MathExecutorException.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vitaliy Zhuk <zhuk2205@gmail.com>
|
||||||
|
*/
|
||||||
|
abstract class MathExecutorException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
19
src/NXP/Exception/UnknownFunctionException.php
Normal file
19
src/NXP/Exception/UnknownFunctionException.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vitaliy Zhuk <zhuk2205@gmail.com>
|
||||||
|
*/
|
||||||
|
class UnknownFunctionException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
19
src/NXP/Exception/UnknownOperatorException.php
Normal file
19
src/NXP/Exception/UnknownOperatorException.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vitaliy Zhuk <zhuk2205@gmail.com>
|
||||||
|
*/
|
||||||
|
class UnknownOperatorException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
19
src/NXP/Exception/UnknownTokenException.php
Normal file
19
src/NXP/Exception/UnknownTokenException.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vitaliy Zhuk <zhuk2205@gmail.com>
|
||||||
|
*/
|
||||||
|
class UnknownTokenException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
|
@ -1,28 +1,51 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author: Alexander "NeonXP" Kiryukhin
|
* This file is part of the MathExecutor package
|
||||||
* Date: 14.03.13
|
*
|
||||||
* Time: 1:01
|
* (c) Alexander Kiryukhin
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace NXP;
|
namespace NXP;
|
||||||
|
|
||||||
use NXP\Classes\Func;
|
use NXP\Classes\Func;
|
||||||
use NXP\Classes\Operand;
|
use NXP\Classes\Operand;
|
||||||
use NXP\Classes\Token;
|
use NXP\Classes\Token;
|
||||||
use NXP\Classes\TokenParser;
|
use NXP\Classes\TokenParser;
|
||||||
|
use NXP\Exception\IncorrectExpressionException;
|
||||||
|
use NXP\Exception\UnknownFunctionException;
|
||||||
|
use NXP\Exception\UnknownOperatorException;
|
||||||
|
use NXP\Exception\UnknownTokenException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class MathExecutor
|
* Class MathExecutor
|
||||||
* @package NXP
|
* @package NXP
|
||||||
*/
|
*/
|
||||||
class MathExecutor {
|
class MathExecutor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Available operators
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $operators = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available functions
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $functions = array();
|
||||||
|
|
||||||
private $operators = [ ];
|
/**
|
||||||
|
* Available variables
|
||||||
private $functions = [ ];
|
*
|
||||||
|
* @var array
|
||||||
private $variables = [ ];
|
*/
|
||||||
|
private $variables = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \SplStack
|
* @var \SplStack
|
||||||
|
@ -38,6 +61,23 @@ class MathExecutor {
|
||||||
* Base math operators
|
* Base math operators
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->addDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
$this->variables = array();
|
||||||
|
$this->operators = array();
|
||||||
|
$this->functions = array();
|
||||||
|
|
||||||
|
$this->addDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default operands and functions
|
||||||
|
*/
|
||||||
|
protected function addDefaults()
|
||||||
{
|
{
|
||||||
$this->addOperator(new Operand('+', 1, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1+$op2; }));
|
$this->addOperator(new Operand('+', 1, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1+$op2; }));
|
||||||
$this->addOperator(new Operand('-', 1, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1-$op2; }));
|
$this->addOperator(new Operand('-', 1, Operand::LEFT_ASSOCIATED, Operand::BINARY, function ($op1, $op2) { return $op1-$op2; }));
|
||||||
|
@ -55,38 +95,100 @@ class MathExecutor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add operator to executor
|
* Add operator to executor
|
||||||
* @param Operand $operator
|
*
|
||||||
|
* @param Operand $operator
|
||||||
|
* @return MathExecutor
|
||||||
*/
|
*/
|
||||||
public function addOperator(Operand $operator)
|
public function addOperator(Operand $operator)
|
||||||
{
|
{
|
||||||
$this->operators[$operator->getSymbol()] = $operator;
|
$this->operators[$operator->getSymbol()] = $operator;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add function to executor
|
* Add function to executor
|
||||||
* @param Func $function
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param callable $function
|
||||||
|
* @return MathExecutor
|
||||||
*/
|
*/
|
||||||
public function addFunction(Func $function)
|
public function addFunction($name, callable $function = null)
|
||||||
{
|
{
|
||||||
$this->functions[$function->getName()] = $function->getCallback();
|
if ($name instanceof Func) {
|
||||||
|
$this->functions[$name->getName()] = $name->getCallback();
|
||||||
|
} else {
|
||||||
|
$this->functions[$name] = $function;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add variable to executor
|
* Add variable to executor
|
||||||
* @param $variable
|
*
|
||||||
* @param $value
|
* @param string $variable
|
||||||
|
* @param integer|float $value
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
|
* @return MathExecutor
|
||||||
*/
|
*/
|
||||||
public function setVar($variable, $value)
|
public function setVar($variable, $value)
|
||||||
{
|
{
|
||||||
if (!is_numeric($value)) {
|
if (!is_numeric($value)) {
|
||||||
throw new \Exception("Variable value must be a number");
|
throw new \Exception("Variable value must be a number");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->variables[$variable] = $value;
|
$this->variables[$variable] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add variables to executor
|
||||||
|
*
|
||||||
|
* @param array $variables
|
||||||
|
* @param bool $clear Clear previous variables
|
||||||
|
* @return MathExecutor
|
||||||
|
*/
|
||||||
|
public function setVars(array $variables, $clear = true)
|
||||||
|
{
|
||||||
|
if ($clear) {
|
||||||
|
$this->removeVars();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($variables as $name => $value) {
|
||||||
|
$this->setVar($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove variable from executor
|
||||||
|
*
|
||||||
|
* @param string $variable
|
||||||
|
* @return MathExecutor
|
||||||
|
*/
|
||||||
|
public function removeVar($variable)
|
||||||
|
{
|
||||||
|
unset ($this->variables[$variable]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all variables
|
||||||
|
*/
|
||||||
|
public function removeVars()
|
||||||
|
{
|
||||||
|
$this->variables = array();
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute expression
|
* Execute expression
|
||||||
|
*
|
||||||
* @param $expression
|
* @param $expression
|
||||||
* @return int|float
|
* @return int|float
|
||||||
*/
|
*/
|
||||||
|
@ -100,6 +202,7 @@ class MathExecutor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert expression from normal expression form to RPN
|
* Convert expression from normal expression form to RPN
|
||||||
|
*
|
||||||
* @param $expression
|
* @param $expression
|
||||||
* @return \SplQueue
|
* @return \SplQueue
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
|
@ -118,9 +221,11 @@ class MathExecutor {
|
||||||
|
|
||||||
while (!$this->stack->isEmpty()) {
|
while (!$this->stack->isEmpty()) {
|
||||||
$token = $this->stack->pop();
|
$token = $this->stack->pop();
|
||||||
|
|
||||||
if ($token->getType() != Token::OPERATOR) {
|
if ($token->getType() != Token::OPERATOR) {
|
||||||
throw new \Exception('Opening bracket without closing bracket');
|
throw new \Exception('Opening bracket without closing bracket');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->queue->push($token);
|
$this->queue->push($token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +233,7 @@ class MathExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Token $token
|
* @param Token $token
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function categorizeToken(Token $token)
|
private function categorizeToken(Token $token)
|
||||||
|
@ -157,17 +262,23 @@ class MathExecutor {
|
||||||
$previousToken = $this->stack->pop();
|
$previousToken = $this->stack->pop();
|
||||||
}
|
}
|
||||||
if ((!$this->stack->isEmpty()) && ($this->stack->top()->getType() == Token::STRING)) {
|
if ((!$this->stack->isEmpty()) && ($this->stack->top()->getType() == Token::STRING)) {
|
||||||
$string = $this->stack->pop()->getValue();
|
$funcName = $this->stack->pop()->getValue();
|
||||||
if (!array_key_exists($string, $this->functions)) {
|
if (!array_key_exists($funcName, $this->functions)) {
|
||||||
throw new \Exception('Unknown function');
|
throw new UnknownFunctionException(sprintf(
|
||||||
|
'Unknown function: "%s".',
|
||||||
|
$funcName
|
||||||
|
));
|
||||||
}
|
}
|
||||||
$this->queue->push(new Token(Token::FUNC, $string));
|
$this->queue->push(new Token(Token::FUNC, $funcName));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Token::OPERATOR:
|
case Token::OPERATOR:
|
||||||
if (!array_key_exists($token->getValue(), $this->operators)) {
|
if (!array_key_exists($token->getValue(), $this->operators)) {
|
||||||
throw new \Exception("Unknown operator '{$token->getValue()}'");
|
throw new UnknownOperatorException(sprintf(
|
||||||
|
'Unknown operator: "%s".',
|
||||||
|
$token->getValue()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->proceedOperator($token);
|
$this->proceedOperator($token);
|
||||||
|
@ -175,7 +286,10 @@ class MathExecutor {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception('Unknown token');
|
throw new UnknownTokenException(sprintf(
|
||||||
|
'Unknown token: "%s".',
|
||||||
|
$token->getValue()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,17 +297,25 @@ class MathExecutor {
|
||||||
* @param $token
|
* @param $token
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function proceedOperator($token)
|
private function proceedOperator(Token $token)
|
||||||
{
|
{
|
||||||
if (!array_key_exists($token->getValue(), $this->operators)) {
|
if (!array_key_exists($token->getValue(), $this->operators)) {
|
||||||
throw new \Exception('Unknown operator');
|
throw new UnknownOperatorException(sprintf(
|
||||||
|
'Unknown operator: "%s".',
|
||||||
|
$token->getValue()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Operand $operator */
|
/** @var Operand $operator */
|
||||||
$operator = $this->operators[$token->getValue()];
|
$operator = $this->operators[$token->getValue()];
|
||||||
|
|
||||||
while (!$this->stack->isEmpty()) {
|
while (!$this->stack->isEmpty()) {
|
||||||
$top = $this->stack->top();
|
$top = $this->stack->top();
|
||||||
|
|
||||||
if ($top->getType() == Token::OPERATOR) {
|
if ($top->getType() == Token::OPERATOR) {
|
||||||
$priority = $this->operators[$top->getValue()]->getPriority();
|
/** @var Operand $operator */
|
||||||
|
$operator = $this->operators[$top->getValue()];
|
||||||
|
$priority = $operator->getPriority();
|
||||||
if ( $operator->getAssociation() == Operand::RIGHT_ASSOCIATED) {
|
if ( $operator->getAssociation() == Operand::RIGHT_ASSOCIATED) {
|
||||||
if (($priority > $operator->getPriority())) {
|
if (($priority > $operator->getPriority())) {
|
||||||
$this->queue->push($this->stack->pop());
|
$this->queue->push($this->stack->pop());
|
||||||
|
@ -216,19 +338,20 @@ class MathExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \SplQueue $expression
|
* @param \SplQueue $expression
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function calculateReversePolishNotation(\SplQueue $expression)
|
private function calculateReversePolishNotation(\SplQueue $expression)
|
||||||
{
|
{
|
||||||
$this->stack = new \SplStack();
|
$this->stack = new \SplStack();
|
||||||
/** @val Token $token */
|
/** @var Token $token */
|
||||||
foreach ($expression as $token) {
|
foreach ($expression as $token) {
|
||||||
switch ($token->getType()) {
|
switch ($token->getType()) {
|
||||||
case Token::NUMBER :
|
case Token::NUMBER :
|
||||||
$this->stack->push($token);
|
$this->stack->push($token);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Token::OPERATOR:
|
case Token::OPERATOR:
|
||||||
/** @var Operand $operator */
|
/** @var Operand $operator */
|
||||||
$operator = $this->operators[$token->getValue()];
|
$operator = $this->operators[$token->getValue()];
|
||||||
|
@ -241,22 +364,28 @@ class MathExecutor {
|
||||||
}
|
}
|
||||||
$callback = $operator->getCallback();
|
$callback = $operator->getCallback();
|
||||||
|
|
||||||
|
$this->stack->push(new Token(Token::NUMBER, (call_user_func($callback, $arg1, $arg2))));
|
||||||
$this->stack->push(new Token(Token::NUMBER, ($callback($arg1, $arg2))));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Token::FUNC:
|
case Token::FUNC:
|
||||||
/** @var Func $function */
|
/** @var Func $function */
|
||||||
$callback = $this->functions[$token->getValue()];
|
$callback = $this->functions[$token->getValue()];
|
||||||
$arg = $this->stack->pop()->getValue();
|
$arg = $this->stack->pop()->getValue();
|
||||||
$this->stack->push(new Token(Token::NUMBER, ($callback($arg))));
|
$this->stack->push(new Token(Token::NUMBER, (call_user_func($callback, $arg))));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception('Unknown token');
|
throw new UnknownTokenException(sprintf(
|
||||||
|
'Unknown token: "%s".',
|
||||||
|
$token->getValue()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->stack->pop()->getValue();
|
$result = $this->stack->pop()->getValue();
|
||||||
|
|
||||||
if (!$this->stack->isEmpty()) {
|
if (!$this->stack->isEmpty()) {
|
||||||
throw new \Exception('Incorrect expression');
|
throw new IncorrectExpressionException('Incorrect expression.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
12
test.php
12
test.php
|
@ -1,12 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Author: Alexander "NeonXP" Kiryukhin
|
|
||||||
* Date: 14.03.13
|
|
||||||
* Time: 1:08
|
|
||||||
*/
|
|
||||||
require "vendor/autoload.php";
|
|
||||||
|
|
||||||
$e = new \NXP\MathExecutor();
|
|
||||||
|
|
||||||
$r = $e->execute("1 + 2 * (2 - (4+10))^2");
|
|
||||||
var_dump($r);
|
|
51
tests/MathTest.php
Normal file
51
tests/MathTest.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?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\Tests;
|
||||||
|
|
||||||
|
use \NXP\MathExecutor;
|
||||||
|
|
||||||
|
class MathTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider providerExpressions
|
||||||
|
*/
|
||||||
|
public function testCalculating($expression)
|
||||||
|
{
|
||||||
|
$calculator = new MathExecutor();
|
||||||
|
|
||||||
|
/** @var float $phpResult */
|
||||||
|
eval('$phpResult = ' . $expression . ';');
|
||||||
|
$this->assertEquals($calculator->execute($expression), $phpResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expressions data provider
|
||||||
|
*/
|
||||||
|
public function providerExpressions()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('0.1 + 0.2'),
|
||||||
|
array('1 + 2'),
|
||||||
|
|
||||||
|
array('0.1 - 0.2'),
|
||||||
|
array('1 - 2'),
|
||||||
|
|
||||||
|
array('0.1 * 2'),
|
||||||
|
array('1 * 2'),
|
||||||
|
|
||||||
|
array('0.1 / 0.2'),
|
||||||
|
array('1 / 2'),
|
||||||
|
|
||||||
|
array('1 + 0.6 - (3 * 2 / 50)')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
11
tests/bootstrap.php
Normal file
11
tests/bootstrap.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$vendorDir = __DIR__ . '/../../..';
|
||||||
|
|
||||||
|
if (file_exists($file = $vendorDir . '/autoload.php')) {
|
||||||
|
require_once $file;
|
||||||
|
} else if (file_exists($file = './vendor/autoload.php')) {
|
||||||
|
require_once $file;
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException("Not found composer autoload");
|
||||||
|
}
|
Loading…
Reference in a new issue