Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

ExpressionParser.php

Zuletzt modifiziert: 02.04.2025, 15:03 - Dateigröße: 32.50 KiB


001  <?php
002   
003  /*
004   * This file is part of Twig.
005   *
006   * (c) Fabien Potencier
007   * (c) Armin Ronacher
008   *
009   * For the full copyright and license information, please view the LICENSE
010   * file that was distributed with this source code.
011   */
012   
013  namespace Twig;
014   
015  use Twig\Error\SyntaxError;
016  use Twig\Node\Expression\AbstractExpression;
017  use Twig\Node\Expression\ArrayExpression;
018  use Twig\Node\Expression\ArrowFunctionExpression;
019  use Twig\Node\Expression\AssignNameExpression;
020  use Twig\Node\Expression\Binary\ConcatBinary;
021  use Twig\Node\Expression\BlockReferenceExpression;
022  use Twig\Node\Expression\ConditionalExpression;
023  use Twig\Node\Expression\ConstantExpression;
024  use Twig\Node\Expression\GetAttrExpression;
025  use Twig\Node\Expression\MethodCallExpression;
026  use Twig\Node\Expression\NameExpression;
027  use Twig\Node\Expression\ParentExpression;
028  use Twig\Node\Expression\TestExpression;
029  use Twig\Node\Expression\Unary\NegUnary;
030  use Twig\Node\Expression\Unary\NotUnary;
031  use Twig\Node\Expression\Unary\PosUnary;
032  use Twig\Node\Node;
033   
034  /**
035   * Parses expressions.
036   *
037   * This parser implements a "Precedence climbing" algorithm.
038   *
039   * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
040   * @see https://en.wikipedia.org/wiki/Operator-precedence_parser
041   *
042   * @author Fabien Potencier <fabien@symfony.com>
043   *
044   * @internal
045   */
046  class ExpressionParser
047  {
048      public const OPERATOR_LEFT = 1;
049      public const OPERATOR_RIGHT = 2;
050   
051      private $parser;
052      private $env;
053      private $unaryOperators;
054      private $binaryOperators;
055   
056      public function __construct(Parser $parser, Environment $env)
057      {
058          $this->parser = $parser;
059          $this->env = $env;
060          $this->unaryOperators = $env->getUnaryOperators();
061          $this->binaryOperators = $env->getBinaryOperators();
062      }
063   
064      public function parseExpression($precedence = 0, $allowArrow = false)
065      {
066          if ($allowArrow && $arrow = $this->parseArrow()) {
067              return $arrow;
068          }
069   
070          $expr = $this->getPrimary();
071          $token = $this->parser->getCurrentToken();
072          while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
073              $op = $this->binaryOperators[$token->getValue()];
074              $this->parser->getStream()->next();
075   
076              if ('is not' === $token->getValue()) {
077                  $expr = $this->parseNotTestExpression($expr);
078              } elseif ('is' === $token->getValue()) {
079                  $expr = $this->parseTestExpression($expr);
080              } elseif (isset($op['callable'])) {
081                  $expr = $op['callable']($this->parser, $expr);
082              } else {
083                  $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
084                  $class = $op['class'];
085                  $expr = new $class($expr, $expr1, $token->getLine());
086              }
087   
088              $token = $this->parser->getCurrentToken();
089          }
090   
091          if (0 === $precedence) {
092              return $this->parseConditionalExpression($expr);
093          }
094   
095          return $expr;
096      }
097   
098      /**
099       * @return ArrowFunctionExpression|null
100       */
101      private function parseArrow()
102      {
103          $stream = $this->parser->getStream();
104   
105          // short array syntax (one argument, no parentheses)?
106          if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) {
107              $line = $stream->getCurrent()->getLine();
108              $token = $stream->expect(/* Token::NAME_TYPE */ 5);
109              $names = [new AssignNameExpression($token->getValue(), $token->getLine())];
110              $stream->expect(/* Token::ARROW_TYPE */ 12);
111   
112              return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
113          }
114   
115          // first, determine if we are parsing an arrow function by finding => (long form)
116          $i = 0;
117          if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
118              return null;
119          }
120          ++$i;
121          while (true) {
122              // variable name
123              ++$i;
124              if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
125                  break;
126              }
127              ++$i;
128          }
129          if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
130              return null;
131          }
132          ++$i;
133          if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) {
134              return null;
135          }
136   
137          // yes, let's parse it properly
138          $token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(');
139          $line = $token->getLine();
140   
141          $names = [];
142          while (true) {
143              $token = $stream->expect(/* Token::NAME_TYPE */ 5);
144              $names[] = new AssignNameExpression($token->getValue(), $token->getLine());
145   
146              if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
147                  break;
148              }
149          }
150          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')');
151          $stream->expect(/* Token::ARROW_TYPE */ 12);
152   
153          return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
154      }
155   
156      private function getPrimary(): AbstractExpression
157      {
158          $token = $this->parser->getCurrentToken();
159   
160          if ($this->isUnary($token)) {
161              $operator = $this->unaryOperators[$token->getValue()];
162              $this->parser->getStream()->next();
163              $expr = $this->parseExpression($operator['precedence']);
164              $class = $operator['class'];
165   
166              return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
167          } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
168              $this->parser->getStream()->next();
169              $expr = $this->parseExpression();
170              $this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed');
171   
172              return $this->parsePostfixExpression($expr);
173          }
174   
175          return $this->parsePrimaryExpression();
176      }
177   
178      private function parseConditionalExpression($expr): AbstractExpression
179      {
180          while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) {
181              if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
182                  $expr2 = $this->parseExpression();
183                  if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
184                      $expr3 = $this->parseExpression();
185                  } else {
186                      $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
187                  }
188              } else {
189                  $expr2 = $expr;
190                  $expr3 = $this->parseExpression();
191              }
192   
193              $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
194          }
195   
196          return $expr;
197      }
198   
199      private function isUnary(Token $token): bool
200      {
201          return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]);
202      }
203   
204      private function isBinary(Token $token): bool
205      {
206          return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]);
207      }
208   
209      public function parsePrimaryExpression()
210      {
211          $token = $this->parser->getCurrentToken();
212          switch ($token->getType()) {
213              case /* Token::NAME_TYPE */ 5:
214                  $this->parser->getStream()->next();
215                  switch ($token->getValue()) {
216                      case 'true':
217                      case 'TRUE':
218                          $node = new ConstantExpression(true, $token->getLine());
219                          break;
220   
221                      case 'false':
222                      case 'FALSE':
223                          $node = new ConstantExpression(false, $token->getLine());
224                          break;
225   
226                      case 'none':
227                      case 'NONE':
228                      case 'null':
229                      case 'NULL':
230                          $node = new ConstantExpression(null, $token->getLine());
231                          break;
232   
233                      default:
234                          if ('(' === $this->parser->getCurrentToken()->getValue()) {
235                              $node = $this->getFunctionNode($token->getValue(), $token->getLine());
236                          } else {
237                              $node = new NameExpression($token->getValue(), $token->getLine());
238                          }
239                  }
240                  break;
241   
242              case /* Token::NUMBER_TYPE */ 6:
243                  $this->parser->getStream()->next();
244                  $node = new ConstantExpression($token->getValue(), $token->getLine());
245                  break;
246   
247              case /* Token::STRING_TYPE */ 7:
248              case /* Token::INTERPOLATION_START_TYPE */ 10:
249                  $node = $this->parseStringExpression();
250                  break;
251   
252              case /* Token::OPERATOR_TYPE */ 8:
253                  if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
254                      // in this context, string operators are variable names
255                      $this->parser->getStream()->next();
256                      $node = new NameExpression($token->getValue(), $token->getLine());
257                      break;
258                  } elseif (isset($this->unaryOperators[$token->getValue()])) {
259                      $class = $this->unaryOperators[$token->getValue()]['class'];
260   
261                      $ref = new \ReflectionClass($class);
262                      if (!(\in_array($ref->getName(), [NegUnary::class, PosUnary::class, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
263                          || $ref->isSubclassOf(NegUnary::class) || $ref->isSubclassOf(PosUnary::class)
264                          || $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos'))
265                      ) {
266                          throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
267                      }
268   
269                      $this->parser->getStream()->next();
270                      $expr = $this->parsePrimaryExpression();
271   
272                      $node = new $class($expr, $token->getLine());
273                      break;
274                  }
275   
276                  // no break
277              default:
278                  if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
279                      $node = $this->parseArrayExpression();
280                  } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
281                      $node = $this->parseHashExpression();
282                  } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
283                      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());
284                  } else {
285                      throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
286                  }
287          }
288   
289          return $this->parsePostfixExpression($node);
290      }
291   
292      public function parseStringExpression()
293      {
294          $stream = $this->parser->getStream();
295   
296          $nodes = [];
297          // a string cannot be followed by another string in a single expression
298          $nextCanBeString = true;
299          while (true) {
300              if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) {
301                  $nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
302                  $nextCanBeString = false;
303              } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
304                  $nodes[] = $this->parseExpression();
305                  $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11);
306                  $nextCanBeString = true;
307              } else {
308                  break;
309              }
310          }
311   
312          $expr = array_shift($nodes);
313          foreach ($nodes as $node) {
314              $expr = new ConcatBinary($expr, $node, $node->getTemplateLine());
315          }
316   
317          return $expr;
318      }
319   
320      public function parseArrayExpression()
321      {
322          $stream = $this->parser->getStream();
323          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected');
324   
325          $node = new ArrayExpression([], $stream->getCurrent()->getLine());
326          $first = true;
327          while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
328              if (!$first) {
329                  $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma');
330   
331                  // trailing ,?
332                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
333                      break;
334                  }
335              }
336              $first = false;
337   
338              $node->addElement($this->parseExpression());
339          }
340          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed');
341   
342          return $node;
343      }
344   
345      public function parseHashExpression()
346      {
347          $stream = $this->parser->getStream();
348          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected');
349   
350          $node = new ArrayExpression([], $stream->getCurrent()->getLine());
351          $first = true;
352          while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
353              if (!$first) {
354                  $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma');
355   
356                  // trailing ,?
357                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
358                      break;
359                  }
360              }
361              $first = false;
362   
363              // a hash key can be:
364              //
365              //  * a number -- 12
366              //  * a string -- 'a'
367              //  * a name, which is equivalent to a string -- a
368              //  * an expression, which must be enclosed in parentheses -- (1 + 2)
369              if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
370                  $key = new ConstantExpression($token->getValue(), $token->getLine());
371   
372                  // {a} is a shortcut for {a:a}
373                  if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {
374                      $value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
375                      $node->addElement($value, $key);
376                      continue;
377                  }
378              } elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
379                  $key = new ConstantExpression($token->getValue(), $token->getLine());
380              } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
381                  $key = $this->parseExpression();
382              } else {
383                  $current = $stream->getCurrent();
384   
385                  throw new SyntaxError(sprintf('A hash 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());
386              }
387   
388              $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
389              $value = $this->parseExpression();
390   
391              $node->addElement($value, $key);
392          }
393          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
394   
395          return $node;
396      }
397   
398      public function parsePostfixExpression($node)
399      {
400          while (true) {
401              $token = $this->parser->getCurrentToken();
402              if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) {
403                  if ('.' == $token->getValue() || '[' == $token->getValue()) {
404                      $node = $this->parseSubscriptExpression($node);
405                  } elseif ('|' == $token->getValue()) {
406                      $node = $this->parseFilterExpression($node);
407                  } else {
408                      break;
409                  }
410              } else {
411                  break;
412              }
413          }
414   
415          return $node;
416      }
417   
418      public function getFunctionNode($name, $line)
419      {
420          switch ($name) {
421              case 'parent':
422                  $this->parseArguments();
423                  if (!\count($this->parser->getBlockStack())) {
424                      throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
425                  }
426   
427                  if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
428                      throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
429                  }
430   
431                  return new ParentExpression($this->parser->peekBlockStack(), $line);
432              case 'block':
433                  $args = $this->parseArguments();
434                  if (\count($args) < 1) {
435                      throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
436                  }
437   
438                  return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line);
439              case 'attribute':
440                  $args = $this->parseArguments();
441                  if (\count($args) < 2) {
442                      throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
443                  }
444   
445                  return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line);
446              default:
447                  if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
448                      $arguments = new ArrayExpression([], $line);
449                      foreach ($this->parseArguments() as $n) {
450                          $arguments->addElement($n);
451                      }
452   
453                      $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
454                      $node->setAttribute('safe', true);
455   
456                      return $node;
457                  }
458   
459                  $args = $this->parseArguments(true);
460                  $class = $this->getFunctionNodeClass($name, $line);
461   
462                  return new $class($name, $args, $line);
463          }
464      }
465   
466      public function parseSubscriptExpression($node)
467      {
468          $stream = $this->parser->getStream();
469          $token = $stream->next();
470          $lineno = $token->getLine();
471          $arguments = new ArrayExpression([], $lineno);
472          $type = Template::ANY_CALL;
473          if ('.' == $token->getValue()) {
474              $token = $stream->next();
475              if (
476                  /* Token::NAME_TYPE */ 5 == $token->getType()
477                  ||
478                  /* Token::NUMBER_TYPE */ 6 == $token->getType()
479                  ||
480                  (/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
481              ) {
482                  $arg = new ConstantExpression($token->getValue(), $lineno);
483   
484                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
485                      $type = Template::METHOD_CALL;
486                      foreach ($this->parseArguments() as $n) {
487                          $arguments->addElement($n);
488                      }
489                  }
490              } else {
491                  throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
492              }
493   
494              if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
495                  if (!$arg instanceof ConstantExpression) {
496                      throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
497                  }
498   
499                  $name = $arg->getAttribute('value');
500   
501                  $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
502                  $node->setAttribute('safe', true);
503   
504                  return $node;
505              }
506          } else {
507              $type = Template::ARRAY_CALL;
508   
509              // slice?
510              $slice = false;
511              if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
512                  $slice = true;
513                  $arg = new ConstantExpression(0, $token->getLine());
514              } else {
515                  $arg = $this->parseExpression();
516              }
517   
518              if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
519                  $slice = true;
520              }
521   
522              if ($slice) {
523                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
524                      $length = new ConstantExpression(null, $token->getLine());
525                  } else {
526                      $length = $this->parseExpression();
527                  }
528   
529                  $class = $this->getFilterNodeClass('slice', $token->getLine());
530                  $arguments = new Node([$arg, $length]);
531                  $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
532   
533                  $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
534   
535                  return $filter;
536              }
537   
538              $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
539          }
540   
541          return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
542      }
543   
544      public function parseFilterExpression($node)
545      {
546          $this->parser->getStream()->next();
547   
548          return $this->parseFilterExpressionRaw($node);
549      }
550   
551      public function parseFilterExpressionRaw($node, $tag = null)
552      {
553          while (true) {
554              $token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
555   
556              $name = new ConstantExpression($token->getValue(), $token->getLine());
557              if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
558                  $arguments = new Node();
559              } else {
560                  $arguments = $this->parseArguments(true, false, true);
561              }
562   
563              $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
564   
565              $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
566   
567              if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) {
568                  break;
569              }
570   
571              $this->parser->getStream()->next();
572          }
573   
574          return $node;
575      }
576   
577      /**
578       * Parses arguments.
579       *
580       * @param bool $namedArguments Whether to allow named arguments or not
581       * @param bool $definition     Whether we are parsing arguments for a function definition
582       *
583       * @return Node
584       *
585       * @throws SyntaxError
586       */
587      public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false)
588      {
589          $args = [];
590          $stream = $this->parser->getStream();
591   
592          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis');
593          while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
594              if (!empty($args)) {
595                  $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma');
596   
597                  // if the comma above was a trailing comma, early exit the argument parse loop
598                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
599                      break;
600                  }
601              }
602   
603              if ($definition) {
604                  $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name');
605                  $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
606              } else {
607                  $value = $this->parseExpression(0, $allowArrow);
608              }
609   
610              $name = null;
611              if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) {
612                  if (!$value instanceof NameExpression) {
613                      throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
614                  }
615                  $name = $value->getAttribute('name');
616   
617                  if ($definition) {
618                      $value = $this->parsePrimaryExpression();
619   
620                      if (!$this->checkConstantExpression($value)) {
621                          throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext());
622                      }
623                  } else {
624                      $value = $this->parseExpression(0, $allowArrow);
625                  }
626              }
627   
628              if ($definition) {
629                  if (null === $name) {
630                      $name = $value->getAttribute('name');
631                      $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
632                  }
633                  $args[$name] = $value;
634              } else {
635                  if (null === $name) {
636                      $args[] = $value;
637                  } else {
638                      $args[$name] = $value;
639                  }
640              }
641          }
642          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis');
643   
644          return new Node($args);
645      }
646   
647      public function parseAssignmentExpression()
648      {
649          $stream = $this->parser->getStream();
650          $targets = [];
651          while (true) {
652              $token = $this->parser->getCurrentToken();
653              if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
654                  // in this context, string operators are variable names
655                  $this->parser->getStream()->next();
656              } else {
657                  $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to');
658              }
659              $value = $token->getValue();
660              if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
661                  throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
662              }
663              $targets[] = new AssignNameExpression($value, $token->getLine());
664   
665              if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
666                  break;
667              }
668          }
669   
670          return new Node($targets);
671      }
672   
673      public function parseMultitargetExpression()
674      {
675          $targets = [];
676          while (true) {
677              $targets[] = $this->parseExpression();
678              if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
679                  break;
680              }
681          }
682   
683          return new Node($targets);
684      }
685   
686      private function parseNotTestExpression(Node $node): NotUnary
687      {
688          return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
689      }
690   
691      private function parseTestExpression(Node $node): TestExpression
692      {
693          $stream = $this->parser->getStream();
694          list($name, $test) = $this->getTest($node->getTemplateLine());
695   
696          $class = $this->getTestNodeClass($test);
697          $arguments = null;
698          if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
699              $arguments = $this->parseArguments(true);
700          } elseif ($test->hasOneMandatoryArgument()) {
701              $arguments = new Node([0 => $this->parsePrimaryExpression()]);
702          }
703   
704          if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
705              $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
706              $node->setAttribute('safe', true);
707          }
708   
709          return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
710      }
711   
712      private function getTest(int $line): array
713      {
714          $stream = $this->parser->getStream();
715          $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
716   
717          if ($test = $this->env->getTest($name)) {
718              return [$name, $test];
719          }
720   
721          if ($stream->test(/* Token::NAME_TYPE */ 5)) {
722              // try 2-words tests
723              $name = $name.' '.$this->parser->getCurrentToken()->getValue();
724   
725              if ($test = $this->env->getTest($name)) {
726                  $stream->next();
727   
728                  return [$name, $test];
729              }
730          }
731   
732          $e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext());
733          $e->addSuggestions($name, array_keys($this->env->getTests()));
734   
735          throw $e;
736      }
737   
738      private function getTestNodeClass(TwigTest $test): string
739      {
740          if ($test->isDeprecated()) {
741              $stream = $this->parser->getStream();
742              $message = sprintf('Twig Test "%s" is deprecated', $test->getName());
743   
744              if (!\is_bool($test->getDeprecatedVersion())) {
745                  $message .= sprintf(' since version %s', $test->getDeprecatedVersion());
746              }
747              if ($test->getAlternative()) {
748                  $message .= sprintf('. Use "%s" instead', $test->getAlternative());
749              }
750              $src = $stream->getSourceContext();
751              $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
752   
753              @trigger_error($message, \E_USER_DEPRECATED);
754          }
755   
756          return $test->getNodeClass();
757      }
758   
759      private function getFunctionNodeClass(string $name, int $line): string
760      {
761          if (false === $function = $this->env->getFunction($name)) {
762              $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
763              $e->addSuggestions($name, array_keys($this->env->getFunctions()));
764   
765              throw $e;
766          }
767   
768          if ($function->isDeprecated()) {
769              $message = sprintf('Twig Function "%s" is deprecated', $function->getName());
770              if (!\is_bool($function->getDeprecatedVersion())) {
771                  $message .= sprintf(' since version %s', $function->getDeprecatedVersion());
772              }
773              if ($function->getAlternative()) {
774                  $message .= sprintf('. Use "%s" instead', $function->getAlternative());
775              }
776              $src = $this->parser->getStream()->getSourceContext();
777              $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
778   
779              @trigger_error($message, \E_USER_DEPRECATED);
780          }
781   
782          return $function->getNodeClass();
783      }
784   
785      private function getFilterNodeClass(string $name, int $line): string
786      {
787          if (false === $filter = $this->env->getFilter($name)) {
788              $e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
789              $e->addSuggestions($name, array_keys($this->env->getFilters()));
790   
791              throw $e;
792          }
793   
794          if ($filter->isDeprecated()) {
795              $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
796              if (!\is_bool($filter->getDeprecatedVersion())) {
797                  $message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
798              }
799              if ($filter->getAlternative()) {
800                  $message .= sprintf('. Use "%s" instead', $filter->getAlternative());
801              }
802              $src = $this->parser->getStream()->getSourceContext();
803              $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
804   
805              @trigger_error($message, \E_USER_DEPRECATED);
806          }
807   
808          return $filter->getNodeClass();
809      }
810   
811      // checks that the node only contains "constant" elements
812      private function checkConstantExpression(Node $node): bool
813      {
814          if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
815              || $node instanceof NegUnary || $node instanceof PosUnary
816          )) {
817              return false;
818          }
819   
820          foreach ($node as $n) {
821              if (!$this->checkConstantExpression($n)) {
822                  return false;
823              }
824          }
825   
826          return true;
827      }
828  }
829   
830  class_alias('Twig\ExpressionParser', 'Twig_ExpressionParser');
831