feature #34329 [ExpressionLanguage] add XOR operator (ottaviano)
This PR was merged into the 4.4 branch.
Discussion
----------
[ExpressionLanguage] add XOR operator
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | ~
| License | MIT
| Doc PR | ~
👋
little PR for adding the best logical operator: `XOR`
Commits
-------
46fe91781d
[ExpressionLanguage] add XOR operator
This commit is contained in:
commit
d1d4bc8bad
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
4.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* add `xor` operator
|
||||||
|
|
||||||
4.0.0
|
4.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class Lexer
|
|||||||
// strings
|
// strings
|
||||||
$tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
|
$tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
|
||||||
$cursor += \strlen($match[0]);
|
$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
|
// operators
|
||||||
$tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
|
$tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
|
||||||
$cursor += \strlen($match[0]);
|
$cursor += \strlen($match[0]);
|
||||||
|
@ -104,6 +104,8 @@ class BinaryNode extends Node
|
|||||||
case 'or':
|
case 'or':
|
||||||
case '||':
|
case '||':
|
||||||
return $left || $this->nodes['right']->evaluate($functions, $values);
|
return $left || $this->nodes['right']->evaluate($functions, $values);
|
||||||
|
case 'xor':
|
||||||
|
return $left xor $this->nodes['right']->evaluate($functions, $values);
|
||||||
case 'and':
|
case 'and':
|
||||||
case '&&':
|
case '&&':
|
||||||
return $left && $this->nodes['right']->evaluate($functions, $values);
|
return $left && $this->nodes['right']->evaluate($functions, $values);
|
||||||
|
@ -45,6 +45,7 @@ class Parser
|
|||||||
$this->binaryOperators = [
|
$this->binaryOperators = [
|
||||||
'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT],
|
'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT],
|
||||||
'||' => ['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],
|
'and' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT],
|
||||||
'&&' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT],
|
'&&' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT],
|
||||||
'|' => ['precedence' => 16, 'associativity' => self::OPERATOR_LEFT],
|
'|' => ['precedence' => 16, 'associativity' => self::OPERATOR_LEFT],
|
||||||
|
@ -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],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,14 @@ class LexerTest extends TestCase
|
|||||||
[new Token('string', '#foo', 1)],
|
[new Token('string', '#foo', 1)],
|
||||||
'"#foo"',
|
'"#foo"',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
new Token('name', 'a', 1),
|
||||||
|
new Token('operator', 'xor', 3),
|
||||||
|
new Token('name', 'b', 7),
|
||||||
|
],
|
||||||
|
'a xor b',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,11 @@ class ParserTest extends TestCase
|
|||||||
'bar',
|
'bar',
|
||||||
['foo' => 'bar'],
|
['foo' => 'bar'],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
new Node\BinaryNode('xor', new Node\ConstantNode(true), new Node\ConstantNode(false)),
|
||||||
|
'true xor false',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user