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+ // deprecated, to be removed in 4.0
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 = [];
6260 private bool $ deprecationCheck = true ;
6361
6462 public function __construct (
6563 private Parser $ parser ,
6664 private Environment $ env ,
6765 ) {
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 ' ])) {
80- continue ;
81- }
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 ;
88- }
89- }
90- }
66+ $ this ->operators = $ env ->getOperators ();
9167 }
9268
9369 public function parseExpression ($ precedence = 0 )
@@ -102,28 +78,30 @@ public function parseExpression($precedence = 0)
10278
10379 $ expr = $ this ->getPrimary ();
10480 $ token = $ this ->parser ->getCurrentToken ();
105- while ($ this ->isBinary ($ token ) && $ this ->binaryOperators [$ token ->getValue ()]['precedence ' ] >= $ precedence ) {
106- $ op = $ this ->binaryOperators [$ token ->getValue ()];
81+ while ($ token ->test (Token::OPERATOR_TYPE ) && ($ op = $ this ->operators ->getBinary ($ token ->getValue ())) && $ op ->getPrecedence () >= $ precedence ) {
10782 $ this ->parser ->getStream ()->next ();
10883
10984 if ('is not ' === $ token ->getValue ()) {
11085 $ expr = $ this ->parseNotTestExpression ($ expr );
11186 } elseif ('is ' === $ token ->getValue ()) {
11287 $ expr = $ this ->parseTestExpression ($ expr );
113- } elseif (isset ( $ op[ ' callable ' ] )) {
114- $ expr = $ op[ ' callable ' ] ($ this ->parser , $ expr );
88+ } elseif (null !== $ op-> getCallable ( )) {
89+ $ expr = $ op-> getCallable () ($ this ->parser , $ expr );
11590 } else {
11691 $ previous = $ this ->setDeprecationCheck (true );
11792 try {
118- $ expr1 = $ this ->parseExpression (self :: OPERATOR_LEFT === $ op[ ' associativity ' ] ? $ op[ ' precedence ' ] + 1 : $ op[ ' precedence ' ] );
93+ $ expr1 = $ this ->parseExpression (OperatorAssociativity::Left === $ op-> getAssociativity () ? $ op-> getPrecedence () + 1 : $ op-> getPrecedence () );
11994 } finally {
12095 $ this ->setDeprecationCheck ($ previous );
12196 }
122- $ class = $ op ['class ' ];
97+ $ class = $ op ->getNodeClass ();
98+ if (!$ class ) {
99+ throw new \LogicException (\sprintf ('Operator "%s" must have a Node class. ' , $ op ->getOperator ()));
100+ }
123101 $ expr = new $ class ($ expr , $ expr1 , $ token ->getLine ());
124102 }
125103
126- $ expr ->setAttribute ('operator ' , ' binary_ ' . $ token -> getValue () );
104+ $ expr ->setAttribute ('operator ' , $ op );
127105
128106 $ this ->triggerPrecedenceDeprecations ($ expr );
129107
@@ -139,35 +117,35 @@ public function parseExpression($precedence = 0)
139117
140118 private function triggerPrecedenceDeprecations (AbstractExpression $ expr ): void
141119 {
120+ $ precedenceChanges = $ this ->operators ->getPrecedenceChanges ();
142121 // Check that the all nodes that are between the 2 precedences have explicit parentheses
143- if (!$ expr ->hasAttribute ('operator ' ) || !isset ($ this -> precedenceChanges [$ expr ->getAttribute ('operator ' )])) {
122+ if (!$ expr ->hasAttribute ('operator ' ) || !isset ($ precedenceChanges [$ expr ->getAttribute ('operator ' )])) {
144123 return ;
145124 }
146125
147- if (str_starts_with ( $ unaryOp = $ expr ->getAttribute ('operator ' ), ' unary ' )) {
126+ if (OperatorArity::Unary === $ expr ->getAttribute ('operator ' )-> getArity ( )) {
148127 if ($ expr ->hasExplicitParentheses ()) {
149128 return ;
150129 }
151- $ target = explode ( ' _ ' , $ unaryOp )[ 1 ] ;
130+ $ operator = $ expr -> getAttribute ( ' operator ' ) ;
152131 /** @var AbstractExpression $node */
153132 $ node = $ expr ->getNode ('node ' );
154- foreach ($ this -> precedenceChanges as $ operatorName => $ changes ) {
155- if (!\in_array ($ unaryOp , $ changes )) {
133+ foreach ($ precedenceChanges as $ op => $ changes ) {
134+ if (!\in_array ($ operator , $ changes, true )) {
156135 continue ;
157136 }
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 ()));
137+ if ($ node ->hasAttribute ('operator ' ) && $ op === $ node ->getAttribute ('operator ' )) {
138+ $ change = $ operator -> getPrecedenceChange () ;
139+ 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 ()));
161140 }
162141 }
163142 } else {
164- foreach ($ this -> precedenceChanges [$ expr ->getAttribute ('operator ' )] as $ operatorName ) {
143+ foreach ($ precedenceChanges [$ expr ->getAttribute ('operator ' )] as $ operator ) {
165144 foreach ($ expr as $ node ) {
166145 /** @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 ()));
146+ if ($ node ->hasAttribute ('operator ' ) && $ operator === $ node ->getAttribute ('operator ' ) && !$ node ->hasExplicitParentheses ()) {
147+ $ change = $ operator ->getPrecedenceChange ();
148+ 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 ()));
171149 }
172150 }
173151 }
@@ -236,14 +214,16 @@ private function getPrimary(): AbstractExpression
236214 {
237215 $ token = $ this ->parser ->getCurrentToken ();
238216
239- if ($ this ->isUnary ($ token )) {
240- $ operator = $ this ->unaryOperators [$ token ->getValue ()];
217+ if ($ token ->test (Token::OPERATOR_TYPE ) && $ operator = $ this ->operators ->getUnary ($ token ->getValue ())) {
241218 $ this ->parser ->getStream ()->next ();
242- $ expr = $ this ->parseExpression ($ operator ['precedence ' ]);
243- $ class = $ operator ['class ' ];
219+ $ expr = $ this ->parseExpression ($ operator ->getPrecedence ());
220+ $ class = $ operator ->getNodeClass ();
221+ if (!$ class ) {
222+ throw new \LogicException (\sprintf ('Operator "%s" must have a Node class. ' , $ operator ->getOperator ()));
223+ }
244224
245225 $ expr = new $ class ($ expr , $ token ->getLine ());
246- $ expr ->setAttribute ('operator ' , ' unary_ ' . $ token -> getValue () );
226+ $ expr ->setAttribute ('operator ' , $ operator );
247227
248228 if ($ this ->deprecationCheck ) {
249229 $ this ->triggerPrecedenceDeprecations ($ expr );
@@ -284,16 +264,6 @@ private function parseConditionalExpression($expr): AbstractExpression
284264 return $ expr ;
285265 }
286266
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-
297267 public function parsePrimaryExpression ()
298268 {
299269 $ token = $ this ->parser ->getCurrentToken ();
0 commit comments