PluginProbe
Elementor Website Builder – more than just a page builder / 3.28.4
Elementor Website Builder – more than just a page builder v3.28.4
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / vendor_prefixed / twig / src / ExpressionParser.php

ExpressionParser.php in Elementor Website Builder – more than just a page builder 3.28.4, at vendor_prefixed/twig/src/ExpressionParser.php

959 lines 36.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 * (c) Armin Ronacher
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12 namespace ElementorDeps\Twig;
13
14 use ElementorDeps\Twig\Error\SyntaxError;
15 use ElementorDeps\Twig\Node\Expression\AbstractExpression;
16 use ElementorDeps\Twig\Node\Expression\ArrayExpression;
17 use ElementorDeps\Twig\Node\Expression\ArrowFunctionExpression;
18 use ElementorDeps\Twig\Node\Expression\AssignNameExpression;
19 use ElementorDeps\Twig\Node\Expression\Binary\AbstractBinary;
20 use ElementorDeps\Twig\Node\Expression\Binary\ConcatBinary;
21 use ElementorDeps\Twig\Node\Expression\BlockReferenceExpression;
22 use ElementorDeps\Twig\Node\Expression\ConditionalExpression;
23 use ElementorDeps\Twig\Node\Expression\ConstantExpression;
24 use ElementorDeps\Twig\Node\Expression\GetAttrExpression;
25 use ElementorDeps\Twig\Node\Expression\MethodCallExpression;
26 use ElementorDeps\Twig\Node\Expression\NameExpression;
27 use ElementorDeps\Twig\Node\Expression\ParentExpression;
28 use ElementorDeps\Twig\Node\Expression\TestExpression;
29 use ElementorDeps\Twig\Node\Expression\Unary\AbstractUnary;
30 use ElementorDeps\Twig\Node\Expression\Unary\NegUnary;
31 use ElementorDeps\Twig\Node\Expression\Unary\NotUnary;
32 use ElementorDeps\Twig\Node\Expression\Unary\PosUnary;
33 use ElementorDeps\Twig\Node\Node;
34 /**
35 * Parses expressions.
36 *
37 * This parser implements a "Precedence climbing" algorithm.
38 *
39 * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
40 * @see https://en.wikipedia.org/wiki/Operator-precedence_parser
41 *
42 * @author Fabien Potencier <fabien@symfony.com>
43 */
44 class ExpressionParser
45 {
46 public const OPERATOR_LEFT = 1;
47 public const OPERATOR_RIGHT = 2;
48 private $parser;
49 private $env;
50 /** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */
51 private $unaryOperators;
52 /** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */
53 private $binaryOperators;
54 public function __construct(Parser $parser, Environment $env)
55 {
56 $this->parser = $parser;
57 $this->env = $env;
58 $this->unaryOperators = $env->getUnaryOperators();
59 $this->binaryOperators = $env->getBinaryOperators();
60 }
61 public function parseExpression($precedence = 0, $allowArrow = \false)
62 {
63 if ($allowArrow && ($arrow = $this->parseArrow())) {
64 return $arrow;
65 }
66 $expr = $this->getPrimary();
67 $token = $this->parser->getCurrentToken();
68 while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
69 $op = $this->binaryOperators[$token->getValue()];
70 $this->parser->getStream()->next();
71 if ('is not' === $token->getValue()) {
72 $expr = $this->parseNotTestExpression($expr);
73 } elseif ('is' === $token->getValue()) {
74 $expr = $this->parseTestExpression($expr);
75 } elseif (isset($op['callable'])) {
76 $expr = $op['callable']($this->parser, $expr);
77 } else {
78 $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], \true);
79 $class = $op['class'];
80 $expr = new $class($expr, $expr1, $token->getLine());
81 }
82 $token = $this->parser->getCurrentToken();
83 }
84 if (0 === $precedence) {
85 return $this->parseConditionalExpression($expr);
86 }
87 return $expr;
88 }
89 /**
90 * @return ArrowFunctionExpression|null
91 */
92 private function parseArrow()
93 {
94 $stream = $this->parser->getStream();
95 // short array syntax (one argument, no parentheses)?
96 if ($stream->look(1)->test(
97 /* Token::ARROW_TYPE */
98 12
99 )) {
100 $line = $stream->getCurrent()->getLine();
101 $token = $stream->expect(
102 /* Token::NAME_TYPE */
103 5
104 );
105 $names = [new AssignNameExpression($token->getValue(), $token->getLine())];
106 $stream->expect(
107 /* Token::ARROW_TYPE */
108 12
109 );
110 return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
111 }
112 // first, determine if we are parsing an arrow function by finding => (long form)
113 $i = 0;
114 if (!$stream->look($i)->test(
115 /* Token::PUNCTUATION_TYPE */
116 9,
117 '('
118 )) {
119 return null;
120 }
121 ++$i;
122 while (\true) {
123 // variable name
124 ++$i;
125 if (!$stream->look($i)->test(
126 /* Token::PUNCTUATION_TYPE */
127 9,
128 ','
129 )) {
130 break;
131 }
132 ++$i;
133 }
134 if (!$stream->look($i)->test(
135 /* Token::PUNCTUATION_TYPE */
136 9,
137 ')'
138 )) {
139 return null;
140 }
141 ++$i;
142 if (!$stream->look($i)->test(
143 /* Token::ARROW_TYPE */
144 12
145 )) {
146 return null;
147 }
148 // yes, let's parse it properly
149 $token = $stream->expect(
150 /* Token::PUNCTUATION_TYPE */
151 9,
152 '('
153 );
154 $line = $token->getLine();
155 $names = [];
156 while (\true) {
157 $token = $stream->expect(
158 /* Token::NAME_TYPE */
159 5
160 );
161 $names[] = new AssignNameExpression($token->getValue(), $token->getLine());
162 if (!$stream->nextIf(
163 /* Token::PUNCTUATION_TYPE */
164 9,
165 ','
166 )) {
167 break;
168 }
169 }
170 $stream->expect(
171 /* Token::PUNCTUATION_TYPE */
172 9,
173 ')'
174 );
175 $stream->expect(
176 /* Token::ARROW_TYPE */
177 12
178 );
179 return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
180 }
181 private function getPrimary() : AbstractExpression
182 {
183 $token = $this->parser->getCurrentToken();
184 if ($this->isUnary($token)) {
185 $operator = $this->unaryOperators[$token->getValue()];
186 $this->parser->getStream()->next();
187 $expr = $this->parseExpression($operator['precedence']);
188 $class = $operator['class'];
189 return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
190 } elseif ($token->test(
191 /* Token::PUNCTUATION_TYPE */
192 9,
193 '('
194 )) {
195 $this->parser->getStream()->next();
196 $expr = $this->parseExpression();
197 $this->parser->getStream()->expect(
198 /* Token::PUNCTUATION_TYPE */
199 9,
200 ')',
201 'An opened parenthesis is not properly closed'
202 );
203 return $this->parsePostfixExpression($expr);
204 }
205 return $this->parsePrimaryExpression();
206 }
207 private function parseConditionalExpression($expr) : AbstractExpression
208 {
209 while ($this->parser->getStream()->nextIf(
210 /* Token::PUNCTUATION_TYPE */
211 9,
212 '?'
213 )) {
214 if (!$this->parser->getStream()->nextIf(
215 /* Token::PUNCTUATION_TYPE */
216 9,
217 ':'
218 )) {
219 $expr2 = $this->parseExpression();
220 if ($this->parser->getStream()->nextIf(
221 /* Token::PUNCTUATION_TYPE */
222 9,
223 ':'
224 )) {
225 // Ternary operator (expr ? expr2 : expr3)
226 $expr3 = $this->parseExpression();
227 } else {
228 // Ternary without else (expr ? expr2)
229 $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
230 }
231 } else {
232 // Ternary without then (expr ?: expr3)
233 $expr2 = $expr;
234 $expr3 = $this->parseExpression();
235 }
236 $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
237 }
238 return $expr;
239 }
240 private function isUnary(Token $token) : bool
241 {
242 return $token->test(
243 /* Token::OPERATOR_TYPE */
244 8
245 ) && isset($this->unaryOperators[$token->getValue()]);
246 }
247 private function isBinary(Token $token) : bool
248 {
249 return $token->test(
250 /* Token::OPERATOR_TYPE */
251 8
252 ) && isset($this->binaryOperators[$token->getValue()]);
253 }
254 public function parsePrimaryExpression()
255 {
256 $token = $this->parser->getCurrentToken();
257 switch ($token->getType()) {
258 case 5:
259 $this->parser->getStream()->next();
260 switch ($token->getValue()) {
261 case 'true':
262 case 'TRUE':
263 $node = new ConstantExpression(\true, $token->getLine());
264 break;
265 case 'false':
266 case 'FALSE':
267 $node = new ConstantExpression(\false, $token->getLine());
268 break;
269 case 'none':
270 case 'NONE':
271 case 'null':
272 case 'NULL':
273 $node = new ConstantExpression(null, $token->getLine());
274 break;
275 default:
276 if ('(' === $this->parser->getCurrentToken()->getValue()) {
277 $node = $this->getFunctionNode($token->getValue(), $token->getLine());
278 } else {
279 $node = new NameExpression($token->getValue(), $token->getLine());
280 }
281 }
282 break;
283 case 6:
284 $this->parser->getStream()->next();
285 $node = new ConstantExpression($token->getValue(), $token->getLine());
286 break;
287 case 7:
288 case 10:
289 $node = $this->parseStringExpression();
290 break;
291 case 8:
292 if (\preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
293 // in this context, string operators are variable names
294 $this->parser->getStream()->next();
295 $node = new NameExpression($token->getValue(), $token->getLine());
296 break;
297 }
298 if (isset($this->unaryOperators[$token->getValue()])) {
299 $class = $this->unaryOperators[$token->getValue()]['class'];
300 if (!\in_array($class, [NegUnary::class, PosUnary::class])) {
301 throw new SyntaxError(\sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
302 }
303 $this->parser->getStream()->next();
304 $expr = $this->parsePrimaryExpression();
305 $node = new $class($expr, $token->getLine());
306 break;
307 }
308 // no break
309 default:
310 if ($token->test(
311 /* Token::PUNCTUATION_TYPE */
312 9,
313 '['
314 )) {
315 $node = $this->parseSequenceExpression();
316 } elseif ($token->test(
317 /* Token::PUNCTUATION_TYPE */
318 9,
319 '{'
320 )) {
321 $node = $this->parseMappingExpression();
322 } elseif ($token->test(
323 /* Token::OPERATOR_TYPE */
324 8,
325 '='
326 ) && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
327 throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
328 } else {
329 throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
330 }
331 }
332 return $this->parsePostfixExpression($node);
333 }
334 public function parseStringExpression()
335 {
336 $stream = $this->parser->getStream();
337 $nodes = [];
338 // a string cannot be followed by another string in a single expression
339 $nextCanBeString = \true;
340 while (\true) {
341 if ($nextCanBeString && ($token = $stream->nextIf(
342 /* Token::STRING_TYPE */
343 7
344 ))) {
345 $nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
346 $nextCanBeString = \false;
347 } elseif ($stream->nextIf(
348 /* Token::INTERPOLATION_START_TYPE */
349 10
350 )) {
351 $nodes[] = $this->parseExpression();
352 $stream->expect(
353 /* Token::INTERPOLATION_END_TYPE */
354 11
355 );
356 $nextCanBeString = \true;
357 } else {
358 break;
359 }
360 }
361 $expr = \array_shift($nodes);
362 foreach ($nodes as $node) {
363 $expr = new ConcatBinary($expr, $node, $node->getTemplateLine());
364 }
365 return $expr;
366 }
367 /**
368 * @deprecated since 3.11, use parseSequenceExpression() instead
369 */
370 public function parseArrayExpression()
371 {
372 trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__);
373 return $this->parseSequenceExpression();
374 }
375 public function parseSequenceExpression()
376 {
377 $stream = $this->parser->getStream();
378 $stream->expect(
379 /* Token::PUNCTUATION_TYPE */
380 9,
381 '[',
382 'A sequence element was expected'
383 );
384 $node = new ArrayExpression([], $stream->getCurrent()->getLine());
385 $first = \true;
386 while (!$stream->test(
387 /* Token::PUNCTUATION_TYPE */
388 9,
389 ']'
390 )) {
391 if (!$first) {
392 $stream->expect(
393 /* Token::PUNCTUATION_TYPE */
394 9,
395 ',',
396 'A sequence element must be followed by a comma'
397 );
398 // trailing ,?
399 if ($stream->test(
400 /* Token::PUNCTUATION_TYPE */
401 9,
402 ']'
403 )) {
404 break;
405 }
406 }
407 $first = \false;
408 if ($stream->test(
409 /* Token::SPREAD_TYPE */
410 13
411 )) {
412 $stream->next();
413 $expr = $this->parseExpression();
414 $expr->setAttribute('spread', \true);
415 $node->addElement($expr);
416 } else {
417 $node->addElement($this->parseExpression());
418 }
419 }
420 $stream->expect(
421 /* Token::PUNCTUATION_TYPE */
422 9,
423 ']',
424 'An opened sequence is not properly closed'
425 );
426 return $node;
427 }
428 /**
429 * @deprecated since 3.11, use parseMappingExpression() instead
430 */
431 public function parseHashExpression()
432 {
433 trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__);
434 return $this->parseMappingExpression();
435 }
436 public function parseMappingExpression()
437 {
438 $stream = $this->parser->getStream();
439 $stream->expect(
440 /* Token::PUNCTUATION_TYPE */
441 9,
442 '{',
443 'A mapping element was expected'
444 );
445 $node = new ArrayExpression([], $stream->getCurrent()->getLine());
446 $first = \true;
447 while (!$stream->test(
448 /* Token::PUNCTUATION_TYPE */
449 9,
450 '}'
451 )) {
452 if (!$first) {
453 $stream->expect(
454 /* Token::PUNCTUATION_TYPE */
455 9,
456 ',',
457 'A mapping value must be followed by a comma'
458 );
459 // trailing ,?
460 if ($stream->test(
461 /* Token::PUNCTUATION_TYPE */
462 9,
463 '}'
464 )) {
465 break;
466 }
467 }
468 $first = \false;
469 if ($stream->test(
470 /* Token::SPREAD_TYPE */
471 13
472 )) {
473 $stream->next();
474 $value = $this->parseExpression();
475 $value->setAttribute('spread', \true);
476 $node->addElement($value);
477 continue;
478 }
479 // a mapping key can be:
480 //
481 // * a number -- 12
482 // * a string -- 'a'
483 // * a name, which is equivalent to a string -- a
484 // * an expression, which must be enclosed in parentheses -- (1 + 2)
485 if ($token = $stream->nextIf(
486 /* Token::NAME_TYPE */
487 5
488 )) {
489 $key = new ConstantExpression($token->getValue(), $token->getLine());
490 // {a} is a shortcut for {a:a}
491 if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {
492 $value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
493 $node->addElement($value, $key);
494 continue;
495 }
496 } elseif (($token = $stream->nextIf(
497 /* Token::STRING_TYPE */
498 7
499 )) || ($token = $stream->nextIf(
500 /* Token::NUMBER_TYPE */
501 6
502 ))) {
503 $key = new ConstantExpression($token->getValue(), $token->getLine());
504 } elseif ($stream->test(
505 /* Token::PUNCTUATION_TYPE */
506 9,
507 '('
508 )) {
509 $key = $this->parseExpression();
510 } else {
511 $current = $stream->getCurrent();
512 throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
513 }
514 $stream->expect(
515 /* Token::PUNCTUATION_TYPE */
516 9,
517 ':',
518 'A mapping key must be followed by a colon (:)'
519 );
520 $value = $this->parseExpression();
521 $node->addElement($value, $key);
522 }
523 $stream->expect(
524 /* Token::PUNCTUATION_TYPE */
525 9,
526 '}',
527 'An opened mapping is not properly closed'
528 );
529 return $node;
530 }
531 public function parsePostfixExpression($node)
532 {
533 while (\true) {
534 $token = $this->parser->getCurrentToken();
535 if (9 == $token->getType()) {
536 if ('.' == $token->getValue() || '[' == $token->getValue()) {
537 $node = $this->parseSubscriptExpression($node);
538 } elseif ('|' == $token->getValue()) {
539 $node = $this->parseFilterExpression($node);
540 } else {
541 break;
542 }
543 } else {
544 break;
545 }
546 }
547 return $node;
548 }
549 public function getFunctionNode($name, $line)
550 {
551 switch ($name) {
552 case 'parent':
553 $this->parseArguments();
554 if (!\count($this->parser->getBlockStack())) {
555 throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
556 }
557 if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
558 throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
559 }
560 return new ParentExpression($this->parser->peekBlockStack(), $line);
561 case 'block':
562 $args = $this->parseArguments();
563 if (\count($args) < 1) {
564 throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
565 }
566 return new BlockReferenceExpression($args->getNode('0'), \count($args) > 1 ? $args->getNode('1') : null, $line);
567 case 'attribute':
568 $args = $this->parseArguments();
569 if (\count($args) < 2) {
570 throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
571 }
572 return new GetAttrExpression($args->getNode('0'), $args->getNode('1'), \count($args) > 2 ? $args->getNode('2') : null, Template::ANY_CALL, $line);
573 default:
574 if (null !== ($alias = $this->parser->getImportedSymbol('function', $name))) {
575 $arguments = new ArrayExpression([], $line);
576 foreach ($this->parseArguments() as $n) {
577 $arguments->addElement($n);
578 }
579 $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
580 $node->setAttribute('safe', \true);
581 return $node;
582 }
583 $args = $this->parseArguments(\true);
584 $class = $this->getFunctionNodeClass($name, $line);
585 return new $class($name, $args, $line);
586 }
587 }
588 public function parseSubscriptExpression($node)
589 {
590 $stream = $this->parser->getStream();
591 $token = $stream->next();
592 $lineno = $token->getLine();
593 $arguments = new ArrayExpression([], $lineno);
594 $type = Template::ANY_CALL;
595 if ('.' == $token->getValue()) {
596 $token = $stream->next();
597 if (5 == $token->getType() || 6 == $token->getType() || 8 == $token->getType() && \preg_match(Lexer::REGEX_NAME, $token->getValue())) {
598 $arg = new ConstantExpression($token->getValue(), $lineno);
599 if ($stream->test(
600 /* Token::PUNCTUATION_TYPE */
601 9,
602 '('
603 )) {
604 $type = Template::METHOD_CALL;
605 foreach ($this->parseArguments() as $n) {
606 $arguments->addElement($n);
607 }
608 }
609 } else {
610 throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
611 }
612 if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
613 $name = $arg->getAttribute('value');
614 $node = new MethodCallExpression($node, 'macro_' . $name, $arguments, $lineno);
615 $node->setAttribute('safe', \true);
616 return $node;
617 }
618 } else {
619 $type = Template::ARRAY_CALL;
620 // slice?
621 $slice = \false;
622 if ($stream->test(
623 /* Token::PUNCTUATION_TYPE */
624 9,
625 ':'
626 )) {
627 $slice = \true;
628 $arg = new ConstantExpression(0, $token->getLine());
629 } else {
630 $arg = $this->parseExpression();
631 }
632 if ($stream->nextIf(
633 /* Token::PUNCTUATION_TYPE */
634 9,
635 ':'
636 )) {
637 $slice = \true;
638 }
639 if ($slice) {
640 if ($stream->test(
641 /* Token::PUNCTUATION_TYPE */
642 9,
643 ']'
644 )) {
645 $length = new ConstantExpression(null, $token->getLine());
646 } else {
647 $length = $this->parseExpression();
648 }
649 $class = $this->getFilterNodeClass('slice', $token->getLine());
650 $arguments = new Node([$arg, $length]);
651 $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
652 $stream->expect(
653 /* Token::PUNCTUATION_TYPE */
654 9,
655 ']'
656 );
657 return $filter;
658 }
659 $stream->expect(
660 /* Token::PUNCTUATION_TYPE */
661 9,
662 ']'
663 );
664 }
665 return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
666 }
667 public function parseFilterExpression($node)
668 {
669 $this->parser->getStream()->next();
670 return $this->parseFilterExpressionRaw($node);
671 }
672 public function parseFilterExpressionRaw($node, $tag = null)
673 {
674 while (\true) {
675 $token = $this->parser->getStream()->expect(
676 /* Token::NAME_TYPE */
677 5
678 );
679 $name = new ConstantExpression($token->getValue(), $token->getLine());
680 if (!$this->parser->getStream()->test(
681 /* Token::PUNCTUATION_TYPE */
682 9,
683 '('
684 )) {
685 $arguments = new Node();
686 } else {
687 $arguments = $this->parseArguments(\true, \false, \true);
688 }
689 $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
690 $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
691 if (!$this->parser->getStream()->test(
692 /* Token::PUNCTUATION_TYPE */
693 9,
694 '|'
695 )) {
696 break;
697 }
698 $this->parser->getStream()->next();
699 }
700 return $node;
701 }
702 /**
703 * Parses arguments.
704 *
705 * @param bool $namedArguments Whether to allow named arguments or not
706 * @param bool $definition Whether we are parsing arguments for a function definition
707 *
708 * @return Node
709 *
710 * @throws SyntaxError
711 */
712 public function parseArguments($namedArguments = \false, $definition = \false, $allowArrow = \false)
713 {
714 $args = [];
715 $stream = $this->parser->getStream();
716 $stream->expect(
717 /* Token::PUNCTUATION_TYPE */
718 9,
719 '(',
720 'A list of arguments must begin with an opening parenthesis'
721 );
722 while (!$stream->test(
723 /* Token::PUNCTUATION_TYPE */
724 9,
725 ')'
726 )) {
727 if (!empty($args)) {
728 $stream->expect(
729 /* Token::PUNCTUATION_TYPE */
730 9,
731 ',',
732 'Arguments must be separated by a comma'
733 );
734 // if the comma above was a trailing comma, early exit the argument parse loop
735 if ($stream->test(
736 /* Token::PUNCTUATION_TYPE */
737 9,
738 ')'
739 )) {
740 break;
741 }
742 }
743 if ($definition) {
744 $token = $stream->expect(
745 /* Token::NAME_TYPE */
746 5,
747 null,
748 'An argument must be a name'
749 );
750 $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
751 } else {
752 $value = $this->parseExpression(0, $allowArrow);
753 }
754 $name = null;
755 if ($namedArguments && ($token = $stream->nextIf(
756 /* Token::OPERATOR_TYPE */
757 8,
758 '='
759 ))) {
760 if (!$value instanceof NameExpression) {
761 throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
762 }
763 $name = $value->getAttribute('name');
764 if ($definition) {
765 $value = $this->parsePrimaryExpression();
766 if (!$this->checkConstantExpression($value)) {
767 throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());
768 }
769 } else {
770 $value = $this->parseExpression(0, $allowArrow);
771 }
772 }
773 if ($definition) {
774 if (null === $name) {
775 $name = $value->getAttribute('name');
776 $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
777 }
778 $args[$name] = $value;
779 } else {
780 if (null === $name) {
781 $args[] = $value;
782 } else {
783 $args[$name] = $value;
784 }
785 }
786 }
787 $stream->expect(
788 /* Token::PUNCTUATION_TYPE */
789 9,
790 ')',
791 'A list of arguments must be closed by a parenthesis'
792 );
793 return new Node($args);
794 }
795 public function parseAssignmentExpression()
796 {
797 $stream = $this->parser->getStream();
798 $targets = [];
799 while (\true) {
800 $token = $this->parser->getCurrentToken();
801 if ($stream->test(
802 /* Token::OPERATOR_TYPE */
803 8
804 ) && \preg_match(Lexer::REGEX_NAME, $token->getValue())) {
805 // in this context, string operators are variable names
806 $this->parser->getStream()->next();
807 } else {
808 $stream->expect(
809 /* Token::NAME_TYPE */
810 5,
811 null,
812 'Only variables can be assigned to'
813 );
814 }
815 $value = $token->getValue();
816 if (\in_array(\strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
817 throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
818 }
819 $targets[] = new AssignNameExpression($value, $token->getLine());
820 if (!$stream->nextIf(
821 /* Token::PUNCTUATION_TYPE */
822 9,
823 ','
824 )) {
825 break;
826 }
827 }
828 return new Node($targets);
829 }
830 public function parseMultitargetExpression()
831 {
832 $targets = [];
833 while (\true) {
834 $targets[] = $this->parseExpression();
835 if (!$this->parser->getStream()->nextIf(
836 /* Token::PUNCTUATION_TYPE */
837 9,
838 ','
839 )) {
840 break;
841 }
842 }
843 return new Node($targets);
844 }
845 private function parseNotTestExpression(Node $node) : NotUnary
846 {
847 return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
848 }
849 private function parseTestExpression(Node $node) : TestExpression
850 {
851 $stream = $this->parser->getStream();
852 [$name, $test] = $this->getTest($node->getTemplateLine());
853 $class = $this->getTestNodeClass($test);
854 $arguments = null;
855 if ($stream->test(
856 /* Token::PUNCTUATION_TYPE */
857 9,
858 '('
859 )) {
860 $arguments = $this->parseArguments(\true);
861 } elseif ($test->hasOneMandatoryArgument()) {
862 $arguments = new Node([0 => $this->parsePrimaryExpression()]);
863 }
864 if ('defined' === $name && $node instanceof NameExpression && null !== ($alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name')))) {
865 $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
866 $node->setAttribute('safe', \true);
867 }
868 return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
869 }
870 private function getTest(int $line) : array
871 {
872 $stream = $this->parser->getStream();
873 $name = $stream->expect(
874 /* Token::NAME_TYPE */
875 5
876 )->getValue();
877 if ($test = $this->env->getTest($name)) {
878 return [$name, $test];
879 }
880 if ($stream->test(
881 /* Token::NAME_TYPE */
882 5
883 )) {
884 // try 2-words tests
885 $name = $name . ' ' . $this->parser->getCurrentToken()->getValue();
886 if ($test = $this->env->getTest($name)) {
887 $stream->next();
888 return [$name, $test];
889 }
890 }
891 $e = new SyntaxError(\sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext());
892 $e->addSuggestions($name, \array_keys($this->env->getTests()));
893 throw $e;
894 }
895 private function getTestNodeClass(TwigTest $test) : string
896 {
897 if ($test->isDeprecated()) {
898 $stream = $this->parser->getStream();
899 $message = \sprintf('Twig Test "%s" is deprecated', $test->getName());
900 if ($test->getAlternative()) {
901 $message .= \sprintf('. Use "%s" instead', $test->getAlternative());
902 }
903 $src = $stream->getSourceContext();
904 $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
905 trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message);
906 }
907 return $test->getNodeClass();
908 }
909 private function getFunctionNodeClass(string $name, int $line) : string
910 {
911 if (!($function = $this->env->getFunction($name))) {
912 $e = new SyntaxError(\sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
913 $e->addSuggestions($name, \array_keys($this->env->getFunctions()));
914 throw $e;
915 }
916 if ($function->isDeprecated()) {
917 $message = \sprintf('Twig Function "%s" is deprecated', $function->getName());
918 if ($function->getAlternative()) {
919 $message .= \sprintf('. Use "%s" instead', $function->getAlternative());
920 }
921 $src = $this->parser->getStream()->getSourceContext();
922 $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
923 trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message);
924 }
925 return $function->getNodeClass();
926 }
927 private function getFilterNodeClass(string $name, int $line) : string
928 {
929 if (!($filter = $this->env->getFilter($name))) {
930 $e = new SyntaxError(\sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
931 $e->addSuggestions($name, \array_keys($this->env->getFilters()));
932 throw $e;
933 }
934 if ($filter->isDeprecated()) {
935 $message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName());
936 if ($filter->getAlternative()) {
937 $message .= \sprintf('. Use "%s" instead', $filter->getAlternative());
938 }
939 $src = $this->parser->getStream()->getSourceContext();
940 $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
941 trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message);
942 }
943 return $filter->getNodeClass();
944 }
945 // checks that the node only contains "constant" elements
946 private function checkConstantExpression(Node $node) : bool
947 {
948 if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression || $node instanceof NegUnary || $node instanceof PosUnary)) {
949 return \false;
950 }
951 foreach ($node as $n) {
952 if (!$this->checkConstantExpression($n)) {
953 return \false;
954 }
955 }
956 return \true;
957 }
958 }
959