From 9e968378270b0e7022a766cb684c01d13364cc47 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Martin=20Haso=C5=88?= Date: Sat, 11 May 2013 18:52:48 +0200 Subject: [PATCH] Refactored parsing of macros --- lib/Twig/ExpressionParser.php | 61 +++++++++++++------------- lib/Twig/Node/Expression/MacroCall.php | 36 +++++++++++++++ lib/Twig/Node/Macro.php | 4 +- lib/Twig/Node/Module.php | 25 ++++++++++- lib/Twig/NodeVisitor/SafeAnalysis.php | 2 + lib/Twig/Template.php | 20 ++++++++ lib/Twig/TokenParser/From.php | 2 +- test/Twig/Tests/Node/MacroTest.php | 2 +- test/Twig/Tests/Node/ModuleTest.php | 10 +++- test/Twig/Tests/Node/SandboxedModuleTest.php | 8 +++- 10 files changed, 132 insertions(+), 38 deletions(-) create mode 100644 lib/Twig/Node/Expression/MacroCall.php diff --git a/lib/Twig/ExpressionParser.php b/lib/Twig/ExpressionParser.php index 9cf1934..18ac2a3 100644 --- a/lib/Twig/ExpressionParser.php +++ b/lib/Twig/ExpressionParser.php @@ -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 index 0000000..0618bcc --- /dev/null +++ b/lib/Twig/Node/Expression/MacroCall.php @@ -0,0 +1,36 @@ + + */ +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(')') + ; + } +} diff --git a/lib/Twig/Node/Macro.php b/lib/Twig/Node/Macro.php index 8991061..43c75e5 100644 --- a/lib/Twig/Node/Macro.php +++ b/lib/Twig/Node/Macro.php @@ -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')); diff --git a/lib/Twig/Node/Module.php b/lib/Twig/Node/Module.php index 585048b..959e2ff 100644 --- a/lib/Twig/Node/Module.php +++ b/lib/Twig/Node/Module.php @@ -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") ; } diff --git a/lib/Twig/NodeVisitor/SafeAnalysis.php b/lib/Twig/NodeVisitor/SafeAnalysis.php index c4bbd81..b0c658c 100644 --- a/lib/Twig/NodeVisitor/SafeAnalysis.php +++ b/lib/Twig/NodeVisitor/SafeAnalysis.php @@ -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 diff --git a/lib/Twig/Template.php b/lib/Twig/Template.php index a001ca0..288a03e 100644 --- a/lib/Twig/Template.php +++ b/lib/Twig/Template.php @@ -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() diff --git a/lib/Twig/TokenParser/From.php b/lib/Twig/TokenParser/From.php index a54054d..ff6e575 100644 --- a/lib/Twig/TokenParser/From.php +++ b/lib/Twig/TokenParser/From.php @@ -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; diff --git a/test/Twig/Tests/Node/MacroTest.php b/test/Twig/Tests/Node/MacroTest.php index 4d2f641..304e3e2 100644 --- a/test/Twig/Tests/Node/MacroTest.php +++ b/test/Twig/Tests/Node/MacroTest.php @@ -46,7 +46,7 @@ class Twig_Tests_Node_MacroTest extends Twig_Test_NodeTestCase return array( array($node, <<env->mergeGlobals(array( "foo" => \$_foo, diff --git a/test/Twig/Tests/Node/ModuleTest.php b/test/Twig/Tests/Node/ModuleTest.php index 9411e99..7dde513 100644 --- a/test/Twig/Tests/Node/ModuleTest.php +++ b/test/Twig/Tests/Node/ModuleTest.php @@ -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 diff --git a/test/Twig/Tests/Node/SandboxedModuleTest.php b/test/Twig/Tests/Node/SandboxedModuleTest.php index 217e340..cec94ca 100644 --- a/test/Twig/Tests/Node/SandboxedModuleTest.php +++ b/test/Twig/Tests/Node/SandboxedModuleTest.php @@ -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) -- 1.7.2.5