source: pro-violet-viettel/sourcecode/application/third_party/Twig/ExpressionParser.php @ 612

Last change on this file since 612 was 345, checked in by quyenla, 11 years ago

collaborator page

File size: 14.1 KB
Line 
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 * @package    twig
22 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
23 */
24class Twig_ExpressionParser
25{
26    const OPERATOR_LEFT = 1;
27    const OPERATOR_RIGHT = 2;
28
29    protected $parser;
30    protected $unaryOperators;
31    protected $binaryOperators;
32
33    public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators)
34    {
35        $this->parser = $parser;
36        $this->unaryOperators = $unaryOperators;
37        $this->binaryOperators = $binaryOperators;
38    }
39
40    public function parseExpression($precedence = 0)
41    {
42        $expr = $this->getPrimary();
43        $token = $this->parser->getCurrentToken();
44        while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
45            $op = $this->binaryOperators[$token->getValue()];
46            $this->parser->getStream()->next();
47
48            if (isset($op['callable'])) {
49                $expr = call_user_func($op['callable'], $this->parser, $expr);
50            } else {
51                $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
52                $class = $op['class'];
53                $expr = new $class($expr, $expr1, $token->getLine());
54            }
55
56            $token = $this->parser->getCurrentToken();
57        }
58
59        if (0 === $precedence) {
60            return $this->parseConditionalExpression($expr);
61        }
62
63        return $expr;
64    }
65
66    protected function getPrimary()
67    {
68        $token = $this->parser->getCurrentToken();
69
70        if ($this->isUnary($token)) {
71            $operator = $this->unaryOperators[$token->getValue()];
72            $this->parser->getStream()->next();
73            $expr = $this->parseExpression($operator['precedence']);
74            $class = $operator['class'];
75
76            return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
77        } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
78            $this->parser->getStream()->next();
79            $expr = $this->parseExpression();
80            $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
81
82            return $this->parsePostfixExpression($expr);
83        }
84
85        return $this->parsePrimaryExpression();
86    }
87
88    protected function parseConditionalExpression($expr)
89    {
90        while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
91            $this->parser->getStream()->next();
92            $expr2 = $this->parseExpression();
93            $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'The ternary operator must have a default value');
94            $expr3 = $this->parseExpression();
95
96            $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
97        }
98
99        return $expr;
100    }
101
102    protected function isUnary(Twig_Token $token)
103    {
104        return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
105    }
106
107    protected function isBinary(Twig_Token $token)
108    {
109        return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
110    }
111
112    public function parsePrimaryExpression()
113    {
114        $token = $this->parser->getCurrentToken();
115        switch ($token->getType()) {
116            case Twig_Token::NAME_TYPE:
117                $this->parser->getStream()->next();
118                switch ($token->getValue()) {
119                    case 'true':
120                    case 'TRUE':
121                        $node = new Twig_Node_Expression_Constant(true, $token->getLine());
122                        break;
123
124                    case 'false':
125                    case 'FALSE':
126                        $node = new Twig_Node_Expression_Constant(false, $token->getLine());
127                        break;
128
129                    case 'none':
130                    case 'NONE':
131                    case 'null':
132                    case 'NULL':
133                        $node = new Twig_Node_Expression_Constant(null, $token->getLine());
134                        break;
135
136                    default:
137                        $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
138                }
139                break;
140
141            case Twig_Token::NUMBER_TYPE:
142            case Twig_Token::STRING_TYPE:
143                $this->parser->getStream()->next();
144                $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
145                break;
146
147            default:
148                if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
149                    $node = $this->parseArrayExpression();
150                } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
151                    $node = $this->parseHashExpression();
152                } else {
153                    throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine());
154                }
155        }
156
157        return $this->parsePostfixExpression($node);
158    }
159
160    public function parseArrayExpression()
161    {
162        $stream = $this->parser->getStream();
163        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
164        $elements = array();
165        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
166            if (!empty($elements)) {
167                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
168
169                // trailing ,?
170                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
171                    break;
172                }
173            }
174
175            $elements[] = $this->parseExpression();
176        }
177        $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
178
179        return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine());
180    }
181
182    public function parseHashExpression()
183    {
184        $stream = $this->parser->getStream();
185        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
186        $elements = array();
187        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
188            if (!empty($elements)) {
189                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
190
191                // trailing ,?
192                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
193                    break;
194                }
195            }
196
197            if (!$stream->test(Twig_Token::STRING_TYPE) && !$stream->test(Twig_Token::NUMBER_TYPE)) {
198                $current = $stream->getCurrent();
199                throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string or a number (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine());
200            }
201
202            $key = $stream->next()->getValue();
203            $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
204            $elements[$key] = $this->parseExpression();
205        }
206        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
207
208        return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine());
209    }
210
211    public function parsePostfixExpression($node)
212    {
213        $firstPass = true;
214        while (true) {
215            $token = $this->parser->getCurrentToken();
216            if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
217                if ('.' == $token->getValue() || '[' == $token->getValue()) {
218                    $node = $this->parseSubscriptExpression($node);
219                } elseif ('|' == $token->getValue()) {
220                    $node = $this->parseFilterExpression($node);
221                } elseif ($firstPass && $node instanceof Twig_Node_Expression_Name && '(' == $token->getValue()) {
222                    $node = $this->getFunctionNode($node);
223                } else {
224                    break;
225                }
226            } else {
227                break;
228            }
229
230            $firstPass = false;
231        }
232
233        return $node;
234    }
235
236    public function getFunctionNode(Twig_Node_Expression_Name $node)
237    {
238        $args = $this->parseArguments();
239
240        if ('parent' === $node->getAttribute('name')) {
241            if (!count($this->parser->getBlockStack())) {
242                throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $node->getLine());
243            }
244
245            if (!$this->parser->getParent()) {
246                throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend another one is forbidden', $node->getLine());
247            }
248
249            return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $node->getLine());
250        }
251
252        if ('block' === $node->getAttribute('name')) {
253            return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $node->getLine());
254        }
255
256        if (null !== $alias = $this->parser->getImportedFunction($node->getAttribute('name'))) {
257            return new Twig_Node_Expression_GetAttr($alias['node'], new Twig_Node_Expression_Constant($alias['name'], $node->getLine()), $args, Twig_TemplateInterface::METHOD_CALL, $node->getLine());
258        }
259
260        return new Twig_Node_Expression_Function($node, $args, $node->getLine());
261    }
262
263    public function parseSubscriptExpression($node)
264    {
265        $token = $this->parser->getStream()->next();
266        $lineno = $token->getLine();
267        $arguments = new Twig_Node();
268        $type = Twig_TemplateInterface::ANY_CALL;
269        if ($token->getValue() == '.') {
270            $token = $this->parser->getStream()->next();
271            if (
272                $token->getType() == Twig_Token::NAME_TYPE
273                ||
274                $token->getType() == Twig_Token::NUMBER_TYPE
275                ||
276                ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
277            ) {
278                $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
279
280                if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
281                    $type = Twig_TemplateInterface::METHOD_CALL;
282                    $arguments = $this->parseArguments();
283                } else {
284                    $arguments = new Twig_Node();
285                }
286            } else {
287                throw new Twig_Error_Syntax('Expected name or number', $lineno);
288            }
289        } else {
290            $type = Twig_TemplateInterface::ARRAY_CALL;
291
292            $arg = $this->parseExpression();
293            $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ']');
294        }
295
296        return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
297    }
298
299    public function parseFilterExpression($node)
300    {
301        $this->parser->getStream()->next();
302
303        return $this->parseFilterExpressionRaw($node);
304    }
305
306    public function parseFilterExpressionRaw($node, $tag = null)
307    {
308        while (true) {
309            $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
310
311            $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
312            if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
313                $arguments = new Twig_Node();
314            } else {
315                $arguments = $this->parseArguments();
316            }
317
318            $node = new Twig_Node_Expression_Filter($node, $name, $arguments, $token->getLine(), $tag);
319
320            if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
321                break;
322            }
323
324            $this->parser->getStream()->next();
325        }
326
327        return $node;
328    }
329
330    public function parseArguments()
331    {
332        $args = array();
333        $stream = $this->parser->getStream();
334
335        $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis');
336        while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
337            if (!empty($args)) {
338                $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
339            }
340            $args[] = $this->parseExpression();
341        }
342        $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
343
344        return new Twig_Node($args);
345    }
346
347    public function parseAssignmentExpression()
348    {
349        $targets = array();
350        while (true) {
351            $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
352            if (in_array($token->getValue(), array('true', 'false', 'none'))) {
353                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine());
354            }
355            $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
356
357            if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
358                break;
359            }
360            $this->parser->getStream()->next();
361        }
362
363        return new Twig_Node($targets);
364    }
365
366    public function parseMultitargetExpression()
367    {
368        $targets = array();
369        while (true) {
370            $targets[] = $this->parseExpression();
371            if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
372                break;
373            }
374            $this->parser->getStream()->next();
375        }
376
377        return new Twig_Node($targets);
378    }
379}
Note: See TracBrowser for help on using the repository browser.