Twig_ExpressionParser::parseExpression()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 25
rs 8.439
cc 6
eloc 16
nc 6
nop 1
1
<?php
2
3
/*
4
 * This file is part of Twig.
5
 *
6
 * (c) 2009 Fabien Potencier
7
 * (c) 2009 Armin Ronacher
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
/**
14
 * Parses expressions.
15
 *
16
 * This parser implements a "Precedence climbing" algorithm.
17
 *
18
 * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
19
 * @see http://en.wikipedia.org/wiki/Operator-precedence_parser
20
 *
21
 * @author Fabien Potencier <[email protected]>
22
 */
23
class Twig_ExpressionParser
24
{
25
    const OPERATOR_LEFT = 1;
26
    const OPERATOR_RIGHT = 2;
27
28
    protected $parser;
29
    protected $unaryOperators;
30
    protected $binaryOperators;
31
32
    public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators)
33
    {
34
        $this->parser = $parser;
35
        $this->unaryOperators = $unaryOperators;
36
        $this->binaryOperators = $binaryOperators;
37
    }
38
39
    public function parseExpression($precedence = 0)
40
    {
41
        $expr = $this->getPrimary();
42
        $token = $this->parser->getCurrentToken();
43
        while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
44
            $op = $this->binaryOperators[$token->getValue()];
45
            $this->parser->getStream()->next();
46
47
            if (isset($op['callable'])) {
48
                $expr = call_user_func($op['callable'], $this->parser, $expr);
49
            } else {
50
                $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
51
                $class = $op['class'];
52
                $expr = new $class($expr, $expr1, $token->getLine());
53
            }
54
55
            $token = $this->parser->getCurrentToken();
56
        }
57
58
        if (0 === $precedence) {
59
            return $this->parseConditionalExpression($expr);
60
        }
61
62
        return $expr;
63
    }
64
65
    protected function getPrimary()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
66
    {
67
        $token = $this->parser->getCurrentToken();
68
69
        if ($this->isUnary($token)) {
70
            $operator = $this->unaryOperators[$token->getValue()];
71
            $this->parser->getStream()->next();
72
            $expr = $this->parseExpression($operator['precedence']);
73
            $class = $operator['class'];
74
75
            return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
76
        } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
77
            $this->parser->getStream()->next();
78
            $expr = $this->parseExpression();
79
            $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
80
81
            return $this->parsePostfixExpression($expr);
82
        }
83
84
        return $this->parsePrimaryExpression();
85
    }
86
87
    protected function parseConditionalExpression($expr)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
88
    {
89
        while ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, '?')) {
90
            if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
91
                $expr2 = $this->parseExpression();
92
                if ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
93
                    $expr3 = $this->parseExpression();
94
                } else {
95
                    $expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
96
                }
97
            } else {
98
                $expr2 = $expr;
99
                $expr3 = $this->parseExpression();
100
            }
101
102
            $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
103
        }
104
105
        return $expr;
106
    }
107
108
    protected function isUnary(Twig_Token $token)
109
    {
110
        return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
111
    }
112
113
    protected function isBinary(Twig_Token $token)
114
    {
115
        return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
116
    }
117
118
    public function parsePrimaryExpression()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
