From 46fe91781d195cab857878860ee6722497306947 Mon Sep 17 00:00:00 2001 From: Dimitri Gritsajuk Date: Tue, 12 Nov 2019 13:06:46 +0100 Subject: [PATCH] [ExpressionLanguage] add XOR operator --- .../Component/ExpressionLanguage/CHANGELOG.md | 5 +++ .../Component/ExpressionLanguage/Lexer.php | 2 +- .../ExpressionLanguage/Node/BinaryNode.php | 2 ++ .../Component/ExpressionLanguage/Parser.php | 1 + .../Tests/ExpressionLanguageTest.php | 31 +++++++++++++++++++ .../ExpressionLanguage/Tests/LexerTest.php | 8 +++++ .../ExpressionLanguage/Tests/ParserTest.php | 5 +++ 7 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md index 6c50b2ea42..f3d48fae7a 100644 --- a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md +++ b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +----- + + * add `xor` operator + 4.0.0 ----- diff --git a/src/Symfony/Component/ExpressionLanguage/Lexer.php b/src/Symfony/Component/ExpressionLanguage/Lexer.php index 95b90705dd..8a7790af6f 100644 --- a/src/Symfony/Component/ExpressionLanguage/Lexer.php +++ b/src/Symfony/Component/ExpressionLanguage/Lexer.php @@ -73,7 +73,7 @@ class Lexer // strings $tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1); $cursor += \strlen($match[0]); - } elseif (preg_match('/not in(?=[\s(])|\!\=\=|not(?=[\s(])|and(?=[\s(])|\=\=\=|\>\=|or(?=[\s(])|\<\=|\*\*|\.\.|in(?=[\s(])|&&|\|\||matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) { + } elseif (preg_match('/not in(?=[\s(])|\!\=\=|not(?=[\s(])|and(?=[\s(])|\=\=\=|\>\=|or(?=[\s(])|xor(?=[\s(])|\<\=|\*\*|\.\.|in(?=[\s(])|&&|\|\||matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) { // operators $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1); $cursor += \strlen($match[0]); diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index 0af4f16623..3e81994239 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -104,6 +104,8 @@ class BinaryNode extends Node case 'or': case '||': return $left || $this->nodes['right']->evaluate($functions, $values); + case 'xor': + return $left xor $this->nodes['right']->evaluate($functions, $values); case 'and': case '&&': return $left && $this->nodes['right']->evaluate($functions, $values); diff --git a/src/Symfony/Component/ExpressionLanguage/Parser.php b/src/Symfony/Component/ExpressionLanguage/Parser.php index 59c4d67d78..89c2dd775f 100644 --- a/src/Symfony/Component/ExpressionLanguage/Parser.php +++ b/src/Symfony/Component/ExpressionLanguage/Parser.php @@ -45,6 +45,7 @@ class Parser $this->binaryOperators = [ 'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], '||' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], + 'xor' => ['precedence' => 13, 'associativity' => self::OPERATOR_LEFT], 'and' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], '&&' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], '|' => ['precedence' => 16, 'associativity' => self::OPERATOR_LEFT], diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index 6e01b250ca..d8ecaeff93 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -256,4 +256,35 @@ class ExpressionLanguageTest extends TestCase ], ]; } + + /** + * @dataProvider getLogicalOperators + */ + public function testLogicalOperators($expression, $expected) + { + $this->assertSame($expected, (new ExpressionLanguage())->evaluate($expression)); + } + + public function getLogicalOperators() + { + return [ + // AND + ['true and true', true], + ['true and false', false], + ['false and true', false], + ['false and false', false], + + // OR + ['true or true', true], + ['true or false', true], + ['false or true', true], + ['false or false', false], + + // XOR + ['true xor true', false], + ['true xor false', true], + ['false xor true', true], + ['false xor false', false], + ]; + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php index 2674752aa2..c16d91c709 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php @@ -114,6 +114,14 @@ class LexerTest extends TestCase [new Token('string', '#foo', 1)], '"#foo"', ], + [ + [ + new Token('name', 'a', 1), + new Token('operator', 'xor', 3), + new Token('name', 'b', 7), + ], + 'a xor b', + ], ]; } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php index 84b30dc151..56d08b0af7 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php @@ -151,6 +151,11 @@ class ParserTest extends TestCase 'bar', ['foo' => 'bar'], ], + + [ + new Node\BinaryNode('xor', new Node\ConstantNode(true), new Node\ConstantNode(false)), + 'true xor false', + ], ]; }