1818use Twig \Node \Expression \AbstractExpression ;
1919use Twig \Node \Expression \ArrayExpression ;
2020use Twig \Node \Expression \ArrowFunctionExpression ;
21- use Twig \Node \Expression \Binary \AbstractBinary ;
2221use Twig \Node \Expression \Binary \ConcatBinary ;
2322use Twig \Node \Expression \ConstantExpression ;
2423use Twig \Node \Expression \GetAttrExpression ;
2524use Twig \Node \Expression \MacroReferenceExpression ;
2625use Twig \Node \Expression \NameExpression ;
2726use Twig \Node \Expression \Ternary \ConditionalTernary ;
2827use Twig \Node \Expression \TestExpression ;
29- use Twig \Node \Expression \Unary \AbstractUnary ;
3028use Twig \Node \Expression \Unary \NegUnary ;
3129use Twig \Node \Expression \Unary \NotUnary ;
3230use Twig \Node \Expression \Unary \PosUnary ;
3735use Twig \Node \Expression \Variable \TemplateVariable ;
3836use Twig \Node \Node ;
3937use Twig \Node \Nodes ;
38+ use Twig \Operator \OperatorArity ;
39+ use Twig \Operator \OperatorAssociativity ;
40+ use Twig \Operator \Operators ;
4041
4142/**
4243 * Parses expressions.
5051 */
5152class ExpressionParser
5253{
54+ // FIXME: deprecated, use OperatorAssociativity instead
5355 public const OPERATOR_LEFT = 1 ;
5456 public const OPERATOR_RIGHT = 2 ;
5557
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 ;
58+ private Operators $ operators ;
6059 private $ readyNodes = [];
61- private array $ precedenceChanges = [] ;
60+ private \ WeakMap $ precedenceChanges ;
6261 private bool $ deprecationCheck = true ;
6362
6463 public function __construct (
6564 private Parser $ parser ,
6665 private Environment $ env ,
6766 ) {
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 ' ])) {
67+ $ this ->operators = $ env ->getOperators ();
68+ $ this ->precedenceChanges = new \WeakMap ();
69+ foreach ($ this ->operators as $ op ) {
70+ if (!$ op ->getPrecedenceChange ()) {
8071 continue ;
8172 }
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 ;
73+ $ min = min ($ op ->getPrecedenceChange ()->getNewPrecedence (), $ op ->getPrecedence ());
74+ $ max = max ($ op ->getPrecedenceChange ()->getNewPrecedence (), $ op ->getPrecedence ());
75+ foreach ($ this ->operators as $ o ) {
76+ if ($ o ->getPrecedence () > $ min && $ o ->getPrecedence () < $ max ) {
77+ if (!isset ($ this ->precedenceChanges [$ o ])) {
78+ $ this ->precedenceChanges [$ o ] = [];
79+ }
80+ $ this ->precedenceChanges [$ o ][] = $ op ;
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 ' , $ op );
127119
128120 $ this ->triggerPrecedenceDeprecations ($ expr );
129121
@@ -144,30 +136,29 @@ private function triggerPrecedenceDeprecations(AbstractExpression $expr): void
144136 return ;
145137 }
146138
147- if (str_starts_with ( $ unaryOp = $ expr ->getAttribute ('operator ' ), ' unary ' )) {
139+ if (OperatorArity::Unary === $ expr ->getAttribute ('operator ' )-> getArity ( )) {
148140 if ($ expr ->hasExplicitParentheses ()) {
149141 return ;
150142 }
151- $ target = explode ( ' _ ' , $ unaryOp )[ 1 ] ;
143+ $ operator = $ expr -> getAttribute ( ' operator ' ) ;
152144 /** @var AbstractExpression $node */
153145 $ node = $ expr ->getNode ('node ' );
154- foreach ($ this ->precedenceChanges as $ operatorName => $ changes ) {
155- if (!\in_array ($ unaryOp , $ changes )) {
146+ foreach ($ this ->precedenceChanges as $ op => $ changes ) {
147+ if (!\in_array ($ operator , $ changes, true )) {
156148 continue ;
157149 }
158- if ($ node ->hasAttribute ('operator ' ) && $ operatorName === $ node ->getAttribute ('operator ' )) {
159- $ change = $ this -> unaryOperators [ $ target ][ ' precedence_change ' ] ;
160- 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 ()));
150+ if ($ node ->hasAttribute ('operator ' ) && $ op === $ node ->getAttribute ('operator ' )) {
151+ $ change = $ operator -> getPrecedenceChange () ;
152+ 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. ' , $ operator -> getOperator () , $ this ->parser ->getStream ()->getSourceContext ()->getName (), $ node ->getTemplateLine ()));
161153 }
162154 }
163155 } else {
164- foreach ($ this ->precedenceChanges [$ expr ->getAttribute ('operator ' )] as $ operatorName ) {
156+ foreach ($ this ->precedenceChanges [$ expr ->getAttribute ('operator ' )] as $ operator ) {
165157 foreach ($ expr as $ node ) {
166158 /** @var AbstractExpression $node */
167- if ($ node ->hasAttribute ('operator ' ) && $ operatorName === $ node ->getAttribute ('operator ' ) && !$ node ->hasExplicitParentheses ()) {
168- $ op = explode ('_ ' , $ operatorName )[1 ];
169- $ change = $ this ->binaryOperators [$ op ]['precedence_change ' ];
170- 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 ()));
159+ if ($ node ->hasAttribute ('operator ' ) && $ operator === $ node ->getAttribute ('operator ' ) && !$ node ->hasExplicitParentheses ()) {
160+ $ change = $ operator ->getPrecedenceChange ();
161+ 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. ' , $ operator ->getOperator (), $ this ->parser ->getStream ()->getSourceContext ()->getName (), $ node ->getTemplateLine ()));
171162 }
172163 }
173164 }
@@ -236,14 +227,13 @@ private function getPrimary(): AbstractExpression
236227 {
237228 $ token = $ this ->parser ->getCurrentToken ();
238229
239- if ($ this ->isUnary ($ token )) {
240- $ operator = $ this ->unaryOperators [$ token ->getValue ()];
230+ if ($ token ->test (Token::OPERATOR_TYPE ) && $ operator = $ this ->operators ->getUnary ($ token ->getValue ())) {
241231 $ this ->parser ->getStream ()->next ();
242- $ expr = $ this ->parseExpression ($ operator[ ' precedence ' ] );
243- $ class = $ operator[ ' class ' ] ;
232+ $ expr = $ this ->parseExpression ($ operator-> getPrecedence () );
233+ $ class = $ operator-> getNodeClass () ;
244234
245235 $ expr = new $ class ($ expr , $ token ->getLine ());
246- $ expr ->setAttribute ('operator ' , ' unary_ ' . $ token -> getValue () );
236+ $ expr ->setAttribute ('operator ' , $ operator );
247237
248238 if ($ this ->deprecationCheck ) {
249239 $ this ->triggerPrecedenceDeprecations ($ expr );
@@ -284,16 +274,6 @@ private function parseConditionalExpression($expr): AbstractExpression
284274 return $ expr ;
285275 }
286276
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-
297277 public function parsePrimaryExpression ()
298278 {
299279 $ token = $ this ->parser ->getCurrentToken ();
0 commit comments