Skip to content

Commit 3aaf35c

Browse files
committed
Move operator definitions to objects
1 parent 4f3770e commit 3aaf35c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1712
-192
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 3.19.0 (2025-XX-XX)
22

3+
* Move operator definitions to objects
34
* Fix `constant()` behavior when used with `??`
45
* Add the `invoke` filter
56
* Make `{}` optional for the `types` tag

doc/advanced.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,8 @@ responsible for parsing the tag and compiling it to PHP.
775775
Operators
776776
~~~~~~~~~
777777

778+
# FIXME
779+
778780
The ``getOperators()`` methods lets you add new operators. Here is how to add
779781
the ``!``, ``||``, and ``&&`` operators::
780782

src/Environment.php

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@
2727
use Twig\Loader\ArrayLoader;
2828
use Twig\Loader\ChainLoader;
2929
use Twig\Loader\LoaderInterface;
30-
use Twig\Node\Expression\Binary\AbstractBinary;
31-
use Twig\Node\Expression\Unary\AbstractUnary;
3230
use Twig\Node\ModuleNode;
3331
use Twig\Node\Node;
3432
use Twig\NodeVisitor\NodeVisitorInterface;
33+
use Twig\Operator\Operators;
3534
use Twig\Runtime\EscaperRuntime;
3635
use Twig\RuntimeLoader\FactoryRuntimeLoader;
3736
use Twig\RuntimeLoader\RuntimeLoaderInterface;
@@ -862,22 +861,10 @@ public function mergeGlobals(array $context): array
862861

863862
/**
864863
* @internal
865-
*
866-
* @return array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractUnary>}>
867-
*/
868-
public function getUnaryOperators(): array
869-
{
870-
return $this->extensionSet->getUnaryOperators();
871-
}
872-
873-
/**
874-
* @internal
875-
*
876-
* @return array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
877864
*/
878-
public function getBinaryOperators(): array
865+
public function getOperators(): Operators
879866
{
880-
return $this->extensionSet->getBinaryOperators();
867+
return $this->extensionSet->getOperators();
881868
}
882869

883870
private function updateOptionsHash(): void

src/ExpressionParser.php

Lines changed: 28 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use Twig\Node\Expression\AbstractExpression;
1919
use Twig\Node\Expression\ArrayExpression;
2020
use Twig\Node\Expression\ArrowFunctionExpression;
21-
use Twig\Node\Expression\Binary\AbstractBinary;
2221
use Twig\Node\Expression\Binary\ConcatBinary;
2322
use Twig\Node\Expression\ConstantExpression;
2423
use Twig\Node\Expression\GetAttrExpression;
@@ -37,6 +36,12 @@
3736
use Twig\Node\Expression\Variable\TemplateVariable;
3837
use Twig\Node\Node;
3938
use Twig\Node\Nodes;
39+
use Twig\Operator\Binary\AbstractBinaryOperator;
40+
use Twig\Operator\OperatorArity;
41+
use Twig\Operator\OperatorAssociativity;
42+
use Twig\Operator\OperatorInterface;
43+
use Twig\Operator\Operators;
44+
use Twig\Operator\Unary\AbstractUnaryOperator;
4045

