[ Index ]

PHP Cross Reference of phpBB-3.2.11-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\ArrayExpression;
  17  use Twig\Node\Expression\ArrowFunctionExpression;
  18  use Twig\Node\Expression\AssignNameExpression;
  19  use Twig\Node\Expression\Binary\ConcatBinary;
  20  use Twig\Node\Expression\BlockReferenceExpression;
  21  use Twig\Node\Expression\ConditionalExpression;
  22  use Twig\Node\Expression\ConstantExpression;
  23  use Twig\Node\Expression\GetAttrExpression;
  24  use Twig\Node\Expression\MethodCallExpression;
  25  use Twig\Node\Expression\NameExpression;
  26  use Twig\Node\Expression\ParentExpression;
  27  use Twig\Node\Expression\Unary\NegUnary;
  28  use Twig\Node\Expression\Unary\NotUnary;
  29  use Twig\Node\Expression\Unary\PosUnary;
  30  use Twig\Node\Node;
  31  
  32  /**
  33   * Parses expressions.
  34   *
  35   * This parser implements a "Precedence climbing" algorithm.
  36   *
  37   * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
  38   * @see https://en.wikipedia.org/wiki/Operator-precedence_parser
  39   *
  40   * @author Fabien Potencier <fabien@symfony.com>
  41   *
  42   * @internal
  43   */
  44  class ExpressionParser
  45  {
  46      const OPERATOR_LEFT = 1;
  47      const OPERATOR_RIGHT = 2;
  48  
  49      protected $parser;
  50      protected $unaryOperators;
  51      protected $binaryOperators;
  52  
  53      private $env;
  54  
  55      public function __construct(Parser $parser, $env = null)
  56      {
  57          $this->parser = $parser;
  58  
  59          if ($env instanceof Environment) {
  60              $this->env = $env;
  61              $this->unaryOperators = $env->getUnaryOperators();
  62              $this->binaryOperators = $env->getBinaryOperators();
  63          } else {
  64              @trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED);
  65  
  66              $this->env = $parser->getEnvironment();
  67              $this->unaryOperators = func_get_arg(1);
  68              $this->binaryOperators = func_get_arg(2);
  69          }
  70      }
  71  
  72      public function parseExpression($precedence = 0, $allowArrow = false)
  73      {
  74          if ($allowArrow && $arrow = $this->parseArrow()) {
  75              return $arrow;
  76          }
  77  
  78          $expr = $this->getPrimary();
  79          $token = $this->parser->getCurrentToken();
  80          while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
  81              $op = $this->binaryOperators[$token->getValue()];
  82              $this->parser->getStream()->next();
  83  
  84              if ('is not' === $token->getValue()) {
  85                  $expr = $this->parseNotTestExpression($expr);
  86              } elseif ('is' === $token->getValue()) {
  87                  $expr = $this->parseTestExpression($expr);
  88              } elseif (isset($op['callable'])) {
  89                  $expr = \call_user_func($op['callable'], $this->parser, $expr);
  90              } else {
  91                  $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
  92                  $class = $op['class'];
  93                  $expr = new $class($expr, $expr1, $token->getLine());
  94              }
  95  
  96              $token = $this->parser->getCurrentToken();
  97          }
  98  
  99          if (0 === $precedence) {
 100              return $this->parseConditionalExpression($expr);
 101          }
 102  
 103          return $expr;
 104      }
 105  
 106      /**
 107       * @return ArrowFunctionExpression|null
 108       */
 109      private function parseArrow()
 110      {
 111          $stream = $this->parser->getStream();
 112  
 113          // short array syntax (one argument, no parentheses)?
 114          if ($stream->look(1)->test(Token::ARROW_TYPE)) {
 115              $line = $stream->getCurrent()->getLine();
 116              $token = $stream->expect(Token::NAME_TYPE);
 117              $names = [new AssignNameExpression($token->getValue(), $token->getLine())];
 118              $stream->expect(Token::ARROW_TYPE);
 119  
 120              return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
 121          }
 122  
 123          // first, determine if we are parsing an arrow function by finding => (long form)
 124          $i = 0;
 125          if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) {
 126              return null;
 127          }
 128          ++$i;
 129          while (true) {
 130              // variable name
 131              ++$i;
 132              if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) {
 133                  break;
 134              }
 135              ++$i;
 136          }
 137          if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) {
 138              return null;
 139          }
 140          ++$i;
 141          if (!$stream->look($i)->test(Token::ARROW_TYPE)) {
 142              return null;
 143          }
 144  
 145          // yes, let's parse it properly
 146          $token = $stream->expect(Token::PUNCTUATION_TYPE, '(');
 147          $line = $token->getLine();
 148  
 149          $names = [];
 150          while (true) {
 151              $token = $stream->expect(Token::NAME_TYPE);
 152              $names[] = new AssignNameExpression($token->getValue(), $token->getLine());
 153  
 154              if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
 155                  break;
 156              }
 157          }
 158          $stream->expect(Token::PUNCTUATION_TYPE, ')');
 159          $stream->expect(Token::ARROW_TYPE);
 160  
 161          return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
 162      }
 163  
 164      protected function getPrimary()
 165      {
 166          $token = $this->parser->getCurrentToken();
 167  
 168          if ($this->isUnary($token)) {
 169              $operator = $this->unaryOperators[$token->getValue()];
 170              $this->parser->getStream()->next();
 171              $expr = $this->parseExpression($operator['precedence']);
 172              $class = $operator['class'];
 173  
 174              return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
 175          } elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) {
 176              $this->parser->getStream()->next();
 177              $expr = $this->parseExpression();
 178              $this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
 179  
 180              return $this->parsePostfixExpression($expr);
 181          }
 182  
 183          return $this->parsePrimaryExpression();
 184      }
 185  
 186      protected function parseConditionalExpression($expr)
 187      {
 188          while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) {
 189              if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {
 190                  $expr2 = $this->parseExpression();
 191                  if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {
 192                      $expr3 = $this->parseExpression();
 193                  } else {
 194                      $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
 195                  }
 196              } else {
 197                  $expr2 = $expr;
 198                  $expr3 = $this->parseExpression();
 199              }
 200  
 201              $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
 202          }
 203  
 204          return $expr;
 205      }
 206  
 207      protected function isUnary(Token $token)
 208      {
 209          return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
 210      }
 211  
 212      protected function isBinary(Token $token)
 213      {
 214          return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
 215      }
 216  
 217      public function parsePrimaryExpression()
 218      {
 219          $token = $this->parser->getCurrentToken();
 220          switch ($token->getType()) {
 221              case Token::NAME_TYPE:
 222                  $this->parser->getStream()->next();
 223                  switch ($token->getValue()) {
 224                      case 'true':
 225                      case 'TRUE':
 226                          $node = new ConstantExpression(true, $token->getLine());
 227                          break;
 228  
 229                      case 'false':
 230                      case 'FALSE':
 231                          $node = new ConstantExpression(false, $token->getLine());
 232                          break;
 233  
 234                      case 'none':
 235                      case 'NONE':
 236                      case 'null':
 237                      case 'NULL':
 238                          $node = new ConstantExpression(null, $token->getLine());
 239                          break;
 240  
 241                      default:
 242                          if ('(' === $this->parser->getCurrentToken()->getValue()) {
 243                              $node = $this->getFunctionNode($token->getValue(), $token->getLine());
 244                          } else {
 245                              $node = new NameExpression($token->getValue(), $token->getLine());
 246                          }
 247                  }
 248                  break;
 249  
 250              case Token::NUMBER_TYPE:
 251                  $this->parser->getStream()->next();
 252                  $node = new ConstantExpression($token->getValue(), $token->getLine());
 253                  break;
 254  
 255              case Token::STRING_TYPE:
 256              case Token::INTERPOLATION_START_TYPE:
 257                  $node = $this->parseStringExpression();
 258                  break;
 259  
 260              case Token::OPERATOR_TYPE:
 261                  if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
 262                      // in this context, string operators are variable names
 263                      $this->parser->getStream()->next();
 264                      $node = new NameExpression($token->getValue(), $token->getLine());
 265                      break;
 266                  } elseif (isset($this->unaryOperators[$token->getValue()])) {
 267                      $class = $this->unaryOperators[$token->getValue()]['class'];
 268  
 269                      $ref = new \ReflectionClass($class);
 270                      $negClass = 'Twig\Node\Expression\Unary\NegUnary';
 271                      $posClass = 'Twig\Node\Expression\Unary\PosUnary';
 272                      if (!(\in_array($ref->getName(), [$negClass, $posClass, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
 273                          || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass)
 274                          || $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos'))
 275                      ) {
 276                          throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
 277                      }
 278  
 279                      $this->parser->getStream()->next();
 280                      $expr = $this->parsePrimaryExpression();
 281  
 282                      $node = new $class($expr, $token->getLine());
 283                      break;
 284                  }
 285  
 286                  // no break
 287              default:
 288                  if ($token->test(Token::PUNCTUATION_TYPE, '[')) {
 289                      $node = $this->parseArrayExpression();
 290                  } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {
 291                      $node = $this->parseHashExpression();
 292                  } elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
 293                      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());
 294                  } else {
 295                      throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
 296                  }
 297          }
 298  
 299          return $this->parsePostfixExpression($node);
 300      }
 301  
 302      public function parseStringExpression()
 303      {
 304          $stream = $this->parser->getStream();
 305  
 306          $nodes = [];
 307          // a string cannot be followed by another string in a single expression
 308          $nextCanBeString = true;
 309          while (true) {
 310              if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) {
 311                  $nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
 312                  $nextCanBeString = false;
 313              } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) {
 314                  $nodes[] = $this->parseExpression();
 315                  $stream->expect(Token::INTERPOLATION_END_TYPE);
 316                  $nextCanBeString = true;
 317              } else {
 318                  break;
 319              }
 320          }
 321  
 322          $expr = array_shift($nodes);
 323          foreach ($nodes as $node) {
 324              $expr = new ConcatBinary($expr, $node, $node->getTemplateLine());
 325          }
 326  
 327          return $expr;
 328      }
 329  
 330      public function parseArrayExpression()
 331      {
 332          $stream = $this->parser->getStream();
 333          $stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
 334  
 335          $node = new ArrayExpression([], $stream->getCurrent()->getLine());
 336          $first = true;
 337          while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) {
 338              if (!$first) {
 339                  $stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
 340  
 341                  // trailing ,?
 342                  if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
 343                      break;
 344                  }
 345              }
 346              $first = false;
 347  
 348              $node->addElement($this->parseExpression());
 349          }
 350          $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
 351  
 352          return $node;
 353      }
 354  
 355      public function parseHashExpression()
 356      {
 357          $stream = $this->parser->getStream();
 358          $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
 359  
 360          $node = new ArrayExpression([], $stream->getCurrent()->getLine());
 361          $first = true;
 362          while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) {
 363              if (!$first) {
 364                  $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
 365  
 366                  // trailing ,?
 367                  if ($stream->test(Token::PUNCTUATION_TYPE, '}')) {
 368                      break;
 369                  }
 370              }
 371              $first = false;
 372  
 373              // a hash key can be:
 374              //
 375              //  * a number -- 12
 376              //  * a string -- 'a'
 377              //  * a name, which is equivalent to a string -- a
 378              //  * an expression, which must be enclosed in parentheses -- (1 + 2)
 379              if (($token = $stream->nextIf(Token::STRING_TYPE)) || ($token = $stream->nextIf(Token::NAME_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) {
 380                  $key = new ConstantExpression($token->getValue(), $token->getLine());
 381              } elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
 382                  $key = $this->parseExpression();
 383              } else {
 384                  $current = $stream->getCurrent();
 385  
 386                  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());
 387              }
 388  
 389              $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
 390              $value = $this->parseExpression();
 391  
 392              $node->addElement($value, $key);
 393          }
 394          $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
 395  
 396          return $node;
 397      }
 398  
 399      public function parsePostfixExpression($node)
 400      {
 401          while (true) {
 402              $token = $this->parser->getCurrentToken();
 403              if (Token::PUNCTUATION_TYPE == $token->getType()) {
 404                  if ('.' == $token->getValue() || '[' == $token->getValue()) {
 405                      $node = $this->parseSubscriptExpression($node);
 406                  } elseif ('|' == $token->getValue()) {
 407                      $node = $this->parseFilterExpression($node);
 408                  } else {
 409                      break;
 410                  }
 411              } else {
 412                  break;
 413              }
 414          }
 415  
 416          return $node;
 417      }
 418  
 419      public function getFunctionNode($name, $line)
 420      {
 421          switch ($name) {
 422              case 'parent':
 423                  $this->parseArguments();
 424                  if (!\count($this->parser->getBlockStack())) {
 425                      throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
 426                  }
 427  
 428                  if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
 429                      throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
 430                  }
 431  
 432                  return new ParentExpression($this->parser->peekBlockStack(), $line);
 433              case 'block':
 434                  $args = $this->parseArguments();
 435                  if (\count($args) < 1) {
 436                      throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
 437                  }
 438  
 439                  return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line);
 440              case 'attribute':
 441                  $args = $this->parseArguments();
 442                  if (\count($args) < 2) {
 443                      throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
 444                  }
 445  
 446                  return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line);
 447              default:
 448                  if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
 449                      $arguments = new ArrayExpression([], $line);
 450                      foreach ($this->parseArguments() as $n) {
 451                          $arguments->addElement($n);
 452                      }
 453  
 454                      $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
 455                      $node->setAttribute('safe', true);
 456  
 457                      return $node;
 458                  }
 459  
 460                  $args = $this->parseArguments(true);
 461                  $class = $this->getFunctionNodeClass($name, $line);
 462  
 463                  return new $class($name, $args, $line);
 464          }
 465      }
 466  
 467      public function parseSubscriptExpression($node)
 468      {
 469          $stream = $this->parser->getStream();
 470          $token = $stream->next();
 471          $lineno = $token->getLine();
 472          $arguments = new ArrayExpression([], $lineno);
 473          $type = Template::ANY_CALL;
 474          if ('.' == $token->getValue()) {
 475              $token = $stream->next();
 476              if (
 477                  Token::NAME_TYPE == $token->getType()
 478                  ||
 479                  Token::NUMBER_TYPE == $token->getType()
 480                  ||
 481                  (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
 482              ) {
 483                  $arg = new ConstantExpression($token->getValue(), $lineno);
 484  
 485                  if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
 486                      $type = Template::METHOD_CALL;
 487                      foreach ($this->parseArguments() as $n) {
 488                          $arguments->addElement($n);
 489                      }
 490                  }
 491              } else {
 492                  throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext());
 493              }
 494  
 495              if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
 496                  if (!$arg instanceof ConstantExpression) {
 497                      throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
 498                  }
 499  
 500                  $name = $arg->getAttribute('value');
 501  
 502                  if ($this->parser->isReservedMacroName($name)) {
 503                      throw new SyntaxError(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext());
 504                  }
 505  
 506                  $node = new MethodCallExpression($node, 'get'.$name, $arguments, $lineno);
 507                  $node->setAttribute('safe', true);
 508  
 509                  return $node;
 510              }
 511          } else {
 512              $type = Template::ARRAY_CALL;
 513  
 514              // slice?
 515              $slice = false;
 516              if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {
 517                  $slice = true;
 518                  $arg = new ConstantExpression(0, $token->getLine());
 519              } else {
 520                  $arg = $this->parseExpression();
 521              }
 522  
 523              if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {
 524                  $slice = true;
 525              }
 526  
 527              if ($slice) {
 528                  if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
 529                      $length = new ConstantExpression(null, $token->getLine());
 530                  } else {
 531                      $length = $this->parseExpression();
 532                  }
 533  
 534                  $class = $this->getFilterNodeClass('slice', $token->getLine());
 535                  $arguments = new Node([$arg, $length]);
 536                  $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
 537  
 538                  $stream->expect(Token::PUNCTUATION_TYPE, ']');
 539  
 540                  return $filter;
 541              }
 542  
 543              $stream->expect(Token::PUNCTUATION_TYPE, ']');
 544          }
 545  
 546          return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
 547      }
 548  
 549      public function parseFilterExpression($node)
 550      {
 551          $this->parser->getStream()->next();
 552  
 553          return $this->parseFilterExpressionRaw($node);
 554      }
 555  
 556      public function parseFilterExpressionRaw($node, $tag = null)
 557      {
 558          while (true) {
 559              $token = $this->parser->getStream()->expect(Token::NAME_TYPE);
 560  
 561              $name = new ConstantExpression($token->getValue(), $token->getLine());
 562              if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) {
 563                  $arguments = new Node();
 564              } else {
 565                  $arguments = $this->parseArguments(true, false, true);
 566              }
 567  
 568              $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
 569  
 570              $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
 571  
 572              if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) {
 573                  break;
 574              }
 575  
 576              $this->parser->getStream()->next();
 577          }
 578  
 579          return $node;
 580      }
 581  
 582      /**
 583       * Parses arguments.
 584       *
 585       * @param bool $namedArguments Whether to allow named arguments or not
 586       * @param bool $definition     Whether we are parsing arguments for a function definition
 587       *
 588       * @return Node
 589       *
 590       * @throws SyntaxError
 591       */
 592      public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false)
 593      {
 594          $args = [];
 595          $stream = $this->parser->getStream();
 596  
 597          $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
 598          while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
 599              if (!empty($args)) {
 600                  $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
 601              }
 602  
 603              if ($definition) {
 604                  $token = $stream->expect(Token::NAME_TYPE, 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, '=')) {
 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(sprintf('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, ')', '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) && 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, null, 'Only variables can be assigned to');
 658              }
 659              $value = $token->getValue();
 660              if (\in_array(strtolower($value), ['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, ',')) {
 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, ',')) {
 679                  break;
 680              }
 681          }
 682  
 683          return new Node($targets);
 684      }
 685  
 686      private function parseNotTestExpression(\Twig_NodeInterface $node)
 687      {
 688          return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
 689      }
 690  
 691      private function parseTestExpression(\Twig_NodeInterface $node)
 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, '(')) {
 699              $arguments = $this->parseArguments(true);
 700          }
 701  
 702          return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
 703      }
 704  
 705      private function getTest($line)
 706      {
 707          $stream = $this->parser->getStream();
 708          $name = $stream->expect(Token::NAME_TYPE)->getValue();
 709  
 710          if ($test = $this->env->getTest($name)) {
 711              return [$name, $test];
 712          }
 713  
 714          if ($stream->test(Token::NAME_TYPE)) {
 715              // try 2-words tests
 716              $name = $name.' '.$this->parser->getCurrentToken()->getValue();
 717  
 718              if ($test = $this->env->getTest($name)) {
 719                  $stream->next();
 720  
 721                  return [$name, $test];
 722              }
 723          }
 724  
 725          $e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext());
 726          $e->addSuggestions($name, array_keys($this->env->getTests()));
 727  
 728          throw $e;
 729      }
 730  
 731      private function getTestNodeClass($test)
 732      {
 733          if ($test instanceof TwigTest && $test->isDeprecated()) {
 734              $stream = $this->parser->getStream();
 735              $message = sprintf('Twig Test "%s" is deprecated', $test->getName());
 736              if (!\is_bool($test->getDeprecatedVersion())) {
 737                  $message .= sprintf(' since version %s', $test->getDeprecatedVersion());
 738              }
 739              if ($test->getAlternative()) {
 740                  $message .= sprintf('. Use "%s" instead', $test->getAlternative());
 741              }
 742              $src = $stream->getSourceContext();
 743              $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine());
 744  
 745              @trigger_error($message, E_USER_DEPRECATED);
 746          }
 747  
 748          if ($test instanceof TwigTest) {
 749              return $test->getNodeClass();
 750          }
 751  
 752          return $test instanceof \Twig_Test_Node ? $test->getClass() : 'Twig\Node\Expression\TestExpression';
 753      }
 754  
 755      protected function getFunctionNodeClass($name, $line)
 756      {
 757          if (false === $function = $this->env->getFunction($name)) {
 758              $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
 759              $e->addSuggestions($name, array_keys($this->env->getFunctions()));
 760  
 761              throw $e;
 762          }
 763  
 764          if ($function instanceof TwigFunction && $function->isDeprecated()) {
 765              $message = sprintf('Twig Function "%s" is deprecated', $function->getName());
 766              if (!\is_bool($function->getDeprecatedVersion())) {
 767                  $message .= sprintf(' since version %s', $function->getDeprecatedVersion());
 768              }
 769              if ($function->getAlternative()) {
 770                  $message .= sprintf('. Use "%s" instead', $function->getAlternative());
 771              }
 772              $src = $this->parser->getStream()->getSourceContext();
 773              $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line);
 774  
 775              @trigger_error($message, E_USER_DEPRECATED);
 776          }
 777  
 778          if ($function instanceof TwigFunction) {
 779              return $function->getNodeClass();
 780          }
 781  
 782          return $function instanceof \Twig_Function_Node ? $function->getClass() : 'Twig\Node\Expression\FunctionExpression';
 783      }
 784  
 785      protected function getFilterNodeClass($name, $line)
 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 instanceof TwigFilter && $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->getPath() : $src->getName(), $line);
 804  
 805              @trigger_error($message, E_USER_DEPRECATED);
 806          }
 807  
 808          if ($filter instanceof TwigFilter) {
 809              return $filter->getNodeClass();
 810          }
 811  
 812          return $filter instanceof \Twig_Filter_Node ? $filter->getClass() : 'Twig\Node\Expression\FilterExpression';
 813      }
 814  
 815      // checks that the node only contains "constant" elements
 816      protected function checkConstantExpression(\Twig_NodeInterface $node)
 817      {
 818          if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
 819              || $node instanceof NegUnary || $node instanceof PosUnary
 820          )) {
 821              return false;
 822          }
 823  
 824          foreach ($node as $n) {
 825              if (!$this->checkConstantExpression($n)) {
 826                  return false;
 827              }
 828          }
 829  
 830          return true;
 831      }
 832  }
 833  
 834  class_alias('Twig\ExpressionParser', 'Twig_ExpressionParser');


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1