__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

[email protected]: ~ $
<?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\EmptyNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ArrowFunctionExpression;
use Twig\Node\Expression\Binary\AbstractBinary;
use Twig\Node\Expression\Binary\ConcatBinary;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MacroReferenceExpression;
use Twig\Node\Expression\Ternary\ConditionalTernary;
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\Expression\Unary\SpreadUnary;
use Twig\Node\Expression\Variable\AssignContextVariable;
use Twig\Node\Expression\Variable\ContextVariable;
use Twig\Node\Expression\Variable\LocalVariable;
use Twig\Node\Expression\Variable\TemplateVariable;
use Twig\Node\Node;
use Twig\Node\Nodes;

/**
 * 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 <[email protected]>
 */
class ExpressionParser
{
    public const OPERATOR_LEFT = 1;
    public const OPERATOR_RIGHT = 2;

    /** @var array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractUnary>}> */
    private $unaryOperators;
    /** @var array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */
    private $binaryOperators;
    private $readyNodes = [];
    private array $precedenceChanges = [];
    private bool $deprecationCheck = true;

    public function __construct(
        private Parser $parser,
        private Environment $env,
    ) {
        $this->unaryOperators = $env->getUnaryOperators();
        $this->binaryOperators = $env->getBinaryOperators();

        $ops = [];
        foreach ($this->unaryOperators as $n => $c) {
            $ops[] = $c + ['name' => $n, 'type' => 'unary'];
        }
        foreach ($this->binaryOperators as $n => $c) {
            $ops[] = $c + ['name' => $n, 'type' => 'binary'];
        }
        foreach ($ops as $config) {
            if (!isset($config['precedence_change'])) {
                continue;
            }
            $name = $config['type'].'_'.$config['name'];
            $min = min($config['precedence_change']->getNewPrecedence(), $config['precedence']);
            $max = max($config['precedence_change']->getNewPrecedence(), $config['precedence']);
            foreach ($ops as $c) {
                if ($c['precedence'] > $min && $c['precedence'] < $max) {
                    $this->precedenceChanges[$c['type'].'_'.$c['name']][] = $name;
                }
            }
        }
    }

    public function parseExpression($precedence = 0)
    {
        if (\func_num_args() > 1) {
            trigger_deprecation('twig/twig', '3.15', 'Passing a second argument ($allowArrow) to "%s()" is deprecated.', __METHOD__);
        }

        if ($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 {
                $previous = $this->setDeprecationCheck(true);
                try {
                    $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
                } finally {
                    $this->setDeprecationCheck($previous);
                }
                $class = $op['class'];
                $expr = new $class($expr, $expr1, $token->getLine());
            }

            $expr->setAttribute('operator', 'binary_'.$token->getValue());

            $this->triggerPrecedenceDeprecations($expr);

            $token = $this->parser->getCurrentToken();
        }

        if (0 === $precedence) {
            return $this->parseConditionalExpression($expr);
        }

        return $expr;
    }

    private function triggerPrecedenceDeprecations(AbstractExpression $expr): void
    {
        // Check that the all nodes that are between the 2 precedences have explicit parentheses
        if (!$expr->hasAttribute('operator') || !isset($this->precedenceChanges[$expr->getAttribute('operator')])) {
            return;
        }

        if (str_starts_with($unaryOp = $expr->getAttribute('operator'), 'unary')) {
            if ($expr->hasExplicitParentheses()) {
                return;
            }
            $target = explode('_', $unaryOp)[1];
            /** @var AbstractExpression $node */
            $node = $expr->getNode('node');
            foreach ($this->precedenceChanges as $operatorName => $changes) {
                if (!\in_array($unaryOp, $changes)) {
                    continue;
                }
                if ($node->hasAttribute('operator') && $operatorName === $node->getAttribute('operator')) {
                    $change = $this->unaryOperators[$target]['precedence_change'];
                    trigger_deprecation($change->getPackage(), $change->getVersion(), \sprintf('Add explicit parentheses around the "%s" unary operator to avoid behavior change in the next major version as its precedence will change in "%s" at line %d.', $target, $this->parser->getStream()->getSourceContext()->getName(), $node->getTemplateLine()));
                }
            }
        } else {
            foreach ($this->precedenceChanges[$expr->getAttribute('operator')] as $operatorName) {
                foreach ($expr as $node) {
                    /** @var AbstractExpression $node */
                    if ($node->hasAttribute('operator') && $operatorName === $node->getAttribute('operator') && !$node->hasExplicitParentheses()) {
                        $op = explode('_', $operatorName)[1];
                        $change = $this->binaryOperators[$op]['precedence_change'];
                        trigger_deprecation($change->getPackage(), $change->getVersion(), \sprintf('Add explicit parentheses around the "%s" binary operator to avoid behavior change in the next major version as its precedence will change in "%s" at line %d.', $op, $this->parser->getStream()->getSourceContext()->getName(), $node->getTemplateLine()));
                    }
                }
            }
        }
    }

