From 6a9b1c37e369c0a52fb97a20551b898e794bf626 Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Tue, 30 May 2017 23:38:31 +0700 Subject: [PATCH 01/10] Add =, &&, || boolean operators --- phpunit.xml | 5 ++ src/MathParser/Interpreting/Evaluator.php | 14 ++-- src/MathParser/Lexing/ComplexLexer.php | 4 + src/MathParser/Lexing/StdMathLexer.php | 5 ++ src/MathParser/Lexing/TokenType.php | 6 ++ .../Parsing/Nodes/ExpressionNode.php | 16 ++++ .../Nodes/Factories/BoolAndNodeFactory.php | 73 ++++++++++++++++++ .../Nodes/Factories/BoolEqualNodeFactory.php | 71 ++++++++++++++++++ .../Nodes/Factories/BoolOrNodeFactory.php | 75 +++++++++++++++++++ .../Parsing/Nodes/Factories/NodeFactory.php | 27 +++++++ src/MathParser/Parsing/Nodes/Node.php | 5 ++ src/MathParser/Parsing/Parser.php | 1 + .../EvaluatorBooleanOperatorTest.php | 68 +++++++++++++++++ 13 files changed, 364 insertions(+), 6 deletions(-) create mode 100644 src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php create mode 100644 src/MathParser/Parsing/Nodes/Factories/BoolEqualNodeFactory.php create mode 100644 src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php create mode 100644 tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php 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..6d57795 100644 --- a/src/MathParser/Interpreting/Evaluator.php +++ b/src/MathParser/Interpreting/Evaluator.php @@ -104,16 +104,18 @@ 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 1; default: throw new UnknownOperatorException($operator); diff --git a/src/MathParser/Lexing/ComplexLexer.php b/src/MathParser/Lexing/ComplexLexer.php index 14abe7a..f77d63f 100644 --- a/src/MathParser/Lexing/ComplexLexer.php +++ b/src/MathParser/Lexing/ComplexLexer.php @@ -109,6 +109,10 @@ 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::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/StdMathLexer.php b/src/MathParser/Lexing/StdMathLexer.php index c07b3b6..721a2e3 100644 --- a/src/MathParser/Lexing/StdMathLexer.php +++ b/src/MathParser/Lexing/StdMathLexer.php @@ -112,6 +112,11 @@ 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::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/TokenType.php b/src/MathParser/Lexing/TokenType.php index ef55fd8..52da08a 100644 --- a/src/MathParser/Lexing/TokenType.php +++ b/src/MathParser/Lexing/TokenType.php @@ -67,6 +67,12 @@ final class TokenType /** Token representing postfix subfactorial operator '!!' */ const SemiFactorialOperator = 105; + /** Token representing equal operator */ + const EqualCompareOperator = 106; + const BooleanAndOperator = 107; + const BooleanOrOperator = 108; + + /** 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..b0417f8 100644 --- a/src/MathParser/Parsing/Nodes/ExpressionNode.php +++ b/src/MathParser/Parsing/Nodes/ExpressionNode.php @@ -96,6 +96,22 @@ 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; + + 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..644d023 --- /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($leftOperand->getValue()) && intval($rightOperand->getValue())); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $leftValue = intval($leftValue); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $rightValue = intval($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..020b4e4 --- /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($leftOperand->getValue()) || intval($rightOperand->getValue())); + return new NumberNode($result); + + case Node::NumericRational: + $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); + $leftValue = intval($leftValue); + $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); + $rightValue = intval($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/NodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php index aba72a6..1fa56ac 100644 --- a/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php @@ -75,6 +75,12 @@ class NodeFactory { **/ protected $exponentiationFactory; + protected $boolEqualNodeFactory; + + protected $boolAndNodeFactory; + + protected $boolOrNodeFactory; + /** * Constructor */ @@ -85,6 +91,9 @@ 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(); } /** @@ -164,6 +173,21 @@ 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); + } + + /** * Simplify the given ExpressionNode, using the appropriate factory. * @@ -178,6 +202,9 @@ 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->boolAnd($node->getLeft(), $node->getRight()); + case '||': return $this->boolOr($node->getLeft(), $node->getRight()); } } diff --git a/src/MathParser/Parsing/Nodes/Node.php b/src/MathParser/Parsing/Nodes/Node.php index 72ec60c..cf5b6bb 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,9 @@ public static function factory(Token $token) case TokenType::MultiplicationOperator: case TokenType::DivisionOperator: case TokenType::ExponentiationOperator: + case TokenType::EqualCompareOperator: + case TokenType::BooleanAndOperator: + case TokenType::BooleanOrOperator: return new ExpressionNode(null, $token->getValue(), null); case TokenType::FactorialOperator: diff --git a/src/MathParser/Parsing/Parser.php b/src/MathParser/Parsing/Parser.php index 5c9cae1..1957d64 100644 --- a/src/MathParser/Parsing/Parser.php +++ b/src/MathParser/Parsing/Parser.php @@ -190,6 +190,7 @@ private function shuntingYard(array $tokens) case TokenType::AdditionOperator: $node = null; break; + // Unary -, replace the token. case TokenType::SubtractionOperator: $node->setOperator('~'); diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php new file mode 100644 index 0000000..5b43bfb --- /dev/null +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -0,0 +1,68 @@ +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); + } + + + + public function testNumberExpressionWithAndOperator() { + $this->assertResult('1 && 1', 1); + $this->assertResult('1 && 0', 0); + $this->assertResult('0 && 0', 0); + $this->assertResult('2.2 && 1', 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); + } + +} From f81c3e53bb1708c9ccbfee39538f0e0352118bc5 Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Fri, 2 Jun 2017 22:51:12 +0700 Subject: [PATCH 02/10] Add ! operator for boolean --- src/MathParser/Interpreting/Evaluator.php | 12 +- src/MathParser/Lexing/Token.php | 8 +- .../Parsing/Nodes/NotBooleanNode.php | 90 ++++++++ src/MathParser/Parsing/Parser.php | 217 ++++++++++-------- .../EvaluatorBooleanOperatorTest.php | 26 ++- .../MathParser/Interpreting/EvaluatorTest.php | 2 +- 6 files changed, 249 insertions(+), 106 deletions(-) create mode 100644 src/MathParser/Parsing/Nodes/NotBooleanNode.php diff --git a/src/MathParser/Interpreting/Evaluator.php b/src/MathParser/Interpreting/Evaluator.php index 6d57795..d85adcd 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; @@ -276,8 +277,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/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/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 1957d64..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,13 +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 @@ -211,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; + } } @@ -232,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() == '~') { @@ -271,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) { @@ -280,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() == '~') { @@ -318,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) { @@ -334,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 = []; @@ -354,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 index 5b43bfb..79bdbe3 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -5,14 +5,16 @@ use MathParser\RationalMathParser; use MathParser\StdMathParser; - +use MathParser\Interpreting\ASCIIPrinter; class EvaluatorBooleanOperatorTest extends PHPUnit_Framework_TestCase { private $parser; private $rparser; + private $evaluator; private $variables; + public function setUp() { $this->parser = new StdMathParser(); @@ -65,4 +67,26 @@ public function testNumberExpressionWithOrOperator() { $this->assertResult('2.2 || 1', 1); } + public function testNotFunctionIsValid() { + $expression = $this->parser->parse('!1'); + $this->assertNotNull($expression); + } + + + public function testNotFunctionWithIntegerValue() { + $this->assertResult('!1', 0); + $this->assertResult('!0', 1); + $this->assertResult('!(0+1)', 0); + $this->assertResult('!(!0)', 0); + } + + public function testNotFunctionWithFloatValue() { + + $this->assertResult('!(0.1)', 0); + } + + + + + } 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); } From f2f4b5ed4c10512fe8845a9b5a1c372c42ad2a94 Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Fri, 2 Jun 2017 22:52:44 +0700 Subject: [PATCH 03/10] Update more case --- tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index 79bdbe3..ec81c75 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -83,6 +83,8 @@ public function testNotFunctionWithIntegerValue() { public function testNotFunctionWithFloatValue() { $this->assertResult('!(0.1)', 0); + $this->assertResult('!x', 0); + $this->assertResult('!(x+y)', 0); } From a9c0b73ed74ca4b73b535f427a0e9eec4665940b Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Fri, 2 Jun 2017 22:55:56 +0700 Subject: [PATCH 04/10] Update more case for ! operator --- .../MathParser/Interpreting/EvaluatorBooleanOperatorTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index ec81c75..1e23005 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -87,8 +87,4 @@ public function testNotFunctionWithFloatValue() { $this->assertResult('!(x+y)', 0); } - - - - } From b1764f6c493e65e95f11b10818ad87048aa7ac3f Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Mon, 5 Jun 2017 11:47:20 +0700 Subject: [PATCH 05/10] Add greater operator --- src/MathParser/Lexing/ComplexLexer.php | 1 + src/MathParser/Lexing/StdMathLexer.php | 1 + src/MathParser/Lexing/TokenType.php | 1 + .../Parsing/Nodes/ExpressionNode.php | 5 ++ .../Nodes/Factories/GreaterNodeFactory.php | 71 +++++++++++++++++++ .../Parsing/Nodes/Factories/NodeFactory.php | 8 +++ src/MathParser/Parsing/Nodes/Node.php | 1 + .../EvaluatorBooleanOperatorTest.php | 17 ++++- 8 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/MathParser/Parsing/Nodes/Factories/GreaterNodeFactory.php diff --git a/src/MathParser/Lexing/ComplexLexer.php b/src/MathParser/Lexing/ComplexLexer.php index f77d63f..84a5f92 100644 --- a/src/MathParser/Lexing/ComplexLexer.php +++ b/src/MathParser/Lexing/ComplexLexer.php @@ -110,6 +110,7 @@ public function __construct() $this->add(new TokenDefinition('/\^/', TokenType::ExponentiationOperator)); $this->add(new TokenDefinition('/\=/', TokenType::EqualCompareOperator)); + $this->add(new TokenDefinition('/\>/', TokenType::GreaterOperator)); $this->add(new TokenDefinition('/\&&/', TokenType::BooleanAndOperator)); $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); diff --git a/src/MathParser/Lexing/StdMathLexer.php b/src/MathParser/Lexing/StdMathLexer.php index 721a2e3..0e5a111 100644 --- a/src/MathParser/Lexing/StdMathLexer.php +++ b/src/MathParser/Lexing/StdMathLexer.php @@ -114,6 +114,7 @@ public function __construct() $this->add(new TokenDefinition('/\=/', TokenType::EqualCompareOperator)); + $this->add(new TokenDefinition('/\>/', TokenType::GreaterOperator)); $this->add(new TokenDefinition('/\&&/', TokenType::BooleanAndOperator)); $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); diff --git a/src/MathParser/Lexing/TokenType.php b/src/MathParser/Lexing/TokenType.php index 52da08a..3b65ece 100644 --- a/src/MathParser/Lexing/TokenType.php +++ b/src/MathParser/Lexing/TokenType.php @@ -71,6 +71,7 @@ final class TokenType const EqualCompareOperator = 106; const BooleanAndOperator = 107; const BooleanOrOperator = 108; + const GreaterOperator = 109; /** Token represented a function name, e.g. 'sin' */ diff --git a/src/MathParser/Parsing/Nodes/ExpressionNode.php b/src/MathParser/Parsing/Nodes/ExpressionNode.php index b0417f8..6582e6c 100644 --- a/src/MathParser/Parsing/Nodes/ExpressionNode.php +++ b/src/MathParser/Parsing/Nodes/ExpressionNode.php @@ -111,6 +111,11 @@ function __construct($left, $operator = null, $right = null) $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/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/NodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php index 1fa56ac..956e9c8 100644 --- a/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php @@ -81,6 +81,8 @@ class NodeFactory { protected $boolOrNodeFactory; + protected $greaterNodeFactory; + /** * Constructor */ @@ -94,6 +96,7 @@ public function __construct() $this->boolEqualNodeFactory = new BoolEqualNodeFactory(); $this->boolAndNodeFactory = new BoolAndNodeFactory(); $this->boolOrNodeFactory = new BoolOrNodeFactory(); + $this->greaterNodeFactory = new GreaterNodeFactory(); } /** @@ -187,6 +190,9 @@ public function boolOr($leftOperand, $rightOperand) { return $this->boolOrNodeFactory->makeNode($leftOperand, $rightOperand); } + public function greater($leftOperand, $rightOperand) { + return $this->greaterNodeFactory->makeNode($leftOperand, $rightOperand); + } /** * Simplify the given ExpressionNode, using the appropriate factory. @@ -203,6 +209,8 @@ public function simplify(ExpressionNode $node) 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->boolAnd($node->getLeft(), $node->getRight()); case '||': return $this->boolOr($node->getLeft(), $node->getRight()); } diff --git a/src/MathParser/Parsing/Nodes/Node.php b/src/MathParser/Parsing/Nodes/Node.php index cf5b6bb..dbab443 100644 --- a/src/MathParser/Parsing/Nodes/Node.php +++ b/src/MathParser/Parsing/Nodes/Node.php @@ -123,6 +123,7 @@ public static function factory(Token $token) case TokenType::EqualCompareOperator: case TokenType::BooleanAndOperator: case TokenType::BooleanOrOperator: + case TokenType::GreaterOperator: return new ExpressionNode(null, $token->getValue(), null); case TokenType::FactorialOperator: diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index 1e23005..c90a003 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -73,18 +73,31 @@ public function testNotFunctionIsValid() { } - public function testNotFunctionWithIntegerValue() { + public function testNotOperatorWithIntegerValue() { $this->assertResult('!1', 0); $this->assertResult('!0', 1); $this->assertResult('!(0+1)', 0); $this->assertResult('!(!0)', 0); } - public function testNotFunctionWithFloatValue() { + public function testNotOperatorWithFloatValue() { $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); + } + + } From dca693e5edcabf6fd8755514198f78d625c436ba Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Mon, 5 Jun 2017 11:54:53 +0700 Subject: [PATCH 06/10] Fix the bug of > operator with variable and expression --- src/MathParser/Interpreting/Evaluator.php | 2 ++ src/MathParser/Lexing/Lexer.php | 4 +++- .../MathParser/Interpreting/EvaluatorBooleanOperatorTest.php | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MathParser/Interpreting/Evaluator.php b/src/MathParser/Interpreting/Evaluator.php index d85adcd..a018315 100644 --- a/src/MathParser/Interpreting/Evaluator.php +++ b/src/MathParser/Interpreting/Evaluator.php @@ -117,6 +117,8 @@ public function visitExpressionNode(ExpressionNode $node) return pow($leftValue, $rightValue); case '=': return 1; + case '>': + return ($leftValue > $rightValue); default: throw new UnknownOperatorException($operator); 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/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index c90a003..3d1ecc7 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -97,6 +97,7 @@ public function testGreaterOperator() { $this->assertResult('1 > 3', 0); $this->assertResult('1.1 > 3', 0); $this->assertResult('3.1 > 3', 1); + $this->assertResult('x > y', 0); } From 87c168634b2471d95781224a3d0ec7deb748b881 Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Mon, 5 Jun 2017 13:19:38 +0700 Subject: [PATCH 07/10] Fix issue of expression with variables --- src/MathParser/Interpreting/Evaluator.php | 7 ++++++- .../Parsing/Nodes/Factories/BoolAndNodeFactory.php | 6 +++--- .../Parsing/Nodes/Factories/BoolOrNodeFactory.php | 6 +++--- .../Interpreting/EvaluatorBooleanOperatorTest.php | 10 ++++++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/MathParser/Interpreting/Evaluator.php b/src/MathParser/Interpreting/Evaluator.php index a018315..3079d61 100644 --- a/src/MathParser/Interpreting/Evaluator.php +++ b/src/MathParser/Interpreting/Evaluator.php @@ -116,7 +116,12 @@ public function visitExpressionNode(ExpressionNode $node) case '^': return pow($leftValue, $rightValue); case '=': - return 1; + 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); diff --git a/src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php index 644d023..0c6a129 100644 --- a/src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/BoolAndNodeFactory.php @@ -51,14 +51,14 @@ protected function numericTerms($leftOperand, $rightOperand) $type = $this->resultingType($leftOperand, $rightOperand); switch($type) { case Node::NumericFloat: - $result = ( intval($leftOperand->getValue()) && intval($rightOperand->getValue())); + $result = ( intval(ceil($leftOperand->getValue())) && intval(ceil($rightOperand->getValue()))); return new NumberNode($result); case Node::NumericRational: $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); - $leftValue = intval($leftValue); + $leftValue = intval(ceil($leftValue)); $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); - $rightValue = intval($rightValue); + $rightValue = intval(ceil($rightValue)); $result = ($leftValue && $rightValue); return new IntegerNode($result); diff --git a/src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php index 020b4e4..acfdb95 100644 --- a/src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/BoolOrNodeFactory.php @@ -53,14 +53,14 @@ protected function numericTerms($leftOperand, $rightOperand) $type = $this->resultingType($leftOperand, $rightOperand); switch($type) { case Node::NumericFloat: - $result = ( intval($leftOperand->getValue()) || intval($rightOperand->getValue())); + $result = ( intval(ceil($leftOperand->getValue())) || intval(ceil($rightOperand->getValue()))); return new NumberNode($result); case Node::NumericRational: $leftValue = ($leftOperand->getNumerator() / $leftOperand->getDenominator()); - $leftValue = intval($leftValue); + $leftValue = intval(ceil($leftValue)); $rightValue = ($rightOperand->getDenominator() / $rightOperand->getNumerator()); - $rightValue = intval($rightValue); + $rightValue = intval(ceil($rightValue)); $result = ($leftValue || $rightValue); return new IntegerNode($result); diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index 3d1ecc7..52158e2 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -43,6 +43,7 @@ public function testNumberExpressionWithEqualOperator() { $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); } @@ -52,6 +53,7 @@ public function testNumberExpressionWithAndOperator() { $this->assertResult('1 && 0', 0); $this->assertResult('0 && 0', 0); $this->assertResult('2.2 && 1', 1); + $this->assertResult('x && y', 1); } @@ -65,6 +67,7 @@ public function testNumberExpressionWithOrOperator() { $this->assertResult('1 || 0', 1); $this->assertResult('0 || 0', 0); $this->assertResult('2.2 || 1', 1); + $this->assertResult('x || y', 1); } public function testNotFunctionIsValid() { @@ -73,20 +76,19 @@ public function testNotFunctionIsValid() { } - public function testNotOperatorWithIntegerValue() { + public function testNotOperator() { $this->assertResult('!1', 0); $this->assertResult('!0', 1); $this->assertResult('!(0+1)', 0); $this->assertResult('!(!0)', 0); - } - - public function testNotOperatorWithFloatValue() { $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); From 93b6876dd315f0a395cc88504a017e9ee199fd68 Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Mon, 5 Jun 2017 13:48:02 +0700 Subject: [PATCH 08/10] Add GreaterOrEqual operator --- src/MathParser/Interpreting/Evaluator.php | 3 + src/MathParser/Lexing/ComplexLexer.php | 2 + src/MathParser/Lexing/StdMathLexer.php | 1 + src/MathParser/Lexing/TokenType.php | 1 + .../Parsing/Nodes/ExpressionNode.php | 4 ++ .../Factories/GreaterOrEqualNodeFactory.php | 71 +++++++++++++++++++ .../Parsing/Nodes/Factories/NodeFactory.php | 8 ++- src/MathParser/Parsing/Nodes/Node.php | 1 + .../EvaluatorBooleanOperatorTest.php | 16 +++++ 9 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/MathParser/Parsing/Nodes/Factories/GreaterOrEqualNodeFactory.php diff --git a/src/MathParser/Interpreting/Evaluator.php b/src/MathParser/Interpreting/Evaluator.php index 3079d61..8a9746b 100644 --- a/src/MathParser/Interpreting/Evaluator.php +++ b/src/MathParser/Interpreting/Evaluator.php @@ -124,6 +124,9 @@ public function visitExpressionNode(ExpressionNode $node) case '>': return ($leftValue > $rightValue); + case '>=': + return $leftValue >= $rightValue; + default: throw new UnknownOperatorException($operator); diff --git a/src/MathParser/Lexing/ComplexLexer.php b/src/MathParser/Lexing/ComplexLexer.php index 84a5f92..aac7d50 100644 --- a/src/MathParser/Lexing/ComplexLexer.php +++ b/src/MathParser/Lexing/ComplexLexer.php @@ -110,7 +110,9 @@ public function __construct() $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::BooleanAndOperator)); $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); diff --git a/src/MathParser/Lexing/StdMathLexer.php b/src/MathParser/Lexing/StdMathLexer.php index 0e5a111..60ba8c8 100644 --- a/src/MathParser/Lexing/StdMathLexer.php +++ b/src/MathParser/Lexing/StdMathLexer.php @@ -114,6 +114,7 @@ public function __construct() $this->add(new TokenDefinition('/\=/', TokenType::EqualCompareOperator)); + $this->add(new TokenDefinition('/\>=/', TokenType::GreaterOrEqualOperator)); $this->add(new TokenDefinition('/\>/', TokenType::GreaterOperator)); $this->add(new TokenDefinition('/\&&/', TokenType::BooleanAndOperator)); $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); diff --git a/src/MathParser/Lexing/TokenType.php b/src/MathParser/Lexing/TokenType.php index 3b65ece..780f14d 100644 --- a/src/MathParser/Lexing/TokenType.php +++ b/src/MathParser/Lexing/TokenType.php @@ -72,6 +72,7 @@ final class TokenType const BooleanAndOperator = 107; const BooleanOrOperator = 108; const GreaterOperator = 109; + const GreaterOrEqualOperator = 110; /** Token represented a function name, e.g. 'sin' */ diff --git a/src/MathParser/Parsing/Nodes/ExpressionNode.php b/src/MathParser/Parsing/Nodes/ExpressionNode.php index 6582e6c..ffe306d 100644 --- a/src/MathParser/Parsing/Nodes/ExpressionNode.php +++ b/src/MathParser/Parsing/Nodes/ExpressionNode.php @@ -116,6 +116,10 @@ function __construct($left, $operator = null, $right = null) $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/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 956e9c8..8006275 100644 --- a/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php @@ -82,6 +82,7 @@ class NodeFactory { protected $boolOrNodeFactory; protected $greaterNodeFactory; + protected $greaterOrEqualNodeFactory; /** * Constructor @@ -97,6 +98,7 @@ public function __construct() $this->boolAndNodeFactory = new BoolAndNodeFactory(); $this->boolOrNodeFactory = new BoolOrNodeFactory(); $this->greaterNodeFactory = new GreaterNodeFactory(); + $this->greaterOrEqualNodeFactory = new GreaterOrEqualNodeFactory(); } /** @@ -194,6 +196,9 @@ public function greater($leftOperand, $rightOperand) { return $this->greaterNodeFactory->makeNode($leftOperand, $rightOperand); } + public function greaterOrEqual($leftOperand, $rightOperand) { + return $this->greaterOrEqualNodeFactory->makeNode($leftOperand, $rightOperand); + } /** * Simplify the given ExpressionNode, using the appropriate factory. * @@ -210,9 +215,10 @@ public function simplify(ExpressionNode $node) 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->boolAnd($node->getLeft(), $node->getRight()); case '||': return $this->boolOr($node->getLeft(), $node->getRight()); + } } diff --git a/src/MathParser/Parsing/Nodes/Node.php b/src/MathParser/Parsing/Nodes/Node.php index dbab443..282ce17 100644 --- a/src/MathParser/Parsing/Nodes/Node.php +++ b/src/MathParser/Parsing/Nodes/Node.php @@ -124,6 +124,7 @@ public static function factory(Token $token) case TokenType::BooleanAndOperator: case TokenType::BooleanOrOperator: case TokenType::GreaterOperator: + case TokenType::GreaterOrEqualOperator: return new ExpressionNode(null, $token->getValue(), null); case TokenType::FactorialOperator: diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index 52158e2..cce0c3b 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -103,4 +103,20 @@ public function testGreaterOperator() { } + 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); + } + + } From 7e68326c0977cb7cd610e8d88921df8e48553795 Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Mon, 5 Jun 2017 14:02:07 +0700 Subject: [PATCH 09/10] Add Smaller operator --- src/MathParser/Interpreting/Evaluator.php | 2 + src/MathParser/Lexing/ComplexLexer.php | 1 + src/MathParser/Lexing/StdMathLexer.php | 1 + src/MathParser/Lexing/TokenType.php | 1 + .../Parsing/Nodes/ExpressionNode.php | 5 ++ .../Parsing/Nodes/Factories/NodeFactory.php | 8 +++ .../Nodes/Factories/SmallerNodeFactory.php | 71 +++++++++++++++++++ src/MathParser/Parsing/Nodes/Node.php | 1 + .../EvaluatorBooleanOperatorTest.php | 13 ++++ 9 files changed, 103 insertions(+) create mode 100644 src/MathParser/Parsing/Nodes/Factories/SmallerNodeFactory.php diff --git a/src/MathParser/Interpreting/Evaluator.php b/src/MathParser/Interpreting/Evaluator.php index 8a9746b..c536475 100644 --- a/src/MathParser/Interpreting/Evaluator.php +++ b/src/MathParser/Interpreting/Evaluator.php @@ -126,6 +126,8 @@ public function visitExpressionNode(ExpressionNode $node) return ($leftValue > $rightValue); case '>=': return $leftValue >= $rightValue; + case '<': + return $leftValue < $rightValue; default: diff --git a/src/MathParser/Lexing/ComplexLexer.php b/src/MathParser/Lexing/ComplexLexer.php index aac7d50..9306438 100644 --- a/src/MathParser/Lexing/ComplexLexer.php +++ b/src/MathParser/Lexing/ComplexLexer.php @@ -112,6 +112,7 @@ public function __construct() $this->add(new TokenDefinition('/\=/', TokenType::EqualCompareOperator)); $this->add(new TokenDefinition('/\>=/', TokenType::GreaterOrEqualOperator)); $this->add(new TokenDefinition('/\>/', TokenType::GreaterOperator)); + $this->add(new TokenDefinition('/\add(new TokenDefinition('/\&&/', TokenType::BooleanAndOperator)); $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); diff --git a/src/MathParser/Lexing/StdMathLexer.php b/src/MathParser/Lexing/StdMathLexer.php index 60ba8c8..52e697c 100644 --- a/src/MathParser/Lexing/StdMathLexer.php +++ b/src/MathParser/Lexing/StdMathLexer.php @@ -116,6 +116,7 @@ public function __construct() $this->add(new TokenDefinition('/\=/', TokenType::EqualCompareOperator)); $this->add(new TokenDefinition('/\>=/', TokenType::GreaterOrEqualOperator)); $this->add(new TokenDefinition('/\>/', TokenType::GreaterOperator)); + $this->add(new TokenDefinition('/\add(new TokenDefinition('/\&&/', TokenType::BooleanAndOperator)); $this->add(new TokenDefinition('/\|\|/', TokenType::BooleanOrOperator)); diff --git a/src/MathParser/Lexing/TokenType.php b/src/MathParser/Lexing/TokenType.php index 780f14d..4d9713e 100644 --- a/src/MathParser/Lexing/TokenType.php +++ b/src/MathParser/Lexing/TokenType.php @@ -73,6 +73,7 @@ final class TokenType const BooleanOrOperator = 108; const GreaterOperator = 109; const GreaterOrEqualOperator = 110; + const SmallerOperator = 111; /** Token represented a function name, e.g. 'sin' */ diff --git a/src/MathParser/Parsing/Nodes/ExpressionNode.php b/src/MathParser/Parsing/Nodes/ExpressionNode.php index ffe306d..321f340 100644 --- a/src/MathParser/Parsing/Nodes/ExpressionNode.php +++ b/src/MathParser/Parsing/Nodes/ExpressionNode.php @@ -121,6 +121,11 @@ function __construct($left, $operator = null, $right = null) $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/NodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php index 8006275..bcbdec2 100644 --- a/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php @@ -83,6 +83,7 @@ class NodeFactory { protected $greaterNodeFactory; protected $greaterOrEqualNodeFactory; + protected $smallerNodeFactory; /** * Constructor @@ -99,6 +100,7 @@ public function __construct() $this->boolOrNodeFactory = new BoolOrNodeFactory(); $this->greaterNodeFactory = new GreaterNodeFactory(); $this->greaterOrEqualNodeFactory = new GreaterOrEqualNodeFactory(); + $this->smallerNodeFactory = new SmallerNodeFactory(); } /** @@ -199,6 +201,11 @@ public function greater($leftOperand, $rightOperand) { public function greaterOrEqual($leftOperand, $rightOperand) { return $this->greaterOrEqualNodeFactory->makeNode($leftOperand, $rightOperand); } + + public function smaller($leftOperand, $rightOperand) { + return $this->smallerNodeFactory->makeNode($leftOperand, $rightOperand); + } + /** * Simplify the given ExpressionNode, using the appropriate factory. * @@ -216,6 +223,7 @@ public function simplify(ExpressionNode $node) 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->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/Node.php b/src/MathParser/Parsing/Nodes/Node.php index 282ce17..c057149 100644 --- a/src/MathParser/Parsing/Nodes/Node.php +++ b/src/MathParser/Parsing/Nodes/Node.php @@ -125,6 +125,7 @@ public static function factory(Token $token) case TokenType::BooleanOrOperator: case TokenType::GreaterOperator: case TokenType::GreaterOrEqualOperator: + case TokenType::SmallerOperator: return new ExpressionNode(null, $token->getValue(), null); case TokenType::FactorialOperator: diff --git a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index cce0c3b..f205b05 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -118,5 +118,18 @@ public function testGreaterOrEqualOperator() { $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); + + } + } From a4f6b807be0ce7080e2bedac4ef860e43362bff4 Mon Sep 17 00:00:00 2001 From: Tran Dang Khoa Date: Mon, 5 Jun 2017 15:40:52 +0700 Subject: [PATCH 10/10] Add Smaller or equal operator --- src/MathParser/Interpreting/Evaluator.php | 2 + src/MathParser/Lexing/ComplexLexer.php | 2 + src/MathParser/Lexing/StdMathLexer.php | 2 + src/MathParser/Lexing/TokenType.php | 1 + .../Parsing/Nodes/ExpressionNode.php | 5 ++ .../Parsing/Nodes/Factories/NodeFactory.php | 7 ++ .../Factories/SmallerOrEqualNodeFactory.php | 71 +++++++++++++++++++ src/MathParser/Parsing/Nodes/Node.php | 2 + .../EvaluatorBooleanOperatorTest.php | 4 ++ 9 files changed, 96 insertions(+) create mode 100644 src/MathParser/Parsing/Nodes/Factories/SmallerOrEqualNodeFactory.php diff --git a/src/MathParser/Interpreting/Evaluator.php b/src/MathParser/Interpreting/Evaluator.php index c536475..34931d6 100644 --- a/src/MathParser/Interpreting/Evaluator.php +++ b/src/MathParser/Interpreting/Evaluator.php @@ -128,6 +128,8 @@ public function visitExpressionNode(ExpressionNode $node) return $leftValue >= $rightValue; case '<': return $leftValue < $rightValue; + case '<=': + return $leftValue <= $rightValue; default: diff --git a/src/MathParser/Lexing/ComplexLexer.php b/src/MathParser/Lexing/ComplexLexer.php index 9306438..d0dd23d 100644 --- a/src/MathParser/Lexing/ComplexLexer.php +++ b/src/MathParser/Lexing/ComplexLexer.php @@ -112,8 +112,10 @@ public function __construct() $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)); diff --git a/src/MathParser/Lexing/StdMathLexer.php b/src/MathParser/Lexing/StdMathLexer.php index 52e697c..1e5a140 100644 --- a/src/MathParser/Lexing/StdMathLexer.php +++ b/src/MathParser/Lexing/StdMathLexer.php @@ -116,7 +116,9 @@ public function __construct() $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)); diff --git a/src/MathParser/Lexing/TokenType.php b/src/MathParser/Lexing/TokenType.php index 4d9713e..b19ba13 100644 --- a/src/MathParser/Lexing/TokenType.php +++ b/src/MathParser/Lexing/TokenType.php @@ -74,6 +74,7 @@ final class TokenType const GreaterOperator = 109; const GreaterOrEqualOperator = 110; const SmallerOperator = 111; + const SmallerOrEqualOperator = 112; /** Token represented a function name, e.g. 'sin' */ diff --git a/src/MathParser/Parsing/Nodes/ExpressionNode.php b/src/MathParser/Parsing/Nodes/ExpressionNode.php index 321f340..73c0de2 100644 --- a/src/MathParser/Parsing/Nodes/ExpressionNode.php +++ b/src/MathParser/Parsing/Nodes/ExpressionNode.php @@ -126,6 +126,11 @@ function __construct($left, $operator = null, $right = null) $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/NodeFactory.php b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php index bcbdec2..f60d5bd 100644 --- a/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php +++ b/src/MathParser/Parsing/Nodes/Factories/NodeFactory.php @@ -84,6 +84,7 @@ class NodeFactory { protected $greaterNodeFactory; protected $greaterOrEqualNodeFactory; protected $smallerNodeFactory; + protected $smallerOrEqualNodeFactory; /** * Constructor @@ -101,6 +102,7 @@ public function __construct() $this->greaterNodeFactory = new GreaterNodeFactory(); $this->greaterOrEqualNodeFactory = new GreaterOrEqualNodeFactory(); $this->smallerNodeFactory = new SmallerNodeFactory(); + $this->smallerOrEqualNodeFactory = new SmallerOrEqualNodeFactory(); } /** @@ -206,6 +208,10 @@ 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. * @@ -224,6 +230,7 @@ public function simplify(ExpressionNode $node) 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/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 c057149..9044d57 100644 --- a/src/MathParser/Parsing/Nodes/Node.php +++ b/src/MathParser/Parsing/Nodes/Node.php @@ -126,6 +126,8 @@ public static function factory(Token $token) 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/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php index f205b05..ad036ba 100644 --- a/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php +++ b/tests/MathParser/Interpreting/EvaluatorBooleanOperatorTest.php @@ -131,5 +131,9 @@ public function testSmallerOperator() { } + public function testSmallerOrEqualOperatorValidSyntax() { + $expression = $this->parser->parse('1 <= 3'); + $this->assertNotNull($expression); + } }