[ Index ]

PHP Cross Reference of phpBB-3.3.2-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      const OPERATOR_LEFT = 1;
  49      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::STRING_TYPE */ 7)) || ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
 370                  $key = new ConstantExpression($token->getValue(), $token->getLine());
 371              } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
 372                  $key = $this->parseExpression();
 373              } else {
 374                  $current = $stream->getCurrent();
 375  
 376                  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());
 377              }
 378  
 379              $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
 380              $value = $this->parseExpression();
 381  
 382              $node->addElement($value, $key);
 383          }
 384          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
 385  
 386          return $node;
 387      }
 388  
 389      public function parsePostfixExpression($node)
 390      {
 391          while (true) {
 392              $token = $this->parser->getCurrentToken();
 393              if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) {
 394                  if ('.' == $token->getValue() || '[' == $token->getValue()) {
 395                      $node = $this->parseSubscriptExpression($node);
 396                  } elseif ('|' == $token->getValue()) {
 397                      $node = $this->parseFilterExpression($node);
 398                  } else {
 399                      break;
 400                  }
 401              } else {
 402                  break;
 403              }
 404          }
 405  
 406          return $node;
 407      }
 408  
 409      public function getFunctionNode($name, $line)
 410      {
 411          switch ($name) {
 412              case 'parent':
 413                  $this->parseArguments();
 414                  if (!\count($this->parser->getBlockStack())) {
 415                      throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
 416                  }
 417  
 418                  if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
 419                      throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
 420                  }
 421  
 422                  return new ParentExpression($this->parser->peekBlockStack(), $line);
 423              case 'block':
 424                  $args = $this->parseArguments();
 425                  if (\count($args) < 1) {
 426                      throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
 427                  }
 428  
 429                  return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line);
 430              case 'attribute':
 431                  $args = $this->parseArguments();
 432                  if (\count($args) < 2) {
 433                      throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
 434                  }
 435  
 436                  return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line);
 437              default:
 438                  if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
 439                      $arguments = new ArrayExpression([], $line);
 440                      foreach ($this->parseArguments() as $n) {
 441                          $arguments->addElement($n);
 442                      }
 443  
 444                      $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
 445                      $node->setAttribute('safe', true);
 446  
 447                      return $node;
 448                  }
 449  
 450                  $args = $this->parseArguments(true);
 451                  $class = $this->getFunctionNodeClass($name, $line);
 452  
 453                  return new $class($name, $args, $line);
 454          }
 455      }
 456  
 457      public function parseSubscriptExpression($node)
 458      {
 459          $stream = $this->parser->getStream();
 460          $token = $stream->next();
 461          $lineno = $token->getLine();
 462          $arguments = new ArrayExpression([], $lineno);
 463          $type = Template::ANY_CALL;
 464          if ('.' == $token->getValue()) {
 465              $token = $stream->next();
 466              if (
 467                  /* Token::NAME_TYPE */ 5 == $token->getType()
 468                  ||
 469                  /* Token::NUMBER_TYPE */ 6 == $token->getType()
 470                  ||
 471                  (/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
 472              ) {
 473                  $arg = new ConstantExpression($token->getValue(), $lineno);
 474  
 475                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
 476                      $type = Template::METHOD_CALL;
 477                      foreach ($this->parseArguments() as $n) {
 478                          $arguments->addElement($n);
 479                      }
 480                  }
 481              } else {
 482                  throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext());
 483              }
 484  
 485              if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
 486                  if (!$arg instanceof ConstantExpression) {
 487                      throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
 488                  }
 489  
 490                  $name = $arg->getAttribute('value');
 491  
 492                  $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
 493                  $node->setAttribute('safe', true);
 494  
 495                  return $node;
 496              }
 497          } else {
 498              $type = Template::ARRAY_CALL;
 499  
 500              // slice?
 501              $slice = false;
 502              if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
 503                  $slice = true;
 504                  $arg = new ConstantExpression(0, $token->getLine());
 505              } else {
 506                  $arg = $this->parseExpression();
 507              }
 508  
 509              if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
 510                  $slice = true;
 511              }
 512  
 513              if ($slice) {
 514                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
 515                      $length = new ConstantExpression(null, $token->getLine());
 516                  } else {
 517                      $length = $this->parseExpression();
 518                  }
 519  
 520                  $class = $this->getFilterNodeClass('slice', $token->getLine());
 521                  $arguments = new Node([$arg, $length]);
 522                  $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
 523  
 524                  $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
 525  
 526                  return $filter;
 527              }
 528  
 529              $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
 530          }
 531  
 532          return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
 533      }
 534  
 535      public function parseFilterExpression($node)
 536      {
 537          $this->parser->getStream()->next();
 538  
 539          return $this->parseFilterExpressionRaw($node);
 540      }
 541  
 542      public function parseFilterExpressionRaw($node, $tag = null)
 543      {
 544          while (true) {
 545              $token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
 546  
 547              $name = new ConstantExpression($token->getValue(), $token->getLine());
 548              if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
 549                  $arguments = new Node();
 550              } else {
 551                  $arguments = $this->parseArguments(true, false, true);
 552              }
 553  
 554              $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
 555  
 556              $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
 557  
 558              if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) {
 559                  break;
 560              }
 561  
 562              $this->parser->getStream()->next();
 563          }
 564  
 565          return $node;
 566      }
 567  
 568      /**
 569       * Parses arguments.
 570       *
 571       * @param bool $namedArguments Whether to allow named arguments or not
 572       * @param bool $definition     Whether we are parsing arguments for a function definition
 573       *
 574       * @return Node
 575       *
 576       * @throws SyntaxError
 577       */
 578      public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false)
 579      {
 580          $args = [];
 581          $stream = $this->parser->getStream();
 582  
 583          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis');
 584          while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
 585              if (!empty($args)) {
 586                  $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma');
 587  
 588                  // if the comma above was a trailing comma, early exit the argument parse loop
 589                  if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
 590                      break;
 591                  }
 592              }
 593  
 594              if ($definition) {
 595                  $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name');
 596                  $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
 597              } else {
 598                  $value = $this->parseExpression(0, $allowArrow);
 599              }
 600  
 601              $name = null;
 602              if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) {
 603                  if (!$value instanceof NameExpression) {
 604                      throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
 605                  }
 606                  $name = $value->getAttribute('name');
 607  
 608                  if ($definition) {
 609                      $value = $this->parsePrimaryExpression();
 610  
 611                      if (!$this->checkConstantExpression($value)) {
 612                          throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext());
 613                      }
 614                  } else {
 615                      $value = $this->parseExpression(0, $allowArrow);
 616                  }
 617              }
 618  
 619              if ($definition) {
 620                  if (null === $name) {
 621                      $name = $value->getAttribute('name');
 622                      $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
 623                  }
 624                  $args[$name] = $value;
 625              } else {
 626                  if (null === $name) {
 627                      $args[] = $value;
 628                  } else {
 629                      $args[$name] = $value;
 630                  }
 631              }
 632          }
 633          $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis');
 634  
 635          return new Node($args);
 636      }
 637  
 638      public function parseAssignmentExpression()
 639      {
 640          $stream = $this->parser->getStream();
 641          $targets = [];
 642          while (true) {
 643              $token = $this->parser->getCurrentToken();
 644              if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
 645                  // in this context, string operators are variable names
 646                  $this->parser->getStream()->next();
 647              } else {
 648                  $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to');
 649              }
 650              $value = $token->getValue();
 651              if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
 652                  throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
 653              }
 654              $targets[] = new AssignNameExpression($value, $token->getLine());
 655  
 656              if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
 657                  break;
 658              }
 659          }
 660  
 661          return new Node($targets);
 662      }
 663  
 664      public function parseMultitargetExpression()
 665      {
 666          $targets = [];
 667          while (true) {
 668              $targets[] = $this->parseExpression();
 669              if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
 670                  break;
 671              }
 672          }
 673  
 674          return new Node($targets);
 675      }
 676  
 677      private function parseNotTestExpression(Node $node): NotUnary
 678      {
 679          return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
 680      }
 681  
 682      private function parseTestExpression(Node $node): TestExpression
 683      {
 684          $stream = $this->parser->getStream();
 685          list($name, $test) = $this->getTest($node->getTemplateLine());
 686  
 687          $class = $this->getTestNodeClass($test);
 688          $arguments = null;
 689          if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
 690              $arguments = $this->parseArguments(true);
 691          }
 692  
 693          if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
 694              $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
 695              $node->setAttribute('safe', true);
 696          }
 697  
 698          return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
 699      }
 700  
 701      private function getTest(int $line): array
 702      {
 703          $stream = $this->parser->getStream();
 704          $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
 705  
 706          if ($test = $this->env->getTest($name)) {
 707              return [$name, $test];
 708          }
 709  
 710          if ($stream->test(/* Token::NAME_TYPE */ 5)) {
 711              // try 2-words tests
 712              $name = $name.' '.$this->parser->getCurrentToken()->getValue();
 713  
 714              if ($test = $this->env->getTest($name)) {
 715                  $stream->next();
 716  
 717                  return [$name, $test];
 718              }
 719          }
 720  
 721          $e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext());
 722          $e->addSuggestions($name, array_keys($this->env->getTests()));
 723  
 724          throw $e;
 725      }
 726  
 727      private function getTestNodeClass(TwigTest $test): string
 728      {
 729          if ($test->isDeprecated()) {
 730              $stream = $this->parser->getStream();
 731              $message = sprintf('Twig Test "%s" is deprecated', $test->getName());
 732  
 733              if (!\is_bool($test->getDeprecatedVersion())) {
 734                  $message .= sprintf(' since version %s', $test->getDeprecatedVersion());
 735              }
 736              if ($test->getAlternative()) {
 737                  $message .= sprintf('. Use "%s" instead', $test->getAlternative());
 738              }
 739              $src = $stream->getSourceContext();
 740              $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
 741  
 742              @trigger_error($message, E_USER_DEPRECATED);
 743          }
 744  
 745          return $test->getNodeClass();
 746      }
 747  
 748      private function getFunctionNodeClass(string $name, int $line): string
 749      {
 750          if (false === $function = $this->env->getFunction($name)) {
 751              $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
 752              $e->addSuggestions($name, array_keys($this->env->getFunctions()));
 753  
 754              throw $e;
 755          }
 756  
 757          if ($function->isDeprecated()) {
 758              $message = sprintf('Twig Function "%s" is deprecated', $function->getName());
 759              if (!\is_bool($function->getDeprecatedVersion())) {
 760                  $message .= sprintf(' since version %s', $function->getDeprecatedVersion());
 761              }
 762              if ($function->getAlternative()) {
 763                  $message .= sprintf('. Use "%s" instead', $function->getAlternative());
 764              }
 765              $src = $this->parser->getStream()->getSourceContext();
 766              $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
 767  
 768              @trigger_error($message, E_USER_DEPRECATED);
 769          }
 770  
 771          return $function->getNodeClass();
 772      }
 773  
 774      private function getFilterNodeClass(string $name, int $line): string
 775      {
 776          if (false === $filter = $this->env->getFilter($name)) {
 777              $e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
 778              $e->addSuggestions($name, array_keys($this->env->getFilters()));
 779  
 780              throw $e;
 781          }
 782  
 783          if ($filter->isDeprecated()) {
 784              $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
 785              if (!\is_bool($filter->getDeprecatedVersion())) {
 786                  $message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
 787              }
 788              if ($filter->getAlternative()) {
 789                  $message .= sprintf('. Use "%s" instead', $filter->getAlternative());
 790              }
 791              $src = $this->parser->getStream()->getSourceContext();
 792              $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
 793  
 794              @trigger_error($message, E_USER_DEPRECATED);
 795          }
 796  
 797          return $filter->getNodeClass();
 798      }
 799  
 800      // checks that the node only contains "constant" elements
 801      private function checkConstantExpression(Node $node): bool
 802      {
 803          if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
 804              || $node instanceof NegUnary || $node instanceof PosUnary
 805          )) {
 806              return false;
 807          }
 808  
 809          foreach ($node as $n) {
 810              if (!$this->checkConstantExpression($n)) {
 811                  return false;
 812              }
 813          }
 814  
 815          return true;
 816      }
 817  }
 818  
 819  class_alias('Twig\ExpressionParser', 'Twig_ExpressionParser');


Generated: Wed Nov 11 20:28:18 2020 Cross-referenced by PHPXref 0.7.1