diff --git a/CHANGELOG.md b/CHANGELOG.md index e32b66c..c1daf1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add support for null-coalescing unknown variables * Add support for comments using `/*` & `*/` * Allow passing any iterable as `$providers` list to `ExpressionLanguage` constructor + * Add support for `<<`, `>>`, and `~` bitwise operators 7.1 --- diff --git a/Lexer.php b/Lexer.php index d18d34c..44d0609 100644 --- a/Lexer.php +++ b/Lexer.php @@ -72,7 +72,7 @@ public function tokenize(string $expression): TokenStream } elseif (preg_match('{/\*.*?\*/}A', $expression, $match, 0, $cursor)) { // comments $cursor += \strlen($match[0]); - } elseif (preg_match('/(?<=^|[\s(])starts with(?=[\s(])|(?<=^|[\s(])ends with(?=[\s(])|(?<=^|[\s(])contains(?=[\s(])|(?<=^|[\s(])matches(?=[\s(])|(?<=^|[\s(])not in(?=[\s(])|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\!\=\=|(?<=^|[\s(])or(?=[\s(])|\|\||&&|\=\=|\!\=|\>\=|\<\=|(?<=^|[\s(])in(?=[\s(])|\.\.|\*\*|\!|\||\^|&|\<|\>|\+|\-|~|\*|\/|%/A', $expression, $match, 0, $cursor)) { + } elseif (preg_match('/(?<=^|[\s(])starts with(?=[\s(])|(?<=^|[\s(])ends with(?=[\s(])|(?<=^|[\s(])contains(?=[\s(])|(?<=^|[\s(])matches(?=[\s(])|(?<=^|[\s(])not in(?=[\s(])|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\!\=\=|(?<=^|[\s(])or(?=[\s(])|\|\||&&|\=\=|\!\=|\>\=|\<\=|(?<=^|[\s(])in(?=[\s(])|\.\.|\*\*|\!|\||\^|&|<<|>>|\<|\>|\+|\-|~|\*|\/|%/A', $expression, $match, 0, $cursor)) { // operators $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1); $cursor += \strlen($match[0]); diff --git a/Node/BinaryNode.php b/Node/BinaryNode.php index 4065ed6..68bce60 100644 --- a/Node/BinaryNode.php +++ b/Node/BinaryNode.php @@ -130,6 +130,10 @@ public function evaluate(array $functions, array $values): mixed return $left ^ $right; case '&': return $left & $right; + case '<<': + return $left << $right; + case '>>': + return $left >> $right; case '==': return $left == $right; case '===': diff --git a/Node/UnaryNode.php b/Node/UnaryNode.php index 55e2121..5a78cfa 100644 --- a/Node/UnaryNode.php +++ b/Node/UnaryNode.php @@ -25,6 +25,7 @@ class UnaryNode extends Node 'not' => '!', '+' => '+', '-' => '-', + '~' => '~', ]; public function __construct(string $operator, Node $node) @@ -53,6 +54,7 @@ public function evaluate(array $functions, array $values): mixed 'not', '!' => !$value, '-' => -$value, + '~' => ~$value, default => $value, }; } diff --git a/Parser.php b/Parser.php index da36770..7305d7a 100644 --- a/Parser.php +++ b/Parser.php @@ -43,6 +43,7 @@ public function __construct( '!' => ['precedence' => 50], '-' => ['precedence' => 500], '+' => ['precedence' => 500], + '~' => ['precedence' => 500], ]; $this->binaryOperators = [ 'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], @@ -67,6 +68,8 @@ public function __construct( 'ends with' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], 'matches' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], '..' => ['precedence' => 25, 'associativity' => self::OPERATOR_LEFT], + '<<' => ['precedence' => 25, 'associativity' => self::OPERATOR_LEFT], + '>>' => ['precedence' => 25, 'associativity' => self::OPERATOR_LEFT], '+' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], '-' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], '~' => ['precedence' => 40, 'associativity' => self::OPERATOR_LEFT], diff --git a/Tests/LexerTest.php b/Tests/LexerTest.php index 2ffe988..1ba55b7 100644 --- a/Tests/LexerTest.php +++ b/Tests/LexerTest.php @@ -97,8 +97,11 @@ public static function getTokenizeData() new Token('punctuation', ']', 27), new Token('operator', '-', 29), new Token('number', 1990, 31), + new Token('operator', '+', 39), + new Token('operator', '~', 41), + new Token('name', 'qux', 42), ], - '(3 + 5) ~ foo("bar").baz[4] - 1.99E+3', + '(3 + 5) ~ foo("bar").baz[4] - 1.99E+3 + ~qux', ], [ [new Token('operator', '..', 1)], diff --git a/Tests/Node/BinaryNodeTest.php b/Tests/Node/BinaryNodeTest.php index fd06587..36e3b9b 100644 --- a/Tests/Node/BinaryNodeTest.php +++ b/Tests/Node/BinaryNodeTest.php @@ -35,6 +35,8 @@ public static function getEvaluateData(): array [0, new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))], [6, new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))], [6, new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))], + [32, new BinaryNode('<<', new ConstantNode(2), new ConstantNode(4))], + [2, new BinaryNode('>>', new ConstantNode(32), new ConstantNode(4))], [true, new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))], [true, new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))], @@ -90,6 +92,8 @@ public static function getCompileData(): array ['(2 & 4)', new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))], ['(2 | 4)', new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))], ['(2 ^ 4)', new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))], + ['(2 << 4)', new BinaryNode('<<', new ConstantNode(2), new ConstantNode(4))], + ['(32 >> 4)', new BinaryNode('>>', new ConstantNode(32), new ConstantNode(4))], ['(1 < 2)', new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))], ['(1 <= 2)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))], @@ -142,6 +146,8 @@ public static function getDumpData(): array ['(2 & 4)', new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))], ['(2 | 4)', new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))], ['(2 ^ 4)', new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))], + ['(2 << 4)', new BinaryNode('<<', new ConstantNode(2), new ConstantNode(4))], + ['(32 >> 4)', new BinaryNode('>>', new ConstantNode(32), new ConstantNode(4))], ['(1 < 2)', new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))], ['(1 <= 2)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))], diff --git a/Tests/Node/UnaryNodeTest.php b/Tests/Node/UnaryNodeTest.php index 7da4be7..ec7fb7f 100644 --- a/Tests/Node/UnaryNodeTest.php +++ b/Tests/Node/UnaryNodeTest.php @@ -23,6 +23,7 @@ public static function getEvaluateData(): array [3, new UnaryNode('+', new ConstantNode(3))], [false, new UnaryNode('!', new ConstantNode(true))], [false, new UnaryNode('not', new ConstantNode(true))], + [-6, new UnaryNode('~', new ConstantNode(5))], ]; } @@ -33,6 +34,7 @@ public static function getCompileData(): array ['(+3)', new UnaryNode('+', new ConstantNode(3))], ['(!true)', new UnaryNode('!', new ConstantNode(true))], ['(!true)', new UnaryNode('not', new ConstantNode(true))], + ['(~5)', new UnaryNode('~', new ConstantNode(5))], ]; } @@ -43,6 +45,7 @@ public static function getDumpData(): array ['(+ 3)', new UnaryNode('+', new ConstantNode(3))], ['(! true)', new UnaryNode('!', new ConstantNode(true))], ['(not true)', new UnaryNode('not', new ConstantNode(true))], + ['(~ 5)', new UnaryNode('~', new ConstantNode(5))], ]; } }