4146
/**
4247
* Parses expressions.
@@ -50,13 +55,11 @@
5055
*/
5156
class ExpressionParser
5257
{
58+
// FIXME: deprecated, use OperatorAssociativity instead
5359
public const OPERATOR_LEFT = 1;
5460
public const OPERATOR_RIGHT = 2;
5561

56-
/** @var array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractUnary>}> */
57-
private $unaryOperators;
58-
/** @var array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */
59-
private $binaryOperators;
62+
private Operators $operators;
6063
private $readyNodes = [];
6164
private array $precedenceChanges = [];
6265
private bool $deprecationCheck = true;
@@ -65,26 +68,16 @@ public function __construct(
6568
private Parser $parser,
6669
private Environment $env,
6770
) {
68-
$this->unaryOperators = $env->getUnaryOperators();
69-
$this->binaryOperators = $env->getBinaryOperators();
70-
71-
$ops = [];
72-
foreach ($this->unaryOperators as $n => $c) {
73-
$ops[] = $c + ['name' => $n, 'type' => 'unary'];
74-
}
75-
foreach ($this->binaryOperators as $n => $c) {
76-
$ops[] = $c + ['name' => $n, 'type' => 'binary'];
77-
}
78-
foreach ($ops as $config) {
79-
if (!isset($config['precedence_change'])) {
71+
$this->operators = $env->getOperators();
72+
foreach ($this->operators as $name => $op) {
73+
if (!$op->getPrecedenceChange()) {
8074
continue;
8175
}
82-
$name = $config['type'].'_'.$config['name'];
83-
$min = min($config['precedence_change']->getNewPrecedence(), $config['precedence']);
84-
$max = max($config['precedence_change']->getNewPrecedence(), $config['precedence']);
85-
foreach ($ops as $c) {
86-
if ($c['precedence'] > $min && $c['precedence'] < $max) {
87-
$this->precedenceChanges[$c['type'].'_'.$c['name']][] = $name;
76+
$min = min($op->getPrecedenceChange()->getNewPrecedence(), $op->getPrecedence());
77+
$max = max($op->getPrecedenceChange()->getNewPrecedence(), $op->getPrecedence());
78+
foreach ($this->operators as $n => $o) {
79+
if ($o->getPrecedence() > $min && $o->getPrecedence() < $max) {
80+
$this->precedenceChanges[$n][] = $name;
8881
}
8982
}
9083
}
@@ -102,28 +95,27 @@ public function parseExpression($precedence = 0)
10295

10396
$expr = $this->getPrimary();
10497
$token = $this->parser->getCurrentToken();
105-
while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
106-
$op = $this->binaryOperators[$token->getValue()];
98+
while ($token->test(Token::OPERATOR_TYPE) && ($op = $this->operators->getBinary($token->getValue())) && $op->getPrecedence() >= $precedence) {
10799
$this->parser->getStream()->next();
108100

109101
if ('is not' === $token->getValue()) {
110102
$expr = $this->parseNotTestExpression($expr);
111103
} elseif ('is' === $token->getValue()) {
112104
$expr = $this->parseTestExpression($expr);
113-
} elseif (isset($op['callable'])) {
114-
$expr = $op['callable']($this->parser, $expr);
105+
} elseif (null !== $op->getCallable()) {
106+
$expr = $op->getCallable()($this->parser, $expr);
115107
} else {
116108
$previous = $this->setDeprecationCheck(true);
117109
try {
118-
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
110+
$expr1 = $this->parseExpression(OperatorAssociativity::Left === $op->getAssociativity() ? $op->getPrecedence() + 1 : $op->getPrecedence());
119111
} finally {
120112
$this->setDeprecationCheck($previous);
121113
}
122-
$class = $op['class'];
114+
$class = $op->getNodeClass();
123115
$expr = new $class($expr, $expr1, $token->getLine());
124116
}
125117

126-
$expr->setAttribute('operator', 'binary_'.$token->getValue());
118+
$expr->setAttribute('operator', OperatorArity::Binary->value.'_'.$token->getValue());
127119

128120
$this->triggerPrecedenceDeprecations($expr);
129121

@@ -156,7 +148,7 @@ private function triggerPrecedenceDeprecations(AbstractExpression $expr): void
156148
continue;
157149
}
158150
if ($node->hasAttribute('operator') && $operatorName === $node->getAttribute('operator')) {
159-
$change = $this->unaryOperators[$target]['precedence_change'];
151+
$change = $this->operators->getUnary($target)->getPrecedenceChange();
160152
trigger_deprecation($change->getPackage(), $change->getVersion(), \sprintf('Add explicit parentheses around the "%s" unary operator to avoid behavior change in the next major version as its precedence will change in "%s" at line %d.', $target, $this->parser->getStream()->getSourceContext()->getName(), $node->getTemplateLine()));
161153
}
162154
}
@@ -166,7 +158,7 @@ private function triggerPrecedenceDeprecations(AbstractExpression $expr): void
166158
/** @var AbstractExpression $node */
167159
if ($node->hasAttribute('operator') && $operatorName === $node->getAttribute('operator') && !$node->hasExplicitParentheses()) {
168160
$op = explode('_', $operatorName)[1];
169-
$change = $this->binaryOperators[$op]['precedence_change'];
161+
$change = $this->operators->getBinary($op)->getPrecedenceChange();
170162
trigger_deprecation($change->getPackage(), $change->getVersion(), \sprintf('Add explicit parentheses around the "%s" binary operator to avoid behavior change in the next major version as its precedence will change in "%s" at line %d.', $op, $this->parser->getStream()->getSourceContext()->getName(), $node->getTemplateLine()));
171163
}
172164
}
@@ -236,14 +228,13 @@ private function getPrimary(): AbstractExpression
236228
{
237229
$token = $this->parser->getCurrentToken();
238230

239-
if ($this->isUnary($token)) {
240-
$operator = $this->unaryOperators[$token->getValue()];
231+
if ($token->test(Token::OPERATOR_TYPE) && $operator = $this->operators->getUnary($token->getValue())) {
241232
$this->parser->getStream()->next();
242-
$expr = $this->parseExpression($operator['precedence']);
243-
$class = $operator['class'];
233+
$expr = $this->parseExpression($operator->getPrecedence());
234+
$class = $operator->getNodeClass();
244235

245236
$expr = new $class($expr, $token->getLine());
246-
$expr->setAttribute('operator', 'unary_'.$token->getValue());
237+
$expr->setAttribute('operator', OperatorArity::Unary->value.'_'.$token->getValue());
247238

248239
if ($this->deprecationCheck) {
249240
$this->triggerPrecedenceDeprecations($expr);
@@ -284,16 +275,6 @@ private function parseConditionalExpression($expr): AbstractExpression
284275
return $expr;
285276
}
286277

287-
private function isUnary(Token $token): bool
288-
{
289-
return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
290-
}
291-
292-
private function isBinary(Token $token): bool
293-
{
294-
return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
295-
}
296-
297278
public function parsePrimaryExpression()
298279
{
299280
$token = $this->parser->getCurrentToken();

0 commit comments

Comments
 (0)