Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

ExpressionParser.php

Zuletzt modifiziert: 09.10.2024, 12:57 - Dateigröße: 23.15 KiB


001  <?php
002   
003  /*
004   * This file is part of Twig.
005   *
006   * (c) 2009 Fabien Potencier
007   * (c) 2009 Armin Ronacher
008   *
009   * For the full copyright and license information, please view the LICENSE
010   * file that was distributed with this source code.
011   */
012   
013  /**
014   * Parses expressions.
015   *
016   * This parser implements a "Precedence climbing" algorithm.
017   *
018   * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
019   * @see http://en.wikipedia.org/wiki/Operator-precedence_parser
020   *
021   * @author Fabien Potencier <fabien@symfony.com>
022   */
023  class Twig_ExpressionParser
024  {
025      const OPERATOR_LEFT = 1;
026      const OPERATOR_RIGHT = 2;
027   
028      protected $parser;
029      protected $unaryOperators;
030      protected $binaryOperators;
031   
032      public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators)
033      {
034          $this->parser = $parser;
035          $this->unaryOperators = $unaryOperators;
036          $this->binaryOperators = $binaryOperators;
037      }
038   
039      public function parseExpression($precedence = 0)
040      {
041          $expr = $this->getPrimary();
042          $token = $this->parser->getCurrentToken();
043          while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
044              $op = $this->binaryOperators[$token->getValue()];
045              $this->parser->getStream()->next();
046   
047              if (isset($op['callable'])) {
048                  $expr = call_user_func($op['callable'], $this->parser, $expr);
049              } else {
050                  $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
051                  $class = $op['class'];
052                  $expr = new $class($expr, $expr1, $token->getLine());
053              }
054   
055              $token = $this->parser->getCurrentToken();
056          }
057   
058          if (0 === $precedence) {
059              return $this->parseConditionalExpression($expr);
060          }
061   
062          return $expr;
063      }
064   
065      protected function getPrimary()
066      {
067          $token = $this->parser->getCurrentToken();
068   
069          if ($this->isUnary($token)) {
070              $operator = $this->unaryOperators[$token->getValue()];
071              $this->parser->getStream()->next();
072              $expr = $this->parseExpression($operator['precedence']);
073              $class = $operator['class'];
074   
075              return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
076          } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
077              $this->parser->getStream()->next();
078              $expr = $this->parseExpression();
079              $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
080   
081              return $this->parsePostfixExpression($expr);
082          }
083   
084          return $this->parsePrimaryExpression();
085      }
086   
087      protected function parseConditionalExpression($expr)
088      {
089          while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
090              $this->parser->getStream()->next();
091              if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
092                  $expr2 = $this->parseExpression();
093                  if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
094                      $this->parser->getStream()->next();
095                      $expr3 = $this->parseExpression();
096                  } else {
097                      $expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
098                  }
099              } else {
100                  $this->parser->getStream()->next();
101                  $expr2 = $expr;
102                  $expr3 = $this->parseExpression();
103              }
104   
105              $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
106          }
107   
108          return $expr;
109      }
110   
111      protected function isUnary(Twig_Token $token)
112      {
113          return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
114      }
115   
116      protected function isBinary(Twig_Token $token)
117      {
118          return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
119      }
120   
121      public function parsePrimaryExpression()
122      {
123          $token = $this->parser->getCurrentToken();
124          switch ($token->getType()) {
125              case Twig_Token::NAME_TYPE:
126                  $this->parser->getStream()->next();
127                  switch ($token->getValue()) {
128                      case 'true':
129                      case 'TRUE':
130                          $node = new Twig_Node_Expression_Constant(true, $token->getLine());
131                          break;
132   
133                      case 'false':
134                      case 'FALSE':
135                          $node = new Twig_Node_Expression_Constant(false, $token->getLine());
136                          break;
137   
138                      case 'none':
139                      case 'NONE':
140                      case 'null':
141                      case 'NULL':
142                          $node = new Twig_Node_Expression_Constant(null, $token->getLine());
143                          break;
144   
145                      default:
146                          if ('(' === $this->parser->getCurrentToken()->getValue()) {
147                              $node = $this->getFunctionNode($token->getValue(), $token->getLine());
148                          } else {
149                              $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
150                          }
151                  }
152                  break;
153   
154              case Twig_Token::NUMBER_TYPE:
155                  $this->parser->getStream()->next();
156                  $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
157                  break;
158   
159              case Twig_Token::STRING_TYPE:
160              case Twig_Token::INTERPOLATION_START_TYPE:
161                  $node = $this->parseStringExpression();
162                  break;
163   
164              default:
165                  if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
166                      $node = $this->parseArrayExpression();
167                  } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
168                      $node = $this->parseHashExpression();
169                  } else {
170                      throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
171                  }
172          }
173   
174          return $this->parsePostfixExpression($node);
175      }
176   
177      public function parseStringExpression()
178      {
179          $stream = $this->parser->getStream();
180   
181          $nodes = array();
182          // a string cannot be followed by another string in a single expression
183          $nextCanBeString = true;
184          while (true) {
185              if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
186                  $token = $stream->next();
187                  $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
188                  $nextCanBeString = false;
189              } elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
190                  $stream->next();
191                  $nodes[] = $this->parseExpression();
192                  $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
193                  $nextCanBeString = true;
194              } else {
195                  break;
196              }
197          }
198   
199          $expr = array_shift($nodes);
200          foreach ($nodes as $node) {
201              $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
202          }
203   
204          return $expr;
205      }
206   
207      public function parseArrayExpression()
208      {
209          $stream = $this->parser->getStream();
210          $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
211   
212          $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
213          $first = true;
214          while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
215              if (!$first) {
216                  $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
217   
218                  // trailing ,?
219                  if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
220                      break;
221                  }
222              }
223              $first = false;
224   
225              $node->addElement($this->parseExpression());
226          }
227          $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
228   
229          return $node;
230      }
231   
232      public function parseHashExpression()
233      {
234          $stream = $this->parser->getStream();
235          $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
236   
237          $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
238          $first = true;
239          while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
240              if (!$first) {
241                  $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
242   
243                  // trailing ,?
244                  if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
245                      break;
246                  }
247              }
248              $first = false;
249   
250              // a hash key can be:
251              //
252              //  * a number -- 12
253              //  * a string -- 'a'
254              //  * a name, which is equivalent to a string -- a
255              //  * an expression, which must be enclosed in parentheses -- (1 + 2)
256              if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
257                  $token = $stream->next();
258                  $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
259              } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
260                  $key = $this->parseExpression();
261              } else {
262                  $current = $stream->getCurrent();
263   
264                  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(), $this->parser->getFilename());
265              }
266   
267              $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
268              $value = $this->parseExpression();
269   
270              $node->addElement($value, $key);
271          }
272          $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
273   
274          return $node;
275      }
276   
277      public function parsePostfixExpression($node)
278      {
279          while (true) {
280              $token = $this->parser->getCurrentToken();
281              if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
282                  if ('.' == $token->getValue() || '[' == $token->getValue()) {
283                      $node = $this->parseSubscriptExpression($node);
284                  } elseif ('|' == $token->getValue()) {
285                      $node = $this->parseFilterExpression($node);
286                  } else {
287                      break;
288                  }
289              } else {
290                  break;
291              }
292          }
293   
294          return $node;
295      }
296   
297      public function getFunctionNode($name, $line)
298      {
299          switch ($name) {
300              case 'parent':
301                  $args = $this->parseArguments();
302                  if (!count($this->parser->getBlockStack())) {
303                      throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
304                  }
305   
306                  if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
307                      throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename());
308                  }
309   
310                  return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
311              case 'block':
312                  return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
313              case 'attribute':
314                  $args = $this->parseArguments();
315                  if (count($args) < 2) {
316                      throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
317                  }
318   
319                  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);
320              default:
321                  if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
322                      $arguments = new Twig_Node_Expression_Array(array(), $line);
323                      foreach ($this->parseArguments() as $n) {
324                          $arguments->addElement($n);
325                      }
326   
327                      $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
328                      $node->setAttribute('safe', true);
329   
330                      return $node;
331                  }
332   
333                  $args = $this->parseArguments(true);
334                  $class = $this->getFunctionNodeClass($name, $line);
335   
336                  return new $class($name, $args, $line);
337          }
338      }
339   
340      public function parseSubscriptExpression($node)
341      {
342          $stream = $this->parser->getStream();
343          $token = $stream->next();
344          $lineno = $token->getLine();
345          $arguments = new Twig_Node_Expression_Array(array(), $lineno);
346          $type = Twig_TemplateInterface::ANY_CALL;
347          if ($token->getValue() == '.') {
348              $token = $stream->next();
349              if (
350                  $token->getType() == Twig_Token::NAME_TYPE
351                  ||
352                  $token->getType() == Twig_Token::NUMBER_TYPE
353                  ||
354                  ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
355              ) {
356                  $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
357   
358                  if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
359                      $type = Twig_TemplateInterface::METHOD_CALL;
360                      foreach ($this->parseArguments() as $n) {
361                          $arguments->addElement($n);
362                      }
363                  }
364              } else {
365                  throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
366              }
367   
368              if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
369                  if (!$arg instanceof Twig_Node_Expression_Constant) {
370                      throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
371                  }
372   
373                  $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
374                  $node->setAttribute('safe', true);
375   
376                  return $node;
377              }
378          } else {
379              $type = Twig_TemplateInterface::ARRAY_CALL;
380   
381              // slice?
382              $slice = false;
383              if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
384                  $slice = true;
385                  $arg = new Twig_Node_Expression_Constant(0, $token->getLine());
386              } else {
387                  $arg = $this->parseExpression();
388              }
389   
390              if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
391                  $slice = true;
392                  $stream->next();
393              }
394   
395              if ($slice) {
396                  if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
397                      $length = new Twig_Node_Expression_Constant(null, $token->getLine());
398                  } else {
399                      $length = $this->parseExpression();
400                  }
401   
402                  $class = $this->getFilterNodeClass('slice', $token->getLine());
403                  $arguments = new Twig_Node(array($arg, $length));
404                  $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
405   
406                  $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
407   
408                  return $filter;
409              }
410   
411              $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
412          }
413   
414          return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
415      }
416   
417      public function parseFilterExpression($node)
418      {
419          $this->parser->getStream()->next();
420   
421          return $this->parseFilterExpressionRaw($node);
422      }
423   
424      public function parseFilterExpressionRaw($node, $tag = null)
425      {
426          while (true) {
427              $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
428   
429              $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
430              if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
431                  $arguments = new Twig_Node();
432              } else {
433                  $arguments = $this->parseArguments(true);
434              }
435   
436              $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
437   
438              $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
439   
440              if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
441                  break;
442              }
443   
444              $this->parser->getStream()->next();
445          }
446   
447          return $node;
448      }
449   
450      /**
451       * Parses arguments.
452       *
453       * @param Boolean $namedArguments Whether to allow named arguments or not
454       * @param Boolean $definition     Whether we are parsing arguments for a function definition
455       */
456      public function parseArguments($namedArguments = false, $definition = false)
457      {
458          $args = array();
459          $stream = $this->parser->getStream();
460   
461          $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
462          while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
463              if (!empty($args)) {
464                  $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
465              }
466   
467              if ($definition) {
468                  $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
469                  $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
470              } else {
471                  $value = $this->parseExpression();
472              }
473   
474              $name = null;
475              if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
476                  $token = $stream->next();
477                  if (!$value instanceof Twig_Node_Expression_Name) {
478                      throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
479                  }
480                  $name = $value->getAttribute('name');
481   
482                  if ($definition) {
483                      $value = $this->parsePrimaryExpression();
484   
485                      if (!$this->checkConstantExpression($value)) {
486                          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());
487                      }
488                  } else {
489                      $value = $this->parseExpression();
490                  }
491              }
492   
493              if ($definition) {
494                  if (null === $name) {
495                      $name = $value->getAttribute('name');
496                      $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
497                  }
498                  $args[$name] = $value;
499              } else {
500                  if (null === $name) {
501                      $args[] = $value;
502                  } else {
503                      $args[$name] = $value;
504                  }
505              }
506          }
507          $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
508   
509          return new Twig_Node($args);
510      }
511   
512      public function parseAssignmentExpression()
513      {
514          $targets = array();
515          while (true) {
516              $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
517              if (in_array($token->getValue(), array('true', 'false', 'none'))) {
518                  throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
519              }
520              $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
521   
522              if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
523                  break;
524              }
525              $this->parser->getStream()->next();
526          }
527   
528          return new Twig_Node($targets);
529      }
530   
531      public function parseMultitargetExpression()
532      {
533          $targets = array();
534          while (true) {
535              $targets[] = $this->parseExpression();
536              if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
537                  break;
538              }
539              $this->parser->getStream()->next();
540          }
541   
542          return new Twig_Node($targets);
543      }
544   
545      protected function getFunctionNodeClass($name, $line)
546      {
547          $env = $this->parser->getEnvironment();
548   
549          if (false === $function = $env->getFunction($name)) {
550              $message = sprintf('The function "%s" does not exist', $name);
551              if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFunctions()))) {
552                  $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
553              }
554   
555              throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
556          }
557   
558          if ($function instanceof Twig_SimpleFunction) {
559              return $function->getNodeClass();
560          }
561   
562          return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function';
563      }
564   
565      protected function getFilterNodeClass($name, $line)
566      {
567          $env = $this->parser->getEnvironment();
568   
569          if (false === $filter = $env->getFilter($name)) {
570              $message = sprintf('The filter "%s" does not exist', $name);
571              if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFilters()))) {
572                  $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
573              }
574   
575              throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
576          }
577   
578          if ($filter instanceof Twig_SimpleFilter) {
579              return $filter->getNodeClass();
580          }
581   
582          return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
583      }
584   
585      // checks that the node only contains "constant" elements
586      protected function checkConstantExpression(Twig_NodeInterface $node)
587      {
588          if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) {
589              return false;
590          }
591   
592          foreach ($node as $n) {
593              if (!$this->checkConstantExpression($n)) {
594                  return false;
595              }
596          }
597   
598          return true;
599      }
600  }
601