| 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@symfony.com>
 | 
  
    | 23 |  */
 | 
  
    | 24 | class 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 |                         if ('(' === $this->parser->getCurrentToken()->getValue()) {
 | 
  
    | 138 |                             $node = $this->getFunctionNode($token->getValue(), $token->getLine());
 | 
  
    | 139 |                         } else {
 | 
  
    | 140 |                             $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
 | 
  
    | 141 |                         }
 | 
  
    | 142 |                 }
 | 
  
    | 143 |                 break;
 | 
  
    | 144 | 
 | 
  
    | 145 |             case Twig_Token::NUMBER_TYPE:
 | 
  
    | 146 |                 $this->parser->getStream()->next();
 | 
  
    | 147 |                 $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
 | 
  
    | 148 |                 break;
 | 
  
    | 149 | 
 | 
  
    | 150 |             case Twig_Token::STRING_TYPE:
 | 
  
    | 151 |             case Twig_Token::INTERPOLATION_START_TYPE:
 | 
  
    | 152 |                 $node = $this->parseStringExpression();
 | 
  
    | 153 |                 break;
 | 
  
    | 154 | 
 | 
  
    | 155 |             default:
 | 
  
    | 156 |                 if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
 | 
  
    | 157 |                     $node = $this->parseArrayExpression();
 | 
  
    | 158 |                 } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
 | 
  
    | 159 |                     $node = $this->parseHashExpression();
 | 
  
    | 160 |                 } else {
 | 
  
    | 161 |                     throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine());
 | 
  
    | 162 |                 }
 | 
  
    | 163 |         }
 | 
  
    | 164 | 
 | 
  
    | 165 |         return $this->parsePostfixExpression($node);
 | 
  
    | 166 |     }
 | 
  
    | 167 | 
 | 
  
    | 168 |     public function parseStringExpression()
 | 
  
    | 169 |     {
 | 
  
    | 170 |         $stream = $this->parser->getStream();
 | 
  
    | 171 | 
 | 
  
    | 172 |         $nodes = array();
 | 
  
    | 173 |         // a string cannot be followed by another string in a single expression
 | 
  
    | 174 |         $nextCanBeString = true;
 | 
  
    | 175 |         while (true) {
 | 
  
    | 176 |             if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
 | 
  
    | 177 |                 $token = $stream->next();
 | 
  
    | 178 |                 $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
 | 
  
    | 179 |                 $nextCanBeString = false;
 | 
  
    | 180 |             } elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
 | 
  
    | 181 |                 $stream->next();
 | 
  
    | 182 |                 $nodes[] = $this->parseExpression();
 | 
  
    | 183 |                 $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
 | 
  
    | 184 |                 $nextCanBeString = true;
 | 
  
    | 185 |             } else {
 | 
  
    | 186 |                 break;
 | 
  
    | 187 |             }
 | 
  
    | 188 |         }
 | 
  
    | 189 | 
 | 
  
    | 190 |         $expr = array_shift($nodes);
 | 
  
    | 191 |         foreach ($nodes as $node) {
 | 
  
    | 192 |             $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
 | 
  
    | 193 |         }
 | 
  
    | 194 | 
 | 
  
    | 195 |         return $expr;
 | 
  
    | 196 |     }
 | 
  
    | 197 | 
 | 
  
    | 198 |     public function parseArrayExpression()
 | 
  
    | 199 |     {
 | 
  
    | 200 |         $stream = $this->parser->getStream();
 | 
  
    | 201 |         $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
 | 
  
    | 202 | 
 | 
  
    | 203 |         $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
 | 
  
    | 204 |         $first = true;
 | 
  
    | 205 |         while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
 | 
  
    | 206 |             if (!$first) {
 | 
  
    | 207 |                 $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
 | 
  
    | 208 | 
 | 
  
    | 209 |                 // trailing ,?
 | 
  
    | 210 |                 if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
 | 
  
    | 211 |                     break;
 | 
  
    | 212 |                 }
 | 
  
    | 213 |             }
 | 
  
    | 214 |             $first = false;
 | 
  
    | 215 | 
 | 
  
    | 216 |             $node->addElement($this->parseExpression());
 | 
  
    | 217 |         }
 | 
  
    | 218 |         $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
 | 
  
    | 219 | 
 | 
  
    | 220 |         return $node;
 | 
  
    | 221 |     }
 | 
  
    | 222 | 
 | 
  
    | 223 |     public function parseHashExpression()
 | 
  
    | 224 |     {
 | 
  
    | 225 |         $stream = $this->parser->getStream();
 | 
  
    | 226 |         $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
 | 
  
    | 227 | 
 | 
  
    | 228 |         $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
 | 
  
    | 229 |         $first = true;
 | 
  
    | 230 |         while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
 | 
  
    | 231 |             if (!$first) {
 | 
  
    | 232 |                 $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
 | 
  
    | 233 | 
 | 
  
    | 234 |                 // trailing ,?
 | 
  
    | 235 |                 if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
 | 
  
    | 236 |                     break;
 | 
  
    | 237 |                 }
 | 
  
    | 238 |             }
 | 
  
    | 239 |             $first = false;
 | 
  
    | 240 | 
 | 
  
    | 241 |             // a hash key can be:
 | 
  
    | 242 |             //
 | 
  
    | 243 |             //  * a number -- 12
 | 
  
    | 244 |             //  * a string -- 'a'
 | 
  
    | 245 |             //  * a name, which is equivalent to a string -- a
 | 
  
    | 246 |             //  * an expression, which must be enclosed in parentheses -- (1 + 2)
 | 
  
    | 247 |             if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
 | 
  
    | 248 |                 $token = $stream->next();
 | 
  
    | 249 |                 $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
 | 
  
    | 250 |             } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
 | 
  
    | 251 |                 $key = $this->parseExpression();
 | 
  
    | 252 |             } else {
 | 
  
    | 253 |                 $current = $stream->getCurrent();
 | 
  
    | 254 | 
 | 
  
    | 255 |                 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->getLine()), $current->getValue()), $current->getLine());
 | 
  
    | 256 |             }
 | 
  
    | 257 | 
 | 
  
    | 258 |             $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
 | 
  
    | 259 |             $value = $this->parseExpression();
 | 
  
    | 260 | 
 | 
  
    | 261 |             $node->addElement($value, $key);
 | 
  
    | 262 |         }
 | 
  
    | 263 |         $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
 | 
  
    | 264 | 
 | 
  
    | 265 |         return $node;
 | 
  
    | 266 |     }
 | 
  
    | 267 | 
 | 
  
    | 268 |     public function parsePostfixExpression($node)
 | 
  
    | 269 |     {
 | 
  
    | 270 |         while (true) {
 | 
  
    | 271 |             $token = $this->parser->getCurrentToken();
 | 
  
    | 272 |             if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
 | 
  
    | 273 |                 if ('.' == $token->getValue() || '[' == $token->getValue()) {
 | 
  
    | 274 |                     $node = $this->parseSubscriptExpression($node);
 | 
  
    | 275 |                 } elseif ('|' == $token->getValue()) {
 | 
  
    | 276 |                     $node = $this->parseFilterExpression($node);
 | 
  
    | 277 |                 } else {
 | 
  
    | 278 |                     break;
 | 
  
    | 279 |                 }
 | 
  
    | 280 |             } else {
 | 
  
    | 281 |                 break;
 | 
  
    | 282 |             }
 | 
  
    | 283 |         }
 | 
  
    | 284 | 
 | 
  
    | 285 |         return $node;
 | 
  
    | 286 |     }
 | 
  
    | 287 | 
 | 
  
    | 288 |     public function getFunctionNode($name, $line)
 | 
  
    | 289 |     {
 | 
  
    | 290 |         $args = $this->parseArguments();
 | 
  
    | 291 |         switch ($name) {
 | 
  
    | 292 |             case 'parent':
 | 
  
    | 293 |                 if (!count($this->parser->getBlockStack())) {
 | 
  
    | 294 |                     throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line);
 | 
  
    | 295 |                 }
 | 
  
    | 296 | 
 | 
  
    | 297 |                 if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
 | 
  
    | 298 |                     throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line);
 | 
  
    | 299 |                 }
 | 
  
    | 300 | 
 | 
  
    | 301 |                 return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
 | 
  
    | 302 |             case 'block':
 | 
  
    | 303 |                 return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line);
 | 
  
    | 304 |             case 'attribute':
 | 
  
    | 305 |                 if (count($args) < 2) {
 | 
  
    | 306 |                     throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line);
 | 
  
    | 307 |                 }
 | 
  
    | 308 | 
 | 
  
    | 309 |                 return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line);
 | 
  
    | 310 |             default:
 | 
  
    | 311 |                 if (null !== $alias = $this->parser->getImportedFunction($name)) {
 | 
  
    | 312 |                     $arguments = new Twig_Node_Expression_Array(array(), $line);
 | 
  
    | 313 |                     foreach ($args as $n) {
 | 
  
    | 314 |                         $arguments->addElement($n);
 | 
  
    | 315 |                     }
 | 
  
    | 316 | 
 | 
  
    | 317 |                     $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
 | 
  
    | 318 |                     $node->setAttribute('safe', true);
 | 
  
    | 319 | 
 | 
  
    | 320 |                     return $node;
 | 
  
    | 321 |                 }
 | 
  
    | 322 | 
 | 
  
    | 323 |                 $class = $this->getFunctionNodeClass($name);
 | 
  
    | 324 | 
 | 
  
    | 325 |                 return new $class($name, $args, $line);
 | 
  
    | 326 |         }
 | 
  
    | 327 |     }
 | 
  
    | 328 | 
 | 
  
    | 329 |     public function parseSubscriptExpression($node)
 | 
  
    | 330 |     {
 | 
  
    | 331 |         $stream = $this->parser->getStream();
 | 
  
    | 332 |         $token = $stream->next();
 | 
  
    | 333 |         $lineno = $token->getLine();
 | 
  
    | 334 |         $arguments = new Twig_Node_Expression_Array(array(), $lineno);
 | 
  
    | 335 |         $type = Twig_TemplateInterface::ANY_CALL;
 | 
  
    | 336 |         if ($token->getValue() == '.') {
 | 
  
    | 337 |             $token = $stream->next();
 | 
  
    | 338 |             if (
 | 
  
    | 339 |                 $token->getType() == Twig_Token::NAME_TYPE
 | 
  
    | 340 |                 ||
 | 
  
    | 341 |                 $token->getType() == Twig_Token::NUMBER_TYPE
 | 
  
    | 342 |                 ||
 | 
  
    | 343 |                 ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
 | 
  
    | 344 |             ) {
 | 
  
    | 345 |                 $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
 | 
  
    | 346 | 
 | 
  
    | 347 |                 if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
 | 
  
    | 348 |                     $type = Twig_TemplateInterface::METHOD_CALL;
 | 
  
    | 349 |                     foreach ($this->parseArguments() as $n) {
 | 
  
    | 350 |                         $arguments->addElement($n);
 | 
  
    | 351 |                     }
 | 
  
    | 352 |                 }
 | 
  
    | 353 |             } else {
 | 
  
    | 354 |                 throw new Twig_Error_Syntax('Expected name or number', $lineno);
 | 
  
    | 355 |             }
 | 
  
    | 356 |         } else {
 | 
  
    | 357 |             $type = Twig_TemplateInterface::ARRAY_CALL;
 | 
  
    | 358 | 
 | 
  
    | 359 |             $arg = $this->parseExpression();
 | 
  
    | 360 | 
 | 
  
    | 361 |             // slice?
 | 
  
    | 362 |             if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
 | 
  
    | 363 |                 $stream->next();
 | 
  
    | 364 | 
 | 
  
    | 365 |                 if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
 | 
  
    | 366 |                     $length = new Twig_Node_Expression_Constant(null, $token->getLine());
 | 
  
    | 367 |                 } else {
 | 
  
    | 368 |                     $length = $this->parseExpression();
 | 
  
    | 369 |                 }
 | 
  
    | 370 | 
 | 
  
    | 371 |                 $class = $this->getFilterNodeClass('slice');
 | 
  
    | 372 |                 $arguments = new Twig_Node(array($arg, $length));
 | 
  
    | 373 |                 $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
 | 
  
    | 374 | 
 | 
  
    | 375 |                 $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
 | 
  
    | 376 | 
 | 
  
    | 377 |                 return $filter;
 | 
  
    | 378 |             }
 | 
  
    | 379 | 
 | 
  
    | 380 |             $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
 | 
  
    | 381 |         }
 | 
  
    | 382 | 
 | 
  
    | 383 |         return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
 | 
  
    | 384 |     }
 | 
  
    | 385 | 
 | 
  
    | 386 |     public function parseFilterExpression($node)
 | 
  
    | 387 |     {
 | 
  
    | 388 |         $this->parser->getStream()->next();
 | 
  
    | 389 | 
 | 
  
    | 390 |         return $this->parseFilterExpressionRaw($node);
 | 
  
    | 391 |     }
 | 
  
    | 392 | 
 | 
  
    | 393 |     public function parseFilterExpressionRaw($node, $tag = null)
 | 
  
    | 394 |     {
 | 
  
    | 395 |         while (true) {
 | 
  
    | 396 |             $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
 | 
  
    | 397 | 
 | 
  
    | 398 |             $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
 | 
  
    | 399 |             if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
 | 
  
    | 400 |                 $arguments = new Twig_Node();
 | 
  
    | 401 |             } else {
 | 
  
    | 402 |                 $arguments = $this->parseArguments();
 | 
  
    | 403 |             }
 | 
  
    | 404 | 
 | 
  
    | 405 |             $class = $this->getFilterNodeClass($name->getAttribute('value'));
 | 
  
    | 406 | 
 | 
  
    | 407 |             $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
 | 
  
    | 408 | 
 | 
  
    | 409 |             if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
 | 
  
    | 410 |                 break;
 | 
  
    | 411 |             }
 | 
  
    | 412 | 
 | 
  
    | 413 |             $this->parser->getStream()->next();
 | 
  
    | 414 |         }
 | 
  
    | 415 | 
 | 
  
    | 416 |         return $node;
 | 
  
    | 417 |     }
 | 
  
    | 418 | 
 | 
  
    | 419 |     public function parseArguments()
 | 
  
    | 420 |     {
 | 
  
    | 421 |         $args = array();
 | 
  
    | 422 |         $stream = $this->parser->getStream();
 | 
  
    | 423 | 
 | 
  
    | 424 |         $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis');
 | 
  
    | 425 |         while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
 | 
  
    | 426 |             if (!empty($args)) {
 | 
  
    | 427 |                 $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
 | 
  
    | 428 |             }
 | 
  
    | 429 |             $args[] = $this->parseExpression();
 | 
  
    | 430 |         }
 | 
  
    | 431 |         $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
 | 
  
    | 432 | 
 | 
  
    | 433 |         return new Twig_Node($args);
 | 
  
    | 434 |     }
 | 
  
    | 435 | 
 | 
  
    | 436 |     public function parseAssignmentExpression()
 | 
  
    | 437 |     {
 | 
  
    | 438 |         $targets = array();
 | 
  
    | 439 |         while (true) {
 | 
  
    | 440 |             $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
 | 
  
    | 441 |             if (in_array($token->getValue(), array('true', 'false', 'none'))) {
 | 
  
    | 442 |                 throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine());
 | 
  
    | 443 |             }
 | 
  
    | 444 |             $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
 | 
  
    | 445 | 
 | 
  
    | 446 |             if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
 | 
  
    | 447 |                 break;
 | 
  
    | 448 |             }
 | 
  
    | 449 |             $this->parser->getStream()->next();
 | 
  
    | 450 |         }
 | 
  
    | 451 | 
 | 
  
    | 452 |         return new Twig_Node($targets);
 | 
  
    | 453 |     }
 | 
  
    | 454 | 
 | 
  
    | 455 |     public function parseMultitargetExpression()
 | 
  
    | 456 |     {
 | 
  
    | 457 |         $targets = array();
 | 
  
    | 458 |         while (true) {
 | 
  
    | 459 |             $targets[] = $this->parseExpression();
 | 
  
    | 460 |             if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
 | 
  
    | 461 |                 break;
 | 
  
    | 462 |             }
 | 
  
    | 463 |             $this->parser->getStream()->next();
 | 
  
    | 464 |         }
 | 
  
    | 465 | 
 | 
  
    | 466 |         return new Twig_Node($targets);
 | 
  
    | 467 |     }
 | 
  
    | 468 | 
 | 
  
    | 469 |     protected function getFunctionNodeClass($name)
 | 
  
    | 470 |     {
 | 
  
    | 471 |         $functionMap = $this->parser->getEnvironment()->getFunctions();
 | 
  
    | 472 |         if (isset($functionMap[$name]) && $functionMap[$name] instanceof Twig_Function_Node) {
 | 
  
    | 473 |             return $functionMap[$name]->getClass();
 | 
  
    | 474 |         }
 | 
  
    | 475 | 
 | 
  
    | 476 |         return 'Twig_Node_Expression_Function';
 | 
  
    | 477 |     }
 | 
  
    | 478 | 
 | 
  
    | 479 |     protected function getFilterNodeClass($name)
 | 
  
    | 480 |     {
 | 
  
    | 481 |         $filterMap = $this->parser->getEnvironment()->getFilters();
 | 
  
    | 482 |         if (isset($filterMap[$name]) && $filterMap[$name] instanceof Twig_Filter_Node) {
 | 
  
    | 483 |             return $filterMap[$name]->getClass();
 | 
  
    | 484 |         }
 | 
  
    | 485 | 
 | 
  
    | 486 |         return 'Twig_Node_Expression_Filter';
 | 
  
    | 487 |     }
 | 
  
    | 488 | }
 |