Refactored parsing of macros
authorMartin Hasoň <martin.hason@gmail.com>
Sat, 11 May 2013 16:52:48 +0000 (18:52 +0200)
committerMartin Hasoň <martin.hason@gmail.com>
Tue, 16 Jul 2013 08:43:52 +0000 (10:43 +0200)
lib/Twig/ExpressionParser.php
lib/Twig/Node/Expression/MacroCall.php [new file with mode: 0644]
lib/Twig/Node/Macro.php
lib/Twig/Node/Module.php
lib/Twig/NodeVisitor/SafeAnalysis.php
lib/Twig/Template.php
lib/Twig/TokenParser/From.php
test/Twig/Tests/Node/MacroTest.php
test/Twig/Tests/Node/ModuleTest.php
test/Twig/Tests/Node/SandboxedModuleTest.php

index 9cf1934..18ac2a3 100644 (file)
@@ -318,16 +318,10 @@ class Twig_ExpressionParser
 
                 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);
             default:
-                if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
-                    $arguments = new Twig_Node_Expression_Array(array(), $line);
-                    foreach ($this->parseArguments() as $n) {
-                        $arguments->addElement($n);
-                    }
-
-                    $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
-                    $node->setAttribute('safe', true);
+                if (null !== $alias = $this->parser->getImportedSymbol('macro', $name)) {
+                    $arguments = $this->createArrayFromArguments($this->parseArguments());
 
-                    return $node;
+                    return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $arguments, $line);
                 }
 
                 $args = $this->parseArguments(true);
@@ -354,13 +348,6 @@ class Twig_ExpressionParser
                 ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
             ) {
                 $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
-
-                if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
-                    $type = Twig_TemplateInterface::METHOD_CALL;
-                    foreach ($this->parseArguments() as $n) {
-                        $arguments->addElement($n);
-                    }
-                }
             } else {
                 throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
             }
@@ -370,10 +357,14 @@ class Twig_ExpressionParser
                     throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
                 }
 
-                $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
-                $node->setAttribute('safe', true);
+                $arguments = $this->createArrayFromArguments($this->parseArguments());
 
-                return $node;
+                return new Twig_Node_Expression_MacroCall($node, $arg->getAttribute('value'), $arguments, $lineno);
+            }
+
+            if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+                $type = Twig_TemplateInterface::METHOD_CALL;
+                $arguments = $this->createArrayFromArguments($this->parseArguments());
             }
         } else {
             $type = Twig_TemplateInterface::ARRAY_CALL;
@@ -452,6 +443,8 @@ class Twig_ExpressionParser
      *
      * @param Boolean $namedArguments Whether to allow named arguments or not
      * @param Boolean $definition     Whether we are parsing arguments for a function definition
+     *
+     * @return Twig_Node
      */
     public function parseArguments($namedArguments = false, $definition = false)
     {
@@ -490,18 +483,15 @@ class Twig_ExpressionParser
                 }
             }
 
-            if ($definition) {
-                if (null === $name) {
-                    $name = $value->getAttribute('name');
-                    $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
-                }
-                $args[$name] = $value;
+            if ($definition && null === $name) {
+                $name = $value->getAttribute('name');
+                $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
+            }
+
+            if (null === $name) {
+                $args[] = $value;
             } else {
-                if (null === $name) {
-                    $args[] = $value;
-                } else {
-                    $args[$name] = $value;
-                }
+                $args[$name] = $value;
             }
         }
         $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
@@ -597,4 +587,15 @@ class Twig_ExpressionParser
 
         return true;
     }
+
+    private function createArrayFromArguments(Twig_Node $arguments, $line = null)
+    {
+        $line = null === $line ? $arguments->getLine() : $line;
+        $array = new Twig_Node_Expression_Array(array(), $line);
+        foreach ($arguments as $key => $value) {
+            $array->addElement($value, new Twig_Node_Expression_Constant($key, $value->getLine()));
+        }
+
+        return $array;
+    }
 }
