Twig-1.3.0/lib/Twig/Parser.php
changeset 4 9a001a04b634
equal deleted inserted replaced
3:6d109e3804ac 4:9a001a04b634
       
     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  * Default parser implementation.
       
    15  *
       
    16  * @package twig
       
    17  * @author  Fabien Potencier <fabien@symfony.com>
       
    18  */
       
    19 class Twig_Parser implements Twig_ParserInterface
       
    20 {
       
    21     protected $stream;
       
    22     protected $parent;
       
    23     protected $handlers;
       
    24     protected $visitors;
       
    25     protected $expressionParser;
       
    26     protected $blocks;
       
    27     protected $blockStack;
       
    28     protected $macros;
       
    29     protected $env;
       
    30     protected $reservedMacroNames;
       
    31     protected $importedFunctions;
       
    32     protected $tmpVarCount;
       
    33     protected $traits;
       
    34 
       
    35     /**
       
    36      * Constructor.
       
    37      *
       
    38      * @param Twig_Environment $env A Twig_Environment instance
       
    39      */
       
    40     public function __construct(Twig_Environment $env)
       
    41     {
       
    42         $this->env = $env;
       
    43     }
       
    44 
       
    45     public function getVarName()
       
    46     {
       
    47         return sprintf('__internal_%s_%d', substr($this->env->getTemplateClass($this->stream->getFilename()), strlen($this->env->getTemplateClassPrefix())), ++$this->tmpVarCount);
       
    48     }
       
    49 
       
    50     /**
       
    51      * Converts a token stream to a node tree.
       
    52      *
       
    53      * @param  Twig_TokenStream $stream A token stream instance
       
    54      *
       
    55      * @return Twig_Node_Module A node tree
       
    56      */
       
    57     public function parse(Twig_TokenStream $stream)
       
    58     {
       
    59         $this->tmpVarCount = 0;
       
    60 
       
    61         // tag handlers
       
    62         $this->handlers = $this->env->getTokenParsers();
       
    63         $this->handlers->setParser($this);
       
    64 
       
    65         // node visitors
       
    66         $this->visitors = $this->env->getNodeVisitors();
       
    67 
       
    68         if (null === $this->expressionParser) {
       
    69             $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators());
       
    70         }
       
    71 
       
    72         $this->stream = $stream;
       
    73         $this->parent = null;
       
    74         $this->blocks = array();
       
    75         $this->macros = array();
       
    76         $this->traits = array();
       
    77         $this->blockStack = array();
       
    78         $this->importedFunctions = array(array());
       
    79 
       
    80         try {
       
    81             $body = $this->subparse(null);
       
    82 
       
    83             if (null !== $this->parent) {
       
    84                 if (null === $body = $this->filterBodyNodes($body)) {
       
    85                     $body = new Twig_Node();
       
    86                 }
       
    87             }
       
    88         } catch (Twig_Error_Syntax $e) {
       
    89             if (null === $e->getTemplateFile()) {
       
    90                 $e->setTemplateFile($this->stream->getFilename());
       
    91             }
       
    92 
       
    93             throw $e;
       
    94         }
       
    95 
       
    96         $node = new Twig_Node_Module($body, $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->stream->getFilename());
       
    97 
       
    98         $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
       
    99 
       
   100         return $traverser->traverse($node);
       
   101     }
       
   102 
       
   103     public function subparse($test, $dropNeedle = false)
       
   104     {
       
   105         $lineno = $this->getCurrentToken()->getLine();
       
   106         $rv = array();
       
   107         while (!$this->stream->isEOF()) {
       
   108             switch ($this->getCurrentToken()->getType()) {
       
   109                 case Twig_Token::TEXT_TYPE:
       
   110                     $token = $this->stream->next();
       
   111                     $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine());
       
   112                     break;
       
   113 
       
   114                 case Twig_Token::VAR_START_TYPE:
       
   115                     $token = $this->stream->next();
       
   116                     $expr = $this->expressionParser->parseExpression();
       
   117                     $this->stream->expect(Twig_Token::VAR_END_TYPE);
       
   118                     $rv[] = new Twig_Node_Print($expr, $token->getLine());
       
   119                     break;
       
   120 
       
   121                 case Twig_Token::BLOCK_START_TYPE:
       
   122                     $this->stream->next();
       
   123                     $token = $this->getCurrentToken();
       
   124 
       
   125                     if ($token->getType() !== Twig_Token::NAME_TYPE) {
       
   126                         throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->stream->getFilename());
       
   127                     }
       
   128 
       
   129                     if (null !== $test && call_user_func($test, $token)) {
       
   130                         if ($dropNeedle) {
       
   131                             $this->stream->next();
       
   132                         }
       
   133 
       
   134                         if (1 === count($rv)) {
       
   135                             return $rv[0];
       
   136                         }
       
   137 
       
   138                         return new Twig_Node($rv, array(), $lineno);
       
   139                     }
       
   140 
       
   141                     $subparser = $this->handlers->getTokenParser($token->getValue());
       
   142                     if (null === $subparser) {
       
   143                         if (null !== $test) {
       
   144                             throw new Twig_Error_Syntax(sprintf('Unexpected tag name "%s" (expecting closing tag for the "%s" tag defined near line %s)', $token->getValue(), $test[0]->getTag(), $lineno), $token->getLine(), $this->stream->getFilename());
       
   145                         }
       
   146 
       
   147                         throw new Twig_Error_Syntax(sprintf('Unknown tag name "%s"', $token->getValue()), $token->getLine(), $this->stream->getFilename());
       
   148                     }
       
   149 
       
   150                     $this->stream->next();
       
   151 
       
   152                     $node = $subparser->parse($token);
       
   153                     if (null !== $node) {
       
   154                         $rv[] = $node;
       
   155                     }
       
   156                     break;
       
   157 
       
   158                 default:
       
   159                     throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', -1, $this->stream->getFilename());
       
   160             }
       
   161         }
       
   162 
       
   163         if (1 === count($rv)) {
       
   164             return $rv[0];
       
   165         }
       
   166 
       
   167         return new Twig_Node($rv, array(), $lineno);
       
   168     }
       
   169 
       
   170     public function addHandler($name, $class)
       
   171     {
       
   172         $this->handlers[$name] = $class;
       
   173     }
       
   174 
       
   175     public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
       
   176     {
       
   177         $this->visitors[] = $visitor;
       
   178     }
       
   179 
       
   180     public function getBlockStack()
       
   181     {
       
   182         return $this->blockStack;
       
   183     }
       
   184 
       
   185     public function peekBlockStack()
       
   186     {
       
   187         return $this->blockStack[count($this->blockStack) - 1];
       
   188     }
       
   189 
       
   190     public function popBlockStack()
       
   191     {
       
   192         array_pop($this->blockStack);
       
   193     }
       
   194 
       
   195     public function pushBlockStack($name)
       
   196     {
       
   197         $this->blockStack[] = $name;
       
   198     }
       
   199 
       
   200     public function hasBlock($name)
       
   201     {
       
   202         return isset($this->blocks[$name]);
       
   203     }
       
   204 
       
   205     public function setBlock($name, $value)
       
   206     {
       
   207         $this->blocks[$name] = $value;
       
   208     }
       
   209 
       
   210     public function hasMacro($name)
       
   211     {
       
   212         return isset($this->macros[$name]);
       
   213     }
       
   214 
       
   215     public function setMacro($name, Twig_Node_Macro $node)
       
   216     {
       
   217         if (null === $this->reservedMacroNames) {
       
   218             $this->reservedMacroNames = array();
       
   219             $r = new ReflectionClass($this->env->getBaseTemplateClass());
       
   220             foreach ($r->getMethods() as $method) {
       
   221                 $this->reservedMacroNames[] = $method->getName();
       
   222             }
       
   223         }
       
   224 
       
   225         if (in_array($name, $this->reservedMacroNames)) {
       
   226             throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine());
       
   227         }
       
   228 
       
   229         $this->macros[$name] = $node;
       
   230     }
       
   231 
       
   232     public function addTrait($trait)
       
   233     {
       
   234         $this->traits[] = $trait;
       
   235     }
       
   236 
       
   237     public function hasTraits()
       
   238     {
       
   239         return count($this->traits) > 0;
       
   240     }
       
   241 
       
   242     public function addImportedFunction($alias, $name, Twig_Node_Expression $node)
       
   243     {
       
   244         $this->importedFunctions[0][$alias] = array('name' => $name, 'node' => $node);
       
   245     }
       
   246 
       
   247     public function getImportedFunction($alias)
       
   248     {
       
   249         foreach ($this->importedFunctions as $functions) {
       
   250             if (isset($functions[$alias])) {
       
   251                 return $functions[$alias];
       
   252             }
       
   253         }
       
   254     }
       
   255 
       
   256     public function isMainScope()
       
   257     {
       
   258         return 1 === count($this->importedFunctions);
       
   259     }
       
   260 
       
   261     public function pushLocalScope()
       
   262     {
       
   263         array_unshift($this->importedFunctions, array());
       
   264     }
       
   265 
       
   266     public function popLocalScope()
       
   267     {
       
   268         array_shift($this->importedFunctions);
       
   269     }
       
   270 
       
   271     /**
       
   272      * Gets the expression parser.
       
   273      *
       
   274      * @return Twig_ExpressionParser The expression parser
       
   275      */
       
   276     public function getExpressionParser()
       
   277     {
       
   278         return $this->expressionParser;
       
   279     }
       
   280 
       
   281     public function getParent()
       
   282     {
       
   283         return $this->parent;
       
   284     }
       
   285 
       
   286     public function setParent($parent)
       
   287     {
       
   288         $this->parent = $parent;
       
   289     }
       
   290 
       
   291     /**
       
   292      * Gets the token stream.
       
   293      *
       
   294      * @return Twig_TokenStream The token stream
       
   295      */
       
   296     public function getStream()
       
   297     {
       
   298         return $this->stream;
       
   299     }
       
   300 
       
   301     /**
       
   302      * Gets the current token.
       
   303      *
       
   304      * @return Twig_Token The current token
       
   305      */
       
   306     public function getCurrentToken()
       
   307     {
       
   308         return $this->stream->getCurrent();
       
   309     }
       
   310 
       
   311     protected function filterBodyNodes(Twig_NodeInterface $node)
       
   312     {
       
   313         // check that the body does not contain non-empty output nodes
       
   314         if (
       
   315             ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data')))
       
   316             ||
       
   317             (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
       
   318         ) {
       
   319             throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->stream->getFilename());
       
   320         }
       
   321 
       
   322         // bypass "set" nodes as they "capture" the output
       
   323         if ($node instanceof Twig_Node_Set) {
       
   324             return $node;
       
   325         }
       
   326 
       
   327         if ($node instanceof Twig_NodeOutputInterface) {
       
   328             return;
       
   329         }
       
   330 
       
   331         foreach ($node as $k => $n) {
       
   332             if (null !== $n && null === $n = $this->filterBodyNodes($n)) {
       
   333                 $node->removeNode($k);
       
   334             }
       
   335         }
       
   336 
       
   337         return $node;
       
   338     }
       
   339 }