vendor/twig/twig/src/ExpressionParser.php line 778

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Twig.
  4.  *
  5.  * (c) Fabien Potencier
  6.  * (c) Armin Ronacher
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Twig;
  12. use Twig\Error\SyntaxError;
  13. use Twig\Node\Expression\AbstractExpression;
  14. use Twig\Node\Expression\ArrayExpression;
  15. use Twig\Node\Expression\ArrowFunctionExpression;
  16. use Twig\Node\Expression\AssignNameExpression;
  17. use Twig\Node\Expression\Binary\ConcatBinary;
  18. use Twig\Node\Expression\BlockReferenceExpression;
  19. use Twig\Node\Expression\ConditionalExpression;
  20. use Twig\Node\Expression\ConstantExpression;
  21. use Twig\Node\Expression\GetAttrExpression;
  22. use Twig\Node\Expression\MethodCallExpression;
  23. use Twig\Node\Expression\NameExpression;
  24. use Twig\Node\Expression\ParentExpression;
  25. use Twig\Node\Expression\TestExpression;
  26. use Twig\Node\Expression\Unary\NegUnary;
  27. use Twig\Node\Expression\Unary\NotUnary;
  28. use Twig\Node\Expression\Unary\PosUnary;
  29. use Twig\Node\Node;
  30. /**
  31.  * Parses expressions.
  32.  *
  33.  * This parser implements a "Precedence climbing" algorithm.
  34.  *
  35.  * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
  36.  * @see https://en.wikipedia.org/wiki/Operator-precedence_parser
  37.  *
  38.  * @author Fabien Potencier <fabien@symfony.com>
  39.  *
  40.  * @internal
  41.  */
  42. class ExpressionParser
  43. {
  44.     const OPERATOR_LEFT 1;
  45.     const OPERATOR_RIGHT 2;
  46.     private $parser;
  47.     private $env;
  48.     private $unaryOperators;
  49.     private $binaryOperators;
  50.     public function __construct(Parser $parserEnvironment $env)
  51.     {
  52.         $this->parser $parser;
  53.         $this->env $env;
  54.         $this->unaryOperators $env->getUnaryOperators();
  55.         $this->binaryOperators $env->getBinaryOperators();
  56.     }
  57.     public function parseExpression($precedence 0$allowArrow false)
  58.     {
  59.         if ($allowArrow && $arrow $this->parseArrow()) {
  60.             return $arrow;
  61.         }
  62.         $expr $this->getPrimary();
  63.         $token $this->parser->getCurrentToken();
  64.         while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
  65.             $op $this->binaryOperators[$token->getValue()];
  66.             $this->parser->getStream()->next();
  67.             if ('is not' === $token->getValue()) {
  68.                 $expr $this->parseNotTestExpression($expr);
  69.             } elseif ('is' === $token->getValue()) {
  70.                 $expr $this->parseTestExpression($expr);
  71.             } elseif (isset($op['callable'])) {
  72.                 $expr $op['callable']($this->parser$expr);
  73.             } else {
  74.                 $expr1 $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + $op['precedence']);
  75.                 $class $op['class'];
  76.                 $expr = new $class($expr$expr1$token->getLine());
  77.             }
  78.             $token $this->parser->getCurrentToken();
  79.         }
  80.         if (=== $precedence) {
  81.             return $this->parseConditionalExpression($expr);
  82.         }
  83.         return $expr;
  84.     }
  85.     /**
  86.      * @return ArrowFunctionExpression|null
  87.      */
  88.     private function parseArrow()
  89.     {
  90.         $stream $this->parser->getStream();
  91.         // short array syntax (one argument, no parentheses)?
  92.         if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) {
  93.             $line $stream->getCurrent()->getLine();
  94.             $token $stream->expect(/* Token::NAME_TYPE */ 5);
  95.             $names = [new AssignNameExpression($token->getValue(), $token->getLine())];
  96.             $stream->expect(/* Token::ARROW_TYPE */ 12);
  97.             return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
  98.         }
  99.         // first, determine if we are parsing an arrow function by finding => (long form)
  100.         $i 0;
  101.         if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  102.             return null;
  103.         }
  104.         ++$i;
  105.         while (true) {
  106.             // variable name
  107.             ++$i;
  108.             if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9',')) {
  109.                 break;
  110.             }
  111.             ++$i;
  112.         }
  113.         if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9')')) {
  114.             return null;
  115.         }
  116.         ++$i;
  117.         if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) {
  118.             return null;
  119.         }
  120.         // yes, let's parse it properly
  121.         $token $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'(');
  122.         $line $token->getLine();
  123.         $names = [];
  124.         while (true) {
  125.             $token $stream->expect(/* Token::NAME_TYPE */ 5);
  126.             $names[] = new AssignNameExpression($token->getValue(), $token->getLine());
  127.             if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9',')) {
  128.                 break;
  129.             }
  130.         }
  131.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9')');
  132.         $stream->expect(/* Token::ARROW_TYPE */ 12);
  133.         return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
  134.     }
  135.     private function getPrimary(): AbstractExpression
  136.     {
  137.         $token $this->parser->getCurrentToken();
  138.         if ($this->isUnary($token)) {
  139.             $operator $this->unaryOperators[$token->getValue()];
  140.             $this->parser->getStream()->next();
  141.             $expr $this->parseExpression($operator['precedence']);
  142.             $class $operator['class'];
  143.             return $this->parsePostfixExpression(new $class($expr$token->getLine()));
  144.         } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  145.             $this->parser->getStream()->next();
  146.             $expr $this->parseExpression();
  147.             $this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9')''An opened parenthesis is not properly closed');
  148.             return $this->parsePostfixExpression($expr);
  149.         }
  150.         return $this->parsePrimaryExpression();
  151.     }
  152.     private function parseConditionalExpression($expr): AbstractExpression
  153.     {
  154.         while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9'?')) {
  155.             if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9':')) {
  156.                 $expr2 $this->parseExpression();
  157.                 if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9':')) {
  158.                     $expr3 $this->parseExpression();
  159.                 } else {
  160.                     $expr3 = new ConstantExpression(''$this->parser->getCurrentToken()->getLine());
  161.                 }
  162.             } else {
  163.                 $expr2 $expr;
  164.                 $expr3 $this->parseExpression();
  165.             }
  166.             $expr = new ConditionalExpression($expr$expr2$expr3$this->parser->getCurrentToken()->getLine());
  167.         }
  168.         return $expr;
  169.     }
  170.     private function isUnary(Token $token): bool
  171.     {
  172.         return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]);
  173.     }
  174.     private function isBinary(Token $token): bool
  175.     {
  176.         return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]);
  177.     }
  178.     public function parsePrimaryExpression()
  179.     {
  180.         $token $this->parser->getCurrentToken();
  181.         switch ($token->getType()) {
  182.             case /* Token::NAME_TYPE */ 5:
  183.                 $this->parser->getStream()->next();
  184.                 switch ($token->getValue()) {
  185.                     case 'true':
  186.                     case 'TRUE':
  187.                         $node = new ConstantExpression(true$token->getLine());
  188.                         break;
  189.                     case 'false':
  190.                     case 'FALSE':
  191.                         $node = new ConstantExpression(false$token->getLine());
  192.                         break;
  193.                     case 'none':
  194.                     case 'NONE':
  195.                     case 'null':
  196.                     case 'NULL':
  197.                         $node = new ConstantExpression(null$token->getLine());
  198.                         break;
  199.                     default:
  200.                         if ('(' === $this->parser->getCurrentToken()->getValue()) {
  201.                             $node $this->getFunctionNode($token->getValue(), $token->getLine());
  202.                         } else {
  203.                             $node = new NameExpression($token->getValue(), $token->getLine());
  204.                         }
  205.                 }
  206.                 break;
  207.             case /* Token::NUMBER_TYPE */ 6:
  208.                 $this->parser->getStream()->next();
  209.                 $node = new ConstantExpression($token->getValue(), $token->getLine());
  210.                 break;
  211.             case /* Token::STRING_TYPE */ 7:
  212.             case /* Token::INTERPOLATION_START_TYPE */ 10:
  213.                 $node $this->parseStringExpression();
  214.                 break;
  215.             case /* Token::OPERATOR_TYPE */ 8:
  216.                 if (preg_match(Lexer::REGEX_NAME$token->getValue(), $matches) && $matches[0] == $token->getValue()) {
  217.                     // in this context, string operators are variable names
  218.                     $this->parser->getStream()->next();
  219.                     $node = new NameExpression($token->getValue(), $token->getLine());
  220.                     break;
  221.                 } elseif (isset($this->unaryOperators[$token->getValue()])) {
  222.                     $class $this->unaryOperators[$token->getValue()]['class'];
  223.                     $ref = new \ReflectionClass($class);
  224.                     if (!(\in_array($ref->getName(), [NegUnary::class, PosUnary::class, 'Twig_Node_Expression_Unary_Neg''Twig_Node_Expression_Unary_Pos'])
  225.                         || $ref->isSubclassOf(NegUnary::class) || $ref->isSubclassOf(PosUnary::class)
  226.                         || $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos'))
  227.                     ) {
  228.                         throw new SyntaxError(sprintf('Unexpected unary operator "%s".'$token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
  229.                     }
  230.                     $this->parser->getStream()->next();
  231.                     $expr $this->parsePrimaryExpression();
  232.                     $node = new $class($expr$token->getLine());
  233.                     break;
  234.                 }
  235.                 // no break
  236.             default:
  237.                 if ($token->test(/* Token::PUNCTUATION_TYPE */ 9'[')) {
  238.                     $node $this->parseArrayExpression();
  239.                 } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9'{')) {
  240.                     $node $this->parseHashExpression();
  241.                 } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8'=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
  242.                     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());
  243.                 } else {
  244.                     throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".'Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
  245.                 }
  246.         }
  247.         return $this->parsePostfixExpression($node);
  248.     }
  249.     public function parseStringExpression()
  250.     {
  251.         $stream $this->parser->getStream();
  252.         $nodes = [];
  253.         // a string cannot be followed by another string in a single expression
  254.         $nextCanBeString true;
  255.         while (true) {
  256.             if ($nextCanBeString && $token $stream->nextIf(/* Token::STRING_TYPE */ 7)) {
  257.                 $nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
  258.                 $nextCanBeString false;
  259.             } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
  260.                 $nodes[] = $this->parseExpression();
  261.                 $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11);
  262.                 $nextCanBeString true;
  263.             } else {
  264.                 break;
  265.             }
  266.         }
  267.         $expr array_shift($nodes);
  268.         foreach ($nodes as $node) {
  269.             $expr = new ConcatBinary($expr$node$node->getTemplateLine());
  270.         }
  271.         return $expr;
  272.     }
  273.     public function parseArrayExpression()
  274.     {
  275.         $stream $this->parser->getStream();
  276.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'[''An array element was expected');
  277.         $node = new ArrayExpression([], $stream->getCurrent()->getLine());
  278.         $first true;
  279.         while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9']')) {
  280.             if (!$first) {
  281.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9',''An array element must be followed by a comma');
  282.                 // trailing ,?
  283.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9']')) {
  284.                     break;
  285.                 }
  286.             }
  287.             $first false;
  288.             $node->addElement($this->parseExpression());
  289.         }
  290.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9']''An opened array is not properly closed');
  291.         return $node;
  292.     }
  293.     public function parseHashExpression()
  294.     {
  295.         $stream $this->parser->getStream();
  296.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'{''A hash element was expected');
  297.         $node = new ArrayExpression([], $stream->getCurrent()->getLine());
  298.         $first true;
  299.         while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9'}')) {
  300.             if (!$first) {
  301.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9',''A hash value must be followed by a comma');
  302.                 // trailing ,?
  303.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'}')) {
  304.                     break;
  305.                 }
  306.             }
  307.             $first false;
  308.             // a hash key can be:
  309.             //
  310.             //  * a number -- 12
  311.             //  * a string -- 'a'
  312.             //  * a name, which is equivalent to a string -- a
  313.             //  * an expression, which must be enclosed in parentheses -- (1 + 2)
  314.             if (($token $stream->nextIf(/* Token::STRING_TYPE */ 7)) || ($token $stream->nextIf(/* Token::NAME_TYPE */ 5)) || $token $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
  315.                 $key = new ConstantExpression($token->getValue(), $token->getLine());
  316.             } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  317.                 $key $this->parseExpression();
  318.             } else {
  319.                 $current $stream->getCurrent();
  320.                 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());
  321.             }
  322.             $stream->expect(/* Token::PUNCTUATION_TYPE */ 9':''A hash key must be followed by a colon (:)');
  323.             $value $this->parseExpression();
  324.             $node->addElement($value$key);
  325.         }
  326.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'}''An opened hash is not properly closed');
  327.         return $node;
  328.     }
  329.     public function parsePostfixExpression($node)
  330.     {
  331.         while (true) {
  332.             $token $this->parser->getCurrentToken();
  333.             if (/* Token::PUNCTUATION_TYPE */ == $token->getType()) {
  334.                 if ('.' == $token->getValue() || '[' == $token->getValue()) {
  335.                     $node $this->parseSubscriptExpression($node);
  336.                 } elseif ('|' == $token->getValue()) {
  337.                     $node $this->parseFilterExpression($node);
  338.                 } else {
  339.                     break;
  340.                 }
  341.             } else {
  342.                 break;
  343.             }
  344.         }
  345.         return $node;
  346.     }
  347.     public function getFunctionNode($name$line)
  348.     {
  349.         switch ($name) {
  350.             case 'parent':
  351.                 $this->parseArguments();
  352.                 if (!\count($this->parser->getBlockStack())) {
  353.                     throw new SyntaxError('Calling "parent" outside a block is forbidden.'$line$this->parser->getStream()->getSourceContext());
  354.                 }
  355.                 if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
  356.                     throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.'$line$this->parser->getStream()->getSourceContext());
  357.                 }
  358.                 return new ParentExpression($this->parser->peekBlockStack(), $line);
  359.             case 'block':
  360.                 $args $this->parseArguments();
  361.                 if (\count($args) < 1) {
  362.                     throw new SyntaxError('The "block" function takes one argument (the block name).'$line$this->parser->getStream()->getSourceContext());
  363.                 }
  364.                 return new BlockReferenceExpression($args->getNode(0), \count($args) > $args->getNode(1) : null$line);
  365.             case 'attribute':
  366.                 $args $this->parseArguments();
  367.                 if (\count($args) < 2) {
  368.                     throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).'$line$this->parser->getStream()->getSourceContext());
  369.                 }
  370.                 return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > $args->getNode(2) : nullTemplate::ANY_CALL$line);
  371.             default:
  372.                 if (null !== $alias $this->parser->getImportedSymbol('function'$name)) {
  373.                     $arguments = new ArrayExpression([], $line);
  374.                     foreach ($this->parseArguments() as $n) {
  375.                         $arguments->addElement($n);
  376.                     }
  377.                     $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments$line);
  378.                     $node->setAttribute('safe'true);
  379.                     return $node;
  380.                 }
  381.                 $args $this->parseArguments(true);
  382.                 $class $this->getFunctionNodeClass($name$line);
  383.                 return new $class($name$args$line);
  384.         }
  385.     }
  386.     public function parseSubscriptExpression($node)
  387.     {
  388.         $stream $this->parser->getStream();
  389.         $token $stream->next();
  390.         $lineno $token->getLine();
  391.         $arguments = new ArrayExpression([], $lineno);
  392.         $type Template::ANY_CALL;
  393.         if ('.' == $token->getValue()) {
  394.             $token $stream->next();
  395.             if (
  396.                 /* Token::NAME_TYPE */ == $token->getType()
  397.                 ||
  398.                 /* Token::NUMBER_TYPE */ == $token->getType()
  399.                 ||
  400.                 (/* Token::OPERATOR_TYPE */ == $token->getType() && preg_match(Lexer::REGEX_NAME$token->getValue()))
  401.             ) {
  402.                 $arg = new ConstantExpression($token->getValue(), $lineno);
  403.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  404.                     $type Template::METHOD_CALL;
  405.                     foreach ($this->parseArguments() as $n) {
  406.                         $arguments->addElement($n);
  407.                     }
  408.                 }
  409.             } else {
  410.                 throw new SyntaxError('Expected name or number.'$lineno$stream->getSourceContext());
  411.             }
  412.             if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template'$node->getAttribute('name'))) {
  413.                 if (!$arg instanceof ConstantExpression) {
  414.                     throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").'$node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
  415.                 }
  416.                 $name $arg->getAttribute('value');
  417.                 $node = new MethodCallExpression($node'macro_'.$name$arguments$lineno);
  418.                 $node->setAttribute('safe'true);
  419.                 return $node;
  420.             }
  421.         } else {
  422.             $type Template::ARRAY_CALL;
  423.             // slice?
  424.             $slice false;
  425.             if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9':')) {
  426.                 $slice true;
  427.                 $arg = new ConstantExpression(0$token->getLine());
  428.             } else {
  429.                 $arg $this->parseExpression();
  430.             }
  431.             if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9':')) {
  432.                 $slice true;
  433.             }
  434.             if ($slice) {
  435.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9']')) {
  436.                     $length = new ConstantExpression(null$token->getLine());
  437.                 } else {
  438.                     $length $this->parseExpression();
  439.                 }
  440.                 $class $this->getFilterNodeClass('slice'$token->getLine());
  441.                 $arguments = new Node([$arg$length]);
  442.                 $filter = new $class($node, new ConstantExpression('slice'$token->getLine()), $arguments$token->getLine());
  443.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9']');
  444.                 return $filter;
  445.             }
  446.             $stream->expect(/* Token::PUNCTUATION_TYPE */ 9']');
  447.         }
  448.         return new GetAttrExpression($node$arg$arguments$type$lineno);
  449.     }
  450.     public function parseFilterExpression($node)
  451.     {
  452.         $this->parser->getStream()->next();
  453.         return $this->parseFilterExpressionRaw($node);
  454.     }
  455.     public function parseFilterExpressionRaw($node$tag null)
  456.     {
  457.         while (true) {
  458.             $token $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
  459.             $name = new ConstantExpression($token->getValue(), $token->getLine());
  460.             if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  461.                 $arguments = new Node();
  462.             } else {
  463.                 $arguments $this->parseArguments(truefalsetrue);
  464.             }
  465.             $class $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
  466.             $node = new $class($node$name$arguments$token->getLine(), $tag);
  467.             if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9'|')) {
  468.                 break;
  469.             }
  470.             $this->parser->getStream()->next();
  471.         }
  472.         return $node;
  473.     }
  474.     /**
  475.      * Parses arguments.
  476.      *
  477.      * @param bool $namedArguments Whether to allow named arguments or not
  478.      * @param bool $definition     Whether we are parsing arguments for a function definition
  479.      *
  480.      * @return Node
  481.      *
  482.      * @throws SyntaxError
  483.      */
  484.     public function parseArguments($namedArguments false$definition false$allowArrow false)
  485.     {
  486.         $args = [];
  487.         $stream $this->parser->getStream();
  488.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'(''A list of arguments must begin with an opening parenthesis');
  489.         while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9')')) {
  490.             if (!empty($args)) {
  491.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9',''Arguments must be separated by a comma');
  492.             }
  493.             if ($definition) {
  494.                 $token $stream->expect(/* Token::NAME_TYPE */ 5null'An argument must be a name');
  495.                 $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
  496.             } else {
  497.                 $value $this->parseExpression(0$allowArrow);
  498.             }
  499.             $name null;
  500.             if ($namedArguments && $token $stream->nextIf(/* Token::OPERATOR_TYPE */ 8'=')) {
  501.                 if (!$value instanceof NameExpression) {
  502.                     throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
  503.                 }
  504.                 $name $value->getAttribute('name');
  505.                 if ($definition) {
  506.                     $value $this->parsePrimaryExpression();
  507.                     if (!$this->checkConstantExpression($value)) {
  508.                         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());
  509.                     }
  510.                 } else {
  511.                     $value $this->parseExpression(0$allowArrow);
  512.                 }
  513.             }
  514.             if ($definition) {
  515.                 if (null === $name) {
  516.                     $name $value->getAttribute('name');
  517.                     $value = new ConstantExpression(null$this->parser->getCurrentToken()->getLine());
  518.                 }
  519.                 $args[$name] = $value;
  520.             } else {
  521.                 if (null === $name) {
  522.                     $args[] = $value;
  523.                 } else {
  524.                     $args[$name] = $value;
  525.                 }
  526.             }
  527.         }
  528.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9')''A list of arguments must be closed by a parenthesis');
  529.         return new Node($args);
  530.     }
  531.     public function parseAssignmentExpression()
  532.     {
  533.         $stream $this->parser->getStream();
  534.         $targets = [];
  535.         while (true) {
  536.             $token $stream->expect(/* Token::NAME_TYPE */ 5null'Only variables can be assigned to');
  537.             $value $token->getValue();
  538.             if (\in_array(strtolower($value), ['true''false''none''null'])) {
  539.                 throw new SyntaxError(sprintf('You cannot assign a value to "%s".'$value), $token->getLine(), $stream->getSourceContext());
  540.             }
  541.             $targets[] = new AssignNameExpression($value$token->getLine());
  542.             if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9',')) {
  543.                 break;
  544.             }
  545.         }
  546.         return new Node($targets);
  547.     }
  548.     public function parseMultitargetExpression()
  549.     {
  550.         $targets = [];
  551.         while (true) {
  552.             $targets[] = $this->parseExpression();
  553.             if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9',')) {
  554.                 break;
  555.             }
  556.         }
  557.         return new Node($targets);
  558.     }
  559.     private function parseNotTestExpression(Node $node): NotUnary
  560.     {
  561.         return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
  562.     }
  563.     private function parseTestExpression(Node $node): TestExpression
  564.     {
  565.         $stream $this->parser->getStream();
  566.         list($name$test) = $this->getTest($node->getTemplateLine());
  567.         $class $this->getTestNodeClass($test);
  568.         $arguments null;
  569.         if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  570.             $arguments $this->parseArguments(true);
  571.         }
  572.         return new $class($node$name$arguments$this->parser->getCurrentToken()->getLine());
  573.     }
  574.     private function getTest(int $line): array
  575.     {
  576.         $stream $this->parser->getStream();
  577.         $name $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
  578.         if ($test $this->env->getTest($name)) {
  579.             return [$name$test];
  580.         }
  581.         if ($stream->test(/* Token::NAME_TYPE */ 5)) {
  582.             // try 2-words tests
  583.             $name $name.' '.$this->parser->getCurrentToken()->getValue();
  584.             if ($test $this->env->getTest($name)) {
  585.                 $stream->next();
  586.                 return [$name$test];
  587.             }
  588.         }
  589.         $e = new SyntaxError(sprintf('Unknown "%s" test.'$name), $line$stream->getSourceContext());
  590.         $e->addSuggestions($namearray_keys($this->env->getTests()));
  591.         throw $e;
  592.     }
  593.     private function getTestNodeClass(TwigTest $test): string
  594.     {
  595.         if ($test->isDeprecated()) {
  596.             $stream $this->parser->getStream();
  597.             $message sprintf('Twig Test "%s" is deprecated'$test->getName());
  598.             if (!\is_bool($test->getDeprecatedVersion())) {
  599.                 $message .= sprintf(' since version %s'$test->getDeprecatedVersion());
  600.             }
  601.             if ($test->getAlternative()) {
  602.                 $message .= sprintf('. Use "%s" instead'$test->getAlternative());
  603.             }
  604.             $src $stream->getSourceContext();
  605.             $message .= sprintf(' in %s at line %d.'$src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
  606.             @trigger_error($messageE_USER_DEPRECATED);
  607.         }
  608.         return $test->getNodeClass();
  609.     }
  610.     private function getFunctionNodeClass(string $nameint $line): string
  611.     {
  612.         if (false === $function $this->env->getFunction($name)) {
  613.             $e = new SyntaxError(sprintf('Unknown "%s" function.'$name), $line$this->parser->getStream()->getSourceContext());
  614.             $e->addSuggestions($namearray_keys($this->env->getFunctions()));
  615.             throw $e;
  616.         }
  617.         if ($function->isDeprecated()) {
  618.             $message sprintf('Twig Function "%s" is deprecated'$function->getName());
  619.             if (!\is_bool($function->getDeprecatedVersion())) {
  620.                 $message .= sprintf(' since version %s'$function->getDeprecatedVersion());
  621.             }
  622.             if ($function->getAlternative()) {
  623.                 $message .= sprintf('. Use "%s" instead'$function->getAlternative());
  624.             }
  625.             $src $this->parser->getStream()->getSourceContext();
  626.             $message .= sprintf(' in %s at line %d.'$src->getPath() ?: $src->getName(), $line);
  627.             @trigger_error($messageE_USER_DEPRECATED);
  628.         }
  629.         return $function->getNodeClass();
  630.     }
  631.     private function getFilterNodeClass(string $nameint $line): string
  632.     {
  633.         if (false === $filter $this->env->getFilter($name)) {
  634.             $e = new SyntaxError(sprintf('Unknown "%s" filter.'$name), $line$this->parser->getStream()->getSourceContext());
  635.             $e->addSuggestions($namearray_keys($this->env->getFilters()));
  636.             throw $e;
  637.         }
  638.         if ($filter->isDeprecated()) {
  639.             $message sprintf('Twig Filter "%s" is deprecated'$filter->getName());
  640.             if (!\is_bool($filter->getDeprecatedVersion())) {
  641.                 $message .= sprintf(' since version %s'$filter->getDeprecatedVersion());
  642.             }
  643.             if ($filter->getAlternative()) {
  644.                 $message .= sprintf('. Use "%s" instead'$filter->getAlternative());
  645.             }
  646.             $src $this->parser->getStream()->getSourceContext();
  647.             $message .= sprintf(' in %s at line %d.'$src->getPath() ?: $src->getName(), $line);
  648.             @trigger_error($messageE_USER_DEPRECATED);
  649.         }
  650.         return $filter->getNodeClass();
  651.     }
  652.     // checks that the node only contains "constant" elements
  653.     private function checkConstantExpression(Node $node): bool
  654.     {
  655.         if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
  656.             || $node instanceof NegUnary || $node instanceof PosUnary
  657.         )) {
  658.             return false;
  659.         }
  660.         foreach ($node as $n) {
  661.             if (!$this->checkConstantExpression($n)) {
  662.                 return false;
  663.             }
  664.         }
  665.         return true;
  666.     }
  667. }
  668. class_alias('Twig\ExpressionParser''Twig_ExpressionParser');