[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/twig/twig/src/ -> ExpressionParser.php (source)

   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  
  13  namespace Twig;
  14  
  15  use Twig\Error\SyntaxError;
  16  use Twig\Node\Expression\AbstractExpression;
  17  use Twig\Node\Expression\ArrayExpression;
  18  use Twig\Node\Expression\ArrowFunctionExpression;
  19  use Twig\Node\Expression\AssignNameExpression;
  20  use Twig\Node\Expression\Binary\ConcatBinary;
  21  use Twig\Node\Expression\BlockReferenceExpression;
  22  use Twig\Node\Expression\ConditionalExpression;
  23  use Twig\Node\Expression\ConstantExpression;
  24  use Twig\Node\Expression\GetAttrExpression;
  25  use Twig\Node\Expression\MethodCallExpression;
  26  use Twig\Node\Expression\NameExpression;
  27  use Twig\Node\Expression\ParentExpression;
  28  use Twig\Node\Expression\TestExpression;
  29  use Twig\Node\Expression\Unary\NegUnary;
  30  use Twig\Node\Expression\Unary\NotUnary;
  31  use Twig\Node\Expression\Unary\PosUnary;
  32  use Twig\Node\Node;
  33  
  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   * @internal
  45   */
  46  class ExpressionParser
  47  {
  48      public const OPERATOR_LEFT = 1;
  49      public const OPERATOR_RIGHT = 2;
  50  
  51      private $parser;
  52      private $env;
  53      private $unaryOperators;
  54      private $binaryOperators;
  55  
  56      public function __construct(Parser $parser, Environment $env)
  57      {
  58          $this->parser = $parser;
  59          $this->env = $env;
  60          $this->unaryOperators = $env->getUnaryOperators();
  61          $this->binaryOperators = $env->getBinaryOperators();
  62      }
  63  
  64      public function parseExpression($precedence = 0, $allowArrow = false)
  65      {
  66          if ($allowArrow && $arrow = $this->parseArrow()) {
  67              return $arrow;
  68          }
  69  
  70          $expr = $this->getPrimary();
  71          $token = $this->parser->getCurrentToken();
  72          while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
  73              $op = $this->binaryOperators[$token->getValue()];
  74              $this->parser->getStream()->next();
  75  
  76              if ('is not' === $token->getValue()) {
  77                  $expr = $this->parseNotTestExpression($expr);
  78              } elseif ('is' === $token->getValue()) {
  79                  $expr = $this->parseTestExpression($expr);
  80              } elseif (isset($op['callable'])) {
  81                  $expr = $op['callable']($this->parser, $expr);
  82              } else {
  83                  $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
  84                  $class = $op['class'];
  85                  $expr = new $class($expr, $expr1, $token->getLine());
  86              }
  87  
  88              $token = $this->parser->getCurrentToken();
  89          }
  90  
  91          if (0 === $precedence) {
  92              return $this->parseConditionalExpression($expr);
  93          }
  94  
  95          return $expr;
  96      }
  97  
  98      /**
  99       * @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');


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1