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

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\ExpressionParser\Infix\DotExpressionParser;
  14. use Twig\ExpressionParser\Infix\FilterExpressionParser;
  15. use Twig\ExpressionParser\Infix\SquareBracketExpressionParser;
  16. use Twig\Node\Expression\ArrayExpression;
  17. use Twig\Node\Expression\ConstantExpression;
  18. use Twig\Node\Expression\Unary\NegUnary;
  19. use Twig\Node\Expression\Unary\PosUnary;
  20. use Twig\Node\Expression\Unary\SpreadUnary;
  21. use Twig\Node\Expression\Variable\AssignContextVariable;
  22. use Twig\Node\Expression\Variable\ContextVariable;
  23. use Twig\Node\Node;
  24. use Twig\Node\Nodes;
  25. /**
  26.  * Parses expressions.
  27.  *
  28.  * This parser implements a "Precedence climbing" algorithm.
  29.  *
  30.  * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
  31.  * @see https://en.wikipedia.org/wiki/Operator-precedence_parser
  32.  *
  33.  * @author Fabien Potencier <fabien@symfony.com>
  34.  *
  35.  * @deprecated since Twig 3.21
  36.  */
  37. class ExpressionParser
  38. {
  39.     /**
  40.      * @deprecated since Twig 3.21
  41.      */
  42.     public const OPERATOR_LEFT 1;
  43.     /**
  44.      * @deprecated since Twig 3.21
  45.      */
  46.     public const OPERATOR_RIGHT 2;
  47.     public function __construct(
  48.         private Parser $parser,
  49.         private Environment $env,
  50.     ) {
  51.         trigger_deprecation('twig/twig''3.21''Class "%s" is deprecated, use "Parser::parseExpression()" instead.'__CLASS__);
  52.     }
  53.     public function parseExpression($precedence 0)
  54.     {
  55.         if (\func_num_args() > 1) {
  56.             trigger_deprecation('twig/twig''3.15''Passing a second argument ($allowArrow) to "%s()" is deprecated.'__METHOD__);
  57.         }
  58.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated, use "Parser::parseExpression()" instead.'__METHOD__);
  59.         return $this->parser->parseExpression((int) $precedence);
  60.     }
  61.     /**
  62.      * @deprecated since Twig 3.21
  63.      */
  64.     public function parsePrimaryExpression()
  65.     {
  66.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  67.         return $this->parseExpression();
  68.     }
  69.     /**
  70.      * @deprecated since Twig 3.21
  71.      */
  72.     public function parseStringExpression()
  73.     {
  74.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  75.         return $this->parseExpression();
  76.     }
  77.     /**
  78.      * @deprecated since Twig 3.11, use parseExpression() instead
  79.      */
  80.     public function parseArrayExpression()
  81.     {
  82.         trigger_deprecation('twig/twig''3.11''Calling "%s()" is deprecated, use "parseExpression()" instead.'__METHOD__);
  83.         return $this->parseExpression();
  84.     }
  85.     /**
  86.      * @deprecated since Twig 3.21
  87.      */
  88.     public function parseSequenceExpression()
  89.     {
  90.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  91.         return $this->parseExpression();
  92.     }
  93.     /**
  94.      * @deprecated since Twig 3.11, use parseExpression() instead
  95.      */
  96.     public function parseHashExpression()
  97.     {
  98.         trigger_deprecation('twig/twig''3.11''Calling "%s()" is deprecated, use "parseExpression()" instead.'__METHOD__);
  99.         return $this->parseExpression();
  100.     }
  101.     /**
  102.      * @deprecated since Twig 3.21
  103.      */
  104.     public function parseMappingExpression()
  105.     {
  106.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  107.         return $this->parseExpression();
  108.     }
  109.     /**
  110.      * @deprecated since Twig 3.21
  111.      */
  112.     public function parsePostfixExpression($node)
  113.     {
  114.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  115.         while (true) {
  116.             $token $this->parser->getCurrentToken();
  117.             if ($token->test(Token::PUNCTUATION_TYPE)) {
  118.                 if ('.' == $token->getValue() || '[' == $token->getValue()) {
  119.                     $node $this->parseSubscriptExpression($node);
  120.                 } elseif ('|' == $token->getValue()) {
  121.                     $node $this->parseFilterExpression($node);
  122.                 } else {
  123.                     break;
  124.                 }
  125.             } else {
  126.                 break;
  127.             }
  128.         }
  129.         return $node;
  130.     }
  131.     /**
  132.      * @deprecated since Twig 3.21
  133.      */
  134.     public function parseSubscriptExpression($node)
  135.     {
  136.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  137.         $parsers = new \ReflectionProperty($this->parser'parsers');
  138.         if ('.' === $this->parser->getStream()->next()->getValue()) {
  139.             return $parsers->getValue($this->parser)->getByClass(DotExpressionParser::class)->parse($this->parser$node$this->parser->getCurrentToken());
  140.         }
  141.         return $parsers->getValue($this->parser)->getByClass(SquareBracketExpressionParser::class)->parse($this->parser$node$this->parser->getCurrentToken());
  142.     }
  143.     /**
  144.      * @deprecated since Twig 3.21
  145.      */
  146.     public function parseFilterExpression($node)
  147.     {
  148.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  149.         $this->parser->getStream()->next();
  150.         return $this->parseFilterExpressionRaw($node);
  151.     }
  152.     /**
  153.      * @deprecated since Twig 3.21
  154.      */
  155.     public function parseFilterExpressionRaw($node)
  156.     {
  157.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  158.         $parsers = new \ReflectionProperty($this->parser'parsers');
  159.         $op $parsers->getValue($this->parser)->getByClass(FilterExpressionParser::class);
  160.         while (true) {
  161.             $node $op->parse($this->parser$node$this->parser->getCurrentToken());
  162.             if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE'|')) {
  163.                 break;
  164.             }
  165.             $this->parser->getStream()->next();
  166.         }
  167.         return $node;
  168.     }
  169.     /**
  170.      * Parses arguments.
  171.      *
  172.      * @return Node
  173.      *
  174.      * @throws SyntaxError
  175.      *
  176.      * @deprecated since Twig 3.19 Use Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments() instead
  177.      */
  178.     public function parseArguments()
  179.     {
  180.         trigger_deprecation('twig/twig''3.19'\sprintf('The "%s()" method is deprecated, use "Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments()" instead.'__METHOD__));
  181.         $parsePrimary = new \ReflectionMethod($this->parser'parsePrimary');
  182.         $namedArguments false;
  183.         $definition false;
  184.         if (\func_num_args() > 1) {
  185.             $definition func_get_arg(1);
  186.         }
  187.         if (\func_num_args() > 0) {
  188.             trigger_deprecation('twig/twig''3.15''Passing arguments to "%s()" is deprecated.'__METHOD__);
  189.             $namedArguments func_get_arg(0);
  190.         }
  191.         $args = [];
  192.         $stream $this->parser->getStream();
  193.         $stream->expect(Token::OPERATOR_TYPE'(''A list of arguments must begin with an opening parenthesis');
  194.         $hasSpread false;
  195.         while (!$stream->test(Token::PUNCTUATION_TYPE')')) {
  196.             if ($args) {
  197.                 $stream->expect(Token::PUNCTUATION_TYPE',''Arguments must be separated by a comma');
  198.                 // if the comma above was a trailing comma, early exit the argument parse loop
  199.                 if ($stream->test(Token::PUNCTUATION_TYPE')')) {
  200.                     break;
  201.                 }
  202.             }
  203.             if ($definition) {
  204.                 $token $stream->expect(Token::NAME_TYPEnull'An argument must be a name');
  205.                 $value = new ContextVariable($token->getValue(), $this->parser->getCurrentToken()->getLine());
  206.             } else {
  207.                 if ($stream->nextIf(Token::SPREAD_TYPE)) {
  208.                     $hasSpread true;
  209.                     $value = new SpreadUnary($this->parseExpression(), $stream->getCurrent()->getLine());
  210.                 } elseif ($hasSpread) {
  211.                     throw new SyntaxError('Normal arguments must be placed before argument unpacking.'$stream->getCurrent()->getLine(), $stream->getSourceContext());
  212.                 } else {
  213.                     $value $this->parseExpression();
  214.                 }
  215.             }
  216.             $name null;
  217.             if ($namedArguments && (($token $stream->nextIf(Token::OPERATOR_TYPE'=')) || (!$definition && $token $stream->nextIf(Token::PUNCTUATION_TYPE':')))) {
  218.                 if (!$value instanceof ContextVariable) {
  219.                     throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.'$value::class), $token->getLine(), $stream->getSourceContext());
  220.                 }
  221.                 $name $value->getAttribute('name');
  222.                 if ($definition) {
  223.                     $value $parsePrimary->invoke($this->parser);
  224.                     if (!$this->checkConstantExpression($value)) {
  225.                         throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).'$token->getLine(), $stream->getSourceContext());
  226.                     }
  227.                 } else {
  228.                     $value $this->parseExpression();
  229.                 }
  230.             }
  231.             if ($definition) {
  232.                 if (null === $name) {
  233.                     $name $value->getAttribute('name');
  234.                     $value = new ConstantExpression(null$this->parser->getCurrentToken()->getLine());
  235.                     $value->setAttribute('is_implicit'true);
  236.                 }
  237.                 $args[$name] = $value;
  238.             } else {
  239.                 if (null === $name) {
  240.                     $args[] = $value;
  241.                 } else {
  242.                     $args[$name] = $value;
  243.                 }
  244.             }
  245.         }
  246.         $stream->expect(Token::PUNCTUATION_TYPE')''A list of arguments must be closed by a parenthesis');
  247.         return new Nodes($args);
  248.     }
  249.     /**
  250.      * @deprecated since Twig 3.21, use "AbstractTokenParser::parseAssignmentExpression()" instead
  251.      */
  252.     public function parseAssignmentExpression()
  253.     {
  254.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated, use "AbstractTokenParser::parseAssignmentExpression()" instead.'__METHOD__);
  255.         $stream $this->parser->getStream();
  256.         $targets = [];
  257.         while (true) {
  258.             $token $this->parser->getCurrentToken();
  259.             if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME$token->getValue())) {
  260.                 // in this context, string operators are variable names
  261.                 $this->parser->getStream()->next();
  262.             } else {
  263.                 $stream->expect(Token::NAME_TYPEnull'Only variables can be assigned to');
  264.             }
  265.             $targets[] = new AssignContextVariable($token->getValue(), $token->getLine());
  266.             if (!$stream->nextIf(Token::PUNCTUATION_TYPE',')) {
  267.                 break;
  268.             }
  269.         }
  270.         return new Nodes($targets);
  271.     }
  272.     /**
  273.      * @deprecated since Twig 3.21
  274.      */
  275.     public function parseMultitargetExpression()
  276.     {
  277.         trigger_deprecation('twig/twig''3.21''The "%s()" method is deprecated.'__METHOD__);
  278.         $targets = [];
  279.         while (true) {
  280.             $targets[] = $this->parseExpression();
  281.             if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE',')) {
  282.                 break;
  283.             }
  284.         }
  285.         return new Nodes($targets);
  286.     }
  287.     // checks that the node only contains "constant" elements
  288.     // to be removed in 4.0
  289.     private function checkConstantExpression(Node $node): bool
  290.     {
  291.         if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
  292.             || $node instanceof NegUnary || $node instanceof PosUnary
  293.         )) {
  294.             return false;
  295.         }
  296.         foreach ($node as $n) {
  297.             if (!$this->checkConstantExpression($n)) {
  298.                 return false;
  299.             }
  300.         }
  301.         return true;
  302.     }
  303.     /**
  304.      * @deprecated since Twig 3.19 Use Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments() instead
  305.      */
  306.     public function parseOnlyArguments()
  307.     {
  308.         trigger_deprecation('twig/twig''3.19'\sprintf('The "%s()" method is deprecated, use "Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments()" instead.'__METHOD__));
  309.         return $this->parseArguments();
  310.     }
  311. }