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

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.     public const OPERATOR_LEFT 1;
  45.     public 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.                 }
  222.                 if (isset($this->unaryOperators[$token->getValue()])) {
  223.                     $class $this->unaryOperators[$token->getValue()]['class'];
  224.                     if (!\in_array($class, [NegUnary::class, PosUnary::class])) {
  225.                         throw new SyntaxError(sprintf('Unexpected unary operator "%s".'$token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
  226.                     }
  227.                     $this->parser->getStream()->next();
  228.                     $expr $this->parsePrimaryExpression();
  229.                     $node = new $class($expr$token->getLine());
  230.                     break;
  231.                 }
  232.                 // no break
  233.             default:
  234.                 if ($token->test(/* Token::PUNCTUATION_TYPE */ 9'[')) {
  235.                     $node $this->parseArrayExpression();
  236.                 } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9'{')) {
  237.                     $node $this->parseHashExpression();
  238.                 } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8'=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
  239.                     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());
  240.                 } else {
  241.                     throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".'Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
  242.                 }
  243.         }
  244.         return $this->parsePostfixExpression($node);
  245.     }
  246.     public function parseStringExpression()
  247.     {
  248.         $stream $this->parser->getStream();
  249.         $nodes = [];
  250.         // a string cannot be followed by another string in a single expression
  251.         $nextCanBeString true;
  252.         while (true) {
  253.             if ($nextCanBeString && $token $stream->nextIf(/* Token::STRING_TYPE */ 7)) {
  254.                 $nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
  255.                 $nextCanBeString false;
  256.             } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
  257.                 $nodes[] = $this->parseExpression();
  258.                 $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11);
  259.                 $nextCanBeString true;
  260.             } else {
  261.                 break;
  262.             }
  263.         }
  264.         $expr array_shift($nodes);
  265.         foreach ($nodes as $node) {
  266.             $expr = new ConcatBinary($expr$node$node->getTemplateLine());
  267.         }
  268.         return $expr;
  269.     }
  270.     public function parseArrayExpression()
  271.     {
  272.         $stream $this->parser->getStream();
  273.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'[''An array element was expected');
  274.         $node = new ArrayExpression([], $stream->getCurrent()->getLine());
  275.         $first true;
  276.         while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9']')) {
  277.             if (!$first) {
  278.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9',''An array element must be followed by a comma');
  279.                 // trailing ,?
  280.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9']')) {
  281.                     break;
  282.                 }
  283.             }
  284.             $first false;
  285.             $node->addElement($this->parseExpression());
  286.         }
  287.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9']''An opened array is not properly closed');
  288.         return $node;
  289.     }
  290.     public function parseHashExpression()
  291.     {
  292.         $stream $this->parser->getStream();
  293.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'{''A hash element was expected');
  294.         $node = new ArrayExpression([], $stream->getCurrent()->getLine());
  295.         $first true;
  296.         while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9'}')) {
  297.             if (!$first) {
  298.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9',''A hash value must be followed by a comma');
  299.                 // trailing ,?
  300.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'}')) {
  301.                     break;
  302.                 }
  303.             }
  304.             $first false;
  305.             // a hash key can be:
  306.             //
  307.             //  * a number -- 12
  308.             //  * a string -- 'a'
  309.             //  * a name, which is equivalent to a string -- a
  310.             //  * an expression, which must be enclosed in parentheses -- (1 + 2)
  311.             if ($token $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
  312.                 $key = new ConstantExpression($token->getValue(), $token->getLine());
  313.                 // {a} is a shortcut for {a:a}
  314.                 if ($stream->test(Token::PUNCTUATION_TYPE, [',''}'])) {
  315.                     $value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
  316.                     $node->addElement($value$key);
  317.                     continue;
  318.                 }
  319.             } elseif (($token $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
  320.                 $key = new ConstantExpression($token->getValue(), $token->getLine());
  321.             } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  322.                 $key $this->parseExpression();
  323.             } else {
  324.                 $current $stream->getCurrent();
  325.                 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());
  326.             }
  327.             $stream->expect(/* Token::PUNCTUATION_TYPE */ 9':''A hash key must be followed by a colon (:)');
  328.             $value $this->parseExpression();
  329.             $node->addElement($value$key);
  330.         }
  331.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'}''An opened hash is not properly closed');
  332.         return $node;
  333.     }
  334.     public function parsePostfixExpression($node)
  335.     {
  336.         while (true) {
  337.             $token $this->parser->getCurrentToken();
  338.             if (/* Token::PUNCTUATION_TYPE */ == $token->getType()) {
  339.                 if ('.' == $token->getValue() || '[' == $token->getValue()) {
  340.                     $node $this->parseSubscriptExpression($node);
  341.                 } elseif ('|' == $token->getValue()) {
  342.                     $node $this->parseFilterExpression($node);
  343.                 } else {
  344.                     break;
  345.                 }
  346.             } else {
  347.                 break;
  348.             }
  349.         }
  350.         return $node;
  351.     }
  352.     public function getFunctionNode($name$line)
  353.     {
  354.         switch ($name) {
  355.             case 'parent':
  356.                 $this->parseArguments();
  357.                 if (!\count($this->parser->getBlockStack())) {
  358.                     throw new SyntaxError('Calling "parent" outside a block is forbidden.'$line$this->parser->getStream()->getSourceContext());
  359.                 }
  360.                 if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
  361.                     throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.'$line$this->parser->getStream()->getSourceContext());
  362.                 }
  363.                 return new ParentExpression($this->parser->peekBlockStack(), $line);
  364.             case 'block':
  365.                 $args $this->parseArguments();
  366.                 if (\count($args) < 1) {
  367.                     throw new SyntaxError('The "block" function takes one argument (the block name).'$line$this->parser->getStream()->getSourceContext());
  368.                 }
  369.                 return new BlockReferenceExpression($args->getNode(0), \count($args) > $args->getNode(1) : null$line);
  370.             case 'attribute':
  371.                 $args $this->parseArguments();
  372.                 if (\count($args) < 2) {
  373.                     throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).'$line$this->parser->getStream()->getSourceContext());
  374.                 }
  375.                 return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > $args->getNode(2) : nullTemplate::ANY_CALL$line);
  376.             default:
  377.                 if (null !== $alias $this->parser->getImportedSymbol('function'$name)) {
  378.                     $arguments = new ArrayExpression([], $line);
  379.                     foreach ($this->parseArguments() as $n) {
  380.                         $arguments->addElement($n);
  381.                     }
  382.                     $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments$line);
  383.                     $node->setAttribute('safe'true);
  384.                     return $node;
  385.                 }
  386.                 $args $this->parseArguments(true);
  387.                 $class $this->getFunctionNodeClass($name$line);
  388.                 return new $class($name$args$line);
  389.         }
  390.     }
  391.     public function parseSubscriptExpression($node)
  392.     {
  393.         $stream $this->parser->getStream();
  394.         $token $stream->next();
  395.         $lineno $token->getLine();
  396.         $arguments = new ArrayExpression([], $lineno);
  397.         $type Template::ANY_CALL;
  398.         if ('.' == $token->getValue()) {
  399.             $token $stream->next();
  400.             if (
  401.                 /* Token::NAME_TYPE */ == $token->getType()
  402.                 ||
  403.                 /* Token::NUMBER_TYPE */ == $token->getType()
  404.                 ||
  405.                 (/* Token::OPERATOR_TYPE */ == $token->getType() && preg_match(Lexer::REGEX_NAME$token->getValue()))
  406.             ) {
  407.                 $arg = new ConstantExpression($token->getValue(), $lineno);
  408.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  409.                     $type Template::METHOD_CALL;
  410.                     foreach ($this->parseArguments() as $n) {
  411.                         $arguments->addElement($n);
  412.                     }
  413.                 }
  414.             } else {
  415.                 throw new SyntaxError('Expected name or number.'$lineno$stream->getSourceContext());
  416.             }
  417.             if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template'$node->getAttribute('name'))) {
  418.                 if (!$arg instanceof ConstantExpression) {
  419.                     throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").'$node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
  420.                 }
  421.                 $name $arg->getAttribute('value');
  422.                 $node = new MethodCallExpression($node'macro_'.$name$arguments$lineno);
  423.                 $node->setAttribute('safe'true);
  424.                 return $node;
  425.             }
  426.         } else {
  427.             $type Template::ARRAY_CALL;
  428.             // slice?
  429.             $slice false;
  430.             if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9':')) {
  431.                 $slice true;
  432.                 $arg = new ConstantExpression(0$token->getLine());
  433.             } else {
  434.                 $arg $this->parseExpression();
  435.             }
  436.             if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9':')) {
  437.                 $slice true;
  438.             }
  439.             if ($slice) {
  440.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9']')) {
  441.                     $length = new ConstantExpression(null$token->getLine());
  442.                 } else {
  443.                     $length $this->parseExpression();
  444.                 }
  445.                 $class $this->getFilterNodeClass('slice'$token->getLine());
  446.                 $arguments = new Node([$arg$length]);
  447.                 $filter = new $class($node, new ConstantExpression('slice'$token->getLine()), $arguments$token->getLine());
  448.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9']');
  449.                 return $filter;
  450.             }
  451.             $stream->expect(/* Token::PUNCTUATION_TYPE */ 9']');
  452.         }
  453.         return new GetAttrExpression($node$arg$arguments$type$lineno);
  454.     }
  455.     public function parseFilterExpression($node)
  456.     {
  457.         $this->parser->getStream()->next();
  458.         return $this->parseFilterExpressionRaw($node);
  459.     }
  460.     public function parseFilterExpressionRaw($node$tag null)
  461.     {
  462.         while (true) {
  463.             $token $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
  464.             $name = new ConstantExpression($token->getValue(), $token->getLine());
  465.             if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  466.                 $arguments = new Node();
  467.             } else {
  468.                 $arguments $this->parseArguments(truefalsetrue);
  469.             }
  470.             $class $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
  471.             $node = new $class($node$name$arguments$token->getLine(), $tag);
  472.             if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9'|')) {
  473.                 break;
  474.             }
  475.             $this->parser->getStream()->next();
  476.         }
  477.         return $node;
  478.     }
  479.     /**
  480.      * Parses arguments.
  481.      *
  482.      * @param bool $namedArguments Whether to allow named arguments or not
  483.      * @param bool $definition     Whether we are parsing arguments for a function definition
  484.      *
  485.      * @return Node
  486.      *
  487.      * @throws SyntaxError
  488.      */
  489.     public function parseArguments($namedArguments false$definition false$allowArrow false)
  490.     {
  491.         $args = [];
  492.         $stream $this->parser->getStream();
  493.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9'(''A list of arguments must begin with an opening parenthesis');
  494.         while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9')')) {
  495.             if (!empty($args)) {
  496.                 $stream->expect(/* Token::PUNCTUATION_TYPE */ 9',''Arguments must be separated by a comma');
  497.                 // if the comma above was a trailing comma, early exit the argument parse loop
  498.                 if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9')')) {
  499.                     break;
  500.                 }
  501.             }
  502.             if ($definition) {
  503.                 $token $stream->expect(/* Token::NAME_TYPE */ 5null'An argument must be a name');
  504.                 $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
  505.             } else {
  506.                 $value $this->parseExpression(0$allowArrow);
  507.             }
  508.             $name null;
  509.             if ($namedArguments && $token $stream->nextIf(/* Token::OPERATOR_TYPE */ 8'=')) {
  510.                 if (!$value instanceof NameExpression) {
  511.                     throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
  512.                 }
  513.                 $name $value->getAttribute('name');
  514.                 if ($definition) {
  515.                     $value $this->parsePrimaryExpression();
  516.                     if (!$this->checkConstantExpression($value)) {
  517.                         throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'$token->getLine(), $stream->getSourceContext());
  518.                     }
  519.                 } else {
  520.                     $value $this->parseExpression(0$allowArrow);
  521.                 }
  522.             }
  523.             if ($definition) {
  524.                 if (null === $name) {
  525.                     $name $value->getAttribute('name');
  526.                     $value = new ConstantExpression(null$this->parser->getCurrentToken()->getLine());
  527.                 }
  528.                 $args[$name] = $value;
  529.             } else {
  530.                 if (null === $name) {
  531.                     $args[] = $value;
  532.                 } else {
  533.                     $args[$name] = $value;
  534.                 }
  535.             }
  536.         }
  537.         $stream->expect(/* Token::PUNCTUATION_TYPE */ 9')''A list of arguments must be closed by a parenthesis');
  538.         return new Node($args);
  539.     }
  540.     public function parseAssignmentExpression()
  541.     {
  542.         $stream $this->parser->getStream();
  543.         $targets = [];
  544.         while (true) {
  545.             $token $this->parser->getCurrentToken();
  546.             if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME$token->getValue())) {
  547.                 // in this context, string operators are variable names
  548.                 $this->parser->getStream()->next();
  549.             } else {
  550.                 $stream->expect(/* Token::NAME_TYPE */ 5null'Only variables can be assigned to');
  551.             }
  552.             $value $token->getValue();
  553.             if (\in_array(strtr($value'ABCDEFGHIJKLMNOPQRSTUVWXYZ''abcdefghijklmnopqrstuvwxyz'), ['true''false''none''null'])) {
  554.                 throw new SyntaxError(sprintf('You cannot assign a value to "%s".'$value), $token->getLine(), $stream->getSourceContext());
  555.             }
  556.             $targets[] = new AssignNameExpression($value$token->getLine());
  557.             if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9',')) {
  558.                 break;
  559.             }
  560.         }
  561.         return new Node($targets);
  562.     }
  563.     public function parseMultitargetExpression()
  564.     {
  565.         $targets = [];
  566.         while (true) {
  567.             $targets[] = $this->parseExpression();
  568.             if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9',')) {
  569.                 break;
  570.             }
  571.         }
  572.         return new Node($targets);
  573.     }
  574.     private function parseNotTestExpression(Node $node): NotUnary
  575.     {
  576.         return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
  577.     }
  578.     private function parseTestExpression(Node $node): TestExpression
  579.     {
  580.         $stream $this->parser->getStream();
  581.         list($name$test) = $this->getTest($node->getTemplateLine());
  582.         $class $this->getTestNodeClass($test);
  583.         $arguments null;
  584.         if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9'(')) {
  585.             $arguments $this->parseArguments(true);
  586.         } elseif ($test->hasOneMandatoryArgument()) {
  587.             $arguments = new Node([=> $this->parsePrimaryExpression()]);
  588.         }
  589.         if ('defined' === $name && $node instanceof NameExpression && null !== $alias $this->parser->getImportedSymbol('function'$node->getAttribute('name'))) {
  590.             $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
  591.             $node->setAttribute('safe'true);
  592.         }
  593.         return new $class($node$name$arguments$this->parser->getCurrentToken()->getLine());
  594.     }
  595.     private function getTest(int $line): array
  596.     {
  597.         $stream $this->parser->getStream();
  598.         $name $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
  599.         if ($test $this->env->getTest($name)) {
  600.             return [$name$test];
  601.         }
  602.         if ($stream->test(/* Token::NAME_TYPE */ 5)) {
  603.             // try 2-words tests
  604.             $name $name.' '.$this->parser->getCurrentToken()->getValue();
  605.             if ($test $this->env->getTest($name)) {
  606.                 $stream->next();
  607.                 return [$name$test];
  608.             }
  609.         }
  610.         $e = new SyntaxError(sprintf('Unknown "%s" test.'$name), $line$stream->getSourceContext());
  611.         $e->addSuggestions($namearray_keys($this->env->getTests()));
  612.         throw $e;
  613.     }
  614.     private function getTestNodeClass(TwigTest $test): string
  615.     {
  616.         if ($test->isDeprecated()) {
  617.             $stream $this->parser->getStream();
  618.             $message sprintf('Twig Test "%s" is deprecated'$test->getName());
  619.             if ($test->getDeprecatedVersion()) {
  620.                 $message .= sprintf(' since version %s'$test->getDeprecatedVersion());
  621.             }
  622.             if ($test->getAlternative()) {
  623.                 $message .= sprintf('. Use "%s" instead'$test->getAlternative());
  624.             }
  625.             $src $stream->getSourceContext();
  626.             $message .= sprintf(' in %s at line %d.'$src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
  627.             @trigger_error($message, \E_USER_DEPRECATED);
  628.         }
  629.         return $test->getNodeClass();
  630.     }
  631.     private function getFunctionNodeClass(string $nameint $line): string
  632.     {
  633.         if (!$function $this->env->getFunction($name)) {
  634.             $e = new SyntaxError(sprintf('Unknown "%s" function.'$name), $line$this->parser->getStream()->getSourceContext());
  635.             $e->addSuggestions($namearray_keys($this->env->getFunctions()));
  636.             throw $e;
  637.         }
  638.         if ($function->isDeprecated()) {
  639.             $message sprintf('Twig Function "%s" is deprecated'$function->getName());
  640.             if ($function->getDeprecatedVersion()) {
  641.                 $message .= sprintf(' since version %s'$function->getDeprecatedVersion());
  642.             }
  643.             if ($function->getAlternative()) {
  644.                 $message .= sprintf('. Use "%s" instead'$function->getAlternative());
  645.             }
  646.             $src $this->parser->getStream()->getSourceContext();
  647.             $message .= sprintf(' in %s at line %d.'$src->getPath() ?: $src->getName(), $line);
  648.             @trigger_error($message, \E_USER_DEPRECATED);
  649.         }
  650.         return $function->getNodeClass();
  651.     }
  652.     private function getFilterNodeClass(string $nameint $line): string
  653.     {
  654.         if (!$filter $this->env->getFilter($name)) {
  655.             $e = new SyntaxError(sprintf('Unknown "%s" filter.'$name), $line$this->parser->getStream()->getSourceContext());
  656.             $e->addSuggestions($namearray_keys($this->env->getFilters()));
  657.             throw $e;
  658.         }
  659.         if ($filter->isDeprecated()) {
  660.             $message sprintf('Twig Filter "%s" is deprecated'$filter->getName());
  661.             if ($filter->getDeprecatedVersion()) {
  662.                 $message .= sprintf(' since version %s'$filter->getDeprecatedVersion());
  663.             }
  664.             if ($filter->getAlternative()) {
  665.                 $message .= sprintf('. Use "%s" instead'$filter->getAlternative());
  666.             }
  667.             $src $this->parser->getStream()->getSourceContext();
  668.             $message .= sprintf(' in %s at line %d.'$src->getPath() ?: $src->getName(), $line);
  669.             @trigger_error($message, \E_USER_DEPRECATED);
  670.         }
  671.         return $filter->getNodeClass();
  672.     }
  673.     // checks that the node only contains "constant" elements
  674.     private function checkConstantExpression(Node $node): bool
  675.     {
  676.         if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
  677.             || $node instanceof NegUnary || $node instanceof PosUnary
  678.         )) {
  679.             return false;
  680.         }
  681.         foreach ($node as $n) {
  682.             if (!$this->checkConstantExpression($n)) {
  683.                 return false;
  684.             }
  685.         }
  686.         return true;
  687.     }
  688. }