119
    {
120
        $token = $this->parser->getCurrentToken();
121
        switch ($token->getType()) {
122
            case Twig_Token::NAME_TYPE:
123
                $this->parser->getStream()->next();
124
                switch ($token->getValue()) {
125
                    case 'true':
126
                    case 'TRUE':
127
                        $node = new Twig_Node_Expression_Constant(true, $token->getLine());
128
                        break;
129
130
                    case 'false':
131
                    case 'FALSE':
132
                        $node = new Twig_Node_Expression_Constant(false, $token->getLine());
133
                        break;
134
135
                    case 'none':
136
                    case 'NONE':
137
                    case 'null':
138
                    case 'NULL':
139
                        $node = new Twig_Node_Expression_Constant(null, $token->getLine());
140
                        break;
141
142
                    default:
143
                        if ('(' === $this->parser->getCurrentToken()->getValue()) {
144
                            $node = $this->getFunctionNode($token->getValue(), $token->getLine());
145
                        } else {
146
                            $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
147
                        }
148
                }
149
                break;
150
151
            case Twig_Token::NUMBER_TYPE:
152
                $this->parser->getStream()->next();
153
                $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
154
                break;
155
156
            case Twig_Token::STRING_TYPE:
157
            case Twig_Token::INTERPOLATION_START_TYPE:
158
                $node = $this->parseStringExpression();
159
                break;
160
161
            case Twig_Token::OPERATOR_TYPE:
162
                if (preg_match(Twig_Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
163
                    // in this context, string operators are variable names
164
                    $this->parser->getStream()->next();
165
                    $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
166
                    break;
167
                } elseif (isset($this->unaryOperators[$token->getValue()])) {
168
                    $class = $this->unaryOperators[$token->getValue()]['class'];
169
170
                    $ref = new ReflectionClass($class);
171
                    $negClass = 'Twig_Node_Expression_Unary_Neg';
172
                    $posClass = 'Twig_Node_Expression_Unary_Pos';
173
                    if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) {
0 ignored issues
show
Bug introduced by
Consider using $ref->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
174
                        throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
175
                    }
176
177
                    $this->parser->getStream()->next();
178
                    $expr = $this->parsePrimaryExpression();
179
180
                    $node = new $class($expr, $token->getLine());
181
                    break;
182
                }
183
184
            default:
185
                if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
186
                    $node = $this->parseArrayExpression();
187
                } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
188
                    $node = $this->parseHashExpression();
189
                } else {
190
                    throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
191
                }
192
        }
193
194
        return $this->parsePostfixExpression($node);
195
    }
196
197
    public function parseStringExpression()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
198
    {
199
        $stream = $this->parser->getStream();
200
201
        $nodes = array();
202
        // a string cannot be followed by another string in a single expression
203
        $nextCanBeString = true;
204
        while (true) {
205
            if ($nextCanBeString && $token = $stream->nextIf(Twig_Token::STRING_TYPE)) {
206
                $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
207
                $nextCanBeString = false;
208
            } elseif ($stream->nextIf(Twig_Token::INTERPOLATION_START_TYPE)) {
209
                $nodes[] = $this->parseExpression();
210
                $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
211
                $nextCanBeString = true;
212
            } else {
213
                break;
214
            }
215
        }
216
217
        $expr = array_shift($nodes);
218
        foreach ($nodes as $node) {
219
            $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
220
        }
221
222
        return $expr;
223
    }
224
225
    public function parseArrayExpression()
226
    {
227
        $stream = $this->parser->getStream();
228
        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
229
230
        $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
231
        $first = true;
232
        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
233
            if (!$first) {
234
                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
235
236
                // trailing ,?
237
                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
238
                    break;
239
                }
240
            }
241
            $first = false;
242
243
            $node->addElement($this->parseExpression());
244
        }
245
        $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
246
247
        return $node;
248
    }
249
250
    public function parseHashExpression()
251
    {
252
        $stream = $this->parser->getStream();
253
        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
254
255
        $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
256
        $first = true;
257
        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
258
            if (!$first) {
259
                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
260
261
                // trailing ,?
262
                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
263
                    break;
264
                }
265
            }
266
            $first = false;
267
268
            // a hash key can be:
269
            //
270
            //  * a number -- 12
271
            //  * a string -- 'a'
272
            //  * a name, which is equivalent to a string -- a
273
            //  * an expression, which must be enclosed in parentheses -- (1 + 2)
