diff --git a/phpunit.xml b/phpunit.xml index ea7c8e9..886c9ee 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -26,6 +26,11 @@ tests + + + tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php + + diff --git a/src/MathParser/Interpreting/Evaluator.php b/src/MathParser/Interpreting/Evaluator.php index 4b17c73..34931d6 100644 --- a/src/MathParser/Interpreting/Evaluator.php +++ b/src/MathParser/Interpreting/Evaluator.php @@ -11,6 +11,7 @@ use MathParser\Interpreting\Visitors\Visitor; use MathParser\Parsing\Nodes\Node; use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\NotBooleanNode; use MathParser\Parsing\Nodes\NumberNode; use MathParser\Parsing\Nodes\VariableNode; use MathParser\Parsing\Nodes\FunctionNode; @@ -104,16 +105,32 @@ public function visitExpressionNode(ExpressionNode $node) // Perform the right operation based on the operator switch ($operator) { case '+': - return $leftValue + $rightValue; + return $leftValue + $rightValue; case '-': - return $rightValue === null ? -$leftValue : $leftValue - $rightValue; + return $rightValue === null ? -$leftValue : $leftValue - $rightValue; case '*': - return $rightValue * $leftValue; + return $rightValue * $leftValue; case '/': - if ($rightValue == 0) throw new DivisionByZeroException(); - return $leftValue/$rightValue; + if ($rightValue == 0) throw new DivisionByZeroException(); + return $leftValue/$rightValue; case '^': - return pow($leftValue, $rightValue); + return pow($leftValue, $rightValue); + case '=': + return intval(ceil($leftValue)) === intval(ceil($rightValue)); + case '&&': + return intval(ceil($leftValue)) && intval(ceil($rightValue)); + case '||': + return intval(ceil($leftValue)) || intval(ceil($rightValue)); + + case '>': + return ($leftValue > $rightValue); + case '>=': + return $leftValue >= $rightValue; + case '<': + return $leftValue < $rightValue; + case '<=': + return $leftValue <= $rightValue; + default: throw new UnknownOperatorException($operator); @@ -274,8 +291,15 @@ public function visitFunctionNode(FunctionNode $node) return $inner >= 0 ? 1 : -1; case '!': - $logGamma = Math::logGamma(1+$inner); - return exp($logGamma); + if($node instanceof NotBooleanNode) { + $intValue = intval(ceil($inner)); + $result = !$intValue; + } + else { + $logGamma = Math::logGamma(1+$inner); + $result = exp($logGamma); + } + return $result; case '!!': if (round($inner) != $inner)throw new \UnexpectedValueException("Expecting positive integer (semifactorial)"); diff --git a/src/MathParser/Lexing/ComplexLexer.php b/src/MathParser/Lexing/ComplexLexer.php index 14abe7a..d0dd23d 100644 --- a/src/MathParser/Lexing/ComplexLexer.php +++ b/src/MathParser/Lexing/ComplexLexer.php @@ -109,6 +109,16 @@ public function __construct() $this->add(new TokenDefinition('/\//', TokenType::DivisionOperator)); $this->add(new TokenDefinition('/\^/', TokenType::ExponentiationOperator)); + $this->add(new TokenDefinition('/\=/', TokenType::EqualCompareOperator)); + $this->add(new TokenDefinition('/\>=/', TokenType::GreaterOrEqualOperator)); + $this->add(new TokenDefinition('/\>/', TokenType::GreaterOperator)); + $this->add(new TokenDefinition('/\<=/', TokenType::SmallerOrEqualOperator)); + $this->add(new TokenDefinition('/\add(new TokenDefinition('/\&&/', TokenType::BooleanAndOperator)); + $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); + $this->add(new TokenDefinition('/pi/', TokenType::Constant)); $this->add(new TokenDefinition('/e/', TokenType::Constant)); $this->add(new TokenDefinition('/i/', TokenType::Constant)); diff --git a/src/MathParser/Lexing/Lexer.php b/src/MathParser/Lexing/Lexer.php index 2bfa0cd..6553f4a 100644 --- a/src/MathParser/Lexing/Lexer.php +++ b/src/MathParser/Lexing/Lexer.php @@ -81,8 +81,10 @@ public function tokenize($input) // If no tokens were matched, it means that the string has invalid tokens // for which we did not define a token definition - if (!$token) + if (!$token) { throw new UnknownTokenException(0,$currentIndex); + } + // Add the matched token to our list of token $tokens[] = $token; diff --git a/src/MathParser/Lexing/StdMathLexer.php b/src/MathParser/Lexing/StdMathLexer.php index c07b3b6..1e5a140 100644 --- a/src/MathParser/Lexing/StdMathLexer.php +++ b/src/MathParser/Lexing/StdMathLexer.php @@ -112,6 +112,16 @@ public function __construct() $this->add(new TokenDefinition('/\//', TokenType::DivisionOperator)); $this->add(new TokenDefinition('/\^/', TokenType::ExponentiationOperator)); + + $this->add(new TokenDefinition('/\=/', TokenType::EqualCompareOperator)); + $this->add(new TokenDefinition('/\>=/', TokenType::GreaterOrEqualOperator)); + $this->add(new TokenDefinition('/\>/', TokenType::GreaterOperator)); + $this->add(new TokenDefinition('/\<=/', TokenType::SmallerOrEqualOperator)); + $this->add(new TokenDefinition('/\add(new TokenDefinition('/\&&/', TokenType::BooleanAndOperator)); + $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); + // Postfix operators $this->add(new TokenDefinition('/\!\!/', TokenType::SemiFactorialOperator)); $this->add(new TokenDefinition('/\!/', TokenType::FactorialOperator)); diff --git a/src/MathParser/Lexing/Token.php b/src/MathParser/Lexing/Token.php index 82ac9c2..00ee256 100644 --- a/src/MathParser/Lexing/Token.php +++ b/src/MathParser/Lexing/Token.php @@ -146,8 +146,14 @@ public static function canFactorsInImplicitMultiplication($token1, $token2) if (!$check2) return false; - if ($token1->type == TokenType::FunctionName && $token2->type == TokenType::OpenParenthesis) + if ($token1->type == TokenType::FunctionName && $token2->type == TokenType::OpenParenthesis) { return false; + } + + if($token1->type == TokenType::FactorialOperator && $check2) { + return false; + } + return true; } diff --git a/src/MathParser/Lexing/TokenType.php b/src/MathParser/Lexing/TokenType.php index ef55fd8..b19ba13 100644 --- a/src/MathParser/Lexing/TokenType.php +++ b/src/MathParser/Lexing/TokenType.php @@ -67,6 +67,16 @@ final class TokenType /** Token representing postfix subfactorial operator '!!' */ const SemiFactorialOperator = 105; + /** Token representing equal operator */ + const EqualCompareOperator = 106; + const BooleanAndOperator = 107; + const BooleanOrOperator = 108; + const GreaterOperator = 109; + const GreaterOrEqualOperator = 110; + const SmallerOperator = 111; + const SmallerOrEqualOperator = 112; + + /** Token represented a function name, e.g. 'sin' */ const FunctionName = 200; diff --git a/src/MathParser/Parsing/Nodes/ExpressionNode.php b/src/MathParser/Parsing/Nodes/ExpressionNode.php index e6d7992..73c0de2 100644 --- a/src/MathParser/Parsing/Nodes/ExpressionNode.php +++ b/src/MathParser/Parsing/Nodes/ExpressionNode.php @@ -96,6 +96,41 @@ function __construct($left, $operator = null, $right = null) $this->associativity = self::RIGHT_ASSOC; break; + case '=': + $this->precedence = 5; + $this->associativity = self::LEFT_ASSOC; + break; + + case '&&': + $this->precedence = 5; + $this->associativity = self::LEFT_ASSOC; + break; + + case '||': + $this->precedence = 5; + $this->associativity = self::LEFT_ASSOC; + break; + + case '>': + $this->precedence = 5; + $this->associativity = self::LEFT_ASSOC; + break; + + case '>=': + $this->precedence = 5; + $this->associativity = self::LEFT_ASSOC; + break; + + case '<': + $this->precedence = 5; + $this->associativity = self::LEFT_ASSOC; + break; + + case '<=': + $this->precedence = 5; + $this->associativity = self::LEFT_ASSOC; + break; + default: throw new UnknownOperatorException($operator); } diff --git a/src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php new file mode 100644 index 0000000..0c6a129 --- /dev/null +++ b/src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php @@ -0,0 +1,73 @@ + +* @copyright 2015 Frank Wikström +* @license http://www.opensource.org/licenses/lgpl-license.php LGPL +* +*/ + +namespace MathParser\Parsing\Nodes\Factories; + +use MathParser\Parsing\Nodes\Interfaces\ExpressionNodeFactory; + +use MathParser\Parsing\Nodes\Node; +use MathParser\Parsing\Nodes\NumberNode; +use MathParser\Parsing\Nodes\IntegerNode; +use MathParser\Parsing\Nodes\RationalNode; + +use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\Traits\Sanitize; +use MathParser\Parsing\Nodes\Traits\Numeric; + +/** +* Factory for creating an ExpressionNode representing '&&'. +* +* Some basic simplification is applied to the resulting Node. +* +*/ +class BoolAndNodeFactory implements ExpressionNodeFactory +{ + use Sanitize; + use Numeric; + + public function makeNode($leftOperand, $rightOperand) + { + $leftOperand = $this->sanitize($leftOperand); + $rightOperand = $this->sanitize($rightOperand); + + $node = $this->numericTerms($leftOperand, $rightOperand); + if ($node) return $node; + + return new ExpressionNode($leftOperand, '&&', $rightOperand); + } + + protected function numericTerms($leftOperand, $rightOperand) + { + + if (!$this->isNumeric($leftOperand) || !$this->isNumeric($rightOperand)) { + return null; + } + $type = $this->resultingType($leftOperand, $rightOperand); + switch($type) { + case Node::NumericFloat: + $result = ( intval(ceil($leftOperand->getValue())) && intval(ceil($rightOperand->getValue()))); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $leftValue = intval(ceil($leftValue)); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $rightValue = intval(ceil($rightValue)); + $result = ($leftValue && $rightValue); + return new IntegerNode($result); + + case Node::NumericInteger: + $result = ($leftOperand->getValue() && $rightOperand->getValue()); + return new IntegerNode($result); + } + + + return null; + } +} diff --git a/src/MathParser/Parsing/Nodes/Factories/BoolEqualNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/BoolEqualNodeFactory.php new file mode 100644 index 0000000..043a716 --- /dev/null +++ b/src/MathParser/Parsing/Nodes/Factories/BoolEqualNodeFactory.php @@ -0,0 +1,71 @@ + +* @copyright 2015 Frank Wikström +* @license http://www.opensource.org/licenses/lgpl-license.php LGPL +* +*/ + +namespace MathParser\Parsing\Nodes\Factories; + +use MathParser\Parsing\Nodes\Interfaces\ExpressionNodeFactory; + +use MathParser\Parsing\Nodes\Node; +use MathParser\Parsing\Nodes\NumberNode; +use MathParser\Parsing\Nodes\IntegerNode; +use MathParser\Parsing\Nodes\RationalNode; + +use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\Traits\Sanitize; +use MathParser\Parsing\Nodes\Traits\Numeric; + +/** +* Factory for creating an ExpressionNode representing '='. +* +* Some basic simplification is applied to the resulting Node. +* +*/ +class BoolEqualNodeFactory implements ExpressionNodeFactory +{ + use Sanitize; + use Numeric; + + public function makeNode($leftOperand, $rightOperand) + { + $leftOperand = $this->sanitize($leftOperand); + $rightOperand = $this->sanitize($rightOperand); + + $node = $this->numericTerms($leftOperand, $rightOperand); + if ($node) return $node; + + return new ExpressionNode($leftOperand, '=', $rightOperand); + } + + protected function numericTerms($leftOperand, $rightOperand) + { + if (!$this->isNumeric($leftOperand) || !$this->isNumeric($rightOperand)) { + return null; + } + $type = $this->resultingType($leftOperand, $rightOperand); + + switch($type) { + case Node::NumericFloat: + $result = ($leftOperand->getValue() === $rightOperand->getValue()); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $result = ($leftValue === $rightValue); + return new IntegerNode($result); + + case Node::NumericInteger: + $result = ($leftOperand->getValue() === $rightOperand->getValue()); + return new IntegerNode($result); + } + + + return null; + } +} diff --git a/src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php new file mode 100644 index 0000000..acfdb95 --- /dev/null +++ b/src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php @@ -0,0 +1,75 @@ + +* @copyright 2015 Frank Wikström +* @license http://www.opensource.org/licenses/lgpl-license.php LGPL +* +*/ + +namespace MathParser\Parsing\Nodes\Factories; + +use MathParser\Parsing\Nodes\Interfaces\ExpressionNodeFactory; + +use MathParser\Parsing\Nodes\Node; +use MathParser\Parsing\Nodes\NumberNode; +use MathParser\Parsing\Nodes\IntegerNode; +use MathParser\Parsing\Nodes\RationalNode; + +use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\Traits\Sanitize; +use MathParser\Parsing\Nodes\Traits\Numeric; + +/** +* Factory for creating an ExpressionNode representing '||'. +* +* Some basic simplification is applied to the resulting Node. +* +*/ +class BoolOrNodeFactory implements ExpressionNodeFactory +{ + use Sanitize; + use Numeric; + + + public function makeNode($leftOperand, $rightOperand) + { + $leftOperand = $this->sanitize($leftOperand); + $rightOperand = $this->sanitize($rightOperand); + + $node = $this->numericTerms($leftOperand, $rightOperand); + if ($node) return $node; + + return new ExpressionNode($leftOperand, '||', $rightOperand); + } + + + protected function numericTerms($leftOperand, $rightOperand) + { + + if (!$this->isNumeric($leftOperand) || !$this->isNumeric($rightOperand)) { + return null; + } + $type = $this->resultingType($leftOperand, $rightOperand); + switch($type) { + case Node::NumericFloat: + $result = ( intval(ceil($leftOperand->getValue())) || intval(ceil($rightOperand->getValue()))); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $leftValue = intval(ceil($leftValue)); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $rightValue = intval(ceil($rightValue)); + $result = ($leftValue || $rightValue); + return new IntegerNode($result); + + case Node::NumericInteger: + $result = ($leftOperand->getValue() || $rightOperand->getValue()); + return new IntegerNode($result); + } + + + return null; + } +} diff --git a/src/MathParser/Parsing/Nodes/Factories/GreaterNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/GreaterNodeFactory.php new file mode 100644 index 0000000..33a727b --- /dev/null +++ b/src/MathParser/Parsing/Nodes/Factories/GreaterNodeFactory.php @@ -0,0 +1,71 @@ + +* @copyright 2015 Frank Wikström +* @license http://www.opensource.org/licenses/lgpl-license.php LGPL +* +*/ + +namespace MathParser\Parsing\Nodes\Factories; + +use MathParser\Parsing\Nodes\Interfaces\ExpressionNodeFactory; + +use MathParser\Parsing\Nodes\Node; +use MathParser\Parsing\Nodes\NumberNode; +use MathParser\Parsing\Nodes\IntegerNode; +use MathParser\Parsing\Nodes\RationalNode; + +use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\Traits\Sanitize; +use MathParser\Parsing\Nodes\Traits\Numeric; + +/** +* Factory for creating an ExpressionNode representing '='. +* +* Some basic simplification is applied to the resulting Node. +* +*/ +class GreaterNodeFactory implements ExpressionNodeFactory +{ + use Sanitize; + use Numeric; + + public function makeNode($leftOperand, $rightOperand) + { + $leftOperand = $this->sanitize($leftOperand); + $rightOperand = $this->sanitize($rightOperand); + + $node = $this->numericTerms($leftOperand, $rightOperand); + if ($node) return $node; + + return new ExpressionNode($leftOperand, '>', $rightOperand); + } + + protected function numericTerms($leftOperand, $rightOperand) + { + if (!$this->isNumeric($leftOperand) || !$this->isNumeric($rightOperand)) { + return null; + } + $type = $this->resultingType($leftOperand, $rightOperand); + + switch($type) { + case Node::NumericFloat: + $result = ($leftOperand->getValue() > $rightOperand->getValue()); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $result = ($leftValue > $rightValue); + return new IntegerNode($result); + + case Node::NumericInteger: + $result = ($leftOperand->getValue() > $rightOperand->getValue()); + return new IntegerNode($result); + } + + + return null; + } +} diff --git a/src/MathParser/Parsing/Nodes/Factories/GreaterOrEqualNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/GreaterOrEqualNodeFactory.php new file mode 100644 index 0000000..16c62b2 --- /dev/null +++ b/src/MathParser/Parsing/Nodes/Factories/GreaterOrEqualNodeFactory.php @@ -0,0 +1,71 @@ + +* @copyright 2015 Frank Wikström +* @license http://www.opensource.org/licenses/lgpl-license.php LGPL +* +*/ + +namespace MathParser\Parsing\Nodes\Factories; + +use MathParser\Parsing\Nodes\Interfaces\ExpressionNodeFactory; + +use MathParser\Parsing\Nodes\Node; +use MathParser\Parsing\Nodes\NumberNode; +use MathParser\Parsing\Nodes\IntegerNode; +use MathParser\Parsing\Nodes\RationalNode; + +use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\Traits\Sanitize; +use MathParser\Parsing\Nodes\Traits\Numeric; + +/** +* Factory for creating an ExpressionNode representing >= +* +* Some basic simplification is applied to the resulting Node. +* +*/ +class GreaterOrEqualNodeFactory implements ExpressionNodeFactory +{ + use Sanitize; + use Numeric; + + public function makeNode($leftOperand, $rightOperand) + { + $leftOperand = $this->sanitize($leftOperand); + $rightOperand = $this->sanitize($rightOperand); + + $node = $this->numericTerms($leftOperand, $rightOperand); + if ($node) return $node; + + return new ExpressionNode($leftOperand, '>=', $rightOperand); + } + + protected function numericTerms($leftOperand, $rightOperand) + { + if (!$this->isNumeric($leftOperand) || !$this->isNumeric($rightOperand)) { + return null; + } + $type = $this->resultingType($leftOperand, $rightOperand); + + switch($type) { + case Node::NumericFloat: + $result = ($leftOperand->getValue() >= $rightOperand->getValue()); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $result = ($leftValue >= $rightValue); + return new IntegerNode($result); + + case Node::NumericInteger: + $result = ($leftOperand->getValue() >= $rightOperand->getValue()); + return new IntegerNode($result); + } + + + return null; + } +} diff --git a/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php index aba72a6..f60d5bd 100644 --- a/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php @@ -75,6 +75,17 @@ class NodeFactory { **/ protected $exponentiationFactory; + protected $boolEqualNodeFactory; + + protected $boolAndNodeFactory; + + protected $boolOrNodeFactory; + + protected $greaterNodeFactory; + protected $greaterOrEqualNodeFactory; + protected $smallerNodeFactory; + protected $smallerOrEqualNodeFactory; + /** * Constructor */ @@ -85,6 +96,13 @@ public function __construct() $this->multiplicationFactory = new MultiplicationNodeFactory(); $this->divisionFactory = new DivisionNodeFactory(); $this->exponentiationFactory = new ExponentiationNodeFactory(); + $this->boolEqualNodeFactory = new BoolEqualNodeFactory(); + $this->boolAndNodeFactory = new BoolAndNodeFactory(); + $this->boolOrNodeFactory = new BoolOrNodeFactory(); + $this->greaterNodeFactory = new GreaterNodeFactory(); + $this->greaterOrEqualNodeFactory = new GreaterOrEqualNodeFactory(); + $this->smallerNodeFactory = new SmallerNodeFactory(); + $this->smallerOrEqualNodeFactory = new SmallerOrEqualNodeFactory(); } /** @@ -164,6 +182,36 @@ public function unaryMinus($operand) return $this->subtractionFactory->createUnaryMinusNode($operand); } + + public function compareEqual($leftOperand, $rightOperand) { + return $this->boolEqualNodeFactory->makeNode($leftOperand, $rightOperand); + } + + + public function boolAnd($leftOperand, $rightOperand) { + return $this->boolAndNodeFactory->makeNode($leftOperand, $rightOperand); + } + + public function boolOr($leftOperand, $rightOperand) { + return $this->boolOrNodeFactory->makeNode($leftOperand, $rightOperand); + } + + public function greater($leftOperand, $rightOperand) { + return $this->greaterNodeFactory->makeNode($leftOperand, $rightOperand); + } + + public function greaterOrEqual($leftOperand, $rightOperand) { + return $this->greaterOrEqualNodeFactory->makeNode($leftOperand, $rightOperand); + } + + public function smaller($leftOperand, $rightOperand) { + return $this->smallerNodeFactory->makeNode($leftOperand, $rightOperand); + } + + public function smallerOrEqual($leftOperand, $rightOperand) { + return $this->smallerOrEqualNodeFactory->makeNode($leftOperand, $rightOperand); + } + /** * Simplify the given ExpressionNode, using the appropriate factory. * @@ -178,6 +226,14 @@ public function simplify(ExpressionNode $node) case '*': return $this->multiplication($node->getLeft(), $node->getRight()); case '/': return $this->division($node->getLeft(), $node->getRight()); case '^': return $this->exponentiation($node->getLeft(), $node->getRight()); + case '=': return $this->compareEqual($node->getLeft(), $node->getRight()); + case '>': return $this->greater($node->getLeft(), $node->getRight()); + case '>=': return $this->greaterOrEqual($node->getLeft(), $node->getRight()); + case '<': return $this->smaller($node->getLeft(), $node->getRight()); + case '<=': return $this->smallerOrEqual($node->getLeft(), $node->getRight()); + case '&&': return $this->boolAnd($node->getLeft(), $node->getRight()); + case '||': return $this->boolOr($node->getLeft(), $node->getRight()); + } } diff --git a/src/MathParser/Parsing/Nodes/Factories/SmallerNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/SmallerNodeFactory.php new file mode 100644 index 0000000..86ba8aa --- /dev/null +++ b/src/MathParser/Parsing/Nodes/Factories/SmallerNodeFactory.php @@ -0,0 +1,71 @@ + +* @copyright 2015 Frank Wikström +* @license http://www.opensource.org/licenses/lgpl-license.php LGPL +* +*/ + +namespace MathParser\Parsing\Nodes\Factories; + +use MathParser\Parsing\Nodes\Interfaces\ExpressionNodeFactory; + +use MathParser\Parsing\Nodes\Node; +use MathParser\Parsing\Nodes\NumberNode; +use MathParser\Parsing\Nodes\IntegerNode; +use MathParser\Parsing\Nodes\RationalNode; + +use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\Traits\Sanitize; +use MathParser\Parsing\Nodes\Traits\Numeric; + +/** +* Factory for creating an ExpressionNode representing '<'. +* +* Some basic simplification is applied to the resulting Node. +* +*/ +class SmallerNodeFactory implements ExpressionNodeFactory +{ + use Sanitize; + use Numeric; + + public function makeNode($leftOperand, $rightOperand) + { + $leftOperand = $this->sanitize($leftOperand); + $rightOperand = $this->sanitize($rightOperand); + + $node = $this->numericTerms($leftOperand, $rightOperand); + if ($node) return $node; + + return new ExpressionNode($leftOperand, '<', $rightOperand); + } + + protected function numericTerms($leftOperand, $rightOperand) + { + if (!$this->isNumeric($leftOperand) || !$this->isNumeric($rightOperand)) { + return null; + } + $type = $this->resultingType($leftOperand, $rightOperand); + + switch($type) { + case Node::NumericFloat: + $result = ($leftOperand->getValue() < $rightOperand->getValue()); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $result = ($leftValue < $rightValue); + return new IntegerNode($result); + + case Node::NumericInteger: + $result = ($leftOperand->getValue() < $rightOperand->getValue()); + return new IntegerNode($result); + } + + + return null; + } +} diff --git a/src/MathParser/Parsing/Nodes/Factories/SmallerOrEqualNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/SmallerOrEqualNodeFactory.php new file mode 100644 index 0000000..55b3b52 --- /dev/null +++ b/src/MathParser/Parsing/Nodes/Factories/SmallerOrEqualNodeFactory.php @@ -0,0 +1,71 @@ + +* @copyright 2015 Frank Wikström +* @license http://www.opensource.org/licenses/lgpl-license.php LGPL +* +*/ + +namespace MathParser\Parsing\Nodes\Factories; + +use MathParser\Parsing\Nodes\Interfaces\ExpressionNodeFactory; + +use MathParser\Parsing\Nodes\Node; +use MathParser\Parsing\Nodes\NumberNode; +use MathParser\Parsing\Nodes\IntegerNode; +use MathParser\Parsing\Nodes\RationalNode; + +use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\Traits\Sanitize; +use MathParser\Parsing\Nodes\Traits\Numeric; + +/** +* Factory for creating an ExpressionNode representing '<'. +* +* Some basic simplification is applied to the resulting Node. +* +*/ +class SmallerOrEqualNodeFactory implements ExpressionNodeFactory +{ + use Sanitize; + use Numeric; + + public function makeNode($leftOperand, $rightOperand) + { + $leftOperand = $this->sanitize($leftOperand); + $rightOperand = $this->sanitize($rightOperand); + + $node = $this->numericTerms($leftOperand, $rightOperand); + if ($node) return $node; + + return new ExpressionNode($leftOperand, '<=', $rightOperand); + } + + protected function numericTerms($leftOperand, $rightOperand) + { + if (!$this->isNumeric($leftOperand) || !$this->isNumeric($rightOperand)) { + return null; + } + $type = $this->resultingType($leftOperand, $rightOperand); + + switch($type) { + case Node::NumericFloat: + $result = ($leftOperand->getValue() <= $rightOperand->getValue()); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $result = ($leftValue <= $rightValue); + return new IntegerNode($result); + + case Node::NumericInteger: + $result = ($leftOperand->getValue() <= $rightOperand->getValue()); + return new IntegerNode($result); + } + + + return null; + } +} diff --git a/src/MathParser/Parsing/Nodes/Node.php b/src/MathParser/Parsing/Nodes/Node.php index 72ec60c..9044d57 100644 --- a/src/MathParser/Parsing/Nodes/Node.php +++ b/src/MathParser/Parsing/Nodes/Node.php @@ -71,6 +71,8 @@ public static function rationalFactory(Token $token) case TokenType::MultiplicationOperator: case TokenType::DivisionOperator: case TokenType::ExponentiationOperator: + case TokenType::EqualCompareOperator: + case TokenType::BooleanAndOperator: return new ExpressionNode(null, $token->getValue(), null); case TokenType::FactorialOperator: @@ -118,6 +120,14 @@ public static function factory(Token $token) case TokenType::MultiplicationOperator: case TokenType::DivisionOperator: case TokenType::ExponentiationOperator: + case TokenType::EqualCompareOperator: + case TokenType::BooleanAndOperator: + case TokenType::BooleanOrOperator: + case TokenType::GreaterOperator: + case TokenType::GreaterOrEqualOperator: + case TokenType::SmallerOperator: + case TokenType::SmallerOrEqualOperator: + return new ExpressionNode(null, $token->getValue(), null); case TokenType::FactorialOperator: diff --git a/src/MathParser/Parsing/Nodes/NotBooleanNode.php b/src/MathParser/Parsing/Nodes/NotBooleanNode.php new file mode 100644 index 0000000..e0b3551 --- /dev/null +++ b/src/MathParser/Parsing/Nodes/NotBooleanNode.php @@ -0,0 +1,90 @@ + + * @copyright 2015 Frank Wikström + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * + */ + +namespace MathParser\Parsing\Nodes; + +use MathParser\Interpreting\Visitors\Visitor; + +/** + * AST node representing a function applications (e.g. sin(...)) + */ +class NotBooleanNode extends FunctionNode +{ + /** string $name Function name, e.g. 'sin' */ + private $name; + /** Node $operand AST of function operand */ + private $operand; + + /** Constructor, create a FunctionNode with given name and operand */ + function __construct($name, $operand) + { + $this->name = $name; + if (is_int($operand)) { + $operand = new NumberNode($operand); + } + $this->operand = $operand; + } + + /** + * Return the name of the function + * @retval string + */ + public function getName() + { + return $this->name; + } + + /** + * Return the operand + * @retval Node + */ + public function getOperand() + { + return $this->operand; + } + + /** + * Set the operand + * @retval void + */ + public function setOperand($operand) + { + return $this->operand = $operand; + } + + public function getOperator() + { + return $this->name; + } + + /** + * Implementing the Visitable interface. + */ + public function accept(Visitor $visitor) + { + return $visitor->visitFunctionNode($this); + } + + /** Implementing the compareTo abstract method. */ + public function compareTo($other) + { + if ($other === null) { + return false; + } + if (!($other instanceof FunctionNode)) { + return false; + } + + $thisOperand = $this->getOperand(); + $otherOperand = $other->getOperand(); + + return $this->getName() == $other->getName() && $thisOperand->compareTo($otherOperand); + } + +} diff --git a/src/MathParser/Parsing/Parser.php b/src/MathParser/Parsing/Parser.php index 5c9cae1..a86c5c1 100644 --- a/src/MathParser/Parsing/Parser.php +++ b/src/MathParser/Parsing/Parser.php @@ -9,10 +9,10 @@ /** -* @namespace MathParser::Parsing -* -* Parser related classes -*/ + * @namespace MathParser::Parsing + * + * Parser related classes + */ namespace MathParser\Parsing; use MathParser\Lexing\Token; @@ -22,6 +22,7 @@ use MathParser\Parsing\Nodes\Node; use MathParser\Parsing\Nodes\ExpressionNode; +use MathParser\Parsing\Nodes\NotBooleanNode; use MathParser\Parsing\Nodes\NumberNode; use MathParser\Parsing\Nodes\VariableNode; use MathParser\Parsing\Nodes\FunctionNode; @@ -40,49 +41,50 @@ use MathParser\Interpreting\PrettyPrinter; /** -* Mathematical expression parser, based on the shunting yard algorithm. -* -* Parse a token string into an abstract syntax tree (AST). -* -* As the parser loops over the individual tokens, two stacks are kept -* up to date. One stack ($operatorStack) consists of hitherto unhandled -* tokens corresponding to ''operators'' (unary and binary operators, function -* applications and parenthesis) and a stack of parsed sub-expressions (the -* $operandStack). -* -* If the current token is a terminal token (number, variable or constant), -* a corresponding node is pushed onto the operandStack. -* -* Otherwise, the precedence of the current token is compared to the top -* element(t) on the operatorStack, and as long as the current token has -* lower precedence, we keep popping operators from the stack to constuct -* more complicated subexpressions together with the top items on the operandStack. -* -* Once the token list is empty, we pop the remaining operators as above, and -* if the formula was well-formed, the only thing remaining on the operandStack -* is a completely parsed AST, which we return. -*/ + * Mathematical expression parser, based on the shunting yard algorithm. + * + * Parse a token string into an abstract syntax tree (AST). + * + * As the parser loops over the individual tokens, two stacks are kept + * up to date. One stack ($operatorStack) consists of hitherto unhandled + * tokens corresponding to ''operators'' (unary and binary operators, function + * applications and parenthesis) and a stack of parsed sub-expressions (the + * $operandStack). + * + * If the current token is a terminal token (number, variable or constant), + * a corresponding node is pushed onto the operandStack. + * + * Otherwise, the precedence of the current token is compared to the top + * element(t) on the operatorStack, and as long as the current token has + * lower precedence, we keep popping operators from the stack to constuct + * more complicated subexpressions together with the top items on the operandStack. + * + * Once the token list is empty, we pop the remaining operators as above, and + * if the formula was well-formed, the only thing remaining on the operandStack + * is a completely parsed AST, which we return. + */ class Parser { /** - * Token[] list of tokens to process - */ + * Token[] list of tokens to process + */ protected $tokens; /** - * Stack stack of operators waiting to process - */ + * Stack stack of operators waiting to process + */ protected $operatorStack; /** - * Stack stack of operands waiting to process - */ + * Stack stack of operands waiting to process + */ protected $operandStack; /** * NodeFactory */ - protected $nodeFactory; + protected $nodeFactory; protected $rationalFactory = false; protected $simplifyingParser = true; + protected $lastUnaryOperator = null; /** * Constructor @@ -103,11 +105,11 @@ public function setSimplifying($flag) } /** - * Parse list of tokens - * - * @param array $tokens Array (Token[]) of input tokens. - * @retval Node AST representing the parsed expression. - */ + * Parse list of tokens + * + * @param array $tokens Array (Token[]) of input tokens. + * @retval Node AST representing the parsed expression. + */ public function parse(array $tokens) { // Filter away any whitespace @@ -125,13 +127,13 @@ public function parse(array $tokens) } /** - * Implementation of the shunting yard parsing algorithm - * - * @param array $tokens Token[] array of tokens to process - * @retval Node AST of the parsed expression - * @throws SyntaxErrorException - * @throws ParenthesisMismatchException - */ + * Implementation of the shunting yard parsing algorithm + * + * @param array $tokens Token[] array of tokens to process + * @retval Node AST of the parsed expression + * @throws SyntaxErrorException + * @throws ParenthesisMismatchException + */ private function shuntingYard(array $tokens) { // Clear the oepratorStack @@ -144,14 +146,11 @@ private function shuntingYard(array $tokens) $lastNode = null; // Loop over the tokens + for ($index = 0; $index < count($tokens); $index++) { $token = $tokens[$index]; - // echo "current token $token\n"; - // echo("operands:" . $this->operandStack . "\n"); - // echo("operators: " . $this->operatorStack . "\n\n"); - if ($this->rationalFactory) { $node = Node::rationalFactory($token); } else { @@ -165,7 +164,6 @@ private function shuntingYard(array $tokens) // Push terminal tokens on the operandStack elseif ($node->isTerminal()) { $this->operandStack->push($node); - // Push function applications or open parentheses `(` onto the operatorStack } elseif ($node instanceof FunctionNode) { $this->operatorStack->push($node); @@ -175,9 +173,14 @@ private function shuntingYard(array $tokens) // Handle the remaining operators. } elseif ($node instanceof PostfixOperatorNode) { + $op = $this->operandStack->pop(); - if ($op == NULL) throw new SyntaxErrorException(); - $this->operandStack->push(new FunctionNode($node->getOperator(), $op)); + if ($op == NULL) { + $this->operatorStack->push(new NotBooleanNode($node->getOperator(), null)); + continue; + } else { + $this->operandStack->push(new FunctionNode($node->getOperator(), $op)); + } } elseif ($node instanceof ExpressionNode) { @@ -188,12 +191,13 @@ private function shuntingYard(array $tokens) switch($token->getType()) { // Unary +, just ignore it case TokenType::AdditionOperator: - $node = null; - break; + $node = null; + break; + // Unary -, replace the token. case TokenType::SubtractionOperator: - $node->setOperator('~'); - break; + $node->setOperator('~'); + break; } } else { // Pop operators with higher priority @@ -210,7 +214,9 @@ private function shuntingYard(array $tokens) } // Remember the current token (if it hasn't been nulled, for example being a unary +) - if ($node) $lastNode = $node; + if ($node) { + $lastNode = $node; + } } @@ -231,18 +237,26 @@ private function shuntingYard(array $tokens) } + + /** - * Populate node with operands. - * - * @param Node $node - * @retval Node - * @throws SyntaxErrorException - */ + * Populate node with operands. + * + * @param Node $node + * @retval Node + * @throws SyntaxErrorException + */ protected function handleExpression($node) { + + if ($node instanceof NotBooleanNode) { + $op = $this->operandStack->pop(); + $node->setOperand($op); + return $node; + } + if ($node instanceof FunctionNode) throw new ParenthesisMismatchException($node->getOperator()); if ($node instanceof SubExpressionNode) throw new ParenthesisMismatchException($node->getOperator()); - if (!$this->simplifyingParser) return $this->naiveHandleExpression($node); if ($node->getOperator() == '~') { @@ -270,6 +284,7 @@ protected function handleExpression($node) return $this->nodeFactory->simplify($node); } + $right = $this->operandStack->pop(); $left = $this->operandStack->pop(); if ($right === null || $left === null) { @@ -279,16 +294,17 @@ protected function handleExpression($node) $node->setLeft($left); $node->setRight($right); + return $this->nodeFactory->simplify($node); } /** - * Populate node with operands, without any simplification. - * - * @param Node $node - * @retval Node - * @throws SyntaxErrorException - */ + * Populate node with operands, without any simplification. + * + * @param Node $node + * @retval Node + * @throws SyntaxErrorException + */ protected function naiveHandleExpression($node) { if ($node->getOperator() == '~') { @@ -317,11 +333,11 @@ protected function naiveHandleExpression($node) } /** - * Remove Whitespace from the token list. - * - * @param array $tokens Input list of tokens - * @retval Token[] - */ + * Remove Whitespace from the token list. + * + * @param array $tokens Input list of tokens + * @retval Token[] + */ protected function filterTokens(array $tokens) { $filteredTokens = array_filter($tokens, function (Token $t) { @@ -333,11 +349,11 @@ protected function filterTokens(array $tokens) } /** - * Insert multiplication tokens where needed (taking care of implicit mulitplication). - * - * @param array $tokens Input list of tokens (Token[]) - * @retval Token[] - */ + * Insert multiplication tokens where needed (taking care of implicit mulitplication). + * + * @param array $tokens Input list of tokens (Token[]) + * @retval Token[] + */ protected function parseImplicitMultiplication(array $tokens) { $result = []; @@ -353,27 +369,27 @@ protected function parseImplicitMultiplication(array $tokens) } /** - * Determine if the parser allows implicit multiplication. Create a - * subclass of Parser, overriding this function, returning false instead - * to diallow implicit multiplication. - * - * ### Example: - * - * ~~~{.php} - * class ParserWithoutImplicitMultiplication extends Parser { - * protected static function allowImplicitMultiplication() { - * return false; - * } - * } - * - * $lexer = new StdMathLexer(); - * $tokens = $lexer->tokenize('2x'); - * $parser = new ParserWithoutImplicitMultiplication(); - * $node = $parser->parse($tokens); // Throws a SyntaxErrorException - * ~~~ - * @property allowImplicitMultiplication - * @retval boolean - */ + * Determine if the parser allows implicit multiplication. Create a + * subclass of Parser, overriding this function, returning false instead + * to diallow implicit multiplication. + * + * ### Example: + * + * ~~~{.php} + * class ParserWithoutImplicitMultiplication extends Parser { + * protected static function allowImplicitMultiplication() { + * return false; + * } + * } + * + * $lexer = new StdMathLexer(); + * $tokens = $lexer->tokenize('2x'); + * $parser = new ParserWithoutImplicitMultiplication(); + * $node = $parser->parse($tokens); // Throws a SyntaxErrorException + * ~~~ + * @property allowImplicitMultiplication + * @retval boolean + */ protected static function allowImplicitMultiplication() { return true; diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php new file mode 100644 index 0000000..ad036ba --- /dev/null +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -0,0 +1,139 @@ +parser = new StdMathParser(); + $this->rparser = new RationalMathParser(); + + $this->variables = array('x' => '0.7', 'y' => '2.1'); + $this->evaluator = new Evaluator($this->variables); + } + + private function evaluate($f) + { + $this->evaluator->setVariables($this->variables); + return $f->accept($this->evaluator); + } + + private function assertResult($f, $x) + { + $value = $this->evaluate($this->parser->parse($f)); + $this->assertEquals($x, $value); + } + + + public function testNumberExpressionWithEqualOperator() { + $this->assertResult('10=10', 1); + $this->assertResult('(10+8+2)=(10+10)', 1); + $this->assertResult('10+8+2=10+10', 1); + $this->assertResult('4/3=4/3', 1); + $this->assertResult('4=5', 0); + $this->assertResult('x=y', 0); + } + + + + public function testNumberExpressionWithAndOperator() { + $this->assertResult('1 && 1', 1); + $this->assertResult('1 && 0', 0); + $this->assertResult('0 && 0', 0); + $this->assertResult('2.2 && 1', 1); + $this->assertResult('x && y', 1); + } + + + public function testOrOperatorIsValid() { + $expression = $this->parser->parse('1 || 1'); + $this->assertNotNull($expression); + } + + public function testNumberExpressionWithOrOperator() { + $this->assertResult('1 || 1', 1); + $this->assertResult('1 || 0', 1); + $this->assertResult('0 || 0', 0); + $this->assertResult('2.2 || 1', 1); + $this->assertResult('x || y', 1); + } + + public function testNotFunctionIsValid() { + $expression = $this->parser->parse('!1'); + $this->assertNotNull($expression); + } + + + public function testNotOperator() { + $this->assertResult('!1', 0); + $this->assertResult('!0', 1); + $this->assertResult('!(0+1)', 0); + $this->assertResult('!(!0)', 0); + + $this->assertResult('!(0.1)', 0); + $this->assertResult('!x', 0); + $this->assertResult('!(x+y)', 0); + + } + + + public function testGreaterOperatorValidSyntax() { + $expression = $this->parser->parse('3 > 1'); + $this->assertNotNull($expression); + } + + public function testGreaterOperator() { + $this->assertResult('3 > 1', 1); + $this->assertResult('1 > 3', 0); + $this->assertResult('1.1 > 3', 0); + $this->assertResult('3.1 > 3', 1); + $this->assertResult('x > y', 0); + } + + + public function testGreaterOrEqualOperatorValidSyntax() { + $expression = $this->parser->parse('3 >= 1'); + $this->assertNotNull($expression); + } + + public function testGreaterOrEqualOperator() { + $this->assertResult('3 >= 1', 1); + $this->assertResult('1 >= 3', 0); + $this->assertResult('3 >= 3', 1); + $this->assertResult('3 >= 3.0', 1); + $this->assertResult('1.1 >= 3', 0); + $this->assertResult('3.1 >= 3', 1); + $this->assertResult('x >= y', 0); + } + + public function testSmallerOperatorValidSyntax() { + $expression = $this->parser->parse('1 < 3'); + $this->assertNotNull($expression); + } + + public function testSmallerOperator() { + $this->assertResult('1 < 3', 1); + $this->assertResult('3 < 1', 0); + $this->assertResult('2 < 1.01', 0); + $this->assertResult('x < y', 1); + + } + + public function testSmallerOrEqualOperatorValidSyntax() { + $expression = $this->parser->parse('1 <= 3'); + $this->assertNotNull($expression); + } + +} diff --git a/tests/MathParser/Interpreting/EvaluatorTest.php b/tests/MathParser/Interpreting/EvaluatorTest.php index 0886eed..e93c85d 100644 --- a/tests/MathParser/Interpreting/EvaluatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorTest.php @@ -251,7 +251,7 @@ public function testCanEvaluateFactorial() $this->assertResult('0!', 1); $this->assertResult('3!', 6); $this->assertResult('(3!)!', 720); - $this->assertResult('5!/(2!3!)', 10); + $this->assertResult('5!/(2!*3!)', 10); $this->assertResult('5!!', 15); $this->assertApproximateResult('4.12124!', 28.85455491); }