vendor/twig/twig/src/ExpressionParser.php line 813
<?php/** This file is part of Twig.** (c) Fabien Potencier* (c) Armin Ronacher** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace Twig;use Twig\Attribute\FirstClassTwigCallableReady;use Twig\Error\SyntaxError;use Twig\Node\Expression\AbstractExpression;use Twig\Node\Expression\ArrayExpression;use Twig\Node\Expression\ArrowFunctionExpression;use Twig\Node\Expression\AssignNameExpression;use Twig\Node\Expression\Binary\AbstractBinary;use Twig\Node\Expression\Binary\ConcatBinary;use Twig\Node\Expression\ConditionalExpression;use Twig\Node\Expression\ConstantExpression;use Twig\Node\Expression\GetAttrExpression;use Twig\Node\Expression\MethodCallExpression;use Twig\Node\Expression\NameExpression;use Twig\Node\Expression\TestExpression;use Twig\Node\Expression\Unary\AbstractUnary;use Twig\Node\Expression\Unary\NegUnary;use Twig\Node\Expression\Unary\NotUnary;use Twig\Node\Expression\Unary\PosUnary;use Twig\Node\Node;/*** Parses expressions.** This parser implements a "Precedence climbing" algorithm.** @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm* @see https://en.wikipedia.org/wiki/Operator-precedence_parser** @author Fabien Potencier <fabien@symfony.com>*/class ExpressionParser{public const OPERATOR_LEFT = 1;public const OPERATOR_RIGHT = 2;/** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */private $unaryOperators;/** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */private $binaryOperators;private $readyNodes = [];public function __construct(private Parser $parser,private Environment $env,) {$this->unaryOperators = $env->getUnaryOperators();$this->binaryOperators = $env->getBinaryOperators();}public function parseExpression($precedence = 0, $allowArrow = false){if ($allowArrow && $arrow = $this->parseArrow()) {return $arrow;}$expr = $this->getPrimary();$token = $this->parser->getCurrentToken();while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {$op = $this->binaryOperators[$token->getValue()];$this->parser->getStream()->next();if ('is not' === $token->getValue()) {$expr = $this->parseNotTestExpression($expr);} elseif ('is' === $token->getValue()) {$expr = $this->parseTestExpression($expr);} elseif (isset($op['callable'])) {$expr = $op['callable']($this->parser, $expr);} else {$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], true);$class = $op['class'];$expr = new $class($expr, $expr1, $token->getLine());}$token = $this->parser->getCurrentToken();}if (0 === $precedence) {return $this->parseConditionalExpression($expr);}return $expr;}/*** @return ArrowFunctionExpression|null*/private function parseArrow(){$stream = $this->parser->getStream();// short array syntax (one argument, no parentheses)?if ($stream->look(1)->test(Token::ARROW_TYPE)) {$line = $stream->getCurrent()->getLine();$token = $stream->expect(Token::NAME_TYPE);$names = [new AssignNameExpression($token->getValue(), $token->getLine())];$stream->expect(Token::ARROW_TYPE);return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);}// first, determine if we are parsing an arrow function by finding => (long form)$i = 0;if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) {return null;}++$i;while (true) {// variable name++$i;if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) {break;}++$i;}if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) {return null;}++$i;if (!$stream->look($i)->test(Token::ARROW_TYPE)) {return null;}// yes, let's parse it properly$token = $stream->expect(Token::PUNCTUATION_TYPE, '(');$line = $token->getLine();$names = [];while (true) {$token = $stream->expect(Token::NAME_TYPE);$names[] = new AssignNameExpression($token->getValue(), $token->getLine());if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {break;}}$stream->expect(Token::PUNCTUATION_TYPE, ')');$stream->expect(Token::ARROW_TYPE);return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);}private function getPrimary(): AbstractExpression{$token = $this->parser->getCurrentToken();if ($this->isUnary($token)) {$operator = $this->unaryOperators[$token->getValue()];$this->parser->getStream()->next();$expr = $this->parseExpression($operator['precedence']);$class = $operator['class'];return $this->parsePostfixExpression(new $class($expr, $token->getLine()));} elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) {$this->parser->getStream()->next();$expr = $this->parseExpression();$this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');return $this->parsePostfixExpression($expr);}return $this->parsePrimaryExpression();}private function parseConditionalExpression($expr): AbstractExpression{while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) {if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {$expr2 = $this->parseExpression();if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {// Ternary operator (expr ? expr2 : expr3)$expr3 = $this->parseExpression();} else {// Ternary without else (expr ? expr2)$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());}} else {// Ternary without then (expr ?: expr3)$expr2 = $expr;$expr3 = $this->parseExpression();}$expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());}return $expr;}private function isUnary(Token $token): bool{return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);}private function isBinary(Token $token): bool{return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);}public function parsePrimaryExpression(){$token = $this->parser->getCurrentToken();switch ($token->getType()) {case Token::NAME_TYPE:$this->parser->getStream()->next();switch ($token->getValue()) {case 'true':case 'TRUE':$node = new ConstantExpression(true, $token->getLine());break;case 'false':case 'FALSE':$node = new ConstantExpression(false, $token->getLine());break;case 'none':case 'NONE':case 'null':case 'NULL':$node = new ConstantExpression(null, $token->getLine());break;default:if ('(' === $this->parser->getCurrentToken()->getValue()) {$node = $this->getFunctionNode($token->getValue(), $token->getLine());} else {$node = new NameExpression($token->getValue(), $token->getLine());}}break;case Token::NUMBER_TYPE:$this->parser->getStream()->next();$node = new ConstantExpression($token->getValue(), $token->getLine());break;case Token::STRING_TYPE:case Token::INTERPOLATION_START_TYPE:$node = $this->parseStringExpression();break;case Token::OPERATOR_TYPE:if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {// in this context, string operators are variable names$this->parser->getStream()->next();$node = new NameExpression($token->getValue(), $token->getLine());break;}if (isset($this->unaryOperators[$token->getValue()])) {$class = $this->unaryOperators[$token->getValue()]['class'];if (!\in_array($class, [NegUnary::class, PosUnary::class])) {throw new SyntaxError(\sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());}$this->parser->getStream()->next();$expr = $this->parsePrimaryExpression();$node = new $class($expr, $token->getLine());break;}// no breakdefault:if ($token->test(Token::PUNCTUATION_TYPE, '[')) {$node = $this->parseSequenceExpression();} elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {$node = $this->parseMappingExpression();} elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {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());} else {throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());}}return $this->parsePostfixExpression($node);}public function parseStringExpression(){$stream = $this->parser->getStream();$nodes = [];// a string cannot be followed by another string in a single expression$nextCanBeString = true;while (true) {if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) {$nodes[] = new ConstantExpression($token->getValue(), $token->getLine());$nextCanBeString = false;} elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) {$nodes[] = $this->parseExpression();$stream->expect(Token::INTERPOLATION_END_TYPE);$nextCanBeString = true;} else {break;}}$expr = array_shift($nodes);foreach ($nodes as $node) {$expr = new ConcatBinary($expr, $node, $node->getTemplateLine());}return $expr;}/*** @deprecated since 3.11, use parseSequenceExpression() instead*/public function parseArrayExpression(){trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__);return $this->parseSequenceExpression();}public function parseSequenceExpression(){$stream = $this->parser->getStream();$stream->expect(Token::PUNCTUATION_TYPE, '[', 'A sequence element was expected');$node = new ArrayExpression([], $stream->getCurrent()->getLine());$first = true;while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) {if (!$first) {$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma');// trailing ,?if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {break;}}$first = false;if ($stream->test(Token::SPREAD_TYPE)) {$stream->next();$expr = $this->parseExpression();$expr->setAttribute('spread', true);$node->addElement($expr);} else {$node->addElement($this->parseExpression());}}$stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed');return $node;}/*** @deprecated since 3.11, use parseMappingExpression() instead*/public function parseHashExpression(){trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__);return $this->parseMappingExpression();}public function parseMappingExpression(){$stream = $this->parser->getStream();$stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected');$node = new ArrayExpression([], $stream->getCurrent()->getLine());$first = true;while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) {if (!$first) {$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma');// trailing ,?if ($stream->test(Token::PUNCTUATION_TYPE, '}')) {break;}}$first = false;if ($stream->test(Token::SPREAD_TYPE)) {$stream->next();$value = $this->parseExpression();$value->setAttribute('spread', true);$node->addElement($value);continue;}// a mapping key can be://// * a number -- 12// * a string -- 'a'// * a name, which is equivalent to a string -- a// * an expression, which must be enclosed in parentheses -- (1 + 2)if ($token = $stream->nextIf(Token::NAME_TYPE)) {$key = new ConstantExpression($token->getValue(), $token->getLine());// {a} is a shortcut for {a:a}if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {$value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());$node->addElement($value, $key);continue;}} elseif (($token = $stream->nextIf(Token::STRING_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) {$key = new ConstantExpression($token->getValue(), $token->getLine());} elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) {$key = $this->parseExpression();} else {$current = $stream->getCurrent();throw new SyntaxError(\sprintf('A mapping 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());}$stream->expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)');$value = $this->parseExpression();$node->addElement($value, $key);}$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed');return $node;}public function parsePostfixExpression($node){while (true) {$token = $this->parser->getCurrentToken();if (Token::PUNCTUATION_TYPE == $token->getType()) {if ('.' == $token->getValue() || '[' == $token->getValue()) {$node = $this->parseSubscriptExpression($node);} elseif ('|' == $token->getValue()) {$node = $this->parseFilterExpression($node);} else {break;}} else {break;}}return $node;}public function getFunctionNode($name, $line){if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {$arguments = new ArrayExpression([], $line);foreach ($this->parseArguments() as $n) {$arguments->addElement($n);}$node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);$node->setAttribute('safe', true);return $node;}$args = $this->parseArguments(true);$function = $this->getFunction($name, $line);if ($function->getParserCallable()) {$fakeNode = new Node(lineno: $line);$fakeNode->setSourceContext($this->parser->getStream()->getSourceContext());return ($function->getParserCallable())($this->parser, $fakeNode, $args, $line);}if (!isset($this->readyNodes[$class = $function->getNodeClass()])) {$this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);}if (!$ready = $this->readyNodes[$class]) {trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFunction" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);}return new $class($ready ? $function : $function->getName(), $args, $line);}public function parseSubscriptExpression($node){$stream = $this->parser->getStream();$token = $stream->next();$lineno = $token->getLine();$arguments = new ArrayExpression([], $lineno);$type = Template::ANY_CALL;if ('.' == $token->getValue()) {$token = $stream->next();if (Token::NAME_TYPE == $token->getType()||Token::NUMBER_TYPE == $token->getType()||(Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))) {$arg = new ConstantExpression($token->getValue(), $lineno);if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {$type = Template::METHOD_CALL;foreach ($this->parseArguments() as $n) {$arguments->addElement($n);}}} else {throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());}if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {$name = $arg->getAttribute('value');$node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);$node->setAttribute('safe', true);return $node;}} else {$type = Template::ARRAY_CALL;// slice?$slice = false;if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {$slice = true;$arg = new ConstantExpression(0, $token->getLine());} else {$arg = $this->parseExpression();}if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {$slice = true;}if ($slice) {if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {$length = new ConstantExpression(null, $token->getLine());} else {$length = $this->parseExpression();}$filter = $this->getFilter('slice', $token->getLine());$arguments = new Node([$arg, $length]);$filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine());$stream->expect(Token::PUNCTUATION_TYPE, ']');return $filter;}$stream->expect(Token::PUNCTUATION_TYPE, ']');}return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);}public function parseFilterExpression($node){$this->parser->getStream()->next();return $this->parseFilterExpressionRaw($node);}public function parseFilterExpressionRaw($node){if (\func_num_args() > 1) {trigger_deprecation('twig/twig', '3.12', 'Passing a second argument to "%s()" is deprecated.', __METHOD__);}while (true) {$token = $this->parser->getStream()->expect(Token::NAME_TYPE);if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) {$arguments = new Node();} else {$arguments = $this->parseArguments(true, false, true);}$filter = $this->getFilter($token->getValue(), $token->getLine());$ready = true;if (!isset($this->readyNodes[$class = $filter->getNodeClass()])) {$this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);}if (!$ready = $this->readyNodes[$class]) {trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFilter" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);}$node = new $class($node, $ready ? $filter : new ConstantExpression($filter->getName(), $token->getLine()), $arguments, $token->getLine());if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) {break;}$this->parser->getStream()->next();}return $node;}/*** Parses arguments.** @param bool $namedArguments Whether to allow named arguments or not* @param bool $definition Whether we are parsing arguments for a function (or macro) definition** @return Node** @throws SyntaxError*/public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false){$args = [];$stream = $this->parser->getStream();$stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {if (!empty($args)) {$stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');// if the comma above was a trailing comma, early exit the argument parse loopif ($stream->test(Token::PUNCTUATION_TYPE, ')')) {break;}}if ($definition) {$token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');$value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());} else {$value = $this->parseExpression(0, $allowArrow);}$name = null;if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) {if (!$value instanceof NameExpression) {throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());}$name = $value->getAttribute('name');if ($definition) {$value = $this->parsePrimaryExpression();if (!$this->checkConstantExpression($value)) {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());}} else {$value = $this->parseExpression(0, $allowArrow);}}if ($definition) {if (null === $name) {$name = $value->getAttribute('name');$value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());$value->setAttribute('is_implicit', true);}$args[$name] = $value;} else {if (null === $name) {$args[] = $value;} else {$args[$name] = $value;}}}$stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');return new Node($args);}public function parseAssignmentExpression(){$stream = $this->parser->getStream();$targets = [];while (true) {$token = $this->parser->getCurrentToken();if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {// in this context, string operators are variable names$this->parser->getStream()->next();} else {$stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to');}$value = $token->getValue();if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());}$targets[] = new AssignNameExpression($value, $token->getLine());if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {break;}}return new Node($targets);}public function parseMultitargetExpression(){$targets = [];while (true) {$targets[] = $this->parseExpression();if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) {break;}}return new Node($targets);}private function parseNotTestExpression(Node $node): NotUnary{return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());}private function parseTestExpression(Node $node): TestExpression{$stream = $this->parser->getStream();$test = $this->getTest($node->getTemplateLine());$arguments = null;if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {$arguments = $this->parseArguments(true);} elseif ($test->hasOneMandatoryArgument()) {$arguments = new Node([0 => $this->parsePrimaryExpression()]);}if ('defined' === $test->getName() && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {$node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());$node->setAttribute('safe', true);}$ready = $test instanceof TwigTest;if (!isset($this->readyNodes[$class = $test->getNodeClass()])) {$this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);}if (!$ready = $this->readyNodes[$class]) {trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigTest" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);}return new $class($node, $ready ? $test : $test->getName(), $arguments, $this->parser->getCurrentToken()->getLine());}private function getTest(int $line): TwigTest{$stream = $this->parser->getStream();$name = $stream->expect(Token::NAME_TYPE)->getValue();if ($stream->test(Token::NAME_TYPE)) {// try 2-words tests$name = $name.' '.$this->parser->getCurrentToken()->getValue();if ($test = $this->env->getTest($name)) {$stream->next();}} else {$test = $this->env->getTest($name);}if (!$test) {$e = new SyntaxError(\sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext());$e->addSuggestions($name, array_keys($this->env->getTests()));throw $e;}if ($test->isDeprecated()) {$stream = $this->parser->getStream();$message = \sprintf('Twig Test "%s" is deprecated', $test->getName());if ($test->getAlternative()) {$message .= \sprintf('. Use "%s" instead', $test->getAlternative());}$src = $stream->getSourceContext();$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message);}return $test;}private function getFunction(string $name, int $line): TwigFunction{if (!$function = $this->env->getFunction($name)) {$e = new SyntaxError(\sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());$e->addSuggestions($name, array_keys($this->env->getFunctions()));throw $e;}if ($function->isDeprecated()) {$message = \sprintf('Twig Function "%s" is deprecated', $function->getName());if ($function->getAlternative()) {$message .= \sprintf('. Use "%s" instead', $function->getAlternative());}$src = $this->parser->getStream()->getSourceContext();$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message);}return $function;}private function getFilter(string $name, int $line): TwigFilter{if (!$filter = $this->env->getFilter($name)) {$e = new SyntaxError(\sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());$e->addSuggestions($name, array_keys($this->env->getFilters()));throw $e;}if ($filter->isDeprecated()) {$message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName());if ($filter->getAlternative()) {$message .= \sprintf('. Use "%s" instead', $filter->getAlternative());}$src = $this->parser->getStream()->getSourceContext();$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message);}return $filter;}// checks that the node only contains "constant" elementsprivate function checkConstantExpression(Node $node): bool{if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression|| $node instanceof NegUnary || $node instanceof PosUnary)) {return false;}foreach ($node as $n) {if (!$this->checkConstantExpression($n)) {return false;}}return true;}}