diff --git a/lib/Twig/Node/Expression/MacroCall.php b/lib/Twig/Node/Expression/MacroCall.php
new file mode 100644 (file)
index 0000000..0618bcc
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2012 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a macro call node.
+ *
+ * @author Martin Hasoň <martin.hason@gmail.com>
+ */
+class Twig_Node_Expression_MacroCall extends Twig_Node_Expression
+{
+    public function __construct(Twig_Node_Expression $template, $name, Twig_Node_Expression_Array $arguments, $lineno)
+    {
+        parent::__construct(array('template' => $template, 'arguments' => $arguments), array('name' => $name), $lineno);
+    }
+
+    public function compile(Twig_Compiler $compiler)
+    {
+        $compiler
+            ->raw('$this->callMacro(')
+            ->subcompile($this->getNode('template'))
+            ->raw(', ')
+            ->repr($this->getAttribute('name'))
+            ->raw(', ')
+            ->subcompile($this->getNode('arguments'))
+            ->raw(')')
+        ;
+    }
+}
index 8991061..43c75e5 100644 (file)
@@ -18,7 +18,7 @@ class Twig_Node_Macro extends Twig_Node
 {
     public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
     {
-        parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag);
+        parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name, 'method' => 'get'.ucfirst($name)), $lineno, $tag);
     }
 
     /**
@@ -30,7 +30,7 @@ class Twig_Node_Macro extends Twig_Node
     {
         $compiler
             ->addDebugInfo($this)
-            ->write(sprintf("public function get%s(", $this->getAttribute('name')))
+            ->write(sprintf("public function %s(", $this->getAttribute('method')))
         ;
 
         $count = count($this->getNode('arguments'));
index 585048b..959e2ff 100644 (file)
@@ -235,9 +235,32 @@ class Twig_Node_Module extends Twig_Node
 
         $compiler
             ->outdent()
+            ->write(");\n\n")
+        ;
+
+        // macro information
+        $compiler
+            ->write("\$this->macros = array(\n")
+            ->indent()
+        ;
+
+        foreach ($this->getNode('macros') as $name => $node) {
+            $compiler
+                ->addIndentation()->repr($name)->raw(" => array(\n")
+                ->indent()
+                ->write("'method' => ")->repr($node->getAttribute('method'))->raw(",\n")
+                ->outdent()
+                ->write("),\n")
+            ;
+        }
+        $compiler
+            ->outdent()
             ->write(");\n")
+        ;
+
+        $compiler
             ->outdent()
-            ->write("}\n\n");
+            ->write("}\n\n")
         ;
     }
 
index c4bbd81..b0c658c 100644 (file)
@@ -89,6 +89,8 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
             } else {
                 $this->setSafe($node, array());
             }
+        } elseif ($node instanceof Twig_Node_Expression_MacroCall) {
+            $this->setSafe($node, array('all'));
         } elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) {
             $name = $node->getNode('node')->getAttribute('name');
             // attributes on template instances are safe
index a001ca0..288a03e 100644 (file)
@@ -24,6 +24,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
     protected $env;
     protected $blocks;
     protected $traits;
+    protected $macros;
 
     /**
      * Constructor.
@@ -35,6 +36,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
         $this->env = $env;
         $this->blocks = array();
         $this->traits = array();
+        $this->macros = array();
     }
 
     /**
@@ -446,6 +448,24 @@ abstract class Twig_Template implements Twig_TemplateInterface
     }
 
     /**
+     * Calls macro in a template.
+     *
+     * @param Twig_Template $template  The template
+     * @param string        $macro     The name of macro
+     * @param array         $arguments The arguments of macro
+     *
+     * @return string The content of a macro
+     */
+    protected function callMacro(Twig_Template $template, $macro, array $arguments)
+    {
+        if (!isset($template->macros[$macro]['reflection'])) {
+            $template->macros[$macro]['reflection'] = new ReflectionMethod($template, $template->macros[$macro]['method']);
+        }
+
+        return $template->macros[$macro]['reflection']->invokeArgs($template, $arguments);
+    }
+
+    /**
      * This method is only useful when testing Twig. Do not use it.
      */
     public static function clearCache()
index a54054d..ff6e575 100644 (file)
@@ -56,7 +56,7 @@ class Twig_TokenParser_From extends Twig_TokenParser
         $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag());
 
         foreach ($targets as $name => $alias) {
-            $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var'));
+            $this->parser->addImportedSymbol('macro', $alias, $name, $node->getNode('var'));
         }
 
         return $node;
index 4d2f641..304e3e2 100644 (file)
@@ -46,7 +46,7 @@ class Twig_Tests_Node_MacroTest extends Twig_Test_NodeTestCase
         return array(
             array($node, <<<EOF
 // line 1
-public function getfoo(\$_foo = null, \$_bar = "Foo")
+public function getFoo(\$_foo = null, \$_bar = "Foo")
 {
     \$context = \$this->env->mergeGlobals(array(
         "foo" => \$_foo,
index 9411e99..7dde513 100644 (file)
@@ -75,6 +75,9 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
 
         \$this->blocks = array(
         );
+
+        \$this->macros = array(
+        );
     }
 
     protected function doDisplay(array \$context, array \$blocks = array())
@@ -90,7 +93,7 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
 
     public function getDebugInfo()
     {
-        return array (  19 => 1,);
+        return array (  22 => 1,);
     }
 }
 EOF
@@ -116,6 +119,9 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
 
         \$this->blocks = array(
         );
+
+        \$this->macros = array(
+        );
     }
 
     protected function doGetParent(array \$context)
@@ -142,7 +148,7 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
 
     public function getDebugInfo()
     {
-        return array (  24 => 1,);
+        return array (  27 => 1,);
     }
 }
 EOF
index 217e340..cec94ca 100644 (file)
@@ -73,6 +73,9 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
 
         \$this->blocks = array(
         );
+
+        \$this->macros = array(
+        );
     }
 
     protected function doDisplay(array \$context, array \$blocks = array())
@@ -98,7 +101,7 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
 
     public function getDebugInfo()
     {
-        return array (  20 => 1,);
+        return array (  23 => 1,);
     }
 }
 EOF
@@ -128,6 +131,9 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
 
         \$this->blocks = array(
         );
+
+        \$this->macros = array(
+        );
     }
 
     protected function doGetParent(array \$context)