274
            if (($token = $stream->nextIf(Twig_Token::STRING_TYPE)) || ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) || $token = $stream->nextIf(Twig_Token::NUMBER_TYPE)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $token is correct as $stream->nextIf(\Twig_Token::STRING_TYPE) (which targets Twig_TokenStream::nextIf()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure the assignment to $token is correct as $stream->nextIf(\Twig_Token::NAME_TYPE) (which targets Twig_TokenStream::nextIf()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure the assignment to $token is correct as $stream->nextIf(\Twig_Token::NUMBER_TYPE) (which targets Twig_TokenStream::nextIf()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
275
                $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
0 ignored issues
show
Bug introduced by
The method getValue cannot be called on $token (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug introduced by
The method getLine cannot be called on $token (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
276
            } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
277
                $key = $this->parseExpression();
278
            } else {
279
                $current = $stream->getCurrent();
280
281
                throw new Twig_Error_Syntax(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"', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
282
            }
283
284
            $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
285
            $value = $this->parseExpression();
286
287
            $node->addElement($value, $key);
288
        }
289
        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
290
291
        return $node;
292
    }
293
294
    public function parsePostfixExpression($node)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
295
    {
296
        while (true) {
297
            $token = $this->parser->getCurrentToken();
298
            if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
299
                if ('.' == $token->getValue() || '[' == $token->getValue()) {
300
                    $node = $this->parseSubscriptExpression($node);
301
                } elseif ('|' == $token->getValue()) {
302
                    $node = $this->parseFilterExpression($node);
303
                } else {
304
                    break;
305
                }
306
            } else {
307
                break;
308
            }
309
        }
310
311
        return $node;
312
    }
313
314
    public function getFunctionNode($name, $line)
315
    {
316
        switch ($name) {
317
            case 'parent':
318
                $args = $this->parseArguments();
0 ignored issues
show
Unused Code introduced by
$args is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
319
                if (!count($this->parser->getBlockStack())) {
320
                    throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
321
                }
322
323
                if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
324
                    throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename());
325
                }
326
327
                return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
328
            case 'block':
329
                return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
330
            case 'attribute':
331
                $args = $this->parseArguments();
332
                if (count($args) < 2) {
333
                    throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
334
                }
335
336
                return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line);
0 ignored issues
show
Compatibility introduced by
$args->getNode(0) of type object<Twig_Node> is not a sub-type of object<Twig_Node_Expression>. It seems like you assume a child class of the class Twig_Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$args->getNode(1) of type object<Twig_Node> is not a sub-type of object<Twig_Node_Expression>. It seems like you assume a child class of the class Twig_Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like count($args) > 2 ? $args->getNode(2) : null can also be of type object<Twig_Node>; however, Twig_Node_Expression_GetAttr::__construct() does only seem to accept null|object<Twig_Node_Expression>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
337
            default:
338
                if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
339
                    $arguments = new Twig_Node_Expression_Array(array(), $line);
340
                    foreach ($this->parseArguments() as $n) {
341
                        $arguments->addElement($n);
342
                    }
343
344
                    $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
345
                    $node->setAttribute('safe', true);
346
347
                    return $node;
348
                }
349
350
                $args = $this->parseArguments(true);
351
                $class = $this->getFunctionNodeClass($name, $line);
352
353
                return new $class($name, $args, $line);
354
        }
355
    }
356
357
    public function parseSubscriptExpression($node)
358
    {
359
        $stream = $this->parser->getStream();
360
        $token = $stream->next();
361
        $lineno = $token->getLine();
362
        $arguments = new Twig_Node_Expression_Array(array(), $lineno);
363
        $type = Twig_Template::ANY_CALL;
364
        if ($token->getValue() == '.') {
365
            $token = $stream->next();
366
            if (
367
                $token->getType() == Twig_Token::NAME_TYPE
368
                ||
369
                $token->getType() == Twig_Token::NUMBER_TYPE
370
                ||
371
                ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
372
            ) {
373
                $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
374
375
                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
376
                    $type = Twig_TemplateInterface::METHOD_CALL;
377
                    foreach ($this->parseArguments() as $n) {
378
                        $arguments->addElement($n);
379
                    }
380
                }
381
            } else {
382
                throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
383
            }
384
385
            if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
386
                if (!$arg instanceof Twig_Node_Expression_Constant) {
387
                    throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
388
                }
389
390
                $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
391
                $node->setAttribute('safe', true);
392
393
                return $node;
394
            }
395
        } else {
396
            $type = Twig_Template::ARRAY_CALL;
397
398
            // slice?
399
            $slice = false;
400
            if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
401
                $slice = true;
402
                $arg = new Twig_Node_Expression_Constant(0, $token->getLine());
403
            } else {
404
                $arg = $this->parseExpression();
405
            }
406
407
            if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
408
                $slice = true;
409
            }
410
411
            if ($slice) {
412
                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
413
                    $length = new Twig_Node_Expression_Constant(null, $token->getLine());
414
                } else {
415
                    $length = $this->parseExpression();
416
                }
417
418
                $class = $this->getFilterNodeClass('slice', $token->getLine());
419
                $arguments = new Twig_Node(array($arg, $length));
420
                $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
421
422
                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
423
424
                return $filter;
425
            }
426
427
            $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
428
        }
429
430
        return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
