diff --git a/README.md b/README.md index b535a36..a36eff7 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,26 @@ It can take a string with a math expression and parses it so it can be evaluated The packages supports operations like addition, subtraction, multiplication and division. +Changes in comparison to original project +----------------------------------------- + +- allow multi-character variable names +- allow reuse of formula object: +```php +$formula = new Formula('a + b'); +$formula->setVariable('a', 1); +$formula->setVariable('b', 41); +$c = $formula->calculate(); // 42 +$formula->setVariable('a', 3); +$formula->setVariable('b', 1334); +$d = $formula->calculate(); // 1337 +``` +- remove try-catch to hide exceptions from stdout (e.g. website) +- bugfix for formulas that use brackets like `(a + b) * c` +- remove `round(..., 2)` for divisions to avoid minor calculation errors +- changed division-by-zero behavior from PHP's error to giving result `NAN` (a PHP constant) because I prefer silent error handling in this case ;-) +- added exception if a variable is undefined (missing `setVariable(...)` statement) + Installation ------------ diff --git a/src/ExpressionFactory.php b/src/ExpressionFactory.php index ffd1eb4..0955b04 100644 --- a/src/ExpressionFactory.php +++ b/src/ExpressionFactory.php @@ -15,7 +15,7 @@ public static function factory(string $expression, $variables = []) return new Double( $expression ); } else if ( strpos( $expression, '%' ) !== false ) { return new Percent( $expression ); - } else if ( ( int ) $expression > 0 ) { + } else if ( is_numeric($expression) ) { return new Integer( $expression ); } else { $variable = new Variable( $expression ); diff --git a/src/Formula.php b/src/Formula.php index c9ce60f..24e7a1d 100644 --- a/src/Formula.php +++ b/src/Formula.php @@ -27,6 +27,11 @@ class Formula */ protected $source = ''; + /** + * @var string + */ + protected $parsed = ''; + /** * @var Operator */ @@ -64,7 +69,7 @@ public function getSource(): string */ public function getExpression(): ?Operator { - $key = substr($this->source, 1, -1); + $key = substr($this->parsed, 1, -1); if (array_key_exists($key, $this->expressions)) { return $this->expressions[$key]; @@ -128,10 +133,13 @@ private function getExpressionObject(string $expression): Expression */ public function parse() { + $this->parsed = $this->source; + $this->expressions = []; + + $operandPattern = '([\d\.,%]+|[^\{\}\.,%\(\)\*\/\+-]+|\{[\w\d]+\})'; $patterns = [ - '/(([\d\.,%]+|[^\{\}]|[\{\w\d\}]+)(\*)([\d\.,%]+|[^\{\}]|[\{\w\d\}]+))/i', - '/(([\d\.,%]+|[^\{\}]|[\{\w\d\}]+)(\/)([\d\.,%]+|[^\{\}]|[\{\w\d\}]+))/i', - '/(([\d\.,%]+|[^\{\}]|[\{\w\d\}]+)([\+|-])([\d\.,%]+|[^\{\}]|[\{\w\d\}]+))/i', + '/('.$operandPattern.'(\*|\/)'.$operandPattern.')/i', + '/('.$operandPattern.'(\+|-)'.$operandPattern.')/i' ]; $operators = [ @@ -142,34 +150,30 @@ public function parse() ]; // Выражение в скобках - while (preg_match('/\(((?:(?>[^()]+)|(?R))*)\)/i', $this->source, $results)) { + while (preg_match('/\(((?:(?>[^()]+)|(?R))*)\)/i', $this->parsed, $results)) { $key = $this->generateKey(); $formula = new static($results[1]); - foreach ($this->variables as $key => $var) { - $formula->setVariable($key, $var); + foreach ($this->variables as $varKey => $var) { + $formula->setVariable($varKey, $var); } $formula->parse(); $this->expressions[$key] = $formula->getExpression(); - $this->source = str_replace($results[0], '{' . $key . '}', $this->source); + $this->parsed = str_replace($results[0], '{' . $key . '}', $this->parsed); } foreach ($patterns as $pattern) { - while (preg_match($pattern, $this->source, $results)) { - try { - $left = $this->getExpressionObject( $results[2] ); - $right = $this->getExpressionObject( $results[4] ); - $key = $this->generateKey(); - - $this->expressions[ $key ] = new $operators[$results[3]]( $left, $right ); - - $this->source = str_replace( $results[1], '{' . $key . '}', $this->source ); - } catch(\Exception $e) { - echo $e->getMessage(); - } + while (preg_match($pattern, $this->parsed, $results)) { + $left = $this->getExpressionObject( $results[2] ); + $right = $this->getExpressionObject( $results[4] ); + $key = $this->generateKey(); + + $this->expressions[ $key ] = new $operators[$results[3]]( $left, $right ); + + $this->parsed = str_replace( $results[1], '{' . $key . '}', $this->parsed ); } } - $this->result = $this->getExpressionObject( $this->source ); + $this->result = $this->getExpressionObject( $this->parsed ); } } \ No newline at end of file diff --git a/src/expression/Division.php b/src/expression/Division.php index 63fc788..a2f101f 100644 --- a/src/expression/Division.php +++ b/src/expression/Division.php @@ -9,6 +9,6 @@ class Division extends Operator */ public function doCalculate(float $left, float $right): float { - return round($left / $right, 2); + return $right == 0 ? NAN : $left / $right; } } \ No newline at end of file diff --git a/src/operator/Variable.php b/src/operator/Variable.php index 87bc9a7..1817617 100644 --- a/src/operator/Variable.php +++ b/src/operator/Variable.php @@ -7,7 +7,7 @@ class Variable extends Expression { protected $key; - public function __construct(string $key, string $value = '0') + public function __construct(string $key, string $value = '') { $this->key = $key; parent::__construct($value); @@ -26,6 +26,9 @@ public function setValue(string $value) */ public function calculate(Operator $operator): float { + if($this->value === ''){ + throw new \BadMethodCallException('undefined variable: '.$this->key); + } $this->value = str_replace(',', '.', $this->value); return (float) $this->value; }