1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Twig\ExpressionParser\Infix;
13
14use Twig\Error\SyntaxError;
15use Twig\Node\Expression\ArrayExpression;
16use Twig\Node\Expression\Binary\SetBinary;
17use Twig\Node\Expression\Unary\SpreadUnary;
18use Twig\Node\Expression\Variable\ContextVariable;
19use Twig\Node\Expression\Variable\LocalVariable;
20use Twig\Node\Nodes;
21use Twig\Parser;
22use Twig\Token;
23
24trait ArgumentsTrait
25{
26    private function parseCallableArguments(Parser $parser, int $line, bool $parseOpenParenthesis = true): ArrayExpression
27    {
28        $arguments = new ArrayExpression([], $line);
29        foreach ($this->parseNamedArguments($parser, $parseOpenParenthesis) as $k => $n) {
30            $arguments->addElement($n, new LocalVariable($k, $line));
31        }
32
33        return $arguments;
34    }
35
36    private function parseNamedArguments(Parser $parser, bool $parseOpenParenthesis = true): Nodes
37    {
38        $args = [];
39        $stream = $parser->getStream();
40        if ($parseOpenParenthesis) {
41            $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
42        }
43        $hasSpread = false;
44        while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
45            if ($args) {
46                $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
47
48                // if the comma above was a trailing comma, early exit the argument parse loop
49                if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {
50                    break;
51                }
52            }
53
54            $value = $parser->parseExpression();
55            if ($value instanceof SpreadUnary) {
56                $hasSpread = true;
57            } elseif ($hasSpread) {
58                throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
59            }
60
61            $name = null;
62            if ($value instanceof SetBinary) {
63                $name = $value->getNode('left')->getAttribute('name');
64                $value = $value->getNode('right');
65            } elseif (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) {
66                if (!$value instanceof ContextVariable) {
67                    throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', $value::class), $token->getLine(), $stream->getSourceContext());
68                }
69                $name = $value->getAttribute('name');
70                $value = $parser->parseExpression();
71            }
72
73            if (null === $name) {
74                $args[] = $value;
75            } else {
76                $args[$name] = $value;
77            }
78        }
79        $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
80
81        return new Nodes($args);
82    }
83}
84