Resolve imported functions at compile time
authorArnaud Le Blanc <arnaud.lb@gmail.com>
Thu, 23 Dec 2010 21:10:18 +0000 (22:10 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Thu, 30 Dec 2010 08:31:08 +0000 (09:31 +0100)
{% from %} is compiled as {% import %}, the imported module is placed in
a local variable, and calls to imported functions are compiled as calls
to module functions.

lib/Twig/ExpressionParser.php
lib/Twig/Node/From.php
lib/Twig/Node/Import.php
lib/Twig/Parser.php
lib/Twig/TokenParser/Block.php
lib/Twig/TokenParser/From.php
lib/Twig/TokenParser/Macro.php

index 01a6e70..c988722 100644 (file)
@@ -213,7 +213,11 @@ class Twig_ExpressionParser
                 } elseif ('|' == $token->getValue()) {
                     $node = $this->parseFilterExpression($node);
                 } elseif ($firstPass && $node instanceof Twig_Node_Expression_Name && '(' == $token->getValue()) {
-                    $node = new Twig_Node_Expression_Function($node, $this->parseArguments(), $node->getLine());
+                    if (null !== $alias = $this->parser->getImportedFunction($node->getAttribute('name'))) {
+                        $node = new Twig_Node_Expression_GetAttr($alias['node'], new Twig_Node_Expression_Constant($alias['name'], $node->getLine()), $this->parseArguments(), $node->getLine(), Twig_Node_Expression_GetAttr::TYPE_METHOD);
+                    } else {
+                        $node = new Twig_Node_Expression_Function($node, $this->parseArguments(), $node->getLine());
+                    }
                 } else {
                     break;
                 }
index f75392a..0af0fea 100644 (file)
  */
 class Twig_Node_From extends Twig_Node_Import
 {
-    public function __construct(Twig_Node_Expression $expr, array $imports, $lineno, $tag = null)
+    public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
     {
-        parent::__construct($expr, new Twig_Node_Expression_AssignName('_imported_'.rand(10000, 99999), $lineno), $lineno, $tag);
-
-        $this->setAttribute('imports', $imports);
-    }
-
-    /**
-     * Compiles the node to PHP.
-     *
-     * @param Twig_Compiler A Twig_Compiler instance
-     */
-    public function compile($compiler)
-    {
-        parent::compile($compiler);
-
-        foreach ($this->getAttribute('imports') as $name => $alias) {
-            $compiler
-                ->write('$context[')
-                ->repr('fn_'.$alias)
-                ->raw('] = new Twig_Function(')
-                ->subcompile($this->getNode('var'))
-                ->raw(', ')
-                ->repr($name)
-                ->raw(");\n")
-            ;
-        }
+        parent::__construct($expr, new Twig_Node_Expression_AssignLocalName(null, $lineno), $lineno, $tag);
     }
 }
index f586371..1314b3f 100644 (file)
@@ -17,7 +17,7 @@
  */
 class Twig_Node_Import extends Twig_Node
 {
-    public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression_AssignName $var, $lineno, $tag = null)
+    public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $var, $lineno, $tag = null)
     {
         parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag);
     }
index 2696754..0d13798 100644 (file)
@@ -21,6 +21,7 @@ class Twig_Parser implements Twig_ParserInterface
     protected $macros;
     protected $env;
     protected $reservedMacroNames;
+    protected $importedFunctions;
 
     public function __construct(Twig_Environment $env)
     {
@@ -52,6 +53,7 @@ class Twig_Parser implements Twig_ParserInterface
         $this->blocks = array();
         $this->macros = array();
         $this->blockStack = array();
+        $this->importedFunctions = array(array());
 
         try {
             $body = $this->subparse(null);
@@ -191,6 +193,32 @@ class Twig_Parser implements Twig_ParserInterface
         $this->macros[$name] = $node;
     }
 
+    public function addImportedFunction($alias, $name, Twig_Node_Expression $node)
+    {
+        $this->importedFunctions[0][$alias] = array(
+            'name' => $name,
+            'node' => $node,
+        );
+    }
+
+    public function getImportedFunction($alias)
+    {
+        if (!isset($this->importedFunctions[0][$alias])) {
+            return null;
+        }
+        return $this->importedFunctions[0][$alias];
+    }
+
+    public function pushLocalScope()
+    {
+        array_unshift($this->importedFunctions, array());
+    }
+
+    public function popLocalScope()
+    {
+        array_shift($this->importedFunctions);
+    }
+
     public function getExpressionParser()
     {
         return $this->expressionParser;
index 37d6457..5ff104d 100644 (file)
@@ -26,6 +26,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
         if ($this->parser->hasBlock($name)) {
             throw new Twig_Error_Syntax("The block '$name' has already been defined", $lineno);
         }
+        $this->parser->pushLocalScope();
         $this->parser->pushBlockStack($name);
 
         if ($stream->test(Twig_Token::BLOCK_END_TYPE)) {
@@ -49,6 +50,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
         $block = new Twig_Node_Block($name, $body, $lineno);
         $this->parser->setBlock($name, $block);
         $this->parser->popBlockStack();
+        $this->parser->popLocalScope();
 
         return new Twig_Node_BlockReference($name, $lineno, $this->getTag());
     }
index 9398f1d..f71027a 100644 (file)
@@ -45,7 +45,14 @@ class Twig_TokenParser_From extends Twig_TokenParser
 
         $stream->expect(Twig_Token::BLOCK_END_TYPE);
 
-        return new Twig_Node_From($macro, $targets, $token->getLine(), $this->getTag());
+        $node = new Twig_Node_From($macro, $token->getLine(), $this->getTag());
+
+        foreach($targets as $name => $alias)
+        {
+            $this->parser->addImportedFunction($alias, $name, $node->getNode('var'));
+        }
+
+        return $node;
     }
 
     /**
index 20186c6..a6e4733 100644 (file)
@@ -25,7 +25,9 @@ class Twig_TokenParser_Macro extends Twig_TokenParser
         $arguments = $this->parser->getExpressionParser()->parseArguments();
 
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $this->parser->pushLocalScope();
         $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+        $this->parser->popLocalScope();
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
 
         $this->parser->setMacro($name, new Twig_Node_Macro($name, $body, $arguments, $lineno, $this->getTag()));