    /**
     * @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 AssignContextVariable($token->getValue(), $token->getLine())];
            $stream->expect(Token::ARROW_TYPE);

            return new ArrowFunctionExpression($this->parseExpression(), new Nodes($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 AssignContextVariable($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(), new Nodes($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'];

            $expr = new $class($expr, $token->getLine());
            $expr->setAttribute('operator', 'unary_'.$token->getValue());

            if ($this->deprecationCheck) {
                $this->triggerPrecedenceDeprecations($expr);
            }

            return $this->parsePostfixExpression($expr);
        } elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) {
            $this->parser->getStream()->next();
            $previous = $this->setDeprecationCheck(false);
            try {
                $expr = $this->parseExpression()->setExplicitParentheses();
            } finally {
                $this->setDeprecationCheck($previous);
            }
            $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, '?')) {
            $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());
            }

            $expr = new ConditionalTernary($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 (true) {
            case $token->test(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 ContextVariable($token->getValue(), $token->getLine());
                        }
                }
                break;

            case $token->test(Token::NUMBER_TYPE):
                $this->parser->getStream()->next();
                $node = new ConstantExpression($token->getValue(), $token->getLine());
                break;

            case $token->test(Token::STRING_TYPE):
            case $token->test(Token::INTERPOLATION_START_TYPE):
                $node = $this->parseStringExpression();
                break;

            case $token->test(Token::PUNCTUATION_TYPE):
                $node = match ($token->getValue()) {
                    '[' => $this->parseSequenceExpression(),
                    '{' => $this->parseMappingExpression(),
                    default => throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', $token->toEnglish(), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()),
                };
                break;

            case $token->test(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 ContextVariable($token->getValue(), $token->getLine());
                    break;
                }

                if ('=' === $token->getValue() && ('==' === $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());
                }

                // no break
            default:
                throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', $token->toEnglish(), $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 Twig 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->nextIf(Token::SPREAD_TYPE)) {
                $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 Twig 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->nextIf(Token::SPREAD_TYPE)) {
                $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 ContextVariable($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".', $current->toEnglish(), $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->test(Token::PUNCTUATION_TYPE)) {
                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)) {
            return new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], $this->createArguments($line), $line);
        }

        $args = $this->parseNamedArguments();
        $function = $this->getFunction($name, $line);

        if ($function->getParserCallable()) {
            $fakeNode = new EmptyNode($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)
    {
        if ('.' === $this->parser->getStream()->next()->getValue()) {
            return $this->parseSubscriptExpressionDot($node);
        }

        return $this->parseSubscriptExpressionArray($node);
    }

    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 EmptyNode();
            } else {
                $arguments = $this->parseNamedArguments();
            }

            $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.
     *
     * @return Node
     *
     * @throws SyntaxError
     *
     * @deprecated since Twig 3.19 Use parseNamedArguments() instead
     */
    public function parseArguments()
    {
        trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated, use "%s::parseNamedArguments()" instead.', __METHOD__, __CLASS__));

        $namedArguments = false;
        $definition = false;
        if (\func_num_args() > 1) {
            $definition = func_get_arg(1);
        }
        if (\func_num_args() > 0) {
            trigger_deprecation('twig/twig', '3.15', 'Passing arguments to "%s()" is deprecated.', __METHOD__);
            $namedArguments = func_get_arg(0);
        }

        $args = [];
        $stream = $this->parser->getStream();

        $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
        $hasSpread = false;
        while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
            if ($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 loop
                if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {
                    break;
                }
            }

            if ($definition) {
                $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');
                $value = new ContextVariable($token->getValue(), $this->parser->getCurrentToken()->getLine());
            } else {
                if ($stream->nextIf(Token::SPREAD_TYPE)) {
                    $hasSpread = true;
                    $value = new SpreadUnary($this->parseExpression(), $stream->getCurrent()->getLine());
                } elseif ($hasSpread) {
                    throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
                } else {
                    $value = $this->parseExpression();
                }
            }

            $name = null;
            if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || (!$definition && $token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) {
                if (!$value instanceof ContextVariable) {
                    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->getPrimary();

                    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();
                }
            }

            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 Nodes($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');
            }
            $targets[] = new AssignContextVariable($token->getValue(), $token->getLine());

            if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
                break;
            }
        }

        return new Nodes($targets);
    }

    public function parseMultitargetExpression()
    {
        $targets = [];
        while (true) {
            $targets[] = $this->parseExpression();
            if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) {
                break;
            }
        }

        return new Nodes($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->parseNamedArguments();
        } elseif ($test->hasOneMandatoryArgument()) {
            $arguments = new Nodes([0 => $this->getPrimary()]);
        }

        if ('defined' === $test->getName() && $node instanceof ContextVariable && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
            $node = new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
        }

        $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) {
            if ($this->parser->shouldIgnoreUnknownTwigCallables()) {
                return new TwigTest($name, fn () => '');
            }
            $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();
            $src = $stream->getSourceContext();
            $test->triggerDeprecation($src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
        }

        return $test;
    }

    private function getFunction(string $name, int $line): TwigFunction
    {
        try {
            $function = $this->env->getFunction($name);
        } catch (SyntaxError $e) {
            if (!$this->parser->shouldIgnoreUnknownTwigCallables()) {
                throw $e;
            }

            $function = null;
        }

        if (!$function) {
            if ($this->parser->shouldIgnoreUnknownTwigCallables()) {
                return new TwigFunction($name, fn () => '');
            }
            $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()) {
            $src = $this->parser->getStream()->getSourceContext();
            $function->triggerDeprecation($src->getPath() ?: $src->getName(), $line);
        }

        return $function;
    }

    private function getFilter(string $name, int $line): TwigFilter
    {
        try {
            $filter = $this->env->getFilter($name);
        } catch (SyntaxError $e) {
            if (!$this->parser->shouldIgnoreUnknownTwigCallables()) {
                throw $e;
            }

            $filter = null;
        }
        if (!$filter) {
            if ($this->parser->shouldIgnoreUnknownTwigCallables()) {
                return new TwigFilter($name, fn () => '');
            }
            $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()) {
            $src = $this->parser->getStream()->getSourceContext();
            $filter->triggerDeprecation($src->getPath() ?: $src->getName(), $line);
        }

        return $filter;
    }

    // checks that the node only contains "constant" elements
    // to be removed in 4.0
    private 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;
    }

    private function setDeprecationCheck(bool $deprecationCheck): bool
    {
        $current = $this->deprecationCheck;
        $this->deprecationCheck = $deprecationCheck;

        return $current;
    }

    private function createArguments(int $line): ArrayExpression
    {
        $arguments = new ArrayExpression([], $line);
        foreach ($this->parseNamedArguments() as $k => $n) {
            $arguments->addElement($n, new LocalVariable($k, $line));
        }

        return $arguments;
    }

    /**
     * @deprecated since Twig 3.19 Use parseNamedArguments() instead
     */
    public function parseOnlyArguments()
    {
        trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated, use "%s::parseNamedArguments()" instead.', __METHOD__, __CLASS__));

        return $this->parseNamedArguments();
    }

    public function parseNamedArguments(): Nodes
    {
        $args = [];
        $stream = $this->parser->getStream();
        $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
        $hasSpread = false;
        while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
            if ($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 loop
                if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {
                    break;
                }
            }

            if ($stream->nextIf(Token::SPREAD_TYPE)) {
                $hasSpread = true;
                $value = new SpreadUnary($this->parseExpression(), $stream->getCurrent()->getLine());
            } elseif ($hasSpread) {
                throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
            } else {
                $value = $this->parseExpression();
            }

            $name = null;
            if (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) {
                if (!$value instanceof ContextVariable) {
                    throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
                }
                $name = $value->getAttribute('name');
                $value = $this->parseExpression();
            }

            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 Nodes($args);
    }

    private function parseSubscriptExpressionDot(Node $node): AbstractExpression
    {
        $stream = $this->parser->getStream();
        $token = $stream->getCurrent();
        $lineno = $token->getLine();
        $arguments = new ArrayExpression([], $lineno);
        $type = Template::ANY_CALL;

        if ($stream->nextIf(Token::PUNCTUATION_TYPE, '(')) {
            $attribute = $this->parseExpression();
            $stream->expect(Token::PUNCTUATION_TYPE, ')');
        } else {
            $token = $stream->next();
            if (
                $token->test(Token::NAME_TYPE)
                || $token->test(Token::NUMBER_TYPE)
                || ($token->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue()))
            ) {
                $attribute = new ConstantExpression($token->getValue(), $token->getLine());
            } else {
                throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), $token->toEnglish()), $token->getLine(), $stream->getSourceContext());
            }
        }

        if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
            $type = Template::METHOD_CALL;
            $arguments = $this->createArguments($token->getLine());
        }

        if (
            $node instanceof ContextVariable
            && (
                null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))
                || '_self' === $node->getAttribute('name') && $attribute instanceof ConstantExpression
            )
        ) {
            return new MacroReferenceExpression(new TemplateVariable($node->getAttribute('name'), $node->getTemplateLine()), 'macro_'.$attribute->getAttribute('value'), $arguments, $node->getTemplateLine());
        }

        return new GetAttrExpression($node, $attribute, $arguments, $type, $lineno);
    }

    private function parseSubscriptExpressionArray(Node $node): AbstractExpression
    {
        $stream = $this->parser->getStream();
        $token = $stream->getCurrent();
        $lineno = $token->getLine();
        $arguments = new ArrayExpression([], $lineno);

        // slice?
        $slice = false;
        if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {
            $slice = true;
            $attribute = new ConstantExpression(0, $token->getLine());
        } else {
            $attribute = $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 Nodes([$attribute, $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, $attribute, $arguments, Template::ARRAY_CALL, $lineno);
    }
}

Filemanager

Name Type Size Permission Actions
Attribute Folder 0755
Cache Folder 0755
Error Folder 0755
Extension Folder 0755
Loader Folder 0755
Node Folder 0755
NodeVisitor Folder 0755
Profiler Folder 0755
Resources Folder 0755
Runtime Folder 0755
RuntimeLoader Folder 0755
Sandbox Folder 0755
Test Folder 0755
TokenParser Folder 0755
Util Folder 0755
AbstractTwigCallable.php File 5.36 KB 0644
Compiler.php File 6.17 KB 0644
DeprecatedCallableInfo.php File 1.64 KB 0644
Environment.php File 27.54 KB 0644
ExpressionParser.php File 40.02 KB 0644
ExtensionSet.php File 14.3 KB 0644
FileExtensionEscapingStrategy.php File 1.43 KB 0644
Lexer.php File 22.38 KB 0644
Markup.php File 1.03 KB 0644
NodeTraverser.php File 1.78 KB 0644
OperatorPrecedenceChange.php File 778 B 0644
Parser.php File 14.38 KB 0644
Source.php File 929 B 0644
Template.php File 15.67 KB 0644
TemplateWrapper.php File 2.56 KB 0644
Token.php File 5.58 KB 0644
TokenStream.php File 3.5 KB 0644
TwigCallableInterface.php File 1.17 KB 0644
TwigFilter.php File 1.84 KB 0644
TwigFunction.php File 1.58 KB 0644
TwigTest.php File 1.53 KB 0644
autoload.php File 20.42 KB 0644
Filemanager