431
    }
432
433
    public function parseFilterExpression($node)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
434
    {
435
        $this->parser->getStream()->next();
436
437
        return $this->parseFilterExpressionRaw($node);
438
    }
439
440
    public function parseFilterExpressionRaw($node, $tag = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
441
    {
442
        while (true) {
443
            $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
444
445
            $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
446
            if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
447
                $arguments = new Twig_Node();
448
            } else {
449
                $arguments = $this->parseArguments(true);
450
            }
451
452
            $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
453
454
            $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
455
456
            if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
457
                break;
458
            }
459
460
            $this->parser->getStream()->next();
461
        }
462
463
        return $node;
464
    }
465
466
    /**
467
     * Parses arguments.
468
     *
469
     * @param bool $namedArguments Whether to allow named arguments or not
470
     * @param bool $definition     Whether we are parsing arguments for a function definition
471
     */
472
    public function parseArguments($namedArguments = false, $definition = false)
473
    {
474
        $args = array();
475
        $stream = $this->parser->getStream();
476
477
        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
478
        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
479
            if (!empty($args)) {
480
                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
481
            }
482
483
            if ($definition) {
484
                $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
485
                $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
486
            } else {
487
                $value = $this->parseExpression();
488
            }
489
490
            $name = null;
491
            if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
492
                if (!$value instanceof Twig_Node_Expression_Name) {
493
                    throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
494
                }
495
                $name = $value->getAttribute('name');
496
497
                if ($definition) {
498
                    $value = $this->parsePrimaryExpression();
499
500
                    if (!$this->checkConstantExpression($value)) {
501
                        throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename());
502
                    }
503
                } else {
504
                    $value = $this->parseExpression();
505
                }
506
            }
507
508
            if ($definition) {
509
                if (null === $name) {
510
                    $name = $value->getAttribute('name');
511
                    $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
512
                }
513
                $args[$name] = $value;
514
            } else {
515
                if (null === $name) {
516
                    $args[] = $value;
517
                } else {
518
                    $args[$name] = $value;
519
                }
520
            }
521
        }
522
        $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
523
524
        return new Twig_Node($args);
525
    }
526
527
    public function parseAssignmentExpression()
528
    {
529
        $targets = array();
530
        while (true) {
531
            $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
532
            if (in_array($token->getValue(), array('true', 'false', 'none'))) {
533
                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
534
            }
535
            $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
536
537
            if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
538
                break;
539
            }
540
        }
541
542
        return new Twig_Node($targets);
543
    }
544
545
    public function parseMultitargetExpression()
546
    {
547
        $targets = array();
548
        while (true) {
549
            $targets[] = $this->parseExpression();
550
            if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
551
                break;
552
            }
553
        }
554
555
        return new Twig_Node($targets);
556
    }
557
558
    protected function getFunctionNodeClass($name, $line)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
559
    {
560
        $env = $this->parser->getEnvironment();
561
562
        if (false === $function = $env->getFunction($name)) {
563
            $message = sprintf('The function "%s" does not exist', $name);
564
            if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFunctions()))) {
565
                $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
566
            }
567
568
            throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
569
        }
570
571
        if ($function instanceof Twig_SimpleFunction) {
572
            return $function->getNodeClass();
573
        }
574
575
        return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function';
576
    }
577
578
    protected function getFilterNodeClass($name, $line)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
579
    {
580
        $env = $this->parser->getEnvironment();
581
582
        if (false === $filter = $env->getFilter($name)) {
583
            $message = sprintf('The filter "%s" does not exist', $name);
584
            if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFilters()))) {
585
                $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
586
            }
587
588
            throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
589
        }
590
591
        if ($filter instanceof Twig_SimpleFilter) {
592
            return $filter->getNodeClass();
593
        }
594
595
        return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
596
    }
597
598
    // checks that the node only contains "constant" elements
599
    protected function checkConstantExpression(Twig_NodeInterface $node)
600
    {
601
        if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array
602
            || $node instanceof Twig_Node_Expression_Unary_Neg || $node instanceof Twig_Node_Expression_Unary_Pos
603
        )) {
604
            return false;
605
        }
606
607
        foreach ($node as $n) {
608
            if (!$this->checkConstantExpression($n)) {
609
                return false;
610
            }
611
        }
612
613
        return true;
614
    }
615
}
616