shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

ExpressionParser.php (26668B)


      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 <fabien@symfony.com>
     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()
     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)
     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()
    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))) {
    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()
    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)) {
    275                 $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
    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)
    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                 $this->parseArguments();
    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);
    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_Template::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                 $name = $arg->getAttribute('value');
    391 
    392                 if ($this->parser->isReservedMacroName($name)) {
    393                     throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $this->parser->getFilename());
    394                 }
    395 
    396                 $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno);
    397                 $node->setAttribute('safe', true);
    398 
    399                 return $node;
    400             }
    401         } else {
    402             $type = Twig_Template::ARRAY_CALL;
    403 
    404             // slice?
    405             $slice = false;
    406             if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
    407                 $slice = true;
    408                 $arg = new Twig_Node_Expression_Constant(0, $token->getLine());
    409             } else {
    410                 $arg = $this->parseExpression();
    411             }
    412 
    413             if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
    414                 $slice = true;
    415             }
    416 
    417             if ($slice) {
    418                 if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
    419                     $length = new Twig_Node_Expression_Constant(null, $token->getLine());
    420                 } else {
    421                     $length = $this->parseExpression();
    422                 }
    423 
    424                 $class = $this->getFilterNodeClass('slice', $token->getLine());
    425                 $arguments = new Twig_Node(array($arg, $length));
    426                 $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
    427 
    428                 $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
    429 
    430                 return $filter;
    431             }
    432 
    433             $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
    434         }
    435 
    436         return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
    437     }
    438 
    439     public function parseFilterExpression($node)
    440     {
    441         $this->parser->getStream()->next();
    442 
    443         return $this->parseFilterExpressionRaw($node);
    444     }
    445 
    446     public function parseFilterExpressionRaw($node, $tag = null)
    447     {
    448         while (true) {
    449             $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
    450 
    451             $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
    452             if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
    453                 $arguments = new Twig_Node();
    454             } else {
    455                 $arguments = $this->parseArguments(true);
    456             }
    457 
    458             $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
    459 
    460             $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
    461 
    462             if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
    463                 break;
    464             }
    465 
    466             $this->parser->getStream()->next();
    467         }
    468 
    469         return $node;
    470     }
    471 
    472     /**
    473      * Parses arguments.
    474      *
    475      * @param bool $namedArguments Whether to allow named arguments or not
    476      * @param bool $definition     Whether we are parsing arguments for a function definition
    477      *
    478      * @return Twig_Node
    479      *
    480      * @throws Twig_Error_Syntax
    481      */
    482     public function parseArguments($namedArguments = false, $definition = false)
    483     {
    484         $args = array();
    485         $stream = $this->parser->getStream();
    486 
    487         $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
    488         while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
    489             if (!empty($args)) {
    490                 $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
    491             }
    492 
    493             if ($definition) {
    494                 $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
    495                 $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
    496             } else {
    497                 $value = $this->parseExpression();
    498             }
    499 
    500             $name = null;
    501             if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
    502                 if (!$value instanceof Twig_Node_Expression_Name) {
    503                     throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $this->parser->getFilename());
    504                 }
    505                 $name = $value->getAttribute('name');
    506 
    507                 if ($definition) {
    508                     $value = $this->parsePrimaryExpression();
    509 
    510                     if (!$this->checkConstantExpression($value)) {
    511                         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());
    512                     }
    513                 } else {
    514                     $value = $this->parseExpression();
    515                 }
    516             }
    517 
    518             if ($definition) {
    519                 if (null === $name) {
    520                     $name = $value->getAttribute('name');
    521                     $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
    522                 }
    523                 $args[$name] = $value;
    524             } else {
    525                 if (null === $name) {
    526                     $args[] = $value;
    527                 } else {
    528                     $args[$name] = $value;
    529                 }
    530             }
    531         }
    532         $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
    533 
    534         return new Twig_Node($args);
    535     }
    536 
    537     public function parseAssignmentExpression()
    538     {
    539         $targets = array();
    540         while (true) {
    541             $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
    542             $value = $token->getValue();
    543             if (in_array(strtolower($value), array('true', 'false', 'none', 'null'))) {
    544                 throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $value), $token->getLine(), $this->parser->getFilename());
    545             }
    546             $targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine());
    547 
    548             if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
    549                 break;
    550             }
    551         }
    552 
    553         return new Twig_Node($targets);
    554     }
    555 
    556     public function parseMultitargetExpression()
    557     {
    558         $targets = array();
    559         while (true) {
    560             $targets[] = $this->parseExpression();
    561             if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
    562                 break;
    563             }
    564         }
    565 
    566         return new Twig_Node($targets);
    567     }
    568 
    569     protected function getFunctionNodeClass($name, $line)
    570     {
    571         $env = $this->parser->getEnvironment();
    572 
    573         if (false === $function = $env->getFunction($name)) {
    574             $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getFilename());
    575             $e->addSuggestions($name, array_keys($env->getFunctions()));
    576 
    577             throw $e;
    578         }
    579 
    580         if ($function instanceof Twig_SimpleFunction && $function->isDeprecated()) {
    581             $message = sprintf('Twig Function "%s" is deprecated', $function->getName());
    582             if (!is_bool($function->getDeprecatedVersion())) {
    583                 $message .= sprintf(' since version %s', $function->getDeprecatedVersion());
    584             }
    585             if ($function->getAlternative()) {
    586                 $message .= sprintf('. Use "%s" instead', $function->getAlternative());
    587             }
    588             $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line);
    589 
    590             @trigger_error($message, E_USER_DEPRECATED);
    591         }
    592 
    593         if ($function instanceof Twig_SimpleFunction) {
    594             return $function->getNodeClass();
    595         }
    596 
    597         return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function';
    598     }
    599 
    600     protected function getFilterNodeClass($name, $line)
    601     {
    602         $env = $this->parser->getEnvironment();
    603 
    604         if (false === $filter = $env->getFilter($name)) {
    605             $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getFilename());
    606             $e->addSuggestions($name, array_keys($env->getFilters()));
    607 
    608             throw $e;
    609         }
    610 
    611         if ($filter instanceof Twig_SimpleFilter && $filter->isDeprecated()) {
    612             $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
    613             if (!is_bool($filter->getDeprecatedVersion())) {
    614                 $message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
    615             }
    616             if ($filter->getAlternative()) {
    617                 $message .= sprintf('. Use "%s" instead', $filter->getAlternative());
    618             }
    619             $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line);
    620 
    621             @trigger_error($message, E_USER_DEPRECATED);
    622         }
    623 
    624         if ($filter instanceof Twig_SimpleFilter) {
    625             return $filter->getNodeClass();
    626         }
    627 
    628         return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
    629     }
    630 
    631     // checks that the node only contains "constant" elements
    632     protected function checkConstantExpression(Twig_NodeInterface $node)
    633     {
    634         if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array
    635             || $node instanceof Twig_Node_Expression_Unary_Neg || $node instanceof Twig_Node_Expression_Unary_Pos
    636         )) {
    637             return false;
    638         }
    639 
    640         foreach ($node as $n) {
    641             if (!$this->checkConstantExpression($n)) {
    642                 return false;
    643             }
    644         }
    645 
    646         return true;
    647     }
    648 }