made a big refactoring of Twig internals (and added a bunch of unit tests and phpdoc...
authorFabien Potencier <fabien.potencier@gmail.com>
Tue, 1 Jun 2010 17:56:15 +0000 (19:56 +0200)
committerFabien Potencier <fabien.potencier@gmail.com>
Thu, 3 Jun 2010 06:12:58 +0000 (08:12 +0200)
104 files changed:
CHANGELOG
doc/02-Twig-for-Template-Designers.markdown
doc/03-Twig-for-Developers.markdown
doc/04-Extending-Twig.markdown
lib/Twig/Autoloader.php
lib/Twig/Compiler.php
lib/Twig/Environment.php
lib/Twig/ExpressionParser.php
lib/Twig/Extension/Core.php
lib/Twig/Extension/Sandbox.php
lib/Twig/Node.php
lib/Twig/Node/AutoEscape.php
lib/Twig/Node/Block.php
lib/Twig/Node/BlockReference.php
lib/Twig/Node/Debug.php
lib/Twig/Node/Expression/Array.php
lib/Twig/Node/Expression/AssignName.php
lib/Twig/Node/Expression/Binary.php
lib/Twig/Node/Expression/Binary/FloorDiv.php
lib/Twig/Node/Expression/Compare.php
lib/Twig/Node/Expression/Conditional.php
lib/Twig/Node/Expression/Constant.php
lib/Twig/Node/Expression/Filter.php
lib/Twig/Node/Expression/GetAttr.php
lib/Twig/Node/Expression/Name.php
lib/Twig/Node/Expression/Unary.php
lib/Twig/Node/Filter.php [deleted file]
lib/Twig/Node/For.php
lib/Twig/Node/If.php
lib/Twig/Node/Import.php
lib/Twig/Node/Include.php
lib/Twig/Node/Macro.php
lib/Twig/Node/Module.php
lib/Twig/Node/Parent.php
lib/Twig/Node/Print.php
lib/Twig/Node/Sandbox.php [new file with mode: 0644]
lib/Twig/Node/SandboxedModule.php [new file with mode: 0644]
lib/Twig/Node/SandboxedPrint.php [moved from lib/Twig/Node/SandboxPrint.php with 80% similarity]
lib/Twig/Node/Set.php
lib/Twig/Node/Text.php
lib/Twig/Node/Trans.php
lib/Twig/NodeList.php [deleted file]
lib/Twig/NodeListInterface.php [deleted file]
lib/Twig/NodeTraverser.php
lib/Twig/NodeVisitor/Escaper.php
lib/Twig/NodeVisitor/Filter.php [deleted file]
lib/Twig/NodeVisitor/Sandbox.php
lib/Twig/NodeVisitorInterface.php
lib/Twig/Parser.php
lib/Twig/Resource.php
lib/Twig/TokenParser/Block.php
lib/Twig/TokenParser/Filter.php
lib/Twig/TokenParser/For.php
lib/Twig/TokenParser/If.php
lib/Twig/TokenParser/Import.php
lib/Twig/TokenParser/Include.php
lib/Twig/TokenParser/Sandbox.php [new file with mode: 0644]
lib/Twig/TokenParser/Set.php
lib/Twig/TokenParser/Trans.php
phpunit.xml
test/Twig/Tests/AutoloaderTest.php
test/Twig/Tests/Extension/SandboxTest.php
test/Twig/Tests/Node/AutoEscapeTest.php [new file with mode: 0644]
test/Twig/Tests/Node/BlockReferenceTest.php [new file with mode: 0644]
test/Twig/Tests/Node/BlockTest.php [new file with mode: 0644]
test/Twig/Tests/Node/DebugTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/ArrayTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/AssignNameTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/AddTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/AndTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/ConcatTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/DivTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/ModTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/MulTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/OrTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Binary/SubTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/CompareTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/ConditionalTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/ConstantTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/FilterTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/GetAttrTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/NameTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Unary/NegTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Unary/NotTest.php [new file with mode: 0644]
test/Twig/Tests/Node/Expression/Unary/PosTest.php [new file with mode: 0644]
test/Twig/Tests/Node/ForTest.php [new file with mode: 0644]
test/Twig/Tests/Node/IfTest.php [new file with mode: 0644]
test/Twig/Tests/Node/ImportTest.php [new file with mode: 0644]
test/Twig/Tests/Node/IncludeTest.php [new file with mode: 0644]
test/Twig/Tests/Node/MacroTest.php [new file with mode: 0644]
test/Twig/Tests/Node/ModuleTest.php [new file with mode: 0644]
test/Twig/Tests/Node/ParentTest.php [new file with mode: 0644]
test/Twig/Tests/Node/PrintTest.php [new file with mode: 0644]
test/Twig/Tests/Node/SandboxTest.php [new file with mode: 0644]
test/Twig/Tests/Node/SandboxedModuleTest.php [new file with mode: 0644]
test/Twig/Tests/Node/SandboxedPrintTest.php [new file with mode: 0644]
test/Twig/Tests/Node/SetTest.php [new file with mode: 0644]
test/Twig/Tests/Node/TestCase.php [new file with mode: 0644]
test/Twig/Tests/Node/TextTest.php [new file with mode: 0644]
test/Twig/Tests/Node/TransTest.php [new file with mode: 0644]
test/Twig/Tests/integrationTest.php
test/fixtures/tags/filter/multiple.test
test/fixtures/tags/filter/nested.test

index f0a58e7..033521c 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,8 +1,15 @@
 * 0.9.7-DEV
 
 Backward incompatibilities:
- * The short notation of the `block` tag changed.
-
+ * added a 'as' string to the block tag short notation ({% block title "Title" %} must now be {% block title as "Title" %})
+ * removed the sandboxed attribute of the include tag (use the new sandbox tag instead)
+ * refactored the Node system (if you have custom nodes, you will have to update them to use the new API)
+
+ * removed the Twig_Resource::resolveMissingFilter() method
+ * fixed the filter tag which did not apply filtering to included files
+ * added a bunch of unit tests
+ * added a bunch of phpdoc
+ * added a sandbox tag in the sandbox extension
  * fixed iterator_to_array() usage
  * changed the date filter to support any date format supported by DateTime
  * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default when debug is false)
@@ -12,7 +19,6 @@ Backward incompatibilities:
  * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface
  * changed the generated code to match the new coding standards
  * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }})
- * added a 'as' string to the block tag short notation ({% block title "Title" %} must now be {% block title as "Title" %})
  * added an exception when a child template has a non-empty body (as it is always ignored when rendering)
 
 * 0.9.6 (2010-05-12)
index eff8116..8d98dbf 100644 (file)
@@ -641,12 +641,6 @@ rendered contents of that file into the current namespace:
 
 Included templates have access to the variables of the active context.
 
-An included file can be evaluated in the sandbox environment by appending
-`sandboxed` at the end if the `escaper` extension has been enabled:
-
-    [twig]
-    {% include 'user.html' sandboxed %}
-
 You can also restrict the variables passed to the template by explicitly pass
 them as an array:
 
@@ -656,16 +650,15 @@ them as an array:
     {% set vars as ['foo': 'bar'] %}
     {% include 'foo' with vars %}
 
-The most secure way to include a template is to use both the `sandboxed` mode,
-and to pass the minimum amount of variables needed for the template to be
-rendered correctly:
-
-    [twig]
-    {% include 'foo' sandboxed with vars %}
-
 >**NOTE**
 >The `with` keyword is supported as of Twig 0.9.5.
 
+-
+
+>**TIP**
+>When including a template created by an end user, you should consider
+>sandboxing it. More information in the "Twig for Developers" chapter.
+
 ### Import
 
 Twig supports putting often used code into macros. These macros can go into
index 5dfbceb..bcb81bb 100644 (file)
@@ -389,10 +389,12 @@ The policy object is the first argument of the sandbox constructor:
     $twig->addExtension($sandbox);
 
 By default, the sandbox mode is disabled and should be enabled when including
-untrusted templates:
+untrusted template code by using the `sandbox` tag:
 
-    [php]
-    {% include "user.html" sandboxed %}
+    [twig]
+    {% sandbox %}
+      {% include 'user.html' %}
+    {% endsandbox %}
 
 You can sandbox all templates by passing `true` as the second argument of the
 extension constructor:
index 669c51c..bb4a287 100644 (file)
@@ -481,22 +481,16 @@ The `Project_Set_Node` class itself is rather simple:
     [php]
     class Project_Set_Node extends Twig_Node
     {
-      protected $name;
-      protected $value;
-
       public function __construct($name, Twig_Node_Expression $value, $lineno)
       {
-        parent::__construct($lineno);
-
-        $this->name = $name;
-        $this->value = $value;
+        parent::__construct(array('value' => $value), array('name' => $name), $lineno);
       }
 
       public function compile($compiler)
       {
         $compiler
           ->addDebugInfo($this)
-          ->write('$context[\''.$this->name.'\'] = ')
+          ->write('$context[\''.$this['name'].'\'] = ')
           ->subcompile($this->value)
           ->raw(";\n")
         ;
@@ -533,7 +527,7 @@ developer generate beautiful and readable PHP code:
  * `outdent()`: Outdents the generated code (see `Twig_Node_Block` for a usage
    example).
 
-Creating a Node Transformer
----------------------------
+Creating a Node Visitor
+-----------------------
 
 To be written...
index ec0ffac..f144890 100644 (file)
@@ -37,11 +37,9 @@ class Twig_Autoloader
     static public function autoload($class)
     {
         if (0 !== strpos($class, 'Twig')) {
-            return false;
+            return;
         }
 
         require dirname(__FILE__).'/../'.str_replace('_', '/', $class).'.php';
-
-        return true;
     }
 }
index 989f190..1be505a 100644 (file)
@@ -42,6 +42,16 @@ class Twig_Compiler implements Twig_CompilerInterface
     }
 
     /**
+     * Returns the environment instance related to this compiler.
+     *
+     * @return Twig_Environment The environment instance
+     */
+    public function getEnvironment()
+    {
+        return $this->env;
+    }
+
+    /**
      * Gets the current PHP code after compilation.
      *
      * @return string The PHP code
@@ -211,19 +221,4 @@ class Twig_Compiler implements Twig_CompilerInterface
 
         return $this;
     }
-
-    /**
-     * Returns the environment instance related to this compiler.
-     *
-     * @return Twig_Environment The environment instance
-     */
-    public function getEnvironment()
-    {
-        return $this->env;
-    }
-
-    public function getTemplateClass($name)
-    {
-        return $this->getEnvironment()->getTemplateClass($name);
-    }
 }
index 4a72e60..c10c5ab 100644 (file)
@@ -178,13 +178,14 @@ class Twig_Environment
     /**
      * Loads a template by name.
      *
-     * @param  string $name The template name
+     * @param  string  $name  The template name
+     * @param  Boolean $macro Whether to return the macro object if any, or the template one
      *
      * @return Twig_TemplateInterface A template instance representing the given template name
      */
-    public function loadTemplate($name)
+    public function loadTemplate($name, $macro = false)
     {
-        $cls = $this->getTemplateClass($name);
+        $cls = $this->getTemplateClass($name).($macro ? '_Macro' : '');
 
         if (isset($this->loadedTemplates[$cls])) {
             return $this->loadedTemplates[$cls];
@@ -309,6 +310,10 @@ class Twig_Environment
 
     public function getExtension($name)
     {
+        if (!isset($this->extensions[$name])) {
+            throw new LogicException(sprintf('The "%s" extension is not enabled.', $name));
+        }
+
         return $this->extensions[$name];
     }
 
index c77f534..53c49fd 100644 (file)
@@ -86,14 +86,15 @@ class Twig_ExpressionParser
             ||
             $this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'in')
         ) {
-            $ops[] = array($this->parser->getStream()->next()->getValue(), $this->parseAddExpression());
+            $ops[] = new Twig_Node_Expression_Constant($this->parser->getStream()->next()->getValue(), $lineno);
+            $ops[] = $this->parseAddExpression();
         }
 
         if (empty($ops)) {
             return $expr;
         }
 
-        return new Twig_Node_Expression_Compare($expr, $ops, $lineno);
+        return new Twig_Node_Expression_Compare($expr, new Twig_Node($ops), $lineno);
     }
 
     public function parseAddExpression()
@@ -279,6 +280,7 @@ class Twig_ExpressionParser
                     throw new Twig_SyntaxError(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::getTypeAsString($token->getType()), $token->getValue()), $token->getLine());
                 }
         }
+
         if (!$assignment) {
             $node = $this->parsePostfixExpression($node);
         }
@@ -358,14 +360,16 @@ class Twig_ExpressionParser
 
         $end = $this->parseExpression();
 
-        return new Twig_Node_Expression_Filter($node, array(array('range', array($end))), $lineno);
+        $filters = new Twig_Node(array(new Twig_Node_Expression_Constant('range', $lineno), new Twig_Node(array($end))));
+
+        return new Twig_Node_Expression_Filter($node, $filters, $lineno);
     }
 
     public function parseSubscriptExpression($node)
     {
         $token = $this->parser->getStream()->next();
         $lineno = $token->getLine();
-        $arguments = array();
+        $arguments = new Twig_Node();
         if ($token->getValue() == '.') {
             $token = $this->parser->getStream()->next();
             if ($token->getType() == Twig_Token::NAME_TYPE || $token->getType() == Twig_Token::NUMBER_TYPE) {
@@ -398,7 +402,8 @@ class Twig_ExpressionParser
         while (true) {
             $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
 
-            $filters[] = array($token->getValue(), $this->parseArguments());
+            $filters[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+            $filters[] = $this->parseArguments();
 
             if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '|')) {
                 break;
@@ -407,13 +412,13 @@ class Twig_ExpressionParser
             $this->parser->getStream()->next();
         }
 
-        return $filters;
+        return new Twig_Node($filters);
     }
 
     public function parseArguments()
     {
         if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '(')) {
-            return array();
+            return new Twig_Node();
         }
 
         $args = array();
@@ -426,14 +431,13 @@ class Twig_ExpressionParser
         }
         $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ')');
 
-        return $args;
+        return new Twig_Node($args);
     }
 
     public function parseAssignmentExpression()
     {
         $lineno = $this->parser->getCurrentToken()->getLine();
         $targets = array();
-        $is_multitarget = false;
         while (true) {
             if (!empty($targets)) {
                 $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ',');
@@ -449,13 +453,9 @@ class Twig_ExpressionParser
             if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ',')) {
                 break;
             }
-            $is_multitarget = true;
-        }
-        if (!$is_multitarget && count($targets) == 1) {
-            return array(false, $targets[0]);
         }
 
-        return array(true, $targets);
+        return new Twig_Node($targets);
     }
 
     public function parseMultitargetExpression()
@@ -479,10 +479,7 @@ class Twig_ExpressionParser
             }
             $is_multitarget = true;
         }
-        if (!$is_multitarget && count($targets) == 1) {
-            return array(false, $targets[0]);
-        }
 
-        return array(true, $targets);
+        return array($is_multitarget, new Twig_Node($targets));
     }
 }
index 241614e..9564ee2 100644 (file)
@@ -34,16 +34,6 @@ class Twig_Extension_Core extends Twig_Extension
     }
 
     /**
-     * Returns the node visitor instances to add to the existing list.
-     *
-     * @return array An array of Twig_NodeVisitorInterface instances
-     */
-    public function getNodeVisitors()
-    {
-        return array(new Twig_NodeVisitor_Filter());
-    }
-
-    /**
      * Returns a list of filters to add to the existing list.
      *
      * @return array An array of filters
index c45d9b8..a298749 100644 (file)
@@ -21,6 +21,16 @@ class Twig_Extension_Sandbox extends Twig_Extension
     }
 
     /**
+     * Returns the token parser instance to add to the existing list.
+     *
+     * @return array An array of Twig_TokenParser instances
+     */
+    public function getTokenParsers()
+    {
+        return array(new Twig_TokenParser_Sandbox());
+    }
+
+    /**
      * Returns the node visitor instances to add to the existing list.
      *
      * @return array An array of Twig_NodeVisitorInterface instances
index 4762819..f769077 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-abstract class Twig_Node implements Twig_NodeInterface
+class Twig_Node implements Twig_NodeInterface, ArrayAccess, Countable, Iterator
 {
+    protected $nodes;
+    protected $attributes;
     protected $lineno;
     protected $tag;
 
-    public function __construct($lineno, $tag = null)
+    public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null)
     {
+        $this->nodes = array();
+        foreach ($nodes as $name => $node) {
+            $this->$name = $node;
+        }
+        $this->attributes = $attributes;
         $this->lineno = $lineno;
         $this->tag = $tag;
     }
 
     public function __toString()
     {
-        return get_class($this).'()';
+        $attributes = array();
+        foreach ($this->attributes as $name => $value) {
+            $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
+        }
+
+        $repr = array(get_class($this).'('.implode(', ', $attributes));
+
+        if (count($this->nodes)) {
+            foreach ($this->nodes as $name => $node) {
+                $len = strlen($name) + 4;
+                $noderepr = array();
+                foreach (explode("\n", (string) $node) as $line) {
+                    $noderepr[] = str_repeat(' ', $len).$line;
+                }
+
+                $repr[] = sprintf('  %s: %s', $name, ltrim(implode("\n", $noderepr)));
+            }
+
+            $repr[] = ')';
+        } else {
+            $repr[0] .= ')';
+        }
+
+        return implode("\n", $repr);
+    }
+
+    public function compile($compiler)
+    {
+        foreach ($this->nodes as $node) {
+            $node->compile($compiler);
+        }
     }
 
     public function getLine()
@@ -42,4 +79,132 @@ abstract class Twig_Node implements Twig_NodeInterface
     {
         return $this->tag;
     }
+
+    /**
+     * Returns true if the attribute is defined.
+     *
+     * @param  string  The attribute name
+     *
+     * @return Boolean true if the attribute is defined, false otherwise
+     */
+    public function offsetExists($name)
+    {
+        return $this->attributes[$name];
+    }
+
+    /**
+     * Gets an attribute.
+     *
+     * @param  string The attribute name
+     *
+     * @return mixed  The attribute value
+     */
+    public function offsetGet($name)
+    {
+        if (!array_key_exists($name, $this->attributes)) {
+            throw new InvalidArgumentException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
+        }
+
+        return $this->attributes[$name];
+    }
+
+    /**
+     * Sets an attribute.
+     *
+     * @param string The attribute name
+     * @param mixed  The attribute value
+     */
+    public function offsetSet($name, $value)
+    {
+        $this->attributes[$name] = $value;
+    }
+
+    /**
+     * Removes an attribute.
+     *
+     * @param string The attribute name
+     */
+    public function offsetUnset($name)
+    {
+        unset($this->attributes[$name]);
+    }
+
+    /**
+     * Returns true if the node with the given identifier exists.
+     *
+     * @param  string  The node name
+     *
+     * @return Boolean true if the node with the given name exists, false otherwise
+     */
+    public function __isset($name)
+    {
+        return array_key_exists($name, $this->nodes);
+    }
+
+    /**
+     * Gets a node by name.
+     *
+     * @param  string The node name
+     *
+     * @return Twig_Node A Twig_Node instance
+     */
+    public function __get($name)
+    {
+        if (!array_key_exists($name, $this->nodes)) {
+            throw new InvalidArgumentException(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
+        }
+
+        return $this->nodes[$name];
+    }
+
+    /**
+     * Sets a node.
+     *
+     * @param string    The node name
+     * @param Twig_Node A Twig_Node instance
+     */
+    public function __set($name, $node = null)
+    {
+        $this->nodes[$name] = $node;
+    }
+
+    /**
+     * Removes a node by name.
+     *
+     * @param string The node name
+     */
+    public function __unset($name)
+    {
+        unset($this->nodes[$name]);
+    }
+
+    public function count()
+    {
+        return count($this->nodes);
+    }
+
+    public function rewind()
+    {
+        reset($this->nodes);
+    }
+
+    public function current()
+    {
+        return current($this->nodes);
+    }
+
+    public function key()
+    {
+        return key($this->nodes);
+    }
+
+    public function next()
+    {
+        return next($this->nodes);
+    }
+
+    public function valid()
+    {
+        return false !== current($this->nodes);
+    }
 }
index 01b9e0e..6532a80 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_AutoEscape extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_AutoEscape extends Twig_Node
 {
-    protected $value;
-    protected $body;
-
-    public function __construct($value, Twig_NodeList $body, $lineno, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-        $this->value = $value;
-        $this->body  = $body;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).'('.($this->value ? 'on' : 'off'));
-        foreach (explode("\n", $this->body) as $line) {
-            $repr[] = '    '.$line;
-        }
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        return $this->body->getNodes();
-    }
-
-    public function setNodes(array $nodes)
+    public function __construct($value, Twig_NodeInterface $body, $lineno, $tag = 'autoescape')
     {
-        $this->body = new Twig_NodeList($nodes, $this->lineno);
+        parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $compiler->subcompile($this->body);
     }
-
-    public function getValue()
-    {
-        return $this->value;
-    }
 }
index 26f1b45..1655f86 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_Block extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_Block extends Twig_Node
 {
-    protected $name;
-    protected $body;
-    protected $parent;
-
-    public function __construct($name, Twig_NodeList $body, $lineno, $parent = null, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-        $this->name = $name;
-        $this->body = $body;
-        $this->parent = $parent;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).' '.$this->name.'(');
-        foreach ($this->body->getNodes() as $node) {
-            foreach (explode("\n", $node->__toString()) as $line) {
-                $repr[] = '  '.$line;
-            }
-        }
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        return $this->body->getNodes();
-    }
-
-    public function setNodes(array $nodes)
+    public function __construct($name, Twig_NodeInterface $body, $lineno, $tag = null)
     {
-        $this->body = new Twig_NodeList($nodes, $this->lineno);
-    }
-
-    public function replace($other)
-    {
-        $this->body = $other->body;
+        parent::__construct(array('body' => $body), array('name' => $name), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $compiler
             ->addDebugInfo($this)
-            ->write(sprintf("public function block_%s(\$context)\n", $this->name), "{\n")
+            ->write(sprintf("public function block_%s(\$context)\n", $this['name']), "{\n")
             ->indent()
         ;
 
@@ -73,19 +38,4 @@ class Twig_Node_Block extends Twig_Node implements Twig_NodeListInterface
             ->write("}\n\n")
         ;
     }
-
-    public function getName()
-    {
-        return $this->name;
-    }
-
-    public function getParent()
-    {
-        return $this->parent;
-    }
-
-    public function setParent($parent)
-    {
-        $this->parent = $parent;
-    }
 }
index f950c93..6213404 100644 (file)
  */
 class Twig_Node_BlockReference extends Twig_Node
 {
-    protected $name;
-
     public function __construct($name, $lineno, $tag = null)
     {
-        parent::__construct($lineno, $tag);
-        $this->name = $name;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'('.$this->name.')';
+        parent::__construct(array(), array('name' => $name), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $compiler
             ->addDebugInfo($this)
-            ->write(sprintf('$this->block_%s($context);'."\n", $this->name))
+            ->write(sprintf('$this->block_%s($context);'."\n", $this['name']))
         ;
     }
-
-    public function getName()
-    {
-        return $this->name;
-    }
 }
index 074fd06..8c3cbc7 100644 (file)
@@ -1,19 +1,26 @@
 <?php
 
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a debug node.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
 class Twig_Node_Debug extends Twig_Node
 {
-    protected $expr;
-
     public function __construct(Twig_Node_Expression $expr = null, $lineno, $tag = null)
     {
-        parent::__construct($lineno, $tag);
-
-        $this->expr = $expr;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'('.$this->expr.')';
+        parent::__construct(array('expr' => $expr), array(), $lineno, $tag);
     }
 
     public function compile($compiler)
index 48586ea..d6e71f4 100644 (file)
@@ -8,45 +8,18 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-class Twig_Node_Expression_Array extends Twig_Node_Expression implements Twig_NodeListInterface
+class Twig_Node_Expression_Array extends Twig_Node_Expression
 {
-    protected $elements;
-
-    public function __construct($elements, $lineno)
+    public function __construct(array $elements, $lineno)
     {
-        parent::__construct($lineno);
-
-        $this->elements = $elements;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).'(');
-        foreach ($this->elements as $name => $node) {
-            foreach (explode("\n", '\''.$name.'\' => '.$node) as $line) {
-                $repr[] = '  '.$line;
-            }
-        }
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        return $this->elements;
-    }
-
-    public function setNodes(array $nodes)
-    {
-        $this->elements = $nodes;
+        parent::__construct($elements, array(), $lineno);
     }
 
     public function compile($compiler)
     {
         $compiler->raw('array(');
         $first = true;
-        foreach ($this->elements as $name => $node) {
+        foreach ($this->nodes as $name => $node) {
             if (!$first) {
                 $compiler->raw(', ');
             }
@@ -60,9 +33,4 @@ class Twig_Node_Expression_Array extends Twig_Node_Expression implements Twig_No
         }
         $compiler->raw(')');
     }
-
-    public function getElements()
-    {
-        return $this->elements;
-    }
 }
index 0459e8b..4e33005 100644 (file)
@@ -14,6 +14,6 @@ class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name
 {
     public function compile($compiler)
     {
-        $compiler->raw(sprintf('$context[\'%s\']', $this->name));
+        $compiler->raw(sprintf('$context[\'%s\']', $this['name']));
     }
 }
index 9af1635..cd13f08 100644 (file)
  */
 abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression
 {
-    protected $left;
-    protected $right;
-
     public function __construct(Twig_NodeInterface $left, Twig_NodeInterface $right, $lineno)
     {
-        parent::__construct($lineno);
-        $this->left = $left;
-        $this->right = $right;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).'(');
-
-        foreach (explode("\n", $this->left->__toString()) as $line) {
-            $repr[] = '  '.$line;
-        }
-
-        $repr[] = ', ';
-
-        foreach (explode("\n", $this->right->__toString()) as $line) {
-            $repr[] = '  '.$line;
-        }
-
-        $repr[] = ')';
-
-        return implode("\n", $repr);
+        parent::__construct(array('left' => $left, 'right' => $right), array(), $lineno);
     }
 
     public function compile($compiler)
index 6db622c..f689414 100644 (file)
@@ -12,17 +12,13 @@ class Twig_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary
 {
     public function compile($compiler)
     {
-        $compiler
-            ->raw('floor(')
-            ->subcompile($this->left)
-            ->raw(' / ')
-            ->subcompile($this->right)
-            ->raw(')')
-        ;
+        $compiler->raw('floor(');
+        parent::compile($compiler);
+        $compiler->raw(')');
     }
 
     public function operator($compiler)
     {
-        return;
+        return $compiler->raw('/');
     }
 }
index 8d0774f..e1156d2 100644 (file)
  */
 class Twig_Node_Expression_Compare extends Twig_Node_Expression
 {
-    protected $expr;
-    protected $ops;
-
-    public function __construct(Twig_Node_Expression $expr, $ops, $lineno)
+    public function __construct(Twig_Node_Expression $expr, Twig_NodeInterface $ops, $lineno)
     {
-        parent::__construct($lineno);
-        $this->expr = $expr;
-        $this->ops = $ops;
+        parent::__construct(array('expr' => $expr, 'ops' => $ops), array(), $lineno);
     }
 
     public function compile($compiler)
     {
-        $nbOps = count($this->ops) > 1;
-        if ('in' === $this->ops[0][0]) {
+        if ('in' === $this->ops->{0}['value']) {
             return $this->compileIn($compiler);
         }
 
         $this->expr->compile($compiler);
-        $i = 0;
-        foreach ($this->ops as $op) {
-            if ($i) {
-                $compiler->raw(' && ($tmp'.$i);
+
+        $nbOps = count($this->ops);
+        for ($i = 0; $i < $nbOps; $i += 2) {
+            if ($i > 0) {
+                $compiler->raw(' && ($tmp'.($i / 2));
             }
-            list($op, $node) = $op;
-            $compiler->raw(' '.$op.' ');
 
-            if ($nbOps) {
+            $compiler->raw(' '.$this->ops->{$i}['value'].' ');
+
+            if ($i != $nbOps - 2) {
                 $compiler
-                    ->raw('($tmp'.++$i.' = ')
-                    ->subcompile($node)
+                    ->raw('($tmp'.(($i / 2) + 1).' = ')
+                    ->subcompile($this->ops->{($i + 1)})
                     ->raw(')')
                 ;
             } else {
-                $compiler->subcompile($node);
+                $compiler->subcompile($this->ops->{($i + 1)});
             }
         }
 
-        for ($j = 1; $j < $i; $j++) {
+        for ($j = 1; $j < $i / 2; $j++) {
             $compiler->raw(')');
         }
     }
@@ -59,7 +54,7 @@ class Twig_Node_Expression_Compare extends Twig_Node_Expression
             ->raw('twig_in_filter(')
             ->subcompile($this->expr)
             ->raw(', ')
-            ->subcompile($this->ops[0][1])
+            ->subcompile($this->ops->{1})
             ->raw(')')
         ;
     }
index c04d111..4440716 100644 (file)
  */
 class Twig_Node_Expression_Conditional extends Twig_Node_Expression
 {
-    protected $expr1;
-    protected $expr2;
-    protected $expr3;
-
     public function __construct(Twig_Node_Expression $expr1, Twig_Node_Expression $expr2, Twig_Node_Expression $expr3, $lineno)
     {
-        parent::__construct($lineno);
-        $this->expr1 = $expr1;
-        $this->expr2 = $expr2;
-        $this->expr3 = $expr3;
+        parent::__construct(array('expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3), array(), $lineno);
     }
 
     public function compile($compiler)
index c2a4d0c..5d8d051 100644 (file)
  */
 class Twig_Node_Expression_Constant extends Twig_Node_Expression
 {
-    protected $value;
-
     public function __construct($value, $lineno)
     {
-        parent::__construct($lineno);
-        $this->value = $value;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'(\''.$this->value.'\')';
+        parent::__construct(array(), array('value' => $value), $lineno);
     }
 
     public function compile($compiler)
     {
-        $compiler->repr($this->value);
-    }
-
-    public function getValue()
-    {
-        return $this->value;
+        $compiler->repr($this['value']);
     }
 }
index 3131714..3f5eec9 100644 (file)
@@ -9,45 +9,11 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-class Twig_Node_Expression_Filter extends Twig_Node_Expression implements Twig_NodeListInterface
+class Twig_Node_Expression_Filter extends Twig_Node_Expression
 {
-    protected $node;
-    protected $filters;
-
-    public function __construct(Twig_NodeInterface $node, array $filters, $lineno, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-
-        $this->node = $node;
-        $this->filters = $filters;
-    }
-
-    public function __toString()
-    {
-        $filters = array();
-        foreach ($this->filters as $filter) {
-            $filters[] = $filter[0].'('.implode(', ', $filter[1]).')';
-        }
-
-        $repr = array(get_class($this).'('.implode(', ', $filters));
-
-        foreach (explode("\n", $this->node->__toString()) as $line) {
-            $repr[] = '  '.$line;
-        }
-
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        return array($this->node);
-    }
-
-    public function setNodes(array $nodes)
+    public function __construct(Twig_NodeInterface $node, Twig_NodeInterface $filters, $lineno, $tag = null)
     {
-        $this->node = $nodes[0];
+        parent::__construct(array('node' => $node, 'filters' => $filters), array(), $lineno, $tag);
     }
 
     public function compile($compiler)
@@ -55,20 +21,19 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression implements Twig_N
         $filterMap = $compiler->getEnvironment()->getFilters();
 
         $postponed = array();
-        for ($i = count($this->filters) - 1; $i >= 0; --$i) {
-            list($name, $attrs) = $this->filters[$i];
+        for ($i = count($this->filters) - 1; $i >= 0; $i -= 2) {
+            $name = $this->filters->{$i - 1}['value'];
+            $attrs = $this->filters->{$i};
             if (!isset($filterMap[$name])) {
-                $compiler
-                    ->raw('$this->resolveMissingFilter(')
-                    ->repr($name)
-                    ->raw(', ')
-                ;
+                throw new Twig_SyntaxError(sprintf('The filter "%s" does not exist', $name), $this->getLine());
             } else {
                 $compiler->raw($filterMap[$name]->compile().($filterMap[$name]->needsEnvironment() ? '($this->getEnvironment(), ' : '('));
             }
             $postponed[] = $attrs;
         }
+
         $this->node->compile($compiler);
+
         foreach (array_reverse($postponed) as $attributes) {
             foreach ($attributes as $node) {
                 $compiler
@@ -80,35 +45,40 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression implements Twig_N
         }
     }
 
-    public function getFilters()
+    public function prependFilter(Twig_Node_Expression_Constant $name, Twig_Node $end)
     {
-        return $this->filters;
-    }
+        $filters = array($name, $end);
+        foreach ($this->filters as $node) {
+            $filters[] = $node;
+        }
 
-    public function setFilters(array $filters)
-    {
-        $this->filters = $filters;
+        $this->filters = new Twig_Node($filters, array(), $this->filters->getLine());
     }
 
-    public function prependFilter($filter)
+    public function appendFilter(Twig_Node_Expression_Constant $name, Twig_Node $end)
     {
-        $this->filters = array_merge(array($filter), $this->filters);
-    }
+        $filters = array();
+        foreach ($this->filters as $node) {
+            $filters[] = $node;
+        }
 
-    public function appendFilter($filter)
-    {
-        $this->filters[] = $filter;
+        $filters[] = $name;
+        $filters[] = $end;
+
+        $this->filters = new Twig_Node($filters, array(), $this->filters->getLine());
     }
 
-    public function appendFilters(array $filters)
+    public function appendFilters(Twig_NodeInterface $filters)
     {
-        $this->filters = array_merge($this->filters, $filters);
+        for ($i = 0; $i < count($filters); $i += 2) {
+            $this->appendFilter($filters->{$i}, $filters->{$i + 1});
+        }
     }
 
     public function hasFilter($name)
     {
-        foreach ($this->filters as $filter) {
-            if ($name == $filter[0]) {
+        for ($i = 0; $i < count($this->filters); $i += 2) {
+            if ($name == $this->filters->{$i}['value']) {
                 return true;
             }
         }
index b8989dd..3803849 100644 (file)
@@ -9,39 +9,11 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-class Twig_Node_Expression_GetAttr extends Twig_Node_Expression implements Twig_NodeListInterface
+class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
 {
-    protected $node;
-    protected $attr;
-    protected $arguments;
-
-    public function __construct(Twig_NodeInterface $node, $attr, $arguments, $lineno, $token_value)
-    {
-        parent::__construct($lineno);
-        $this->node = $node;
-        $this->attr = $attr;
-        $this->arguments = $arguments;
-        $this->token_value = $token_value;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'('.$this->node.', '.$this->attr.')';
-    }
-
-    public function getNode()
-    {
-        return $this->node;
-    }
-
-    public function getNodes()
-    {
-        return array($this->node);
-    }
-
-    public function setNodes(array $nodes)
+    public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_NodeInterface $arguments, $lineno, $token_value = null)
     {
-        $this->node = $nodes[0];
+        parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('token_value' => $token_value), $lineno);
     }
 
     public function compile($compiler)
@@ -50,7 +22,7 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression implements Twig_
             ->raw('$this->getAttribute(')
             ->subcompile($this->node)
             ->raw(', ')
-            ->subcompile($this->attr)
+            ->subcompile($this->attribute)
             ->raw(', array(')
         ;
 
@@ -64,7 +36,7 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression implements Twig_
         $compiler->raw(')');
 
         // Don't look for functions if they're using foo[bar]
-        if ('[' == $this->token_value) {
+        if ('[' == $this['token_value']) {
             $compiler->raw(', true');
         }
 
index 00a0160..73b6dab 100644 (file)
  */
 class Twig_Node_Expression_Name extends Twig_Node_Expression
 {
-    protected $name;
-
     public function __construct($name, $lineno)
     {
-        parent::__construct($lineno);
-        $this->name = $name;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'(\''.$this->name.'\')';
+        parent::__construct(array(), array('name' => $name), $lineno);
     }
 
     public function compile($compiler)
     {
-        $compiler->raw(sprintf('$this->getContext($context, \'%s\')', $this->name, $this->name));
-    }
-
-    public function getName()
-    {
-        return $this->name;
+        $compiler->raw(sprintf('$this->getContext($context, \'%s\')', $this['name'], $this['name']));
     }
 }
index 225f6c7..1042d58 100644 (file)
  */
 abstract class Twig_Node_Expression_Unary extends Twig_Node_Expression
 {
-    protected $node;
-
     public function __construct(Twig_NodeInterface $node, $lineno)
     {
-        parent::__construct($lineno);
-        $this->node = $node;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).'(');
-
-        foreach (explode("\n", $this->node->__toString()) as $line) {
-            $repr[] = '  '.$line;
-        }
-
-        $repr[] = ')';
-
-        return implode("\n", $repr);
+        parent::__construct(array('node' => $node), array(), $lineno);
     }
 
     public function compile($compiler)
diff --git a/lib/Twig/Node/Filter.php b/lib/Twig/Node/Filter.php
deleted file mode 100644 (file)
index 20a5b5c..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-/*
- * This file is part of Twig.
- *
- * (c) 2009 Fabien Potencier
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-/**
- * Represents a filter node.
- *
- * @package    twig
- * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
- * @version    SVN: $Id$
- */
-class Twig_Node_Filter extends Twig_Node implements Twig_NodeListInterface
-{
-    protected $filters;
-    protected $body;
-
-    public function __construct($filters, Twig_NodeList $body, $lineno, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-        $this->filters = $filters;
-        $this->body  = $body;
-    }
-
-    public function __toString()
-    {
-        $filters = array();
-        foreach ($this->filters as $filter) {
-            $filters[] = $filter[0].'('.implode(', ', $filter[1]).')';
-        }
-
-        $repr = array(get_class($this).'('.implode(', ', $filters));
-
-        foreach (explode("\n", $this->body->__toString()) as $line) {
-            $repr[] = '  '.$line;
-        }
-
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        return $this->body->getNodes();
-    }
-
-    public function setNodes(array $nodes)
-    {
-        $this->body = new Twig_NodeList($nodes, $this->lineno);
-    }
-
-    public function compile($compiler)
-    {
-        $compiler->subcompile($this->body);
-    }
-
-    public function getFilters()
-    {
-        return $this->filters;
-    }
-}
index 761de92..ace076f 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_For extends Twig_Node
 {
-    protected $isMultitarget;
-    protected $item;
-    protected $seq;
-    protected $body;
-    protected $else;
-    protected $withLoop;
-
-    public function __construct($isMultitarget, $item, $seq, Twig_NodeList $body, Twig_Node $else = null, $withLoop = false, $lineno, $tag = null)
+    public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $withLoop = false, $lineno, $tag = null)
     {
-        parent::__construct($lineno, $tag);
-        $this->isMultitarget = $isMultitarget;
-        $this->item = $item;
-        $this->seq = $seq;
-        $this->body = $body;
-        $this->else = $else;
-        $this->withLoop = $withLoop;
-        $this->lineno = $lineno;
-    }
-
-    public function getNodes()
-    {
-        return $this->body->getNodes();
-    }
-
-    public function setNodes(array $nodes)
-    {
-        $this->body = new Twig_NodeList($nodes, $this->lineno);
+        parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => $withLoop), $lineno, $tag);
     }
 
     public function compile($compiler)
@@ -60,19 +36,13 @@ class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
             $compiler->write("\$context['_iterated'] = false;\n");
         }
 
-        if ($this->isMultitarget) {
-            $loopVars = array($this->item[0]->getName(), $this->item[1]->getName());
-        } else {
-            $loopVars = array('_key', $this->item->getName());
-        }
-
         $compiler
             ->write("\$context['_seq'] = twig_iterator_to_array(")
             ->subcompile($this->seq)
-            ->raw(", ".($this->isMultitarget ? 'true' : 'false').");\n")
+            ->raw(", ".(null !== $this->key_target ? 'true' : 'false').");\n")
         ;
 
-        if ($this->withLoop) {
+        if ($this['with_loop']) {
             $compiler
                 ->write("\$length = count(\$context['_seq']);\n")
 
@@ -90,11 +60,11 @@ class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
         }
 
         $compiler
-            ->write("foreach (\$context['_seq'] as \$context[")
-            ->repr($loopVars[0])
-            ->raw("] => \$context[")
-            ->repr($loopVars[1])
-            ->raw("]) {\n")
+            ->write("foreach (\$context['_seq'] as ")
+            ->subcompile($this->key_target)
+            ->raw(" => ")
+            ->subcompile($this->value_target)
+            ->raw(") {\n")
             ->indent()
         ;
 
@@ -104,7 +74,7 @@ class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
 
         $compiler->subcompile($this->body);
 
-        if ($this->withLoop) {
+        if ($this['with_loop']) {
             $compiler
                 ->write("++\$context['loop']['index0'];\n")
                 ->write("++\$context['loop']['index'];\n")
@@ -122,8 +92,7 @@ class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
 
         if (!is_null($this->else)) {
             $compiler
-                ->write("if (!\$context['_iterated'])\n")
-                ->write("{\n")
+                ->write("if (!\$context['_iterated']) {\n")
                 ->indent()
                 ->subcompile($this->else)
                 ->outdent()
@@ -134,14 +103,9 @@ class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
         $compiler->write('$_parent = $context[\'_parent\'];'."\n");
 
         // remove some "private" loop variables (needed for nested loops)
-        $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$loopVars[0].'\'], $context[\''.$loopVars[1].'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
+        $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->key_target['name'].'\'], $context[\''.$this->value_target['name'].'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
 
         /// keep the values set in the inner context for variables defined in the outer context
         $compiler->write('$context = array_merge($_parent, array_intersect_key($context, $_parent));'."\n");
     }
-
-    public function setWithLoop($boolean)
-    {
-        $this->withLoop = (Boolean) $boolean;
-    }
 }
index 0dc5f60..34dbfa0 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_If extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_If extends Twig_Node
 {
-    protected $tests;
-    protected $else;
-
-    public function __construct($tests, Twig_NodeList $else = null, $lineno, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-        $this->tests = $tests;
-        $this->else = $else;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).'(');
-        foreach ($this->tests as $test) {
-            foreach (explode("\n", $test[0].' => '.$test[1]) as $line) {
-                $repr[] = '    '.$line;
-            }
-        }
-        $repr[] = ')';
-
-        if ($this->else) {
-            foreach (explode("\n", $this->else) as $line) {
-                $repr[] = '    '.$line;
-            }
-        }
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        $nodes = array();
-        foreach ($this->tests as $test) {
-            $nodes[] = $test[1];
-        }
-
-        if ($this->else) {
-            $nodes[] = $this->else;
-        }
-
-        return $nodes;
-    }
-
-    public function setNodes(array $nodes)
+    public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null)
     {
-        foreach ($this->tests as $i => $test) {
-            $this->tests[$i][1] = $nodes[$i];
-        }
-
-        if ($this->else) {
-            $nodes = $nodes[count($nodes) - 1];
-        }
+        parent::__construct(array('tests' => $tests, 'else' => $else), array(), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $compiler->addDebugInfo($this);
-        $idx = 0;
-        foreach ($this->tests as $test) {
-            if ($idx++) {
+        for ($i = 0; $i < count($this->tests); $i += 2) {
+            if ($i > 0) {
                 $compiler
                     ->outdent()
                     ->write("} elseif (")
@@ -90,13 +40,14 @@ class Twig_Node_If extends Twig_Node implements Twig_NodeListInterface
             }
 
             $compiler
-                ->subcompile($test[0])
+                ->subcompile($this->tests->{$i})
                 ->raw(") {\n")
                 ->indent()
-                ->subcompile($test[1])
+                ->subcompile($this->tests->{($i + 1)})
             ;
         }
-        if (!is_null($this->else)) {
+
+        if (isset($this->else) && null !== $this->else) {
             $compiler
                 ->outdent()
                 ->write("} else {\n")
index 1fbde38..984ff31 100644 (file)
  */
 class Twig_Node_Import extends Twig_Node
 {
-    protected $macro;
-    protected $var;
-
-    public function __construct($macro, $var, $lineno, $tag = null)
+    public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression_AssignName $var, $lineno, $tag = null)
     {
-        parent::__construct($lineno, $tag);
-        $this->macro = $macro;
-        $this->var = $var;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'('.$this->macro.', '.$this->var.')';
+        parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $compiler
             ->addDebugInfo($this)
-            ->write('$this->env->loadTemplate(')
-            ->string($this->macro)
-            ->raw(");\n\n")
-            ->write("if (!class_exists(")
-            ->string($compiler->getTemplateClass($this->macro).'_Macro')
-            ->raw(")) {\n")
-            ->indent()
-            ->write(sprintf("throw new InvalidArgumentException('There is no defined macros in template \"%s\".');\n", $this->macro))
-            ->outdent()
-            ->write("}\n")
-            ->write(sprintf("\$context["))
-            ->string($this->var)
-            ->raw(sprintf("] = new %s_Macro(\$this->env);\n", $compiler->getTemplateClass($this->macro)))
+            ->write('')
+            ->subcompile($this->var)
+            ->raw(' = ')
+            ->raw('$this->env->loadTemplate(')
+            ->subcompile($this->expr)
+            ->raw(", true);\n")
         ;
     }
-
-    public function getMacro()
-    {
-        return $this->macro;
-    }
-
-    public function getVar()
-    {
-        return $this->var;
-    }
 }
index 4e613d2..b3c9f78 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_Include extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_Include extends Twig_Node
 {
-    protected $expr;
-    protected $sandboxed;
-    protected $variables;
-
-    public function __construct(Twig_Node_Expression $expr, $sandboxed, $variables, $lineno, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-
-        $this->expr = $expr;
-        $this->sandboxed = $sandboxed;
-        $this->variables = $variables;
-    }
-
-    public function __toString()
+    public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $lineno, $tag = null)
     {
-        return get_class($this).'('.$this->expr.($this->sandboxed ? ', sandboxed' : '').($this->variables ? ', '.$this->variables : '').')';
-    }
-
-    public function getNodes()
-    {
-        if (null === $this->variables) {
-            return array(new Twig_Node_Text('', -1));
-        } else {
-            return array($this->variables);
-        }
-
-        return $this->variables->getNodes();
-    }
-
-    public function setNodes(array $nodes)
-    {
-        if (isset($nodes[0]) && -1 === $nodes[0]->getLine()) {
-            $this->variables = null;
-        } else {
-            $this->variables = $nodes[0];
-        }
-    }
-
-    public function getIncludedFile()
-    {
-        return $this->expr;
-    }
-
-    public function isSandboxed()
-    {
-        return $this->sandboxed;
-    }
-
-    public function getVariables()
-    {
-        return $this->variables;
+        parent::__construct(array('expr' => $expr, 'variables' => $variables), array(), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
-        if (!$compiler->getEnvironment()->hasExtension('sandbox') && $this->sandboxed) {
-            throw new Twig_SyntaxError('Unable to use the sanboxed attribute on an include if the sandbox extension is not enabled.', $this->lineno);
-        }
-
-        $compiler->addDebugInfo($this);
-
-        if ($this->sandboxed) {
-            $compiler
-                ->write("\$sandbox = \$this->env->getExtension('sandbox');\n")
-                ->write("\$alreadySandboxed = \$sandbox->isSandboxed();\n")
-                ->write("\$sandbox->enableSandbox();\n")
-            ;
-        }
-
         $compiler
+            ->addDebugInfo($this)
             ->write('$this->env->loadTemplate(')
             ->subcompile($this->expr)
             ->raw(')->display(')
@@ -101,15 +40,5 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeListInterface
         }
 
         $compiler->raw(");\n");
-
-        if ($this->sandboxed) {
-            $compiler
-                ->write("if (!\$alreadySandboxed) {\n")
-                ->indent()
-                ->write("\$sandbox->disableSandbox();\n")
-                ->outdent()
-                ->write("}\n")
-            ;
-        }
     }
 }
index 3f62e3c..03286a8 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_Macro extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_Macro extends Twig_Node
 {
-    protected $name;
-    protected $body;
-    protected $arguments;
-
-    public function __construct($name, Twig_NodeList $body, $arguments, $lineno, $parent = null, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-        $this->name = $name;
-        $this->body = $body;
-        $this->arguments = $arguments;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).' '.$this->name.'(');
-        foreach ($this->body->getNodes() as $node) {
-            foreach (explode("\n", $node->__toString()) as $line) {
-                $repr[] = '  '.$line;
-            }
-        }
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        return $this->body->getNodes();
-    }
-
-    public function setNodes(array $nodes)
-    {
-        $this->body = new Twig_NodeList($nodes, $this->lineno);
-    }
-
-    public function replace($other)
+    public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
     {
-        $this->body = $other->body;
+        parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $arguments = array();
         foreach ($this->arguments as $argument) {
-            $arguments[] = '$'.$argument->getName().' = null';
+            $arguments[] = '$'.$argument['name'].' = null';
         }
 
         $compiler
             ->addDebugInfo($this)
-            ->write(sprintf("public function get%s(%s)\n", $this->name, implode(', ', $arguments)), "{\n")
+            ->write(sprintf("public function get%s(%s)\n", $this['name'], implode(', ', $arguments)), "{\n")
             ->indent()
             ->write("\$context = array(\n")
             ->indent()
@@ -76,8 +41,8 @@ class Twig_Node_Macro extends Twig_Node implements Twig_NodeListInterface
         foreach ($this->arguments as $argument) {
             $compiler
                 ->write('')
-                ->string($argument->getName())
-                ->raw(' => $'.$argument->getName())
+                ->string($argument['name'])
+                ->raw(' => $'.$argument['name'])
                 ->raw(",\n")
             ;
         }
index 258f008..8070417 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_Module extends Twig_Node
 {
-    protected $body;
-    protected $extends;
-    protected $blocks;
-    protected $macros;
-    protected $filename;
-    protected $usedFilters;
-    protected $usedTags;
-
-    public function __construct(Twig_NodeList $body, $extends, array $blocks, array $macros, $filename)
+    public function __construct(Twig_NodeInterface $body, $extends, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, $filename)
     {
-        parent::__construct(1);
-
-        $this->body = $body;
-        $this->extends = $extends;
-        $this->blocks = array_values($blocks);
-        $this->macros = $macros;
-        $this->filename = $filename;
-        $this->usedFilters = array();
-        $this->usedTags = array();
+        parent::__construct(array('body' => $body, 'blocks' => $blocks, 'macros' => $macros), array('filename' => $filename, 'extends' => $extends), 1);
     }
 
-    public function __toString()
+    public function compile($compiler)
     {
-        $repr = array(get_class($this).'(');
-
-        if ($this->extends)
-        {
-            $repr[] = '  extends: '.$this->extends;
-        }
-
-        $repr[] = '  body:';
-        foreach ($this->body->getNodes() as $node) {
-            foreach (explode("\n", $node->__toString()) as $line) {
-                $repr[] = '    '.$line;
-            }
-        }
+        $this->compileTemplate($compiler);
+        $this->compileMacros($compiler);
+    }
 
-        $repr[] = '  blocks: ';
-        foreach ($this->blocks as $node) {
-            foreach (explode("\n", $node->__toString()) as $line) {
-                $repr[] = '    '.$line;
-            }
-        }
+    protected function compileTemplate($compiler)
+    {
+        $this->compileClassHeader($compiler);
 
-        $repr[] = '  macros: ';
-        foreach ($this->macros as $node) {
-            foreach (explode("\n", $node->__toString()) as $line) {
-                $repr[] = '    '.$line;
-            }
-        }
+        $this->compileDisplayHeader($compiler);
 
-        $repr[] = ')';
+        $this->compileDisplayBody($compiler);
 
-        return implode("\n", $repr);
-    }
+        $this->compileDisplayFooter($compiler);
 
-    public function getFilename()
-    {
-        return $this->filename;
-    }
+        $compiler->subcompile($this->blocks);
 
-    public function getBody()
-    {
-        return $this->body;
-    }
+        $this->compileGetName($compiler);
 
-    public function getNodes()
-    {
-        return array_merge(array($this->body), $this->blocks, $this->macros);
+        $this->compileClassFooter($compiler);
     }
 
-    public function setNodes(array $nodes)
+    protected function compileDisplayBody($compiler)
     {
-        $this->body   = array_shift($nodes);
-        $this->blocks = array();
-        $this->macros = array();
-        foreach ($nodes as $node) {
-            if ($node instanceof Twig_Node_Macro) {
-                $this->macros[] = $node;
-            } else {
-                $this->blocks[] = $node;
+        if (null !== $this['extends']) {
+            // remove all but import nodes
+            foreach ($this->body as $node) {
+                if ($node instanceof Twig_Node_Import) {
+                    $compiler->subcompile($node);
+                }
             }
-        }
-    }
 
-    public function setUsedFilters(array $filters)
-    {
-        $this->usedFilters = $filters;
-    }
-
-    public function setUsedTags(array $tags)
-    {
-        $this->usedTags = $tags;
-    }
-
-    public function compile($compiler)
-    {
-        $this->compileTemplate($compiler);
-        $this->compileMacros($compiler);
+            $compiler
+                ->raw("\n")
+                ->write("parent::display(\$context);\n")
+            ;
+        } else {
+            $compiler->subcompile($this->body);
+        }
     }
 
-    protected function compileTemplate($compiler)
+    protected function compileClassHeader($compiler)
     {
-        $sandboxed = $compiler->getEnvironment()->hasExtension('sandbox');
-
         $compiler->write("<?php\n\n");
 
-        if (!is_null($this->extends)) {
+        if (null !== $this['extends']) {
             $compiler
                 ->write('$this->loadTemplate(')
-                ->repr($this->extends)
+                ->repr($this['extends'])
                 ->raw(");\n\n")
             ;
         }
 
         $compiler
             // if the filename contains */, add a blank to avoid a PHP parse error
-            ->write("/* ".str_replace('*/', '* /', $this->filename)." */\n")
-            ->write('class '.$compiler->getTemplateClass($this->filename))
+            ->write("/* ".str_replace('*/', '* /', $this['filename'])." */\n")
+            ->write('class '.$compiler->getEnvironment()->getTemplateClass($this['filename']))
         ;
 
-        $parent = null === $this->extends ? $compiler->getEnvironment()->getBaseTemplateClass() : $compiler->getTemplateClass($this->extends);
+        $parent = null === $this['extends'] ? $compiler->getEnvironment()->getBaseTemplateClass() : $compiler->getEnvironment()->getTemplateClass($this['extends']);
 
         $compiler
             ->raw(" extends $parent\n")
             ->write("{\n")
             ->indent()
+        ;
+    }
+
+    protected function compileDisplayHeader($compiler)
+    {
+        $compiler
             ->write("public function display(array \$context)\n", "{\n")
             ->indent()
         ;
+    }
 
-        if (null !== $this->extends) {
-            // remove all but import nodes
-            $nodes = array();
-            foreach ($this->body->getNodes() as $node) {
-                if ($node instanceof Twig_Node_Import) {
-                    $nodes[] = $node;
-                }
-            }
-
-            $compiler
-                ->subcompile(new Twig_NodeList($nodes))
-                ->write("\n")
-                ->write("parent::display(\$context);\n")
-                ->outdent()
-                ->write("}\n\n")
-            ;
-        } else {
-            if ($sandboxed) {
-                $compiler->write("\$this->checkSecurity();\n");
-            }
-
-            $compiler
-                ->subcompile($this->body)
-                ->outdent()
-                ->write("}\n\n")
-            ;
-        }
-
-        // blocks
-        foreach ($this->blocks as $node) {
-            $compiler->subcompile($node);
-        }
-
-        if ($sandboxed) {
-            // sandbox information
-            $compiler
-                ->write("protected function checkSecurity()\n", "{\n")
-                ->indent()
-                ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
-                ->indent()
-                ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n")
-                ->write(!$this->usedFilters ? "array()\n" : "array('".implode('\', \'', $this->usedFilters)."')\n")
-                ->outdent()
-                ->write(");\n")
-                ->outdent()
-                ->write("}\n\n")
-            ;
-        }
-
-        // debug information
-        if ($compiler->getEnvironment()->isDebug()) {
-            $compiler
-                ->write("public function __toString()\n", "{\n")
-                ->indent()
-                ->write('return ')
-                ->string($this)
-                ->raw(";\n")
-                ->outdent()
-                ->write("}\n\n")
-            ;
-        }
-
-        // original template name
+    protected function compileGetName($compiler)
+    {
         $compiler
             ->write("public function getName()\n", "{\n")
             ->indent()
             ->write('return ')
-            ->string($this->filename)
+            ->string($this['filename'])
             ->raw(";\n")
             ->outdent()
             ->write("}\n\n")
         ;
+    }
 
+    protected function compileDisplayFooter($compiler)
+    {
+        $compiler
+            ->outdent()
+            ->write("}\n\n")
+        ;
+    }
+
+    protected function compileClassFooter($compiler)
+    {
         $compiler
             ->outdent()
             ->write("}\n")
@@ -231,21 +132,15 @@ class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface
 
     protected function compileMacros($compiler)
     {
-        if (!$this->macros) {
-            return;
-        }
-
         $compiler
             ->write("\n")
-            ->write('class '.$compiler->getTemplateClass($this->filename).'_Macro extends Twig_Macro'."\n")
+            ->write('class '.$compiler->getEnvironment()->getTemplateClass($this['filename']).'_Macro extends Twig_Macro'."\n")
             ->write("{\n")
             ->indent()
         ;
 
         // macros
-        foreach ($this->macros as $node) {
-            $compiler->subcompile($node);
-        }
+        $compiler->subcompile($this->macros);
 
         $compiler
             ->outdent()
index 076e9a2..fd2f490 100644 (file)
  */
 class Twig_Node_Parent extends Twig_Node
 {
-    protected $blockName;
-
-    public function __construct($blockName, $lineno, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-        $this->blockName = $blockName;
-    }
-
-    public function __toString()
+    public function __construct($name, $lineno, $tag = null)
     {
-        return get_class($this).'('.$this->blockName.')';
+        parent::__construct(array(), array('name' => $name), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $compiler
             ->addDebugInfo($this)
-            ->write('parent::block_'.$this->blockName.'($context);'."\n")
+            ->write('parent::block_'.$this['name'].'($context);'."\n")
         ;
     }
 }
index 62edd87..6b57f21 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_Print extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_Print extends Twig_Node
 {
-    protected $expr;
-
     public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
     {
-        parent::__construct($lineno, $tag);
-        $this->expr = $expr;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).'(');
-        foreach (explode("\n", $this->expr->__toString()) as $line) {
-            $repr[] = '  '.$line;
-        }
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        return array($this->expr);
-    }
-
-    public function setNodes(array $nodes)
-    {
-        $this->expr = $nodes[0];
+        parent::__construct(array('expr' => $expr), array(), $lineno, $tag);
     }
 
     public function compile($compiler)
@@ -57,9 +33,4 @@ class Twig_Node_Print extends Twig_Node implements Twig_NodeListInterface
             ->raw(";\n")
         ;
     }
-
-    public function getExpression()
-    {
-        return $this->expr;
-    }
 }
diff --git a/lib/Twig/Node/Sandbox.php b/lib/Twig/Node/Sandbox.php
new file mode 100644 (file)
index 0000000..38af795
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a sandbox node.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
+class Twig_Node_Sandbox extends Twig_Node
+{
+    public function __construct(Twig_NodeInterface $body, $lineno, $tag = null)
+    {
+        parent::__construct(array('body' => $body), array(), $lineno, $tag);
+    }
+
+    public function compile($compiler)
+    {
+        $compiler
+            ->addDebugInfo($this)
+            ->write("\$sandbox = \$this->env->getExtension('sandbox');\n")
+            ->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n")
+            ->indent()
+            ->write("\$sandbox->enableSandbox();\n")
+            ->outdent()
+            ->write("}\n")
+            ->subcompile($this->body)
+            ->write("if (!\$alreadySandboxed) {\n")
+            ->indent()
+            ->write("\$sandbox->disableSandbox();\n")
+            ->outdent()
+            ->write("}\n")
+        ;
+    }
+}
diff --git a/lib/Twig/Node/SandboxedModule.php b/lib/Twig/Node/SandboxedModule.php
new file mode 100644 (file)
index 0000000..1ff09f6
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a module node.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
+class Twig_Node_SandboxedModule extends Twig_Node_Module
+{
+    protected $usedFilters;
+    protected $usedTags;
+
+    public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags)
+    {
+        parent::__construct($node->body, $node['extends'], $node->blocks, $node->macros, $node['filename'], $node->getLine(), $node->getNodeTag());
+
+        $this->usedFilters = $usedFilters;
+        $this->usedTags = $usedTags;
+    }
+
+    protected function compileDisplayBody($compiler)
+    {
+        if (null === $this['extends']) {
+            $compiler->write("\$this->checkSecurity();\n");
+        }
+
+        parent::compileDisplayBody($compiler);
+    }
+
+    protected function compileDisplayFooter($compiler)
+    {
+        parent::compileDisplayFooter($compiler);
+
+        $compiler
+            ->write("protected function checkSecurity() {\n")
+            ->indent()
+            ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
+            ->indent()
+            ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n")
+            ->write(!$this->usedFilters ? "array()\n" : "array('".implode('\', \'', $this->usedFilters)."')\n")
+            ->outdent()
+            ->write(");\n")
+        ;
+
+        if (null !== $this['extends']) {
+            $compiler
+                ->raw("\n")
+                ->write("parent::checkSecurity();\n")
+            ;
+        }
+
+        $compiler
+            ->outdent()
+            ->write("}\n\n")
+        ;
+    }
+}
similarity index 80%
rename from lib/Twig/Node/SandboxPrint.php
rename to lib/Twig/Node/SandboxedPrint.php
index d405c1c..bdc2a41 100644 (file)
@@ -10,7 +10,7 @@
  */
 
 /**
- * Twig_Node_SandboxPrint adds a check for the __toString() method
+ * Twig_Node_SandboxedPrint adds a check for the __toString() method
  * when the variable is an object and the sandbox is activated.
  *
  * When there is a simple Print statement, like {{ article }},
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_SandboxPrint extends Twig_Node_Print
+class Twig_Node_SandboxedPrint extends Twig_Node_Print
 {
+    public function __construct(Twig_Node_Print $node)
+    {
+        parent::__construct($node->expr, $node->getLine(), $node->getNodeTag());
+    }
+
     public function compile($compiler)
     {
         $compiler
index 07e45cf..c621b70 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @version    SVN: $Id$
  */
-class Twig_Node_Set extends Twig_Node implements Twig_NodeListInterface
+class Twig_Node_Set extends Twig_Node
 {
-    protected $names;
-    protected $values;
-    protected $isMultitarget;
-    protected $capture;
-
-    public function __construct($isMultitarget, $capture, $names, $values, $lineno, $tag = null)
-    {
-        parent::__construct($lineno, $tag);
-
-        $this->isMultitarget = $isMultitarget;
-        $this->capture = $capture;
-        $this->names = $names;
-        $this->values = $values;
-    }
-
-    public function __toString()
+    public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null)
     {
-        $repr = array(get_class($this).'('.($this->isMultitarget ? implode(', ', $this->names) : $this->names).',');
-        foreach ($this->isMultitarget ? $this->values : array($this->values) as $node) {
-            foreach (explode("\n", $node->__toString()) as $line) {
-                $repr[] = '  '.$line;
-            }
-        }
-        $repr[] = ')';
-
-        return implode("\n", $repr);
-    }
-
-    public function getNodes()
-    {
-        if ($this->isMultitarget) {
-            return $this->values;
-        } else {
-            return array($this->values);
-        }
-    }
-
-    public function setNodes(array $nodes)
-    {
-        $this->values = $this->isMultitarget ? $nodes : $nodes[0];
+        parent::__construct(array('names' => $names, 'values' => $values), array('capture' => $capture), $lineno, $tag);
     }
 
     public function compile($compiler)
     {
         $compiler->addDebugInfo($this);
 
-        if ($this->isMultitarget) {
+        if (count($this->names) > 1) {
             $compiler->write('list(');
             foreach ($this->names as $idx => $node) {
                 if ($idx) {
@@ -75,7 +38,7 @@ class Twig_Node_Set extends Twig_Node implements Twig_NodeListInterface
             }
             $compiler->raw(')');
         } else {
-            if ($this->capture) {
+            if ($this['capture']) {
                 $compiler
                     ->write("ob_start();\n")
                     ->subcompile($this->values)
@@ -84,15 +47,15 @@ class Twig_Node_Set extends Twig_Node implements Twig_NodeListInterface
 
             $compiler->subcompile($this->names, false);
 
-            if ($this->capture) {
+            if ($this['capture']) {
                 $compiler->raw(" = ob_get_clean()");
             }
         }
 
-        if (!$this->capture) {
+        if (!$this['capture']) {
             $compiler->raw(' = ');
 
-            if ($this->isMultitarget) {
+            if (count($this->names) > 1) {
                 $compiler->write('array(');
                 foreach ($this->values as $idx => $value) {
                     if ($idx) {
@@ -109,9 +72,4 @@ class Twig_Node_Set extends Twig_Node implements Twig_NodeListInterface
 
         $compiler->raw(";\n");
     }
-
-    public function getNames()
-    {
-        return $this->names;
-    }
 }
index 7f55810..1162053 100644 (file)
  */
 class Twig_Node_Text extends Twig_Node
 {
-    protected $data;
-
     public function __construct($data, $lineno)
     {
-        parent::__construct($lineno);
-        $this->data = $data;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'(\''.str_replace("\n", '\n', $this->data).'\')';
+        parent::__construct(array(), array('data' => $data), $lineno);
     }
 
     public function compile($compiler)
@@ -37,13 +29,8 @@ class Twig_Node_Text extends Twig_Node
         $compiler
             ->addDebugInfo($this)
             ->write('echo ')
-            ->string($this->data)
+            ->string($this['data'])
             ->raw(";\n")
         ;
     }
-
-    public function getData()
-    {
-        return $this->data;
-    }
 }
index c26ac9b..139c89e 100644 (file)
  */
 class Twig_Node_Trans extends Twig_Node
 {
-    protected $count, $body, $plural;
-
-    public function __construct($count, Twig_NodeList $body, $plural, $lineno, $tag = null)
+    public function __construct(Twig_Node_Expression $count = null, Twig_NodeInterface $body, Twig_NodeInterface $plural = null, $lineno, $tag = null)
     {
-        parent::__construct($lineno, $tag);
-
-        $this->count = $count;
-        $this->body = $body;
-        $this->plural = $plural;
-    }
-
-    public function __toString()
-    {
-        return get_class($this).'('.$this->body.', '.$this->count.')';
+        parent::__construct(array('count' => $count, 'body' => $body, 'plural' => $plural), array(), $lineno, $tag);
     }
 
     public function compile($compiler)
@@ -40,13 +29,13 @@ class Twig_Node_Trans extends Twig_Node
 
         list($msg, $vars) = $this->compileString($this->body);
 
-        if (false !== $this->plural) {
+        if (null !== $this->plural) {
             list($msg1, $vars1) = $this->compileString($this->plural);
 
             $vars = array_merge($vars, $vars1);
         }
 
-        $function = false === $this->plural ? 'gettext' : 'ngettext';
+        $function = null === $this->plural ? 'gettext' : 'ngettext';
 
         if ($vars) {
             $compiler
@@ -54,7 +43,7 @@ class Twig_Node_Trans extends Twig_Node
                 ->string($msg)
             ;
 
-            if (false !== $this->plural) {
+            if (null !== $this->plural) {
                 $compiler
                     ->raw(', ')
                     ->string($msg1)
@@ -67,7 +56,7 @@ class Twig_Node_Trans extends Twig_Node
             $compiler->raw('), array(');
 
             foreach ($vars as $var) {
-                if ('count' === $var->getName()) {
+                if ('count' === $var['name']) {
                     $compiler
                         ->string('%count%')
                         ->raw(' => abs(')
@@ -76,7 +65,7 @@ class Twig_Node_Trans extends Twig_Node
                     ;
                 } else {
                     $compiler
-                        ->string('%'.$var->getName().'%')
+                        ->string('%'.$var['name'].'%')
                         ->raw(' => ')
                         ->subcompile($var)
                         ->raw(', ')
@@ -94,33 +83,18 @@ class Twig_Node_Trans extends Twig_Node
         }
     }
 
-    public function getBody()
-    {
-        return $this->body;
-    }
-
-    public function getPlural()
-    {
-        return $this->plural;
-    }
-
-    public function getCount()
-    {
-        return $this->count;
-    }
-
-    protected function compileString(Twig_NodeList $body)
+    protected function compileString(Twig_NodeInterface $body)
     {
         $msg = '';
         $vars = array();
-        foreach ($body->getNodes() as $i => $node) {
+        foreach ($body as $i => $node) {
             if ($node instanceof Twig_Node_Text) {
-                $msg .= $node->getData();
-            } elseif ($node instanceof Twig_Node_Print && $node->getExpression() instanceof Twig_Node_Expression_Name) {
-                $msg .= sprintf('%%%s%%', $node->getExpression()->getName());
-                $vars[] = $node->getExpression();
+                $msg .= $node['data'];
+            } elseif ($node instanceof Twig_Node_Print && $node->expr instanceof Twig_Node_Expression_Name) {
+                $msg .= sprintf('%%%s%%', $node->expr['name']);
+                $vars[] = $node->expr;
             } else {
-                throw new Twig_SyntaxError(sprintf('The text to be translated with "trans" can only contain references to simple variable'), $this->lineno);
+                throw new Twig_SyntaxError(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $this->lineno);
             }
         }
 
diff --git a/lib/Twig/NodeList.php b/lib/Twig/NodeList.php
deleted file mode 100644 (file)
index 4f6dec5..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-/*
- * This file is part of Twig.
- *
- * (c) 2009 Fabien Potencier
- * (c) 2009 Armin Ronacher
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-/**
- * Represents a list of nodes.
- *
- * @package    twig
- * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
- * @version    SVN: $Id$
- */
-class Twig_NodeList extends Twig_Node implements Twig_NodeListInterface
-{
-    protected $nodes;
-
-    public function __construct(array $nodes, $lineno = 0)
-    {
-        parent::__construct($lineno);
-
-        $this->nodes = $nodes;
-    }
-
-    public function __toString()
-    {
-        $repr = array(get_class($this).'(');
-        foreach ($this->nodes as $node) {
-            foreach (explode("\n", $node->__toString()) as $line) {
-                $repr[] = '  '.$line;
-            }
-        }
-
-        return implode("\n", $repr);
-    }
-
-    public function compile($compiler)
-    {
-        foreach ($this->nodes as $node) {
-            $node->compile($compiler);
-        }
-    }
-
-    public function getNodes()
-    {
-        return $this->nodes;
-    }
-
-    public function setNodes(array $nodes)
-    {
-        $this->nodes = $nodes;
-    }
-}
diff --git a/lib/Twig/NodeListInterface.php b/lib/Twig/NodeListInterface.php
deleted file mode 100644 (file)
index fe3cccd..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-/*
- * This file is part of Twig.
- *
- * (c) 2009 Fabien Potencier
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-/**
- * Interface implemented by node list classes.
- *
- * @package    twig
- * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
- * @version    SVN: $Id$
- */
-interface Twig_NodeListInterface
-{
-    /**
-     * Returns an array of embedded nodes
-     */
-    public function getNodes();
-
-    /**
-     * Sets the array of embedded nodes
-     */
-    public function setNodes(array $nodes);
-}
index 49aae44..6450954 100644 (file)
@@ -8,29 +8,73 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+
+/**
+ * Twig_NodeTraverser is a node traverser.
+ *
+ * It visits all nodes and their children and call the given visitor for each.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
 class Twig_NodeTraverser
 {
     protected $env;
+    protected $visitors;
 
-    public function __construct(Twig_Environment $env)
+    /**
+     * Constructor.
+     *
+     * @param Twig_Environment $env      A Twig_Environment instance
+     * @param array            $visitors An array of Twig_NodeVisitorInterface instances
+     */
+    public function __construct(Twig_Environment $env, array $visitors = array())
     {
         $this->env = $env;
+        $this->visitors = array();
+        foreach ($visitors as $visitor) {
+            $this->addVisitor($visitor);
+        }
+    }
+
+    /**
+     * Adds a visitor.
+     *
+     * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
+     */
+    public function addVisitor(Twig_NodeVisitorInterface $visitor)
+    {
+        $this->visitors[] = $visitor;
     }
 
-    public function traverse(Twig_NodeInterface $node, Twig_NodeVisitorInterface $visitor)
+    /**
+     * Traverses a node and calls the registered visitors.
+     *
+     * @param Twig_NodeInterface $node A Twig_NodeInterface instance
+     */
+    public function traverse(Twig_NodeInterface $node = null)
     {
-        $node = $visitor->enterNode($node, $this->env);
-
-        if ($node instanceof Twig_NodeListInterface) {
-            $newNodes = array();
-            foreach ($nodes = $node->getNodes() as $k => $n) {
-                if (null !== $n = $this->traverse($n, $visitor)) {
-                    $newNodes[$k] = $n;
-                }
+        if (null === $node) {
+            return null;
+        }
+
+        foreach ($this->visitors as $visitor) {
+            $node = $visitor->enterNode($node, $this->env);
+        }
+
+        foreach ($node as $k => $n) {
+            if (false !== $n = $this->traverse($n)) {
+                $node->$k = $n;
+            } else {
+                unset($node->$k);
             }
-            $node->setNodes($newNodes);
         }
 
-        return $visitor->leaveNode($node, $this->env);
+        foreach ($this->visitors as $visitor) {
+            $node = $visitor->leaveNode($node, $this->env);
+        }
+
+        return $node;
     }
 }
index be8c3ed..1ceb38b 100644 (file)
@@ -8,30 +8,54 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+
+/**
+ * Twig_NodeVisitor_Escaper implements output escaping.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
 class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
 {
     protected $statusStack = array();
     protected $blocks = array();
 
+    /**
+     * Called before child nodes are visited.
+     *
+     * @param Twig_NodeInterface $node The node to visit
+     * @param Twig_Environment   $env  The Twig environment instance
+     *
+     * @param Twig_NodeInterface The modified node
+     */
     public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
     {
         if ($node instanceof Twig_Node_AutoEscape) {
-            $this->statusStack[] = $node->getValue();
+            $this->statusStack[] = $node['value'];
         } elseif ($node instanceof Twig_Node_Print) {
             return $this->escapeNode($node, $env, $this->needEscaping($env));
         } elseif ($node instanceof Twig_Node_Block) {
-            $this->statusStack[] = isset($this->blocks[$node->getName()]) ? $this->blocks[$node->getName()] : $this->needEscaping($env);
+            $this->statusStack[] = isset($this->blocks[$node['name']]) ? $this->blocks[$node['name']] : $this->needEscaping($env);
         }
 
         return $node;
     }
 
+    /**
+     * Called after child nodes are visited.
+     *
+     * @param Twig_NodeInterface $node The node to visit
+     * @param Twig_Environment   $env  The Twig environment instance
+     *
+     * @param Twig_NodeInterface The modified node
+     */
     public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
     {
         if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) {
             array_pop($this->statusStack);
         } elseif ($node instanceof Twig_Node_BlockReference) {
-            $this->blocks[$node->getName()] = $this->needEscaping($env);
+            $this->blocks[$node['name']] = $this->needEscaping($env);
         }
 
         return $node;
@@ -43,19 +67,19 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
             return $node;
         }
 
-        $expression = $node instanceof Twig_Node_Print ? $node->getExpression() : $node;
+        $expression = $node instanceof Twig_Node_Print ? $node->expr : $node;
 
         if ($expression instanceof Twig_Node_Expression_Filter) {
             // don't escape if the primary node of the filter is not a variable
-            $nodes = $expression->getNodes();
-            if (!$nodes[0] instanceof Twig_Node_Expression_Name) {
+            if (!$expression->node instanceof Twig_Node_Expression_Name) {
                 return $node;
             }
 
             // don't escape if there is already an "escaper" in the filter chain
             $filterMap = $env->getFilters();
-            foreach ($expression->getFilters() as $filter) {
-                if (isset($filterMap[$filter[0]]) && $filterMap[$filter[0]]->isEscaper()) {
+            for ($i = 0; $i < count($expression->filters); $i += 2) {
+                $name = $expression->filters->{$i}['value'];
+                if (isset($filterMap[$name]) && $filterMap[$name]->isEscaper()) {
                     return $node;
                 }
             }
@@ -67,24 +91,23 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
         // escape
         if ($expression instanceof Twig_Node_Expression_Filter) {
             // escape all variables in filters arguments
-            $filters = $expression->getFilters();
-            foreach ($filters as $i => $filter) {
-                foreach ($filter[1] as $j => $argument) {
-                    $filters[$i][1][$j] = $this->escapeNode($argument, $env, $type);
+            for ($i = 0; $i < count($expression->filters); $i += 2) {
+                foreach ($expression->filters->{$i + 1} as $j => $n) {
+                    $expression->filters->{$i + 1}->{$j} = $this->escapeNode($n, $env, $type);
                 }
             }
 
-            $expression->setFilters($filters);
-            $expression->prependFilter($this->getEscaperFilter($type));
+            $filter = $this->getEscaperFilter($type, $expression->getLine());
+            $expression->prependFilter($filter[0], $filter[1]);
 
             return $node;
         } elseif ($node instanceof Twig_Node_Print) {
             return new Twig_Node_Print(
-                new Twig_Node_Expression_Filter($expression, array($this->getEscaperFilter($type)), $node->getLine())
+                new Twig_Node_Expression_Filter($expression, new Twig_Node($this->getEscaperFilter($type, $node->getLine())), $node->getLine())
                 , $node->getLine()
             );
         } else {
-            return new Twig_Node_Expression_Filter($node, array($this->getEscaperFilter($type)), $node->getLine());
+            return new Twig_Node_Expression_Filter($node, new Twig_Node($this->getEscaperFilter($type, $node->getLine())), $node->getLine());
         }
     }
 
@@ -97,8 +120,8 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
         }
     }
 
-    protected function getEscaperFilter($type)
+    protected function getEscaperFilter($type, $line)
     {
-        return array('escape', array(new Twig_Node_Expression_Constant((string) $type, -1)));
+        return array(new Twig_Node_Expression_Constant('escape', $line), new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line))));
     }
 }
diff --git a/lib/Twig/NodeVisitor/Filter.php b/lib/Twig/NodeVisitor/Filter.php
deleted file mode 100644 (file)
index e37b673..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-/*
- * This file is part of Twig.
- *
- * (c) 2009 Fabien Potencier
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-class Twig_NodeVisitor_Filter implements Twig_NodeVisitorInterface
-{
-    protected $statusStack = array();
-
-    public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
-    {
-        if ($node instanceof Twig_Node_Filter) {
-            $this->statusStack[] = $node->getFilters();
-        } elseif ($node instanceof Twig_Node_Print || $node instanceof Twig_Node_Text) {
-            return $this->applyFilters($node);
-        }
-
-        return $node;
-    }
-
-    public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
-    {
-        if ($node instanceof Twig_Node_Filter) {
-            array_pop($this->statusStack);
-        }
-
-        return $node;
-    }
-
-    protected function applyFilters(Twig_NodeInterface $node)
-    {
-        if (false === $filters = $this->getCurrentFilters()) {
-            return $node;
-        }
-
-        if ($node instanceof Twig_Node_Text) {
-            $expression = new Twig_Node_Expression_Constant($node->getData(), $node->getLine());
-        } else {
-            $expression = $node->getExpression();
-        }
-
-        // filters
-        if ($expression instanceof Twig_Node_Expression_Filter) {
-            $expression->appendFilters($filters);
-
-            return $node;
-        } else {
-            return new Twig_Node_Print(
-                new Twig_Node_Expression_Filter($expression, $filters, $node->getLine())
-                , $node->getLine()
-            );
-        }
-    }
-
-    protected function getCurrentFilters()
-    {
-        return count($this->statusStack) ? $this->statusStack[count($this->statusStack) - 1] : false;
-    }
-}
index d624f25..921760f 100644 (file)
@@ -8,12 +8,28 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+
+/**
+ * Twig_NodeVisitor_Sandbox implements sandboxing.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
 class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
 {
     protected $inAModule = false;
     protected $tags;
     protected $filters;
 
+    /**
+     * Called before child nodes are visited.
+     *
+     * @param Twig_NodeInterface $node The node to visit
+     * @param Twig_Environment   $env  The Twig environment instance
+     *
+     * @param Twig_NodeInterface The modified node
+     */
     public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
     {
         if ($node instanceof Twig_Node_Module) {
@@ -25,31 +41,39 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
         } elseif ($this->inAModule) {
             // look for tags
             if ($node->getNodeTag()) {
-                $this->tags[$node->getNodeTag()] = true;
+                $this->tags[] = $node->getNodeTag();
             }
 
             // look for filters
             if ($node instanceof Twig_Node_Expression_Filter) {
-                foreach ($node->getFilters() as $filter) {
-                    $this->filters[$filter[0]] = true;
+                for ($i = 0; $i < count($node->filters); $i += 2) {
+                    $this->filters[] = $node->filters->{$i}['value'];
                 }
             }
 
-            // look for simple print statement ({{ article }})
-            if ($node instanceof Twig_Node_Print && $node->getExpression() instanceof Twig_Node_Expression_Name) {
-                return new Twig_Node_SandboxPrint($node->getExpression(), $node->getLine(), $node->getNodeTag());
+            // look for simple print statements ({{ article }})
+            if ($node instanceof Twig_Node_Print && $node->expr instanceof Twig_Node_Expression_Name) {
+                return new Twig_Node_SandboxedPrint($node);
             }
         }
 
         return $node;
     }
 
+    /**
+     * Called after child nodes are visited.
+     *
+     * @param Twig_NodeInterface $node The node to visit
+     * @param Twig_Environment   $env  The Twig environment instance
+     *
+     * @param Twig_NodeInterface The modified node
+     */
     public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
     {
         if ($node instanceof Twig_Node_Module) {
-            $node->setUsedFilters(array_keys($this->filters));
-            $node->setUsedTags(array_keys($this->tags));
             $this->inAModule = false;
+
+            return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags));
         }
 
         return $node;
index 898e3d1..5edb3fa 100644 (file)
@@ -1,8 +1,40 @@
 <?php
 
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Twig_NodeVisitorInterface is the interface the all node visitor classes must implement.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
 interface Twig_NodeVisitorInterface
 {
+    /**
+     * Called before child nodes are visited.
+     *
+     * @param Twig_NodeInterface $node The node to visit
+     * @param Twig_Environment   $env  The Twig environment instance
+     *
+     * @param Twig_NodeInterface The modified node
+     */
     public function enterNode(Twig_NodeInterface $node, Twig_Environment $env);
 
+    /**
+     * Called after child nodes are visited.
+     *
+     * @param Twig_NodeInterface $node The node to visit
+     * @param Twig_Environment   $env  The Twig environment instance
+     *
+     * @param Twig_NodeInterface The modified node
+     */
     public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env);
 }
index 3d07dd9..ccc714e 100644 (file)
@@ -77,10 +77,10 @@ class Twig_Parser implements Twig_ParserInterface
 
         if (!is_null($this->extends)) {
             // check that the body only contains block references and empty text nodes
-            foreach ($body->getNodes() as $node)
+            foreach ($body as $node)
             {
                 if (
-                    ($node instanceof Twig_Node_Text && !preg_match('/^\s*$/s', $node->getData()))
+                    ($node instanceof Twig_Node_Text && !preg_match('/^\s*$/s', $node['data']))
                     ||
                     (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference)
                 ) {
@@ -89,18 +89,15 @@ class Twig_Parser implements Twig_ParserInterface
             }
 
             foreach ($this->blocks as $block) {
-                $block->setParent($this->extends);
+                $block['parent'] = $this->extends;
             }
         }
 
-        $node = new Twig_Node_Module($body, $this->extends, $this->blocks, $this->macros, $this->stream->getFilename());
+        $node = new Twig_Node_Module($body, $this->extends, new Twig_Node($this->blocks), new Twig_Node($this->macros), $this->stream->getFilename());
 
-        $t = new Twig_NodeTraverser($this->env);
-        foreach ($this->visitors as $visitor) {
-            $node = $t->traverse($node, $visitor);
-        }
+        $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
 
-        return $node;
+        return $traverser->traverse($node);
     }
 
     public function subparse($test, $drop_needle = false)
@@ -134,7 +131,7 @@ class Twig_Parser implements Twig_ParserInterface
                             $this->stream->next();
                         }
 
-                        return new Twig_NodeList($rv, $lineno);
+                        return new Twig_Node($rv, array(), $lineno);
                     }
 
                     if (!isset($this->handlers[$token->getValue()])) {
@@ -155,7 +152,7 @@ class Twig_Parser implements Twig_ParserInterface
             }
         }
 
-        return new Twig_NodeList($rv, $lineno);
+        return new Twig_Node($rv, array(), $lineno);
     }
 
     public function addHandler($name, $class)
index 588e11f..c30a115 100644 (file)
@@ -24,11 +24,6 @@ abstract class Twig_Resource
         return $this->env;
     }
 
-    protected function resolveMissingFilter($name)
-    {
-        throw new Twig_RuntimeError(sprintf('The filter "%s" does not exist', $name));
-    }
-
     protected function getContext($context, $item)
     {
         if (isset($context[$item])) {
index 5be60ea..2fa61e5 100644 (file)
@@ -35,7 +35,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
         } else {
             $stream->expect(Twig_Token::NAME_TYPE, 'as');
 
-            $body = new Twig_NodeList(array(
+            $body = new Twig_Node(array(
                 new Twig_Node_Print($this->parser->getExpressionParser()->parseExpression(), $lineno),
             ));
         }
index 29a5a66..31c9387 100644 (file)
@@ -12,14 +12,23 @@ class Twig_TokenParser_Filter extends Twig_TokenParser
 {
     public function parse(Twig_Token $token)
     {
-        $lineno = $token->getLine();
         $filters = $this->parser->getExpressionParser()->parseFilterExpressionRaw();
 
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
         $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
 
-        return new Twig_Node_Filter($filters, $body, $lineno, $this->getTag());
+        $name = '_tmp'.rand(10000, 99999);
+        $ref = new Twig_Node_BlockReference($name, $token->getLine(), $this->getTag());
+
+        $block = new Twig_Node_Block($name, $body, $token->getLine());
+        $this->parser->setBlock($name, $block);
+
+        $set = new Twig_Node_Set(true, new Twig_Node(array(new Twig_Node_Expression_AssignName($name, $token->getLine()))), new Twig_Node(array($ref)), $token->getLine(), $this->getTag());
+        $filter = new Twig_Node_Expression_Filter(new Twig_Node_Expression_Name($name, $token->getLine()), $filters, $token->getLine(), $this->getTag());
+        $filter = new Twig_Node_Print($filter, $token->getLine(), $this->getTag());
+
+        return new Twig_Node(array($set, $filter));
     }
 
     public function decideBlockEnd($token)
index 0694089..f18b72f 100644 (file)
@@ -14,7 +14,7 @@ class Twig_TokenParser_For extends Twig_TokenParser
     public function parse(Twig_Token $token)
     {
         $lineno = $token->getLine();
-        list($isMultitarget, $item) = $this->parser->getExpressionParser()->parseAssignmentExpression();
+        $targets = $this->parser->getExpressionParser()->parseAssignmentExpression();
         $this->parser->getStream()->expect('in');
         $seq = $this->parser->getExpressionParser()->parseExpression();
 
@@ -35,7 +35,15 @@ class Twig_TokenParser_For extends Twig_TokenParser
         }
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
 
-        return new Twig_Node_For($isMultitarget, $item, $seq, $body, $else, $withLoop, $lineno, $this->getTag());
+        if (count($targets) > 1) {
+            $keyTarget = $targets->{0};
+            $valueTarget = $targets->{1};
+        } else {
+            $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno);
+            $valueTarget = $targets->{0};
+        }
+
+        return new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, $withLoop, $lineno, $this->getTag());
     }
 
     public function decideForFork($token)
index b669b17..14a7429 100644 (file)
@@ -17,7 +17,7 @@ class Twig_TokenParser_If extends Twig_TokenParser
         $expr = $this->parser->getExpressionParser()->parseExpression();
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
         $body = $this->parser->subparse(array($this, 'decideIfFork'));
-        $tests = array(array($expr, $body));
+        $tests = array($expr, $body);
         $else = null;
 
         $end = false;
@@ -34,7 +34,8 @@ class Twig_TokenParser_If extends Twig_TokenParser
                         $expr = $this->parser->getExpressionParser()->parseExpression();
                         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
                         $body = $this->parser->subparse(array($this, 'decideIfFork'));
-                        $tests[] = array($expr, $body);
+                        $tests[] = $expr;
+                        $tests[] = $body;
                         break;
 
                     case 'endif':
@@ -51,7 +52,7 @@ class Twig_TokenParser_If extends Twig_TokenParser
 
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
 
-        return new Twig_Node_If($tests, $else, $lineno, $this->getTag());
+        return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag());
     }
 
     public function decideIfFork($token)
index 0f464c0..1bc89ce 100644 (file)
@@ -12,9 +12,9 @@ class Twig_TokenParser_Import extends Twig_TokenParser
 {
     public function parse(Twig_Token $token)
     {
-        $macro = $this->parser->getStream()->expect(Twig_Token::STRING_TYPE)->getValue();
+        $macro = $this->parser->getExpressionParser()->parseExpression();
         $this->parser->getStream()->expect('as');
-        $var = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+        $var = new Twig_Node_Expression_AssignName($this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(), $token->getLine());
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
 
         return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag());
index 963c243..bb68eb6 100644 (file)
@@ -15,21 +15,16 @@ class Twig_TokenParser_Include extends Twig_TokenParser
     {
         $expr = $this->parser->getExpressionParser()->parseExpression();
 
-        $sandboxed = false;
-        if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'sandboxed')) {
-            $this->parser->getStream()->next();
-            $sandboxed = true;
-        }
-
         $variables = null;
         if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'with')) {
             $this->parser->getStream()->next();
+
             $variables = $this->parser->getExpressionParser()->parseExpression();
         }
 
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
 
-        return new Twig_Node_Include($expr, $sandboxed, $variables, $token->getLine(), $this->getTag());
+        return new Twig_Node_Include($expr, $variables, $token->getLine(), $this->getTag());
     }
 
     public function getTag()
diff --git a/lib/Twig/TokenParser/Sandbox.php b/lib/Twig/TokenParser/Sandbox.php
new file mode 100644 (file)
index 0000000..dbca198
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Sandbox extends Twig_TokenParser
+{
+    public function parse(Twig_Token $token)
+    {
+        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+        return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag());
+    }
+
+    public function decideBlockEnd($token)
+    {
+        return $token->test('endsandbox');
+    }
+
+    public function getTag()
+    {
+        return 'sandbox';
+    }
+}
index f5f7dab..72fb028 100644 (file)
@@ -14,7 +14,7 @@ class Twig_TokenParser_Set extends Twig_TokenParser
     {
         $lineno = $token->getLine();
         $stream = $this->parser->getStream();
-        list($isMultitarget, $names) = $this->parser->getExpressionParser()->parseAssignmentExpression();
+        $names = $this->parser->getExpressionParser()->parseAssignmentExpression();
 
         $capture = false;
         if ($stream->test(Twig_Token::NAME_TYPE, 'as')) {
@@ -25,7 +25,7 @@ class Twig_TokenParser_Set extends Twig_TokenParser
         } else {
             $capture = true;
 
-            if ($isMultitarget) {
+            if (count($names) > 1) {
                 throw new Twig_SyntaxError("When using set with a block, you cannot have a multi-target.", $lineno);
             }
 
@@ -39,7 +39,7 @@ class Twig_TokenParser_Set extends Twig_TokenParser
             throw new Twig_SyntaxError("When using set, you must have the same number of variables and assignements.", $lineno);
         }
 
-        return new Twig_Node_Set($isMultitarget, $capture, $names, $values, $lineno, $this->getTag());
+        return new Twig_Node_Set($capture, $names, $values, $lineno, $this->getTag());
     }
 
     public function decideBlockEnd($token)
index 615972a..7aa49f4 100644 (file)
@@ -14,19 +14,19 @@ class Twig_TokenParser_Trans extends Twig_TokenParser
     {
         $lineno = $token->getLine();
         $stream = $this->parser->getStream();
-        $count = false;
+        $count = null;
         if (!$stream->test(Twig_Token::BLOCK_END_TYPE)) {
             $count = new Twig_Node_Expression_Name($stream->expect(Twig_Token::NAME_TYPE)->getValue(), $lineno);
         }
 
         $stream->expect(Twig_Token::BLOCK_END_TYPE);
         $body = $this->parser->subparse(array($this, 'decideForFork'));
-        $plural = false;
+        $plural = null;
         if ('plural' === $stream->next()->getValue()) {
             $stream->expect(Twig_Token::BLOCK_END_TYPE);
             $plural = $this->parser->subparse(array($this, 'decideForEnd'), true);
 
-            if (false === $count) {
+            if (null === $count) {
                 throw new Twig_SyntaxError('When a plural is used, you must pass the count as an argument to the "trans" tag', $lineno);
             }
         }
index 80ccf2b..83dd783 100644 (file)
@@ -6,7 +6,7 @@
          convertErrorsToExceptions="true"
          convertNoticesToExceptions="true"
          convertWarningsToExceptions="true"
-         processIsolation="true"
+         processIsolation="false"
          stopOnFailure="false"
          syntaxCheck="false"
          bootstrap="test/Twig/Tests/bootstrap.php"
index e572284..c8b7999 100644 (file)
@@ -16,7 +16,6 @@ class Twig_Tests_AutoloaderTest extends PHPUnit_Framework_TestCase
         $this->assertFalse(class_exists('FooBarFoo'), '->autoload() does not try to load classes that does not begin with Twig');
 
         $autoloader = new Twig_Autoloader();
-        $this->assertTrue($autoloader->autoload('Twig_Parser'), '->autoload() returns true if it is able to load a class');
-        $this->assertFalse($autoloader->autoload('Foo'), '->autoload() returns false if it is not able to load a class');
+        $this->assertNull($autoloader->autoload('Foo'), '->autoload() returns false if it is not able to load a class');
     }
 }
index eced784..a939ffd 100644 (file)
@@ -97,7 +97,7 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
 
         self::$templates = array(
-            '3_basic'    => '{{ obj.foo }}{% include "3_included" sandboxed %}{{ obj.foo }}',
+            '3_basic'    => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}',
             '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
         );
 
diff --git a/test/Twig/Tests/Node/AutoEscapeTest.php b/test/Twig/Tests/Node/AutoEscapeTest.php
new file mode 100644 (file)
index 0000000..5438390
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_AutoEscapeTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_AutoEscape::__construct
+     */
+    public function testConstructor()
+    {
+        $body = new Twig_Node(array(new Twig_Node_Text('foo', 0)));
+        $node = new Twig_Node_AutoEscape(true, $body, 0);
+
+        $this->assertEquals($body, $node->body);
+        $this->assertEquals(true, $node['value']);
+    }
+
+    /**
+     * @covers Twig_Node_AutoEscape::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $body = new Twig_Node(array(new Twig_Node_Text('foo', 0)));
+        $node = new Twig_Node_AutoEscape(true, $body, 0);
+
+        return array(
+            array($node, 'echo "foo";'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/BlockReferenceTest.php b/test/Twig/Tests/Node/BlockReferenceTest.php
new file mode 100644 (file)
index 0000000..cc5e74b
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_BlockReferenceTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_BlockReference::__construct
+     */
+    public function testConstructor()
+    {
+        $node = new Twig_Node_BlockReference('foo', 0);
+
+        $this->assertEquals('foo', $node['name']);
+    }
+
+    /**
+     * @covers Twig_Node_BlockReference::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        return array(
+            array(new Twig_Node_BlockReference('foo', 0), '$this->block_foo($context);'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/BlockTest.php b/test/Twig/Tests/Node/BlockTest.php
new file mode 100644 (file)
index 0000000..a6470ef
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_BlockTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Block::__construct
+     */
+    public function testConstructor()
+    {
+        $body = new Twig_Node_Text('foo', 0);
+        $node = new Twig_Node_Block('foo', $body, 0);
+
+        $this->assertEquals($body, $node->body);
+        $this->assertEquals('foo', $node['name']);
+    }
+
+    /**
+     * @covers Twig_Node_Block::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $body = new Twig_Node_Text('foo', 0);
+        $node = new Twig_Node_Block('foo', $body, 0);
+
+        return array(
+            array($node, <<<EOF
+public function block_foo(\$context)
+{
+    echo "foo";
+}
+EOF
+            ),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/DebugTest.php b/test/Twig/Tests/Node/DebugTest.php
new file mode 100644 (file)
index 0000000..f2344d3
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_DebugTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Debug::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Name('foo', 0);
+        $node = new Twig_Node_Debug($expr, 0);
+        $this->assertEquals($expr, $node->expr);
+
+        $node = new Twig_Node_Debug(null, 0);
+        $this->assertEquals(null, $node->expr);
+    }
+
+    /**
+     * @covers Twig_Node_Debug::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $tests[] = array(new Twig_Node_Debug(null, 0), <<<EOF
+if (\$this->env->isDebug()) {
+    var_export(\$context);
+}
+EOF
+        );
+
+        $expr = new Twig_Node_Expression_Name('foo', 0);
+        $node = new Twig_Node_Debug($expr, 0);
+
+        $tests[] = array($node, <<<EOF
+if (\$this->env->isDebug()) {
+    var_export(\$this->getContext(\$context, 'foo'));
+}
+EOF
+        );
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/ArrayTest.php b/test/Twig/Tests/Node/Expression/ArrayTest.php
new file mode 100644 (file)
index 0000000..6729225
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_ArrayTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Array::__construct
+     */
+    public function testConstructor()
+    {
+        $elements = array('foo' => $foo = new Twig_Node_Expression_Constant('bar', 0));
+        $node = new Twig_Node_Expression_Array($elements, 0);
+
+        $this->assertEquals($foo, $node->foo);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Array::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $elements = array(
+            'foo' => new Twig_Node_Expression_Constant('bar', 0),
+            'bar' => new Twig_Node_Expression_Constant('foo', 0),
+        );
+        $node = new Twig_Node_Expression_Array($elements, 0);
+
+        return array(
+            array($node, 'array("foo" => "bar", "bar" => "foo")'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/AssignNameTest.php b/test/Twig/Tests/Node/Expression/AssignNameTest.php
new file mode 100644 (file)
index 0000000..5cc9988
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_AssignNameTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_AssignName::__construct
+     */
+    public function testConstructor()
+    {
+        $node = new Twig_Node_Expression_AssignName('foo', 0);
+
+        $this->assertEquals('foo', $node['name']);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_AssignName::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $node = new Twig_Node_Expression_AssignName('foo', 0);
+
+        return array(
+            array($node, '$context[\'foo\']'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/AddTest.php b/test/Twig/Tests/Node/Expression/Binary/AddTest.php
new file mode 100644 (file)
index 0000000..ed49187
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_AddTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_Add::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Add($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_Add::compile
+     * @covers Twig_Node_Expression_Binary_Add::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Add($left, $right, 0);
+
+        return array(
+            array($node, '(1) + (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/AndTest.php b/test/Twig/Tests/Node/Expression/Binary/AndTest.php
new file mode 100644 (file)
index 0000000..0db4cb2
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_AndTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_And::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_And($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_And::compile
+     * @covers Twig_Node_Expression_Binary_And::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_And($left, $right, 0);
+
+        return array(
+            array($node, '(1) && (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php b/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php
new file mode 100644 (file)
index 0000000..a24a86a
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_ConcatTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_Concat::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Concat($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_Concat::compile
+     * @covers Twig_Node_Expression_Binary_Concat::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Concat($left, $right, 0);
+
+        return array(
+            array($node, '(1) . (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/DivTest.php b/test/Twig/Tests/Node/Expression/Binary/DivTest.php
new file mode 100644 (file)
index 0000000..3bea350
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_DivTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_Div::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Div($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_Div::compile
+     * @covers Twig_Node_Expression_Binary_Div::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Div($left, $right, 0);
+
+        return array(
+            array($node, '(1) / (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php b/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php
new file mode 100644 (file)
index 0000000..046e9be
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_FloorDivTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_FloorDiv::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_FloorDiv($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_FloorDiv::compile
+     * @covers Twig_Node_Expression_Binary_FloorDiv::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_FloorDiv($left, $right, 0);
+
+        return array(
+            array($node, 'floor((1) / (2))'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/ModTest.php b/test/Twig/Tests/Node/Expression/Binary/ModTest.php
new file mode 100644 (file)
index 0000000..c56d39c
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_ModTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_Mod::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Mod($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_Mod::compile
+     * @covers Twig_Node_Expression_Binary_Mod::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Mod($left, $right, 0);
+
+        return array(
+            array($node, '(1) % (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/MulTest.php b/test/Twig/Tests/Node/Expression/Binary/MulTest.php
new file mode 100644 (file)
index 0000000..27be8ac
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_MulTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_Mul::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Mul($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_Mul::compile
+     * @covers Twig_Node_Expression_Binary_Mul::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Mul($left, $right, 0);
+
+        return array(
+            array($node, '(1) * (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/OrTest.php b/test/Twig/Tests/Node/Expression/Binary/OrTest.php
new file mode 100644 (file)
index 0000000..ca05101
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_OrTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_Or::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Or($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_Or::compile
+     * @covers Twig_Node_Expression_Binary_Or::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Or($left, $right, 0);
+
+        return array(
+            array($node, '(1) || (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Binary/SubTest.php b/test/Twig/Tests/Node/Expression/Binary/SubTest.php
new file mode 100644 (file)
index 0000000..ac13130
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Binary_SubTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Binary_Sub::__construct
+     */
+    public function testConstructor()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Sub($left, $right, 0);
+
+        $this->assertEquals($left, $node->left);
+        $this->assertEquals($right, $node->right);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Binary_Sub::compile
+     * @covers Twig_Node_Expression_Binary_Sub::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $left = new Twig_Node_Expression_Constant(1, 0);
+        $right = new Twig_Node_Expression_Constant(2, 0);
+        $node = new Twig_Node_Expression_Binary_Sub($left, $right, 0);
+
+        return array(
+            array($node, '(1) - (2)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/CompareTest.php b/test/Twig/Tests/Node/Expression/CompareTest.php
new file mode 100644 (file)
index 0000000..0c08bf9
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_CompareTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Compare::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant(1, 0);
+        $ops = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('>', 0),
+            new Twig_Node_Expression_Constant(2, 0),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Compare($expr, $ops, 0);
+
+        $this->assertEquals($expr, $node->expr);
+        $this->assertEquals($ops, $node->ops);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Compare::compile
+     * @covers Twig_Node_Expression_Compare::compileIn
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $expr = new Twig_Node_Expression_Constant(1, 0);
+        $ops = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('>', 0),
+            new Twig_Node_Expression_Constant(2, 0),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Compare($expr, $ops, 0);
+        $tests[] = array($node, '1 > 2');
+
+        $ops = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('>', 0),
+            new Twig_Node_Expression_Constant(2, 0),
+            new Twig_Node_Expression_Constant('<', 0),
+            new Twig_Node_Expression_Constant(4, 0),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Compare($expr, $ops, 0);
+        $tests[] = array($node, '1 > ($tmp1 = 2) && ($tmp1 < 4)');
+
+        $ops = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('in', 0),
+            new Twig_Node_Expression_Constant(2, 0),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Compare($expr, $ops, 0);
+        $tests[] = array($node, 'twig_in_filter(1, 2)');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/ConditionalTest.php b/test/Twig/Tests/Node/Expression/ConditionalTest.php
new file mode 100644 (file)
index 0000000..3b22fb0
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_ConditionalTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Conditional::__construct
+     */
+    public function testConstructor()
+    {
+        $expr1 = new Twig_Node_Expression_Constant(1, 0);
+        $expr2 = new Twig_Node_Expression_Constant(2, 0);
+        $expr3 = new Twig_Node_Expression_Constant(3, 0);
+        $node = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, 0);
+
+        $this->assertEquals($expr1, $node->expr1);
+        $this->assertEquals($expr2, $node->expr2);
+        $this->assertEquals($expr3, $node->expr3);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Conditional::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $expr1 = new Twig_Node_Expression_Constant(1, 0);
+        $expr2 = new Twig_Node_Expression_Constant(2, 0);
+        $expr3 = new Twig_Node_Expression_Constant(3, 0);
+        $node = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, 0);
+        $tests[] = array($node, '(1) ? (2) : (3)');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/ConstantTest.php b/test/Twig/Tests/Node/Expression/ConstantTest.php
new file mode 100644 (file)
index 0000000..2824518
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_ConstantTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Constant::__construct
+     */
+    public function testConstructor()
+    {
+        $node = new Twig_Node_Expression_Constant('foo', 0);
+
+        $this->assertEquals('foo', $node['value']);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Constant::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $node = new Twig_Node_Expression_Constant('foo', 0);
+        $tests[] = array($node, '"foo"');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/FilterTest.php b/test/Twig/Tests/Node/Expression/FilterTest.php
new file mode 100644 (file)
index 0000000..77f4d5f
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_FilterTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Filter::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Filter($expr, $filters, 0);
+
+        $this->assertEquals($expr, $node->node);
+        $this->assertEquals($filters, $node->filters);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Filter::hasFilter
+     */
+    public function testHasFilter()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Filter($expr, $filters, 0);
+
+        $this->assertTrue($node->hasFilter('upper'));
+        $this->assertFalse($node->hasFilter('lower'));
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Filter::prependFilter
+     */
+    public function testPrependFilter()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Filter($expr, $filters, 0);
+
+        $a = new Twig_Node_Expression_Constant('lower', 0);
+        $b = new Twig_Node_Expression_Constant('foobar', 0);
+        $node->prependFilter($a, $b);
+
+        $filters = new Twig_Node(array(
+            $a,
+            $b,
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+        ), array(), 0);
+
+        $this->assertEquals($filters, $node->filters);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Filter::appendFilter
+     */
+    public function testAppendFilter()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Filter($expr, $filters, 0);
+
+        $a = new Twig_Node_Expression_Constant('lower', 0);
+        $b = new Twig_Node_Expression_Constant('foobar', 0);
+        $node->appendFilter($a, $b);
+
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+            $a,
+            $b,
+        ), array(), 0);
+
+        $this->assertEquals($filters, $node->filters);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Filter::appendFilters
+     */
+    public function testAppendFilters()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Filter($expr, $filters, 0);
+
+        $others = new Twig_Node(array(
+            $a = new Twig_Node_Expression_Constant('lower', 0),
+            $b = new Twig_Node_Expression_Constant('foobar', 0),
+        ), array(), 0);
+        $node->appendFilters($others);
+
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+            $a,
+            $b,
+        ), array(), 0);
+
+        $this->assertEquals($filters, $node->filters);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Filter::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('foobar', 0),
+            new Twig_Node(array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Filter($expr, $filters, 0);
+
+        $tests[] = array($node, '$this->resolveMissingFilter("foobar", array("foo", "bar", "foobar"))');
+
+        try {
+            $node->compile($this->getCompiler());
+            $this->fail();
+        } catch (Exception $e) {
+            $this->assertEquals('Twig_SyntaxError', get_class($e));
+        }
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $filters = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('upper', 0),
+            new Twig_Node(),
+            new Twig_Node_Expression_Constant('lower', 0),
+            new Twig_Node(array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))),
+        ), array(), 0);
+        $node = new Twig_Node_Expression_Filter($expr, $filters, 0);
+
+        $tests[] = array($node, 'twig_lower_filter($this->getEnvironment(), twig_upper_filter($this->getEnvironment(), "foo"), "bar", "foobar")');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/GetAttrTest.php b/test/Twig/Tests/Node/Expression/GetAttrTest.php
new file mode 100644 (file)
index 0000000..b7f4393
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_GetAttrTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_GetAttr::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Name('foo', 0);
+        $attr = new Twig_Node_Expression_Constant('bar', 0);
+        $args = new Twig_Node(array(
+            new Twig_Node_Expression_Name('foo', 0),
+            new Twig_Node_Expression_Constant('bar', 0),
+        ));
+        $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, 0, '[');
+
+        $this->assertEquals($expr, $node->node);
+        $this->assertEquals($attr, $node->attribute);
+        $this->assertEquals($args, $node->arguments);
+        $this->assertEquals('[', $node['token_value']);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_GetAttr::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $expr = new Twig_Node_Expression_Name('foo', 0);
+        $attr = new Twig_Node_Expression_Constant('bar', 0);
+        $args = new Twig_Node();
+        $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, 0);
+        $tests[] = array($node, '$this->getAttribute($this->getContext($context, \'foo\'), "bar", array())');
+
+        $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, 0, '[');
+        $tests[] = array($node, '$this->getAttribute($this->getContext($context, \'foo\'), "bar", array(), true)');
+
+        $args = new Twig_Node(array(
+            new Twig_Node_Expression_Name('foo', 0),
+            new Twig_Node_Expression_Constant('bar', 0),
+        ));
+        $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, 0);
+        $tests[] = array($node, '$this->getAttribute($this->getContext($context, \'foo\'), "bar", array($this->getContext($context, \'foo\'), "bar", ))');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/NameTest.php b/test/Twig/Tests/Node/Expression/NameTest.php
new file mode 100644 (file)
index 0000000..9f52019
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_NameTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Name::__construct
+     */
+    public function testConstructor()
+    {
+        $node = new Twig_Node_Expression_Name('foo', 0);
+
+        $this->assertEquals('foo', $node['name']);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Name::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $node = new Twig_Node_Expression_Name('foo', 0);
+
+        return array(
+            array($node, '$this->getContext($context, \'foo\')'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Unary/NegTest.php b/test/Twig/Tests/Node/Expression/Unary/NegTest.php
new file mode 100644 (file)
index 0000000..b53e59a
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Unary_NegTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Unary_Neg::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant(1, 0);
+        $node = new Twig_Node_Expression_Unary_Neg($expr, 0);
+
+        $this->assertEquals($expr, $node->node);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Unary_Neg::compile
+     * @covers Twig_Node_Expression_Unary_Neg::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $node = new Twig_Node_Expression_Constant(1, 0);
+        $node = new Twig_Node_Expression_Unary_Neg($node, 0);
+
+        return array(
+            array($node, '(-1)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Unary/NotTest.php b/test/Twig/Tests/Node/Expression/Unary/NotTest.php
new file mode 100644 (file)
index 0000000..e5bcb54
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Unary_NotTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Unary_Not::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant(1, 0);
+        $node = new Twig_Node_Expression_Unary_Not($expr, 0);
+
+        $this->assertEquals($expr, $node->node);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Unary_Not::compile
+     * @covers Twig_Node_Expression_Unary_Not::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $node = new Twig_Node_Expression_Constant(1, 0);
+        $node = new Twig_Node_Expression_Unary_Not($node, 0);
+
+        return array(
+            array($node, '(!1)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/Expression/Unary/PosTest.php b/test/Twig/Tests/Node/Expression/Unary/PosTest.php
new file mode 100644 (file)
index 0000000..bb4815c
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/../../TestCase.php';
+
+class Twig_Tests_Node_Expression_Unary_PosTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Unary_Pos::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant(1, 0);
+        $node = new Twig_Node_Expression_Unary_Pos($expr, 0);
+
+        $this->assertEquals($expr, $node->node);
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Unary_Pos::compile
+     * @covers Twig_Node_Expression_Unary_Pos::operator
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $node = new Twig_Node_Expression_Constant(1, 0);
+        $node = new Twig_Node_Expression_Unary_Pos($node, 0);
+
+        return array(
+            array($node, '(+1)'),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/ForTest.php b/test/Twig/Tests/Node/ForTest.php
new file mode 100644 (file)
index 0000000..9517972
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_ForTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_For::__construct
+     */
+    public function testConstructor()
+    {
+        $keyTarget = new Twig_Node_Expression_AssignName('key', 0);
+        $valueTarget = new Twig_Node_Expression_AssignName('item', 0);
+        $seq = new Twig_Node_Expression_Name('items', 0);
+        $body = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0);
+        $else = null;
+        $withLoop = false;
+        $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, $withLoop, 0);
+
+        $this->assertEquals($keyTarget, $node->key_target);
+        $this->assertEquals($valueTarget, $node->value_target);
+        $this->assertEquals($seq, $node->seq);
+        $this->assertEquals($body, $node->body);
+        $this->assertEquals(null, $node->else);
+
+        $this->assertEquals($withLoop, $node['with_loop']);
+
+        $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0);
+        $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, $withLoop, 0);
+        $this->assertEquals($else, $node->else);
+    }
+
+    /**
+     * @covers Twig_Node_For::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $keyTarget = new Twig_Node_Expression_AssignName('key', 0);
+        $valueTarget = new Twig_Node_Expression_AssignName('item', 0);
+        $seq = new Twig_Node_Expression_Name('items', 0);
+        $body = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0);
+        $else = null;
+        $withLoop = false;
+        $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, $withLoop, 0);
+
+        $tests[] = array($node, <<<EOF
+\$context['_parent'] = (array) \$context;
+\$context['_seq'] = twig_iterator_to_array(\$this->getContext(\$context, 'items'), true);
+foreach (\$context['_seq'] as \$context['key'] => \$context['item']) {
+    echo \$this->getContext(\$context, 'foo');
+}
+\$_parent = \$context['_parent'];
+unset(\$context['_seq'], \$context['_iterated'], \$context['key'], \$context['item'], \$context['_parent'], \$context['loop']);
+\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));
+EOF
+        );
+
+        $keyTarget = new Twig_Node_Expression_AssignName('k', 0);
+        $valueTarget = new Twig_Node_Expression_AssignName('v', 0);
+        $seq = new Twig_Node_Expression_Name('values', 0);
+        $body = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0);
+        $else = null;
+        $withLoop = true;
+        $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, $withLoop, 0);
+
+        $tests[] = array($node, <<<EOF
+\$context['_parent'] = (array) \$context;
+\$context['_seq'] = twig_iterator_to_array(\$this->getContext(\$context, 'values'), true);
+\$length = count(\$context['_seq']);
+\$context['loop'] = array(
+  'parent'    => \$context['_parent'],
+  'length'    => \$length,
+  'index0'    => 0,
+  'index'     => 1,
+  'revindex0' => \$length - 1,
+  'revindex'  => \$length,
+  'first'     => true,
+  'last'      => 1 === \$length,
+);
+foreach (\$context['_seq'] as \$context['k'] => \$context['v']) {
+    echo \$this->getContext(\$context, 'foo');
+    ++\$context['loop']['index0'];
+    ++\$context['loop']['index'];
+    --\$context['loop']['revindex0'];
+    --\$context['loop']['revindex'];
+    \$context['loop']['first'] = false;
+    \$context['loop']['last'] = 0 === \$context['loop']['revindex0'];
+}
+\$_parent = \$context['_parent'];
+unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']);
+\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));
+EOF
+        );
+
+        $keyTarget = new Twig_Node_Expression_AssignName('k', 0);
+        $valueTarget = new Twig_Node_Expression_AssignName('v', 0);
+        $seq = new Twig_Node_Expression_Name('values', 0);
+        $body = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0);
+        $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0);
+        $withLoop = true;
+        $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $body, $else, $withLoop, 0);
+
+        $tests[] = array($node, <<<EOF
+\$context['_parent'] = (array) \$context;
+\$context['_iterated'] = false;
+\$context['_seq'] = twig_iterator_to_array(\$this->getContext(\$context, 'values'), true);
+\$length = count(\$context['_seq']);
+\$context['loop'] = array(
+  'parent'    => \$context['_parent'],
+  'length'    => \$length,
+  'index0'    => 0,
+  'index'     => 1,
+  'revindex0' => \$length - 1,
+  'revindex'  => \$length,
+  'first'     => true,
+  'last'      => 1 === \$length,
+);
+foreach (\$context['_seq'] as \$context['k'] => \$context['v']) {
+    \$context['_iterated'] = true;
+    echo \$this->getContext(\$context, 'foo');
+    ++\$context['loop']['index0'];
+    ++\$context['loop']['index'];
+    --\$context['loop']['revindex0'];
+    --\$context['loop']['revindex'];
+    \$context['loop']['first'] = false;
+    \$context['loop']['last'] = 0 === \$context['loop']['revindex0'];
+}
+if (!\$context['_iterated']) {
+    echo \$this->getContext(\$context, 'foo');
+}
+\$_parent = \$context['_parent'];
+unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']);
+\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));
+EOF
+        );
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/IfTest.php b/test/Twig/Tests/Node/IfTest.php
new file mode 100644 (file)
index 0000000..41b5422
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_IfTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_If::__construct
+     */
+    public function testConstructor()
+    {
+        $t = new Twig_Node(array(
+            new Twig_Node_Expression_Constant(true, 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0),
+        ), array(), 0);
+        $else = null;
+        $node = new Twig_Node_If($t, $else, 0);
+
+        $this->assertEquals($t, $node->tests);
+        $this->assertEquals(null, $node->else);
+
+        $else = new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 0), 0);
+        $node = new Twig_Node_If($t, $else, 0);
+        $this->assertEquals($else, $node->else);
+    }
+
+    /**
+     * @covers Twig_Node_If::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $t = new Twig_Node(array(
+            new Twig_Node_Expression_Constant(true, 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0),
+        ), array(), 0);
+        $else = null;
+        $node = new Twig_Node_If($t, $else, 0);
+
+        $tests[] = array($node, <<<EOF
+if (true) {
+    echo \$this->getContext(\$context, 'foo');
+}
+EOF
+        );
+
+        $t = new Twig_Node(array(
+            new Twig_Node_Expression_Constant(true, 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0),
+            new Twig_Node_Expression_Constant(false, 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 0), 0),
+        ), array(), 0);
+        $else = null;
+        $node = new Twig_Node_If($t, $else, 0);
+
+        $tests[] = array($node, <<<EOF
+if (true) {
+    echo \$this->getContext(\$context, 'foo');
+} elseif (false) {
+    echo \$this->getContext(\$context, 'bar');
+}
+EOF
+        );
+
+        $t = new Twig_Node(array(
+            new Twig_Node_Expression_Constant(true, 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0),
+        ), array(), 0);
+        $else = new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 0), 0);
+        $node = new Twig_Node_If($t, $else, 0);
+
+        $tests[] = array($node, <<<EOF
+if (true) {
+    echo \$this->getContext(\$context, 'foo');
+} else {
+    echo \$this->getContext(\$context, 'bar');
+}
+EOF
+        );
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/ImportTest.php b/test/Twig/Tests/Node/ImportTest.php
new file mode 100644 (file)
index 0000000..5d4b200
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_ImportTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Import::__construct
+     */
+    public function testConstructor()
+    {
+        $macro = new Twig_Node_Expression_Constant('foo.twig', 0);
+        $var = new Twig_Node_Expression_AssignName('macro', 0);
+        $node = new Twig_Node_Import($macro, $var, 0);
+
+        $this->assertEquals($macro, $node->expr);
+        $this->assertEquals($var, $node->var);
+    }
+
+    /**
+     * @covers Twig_Node_Import::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $macro = new Twig_Node_Expression_Constant('foo.twig', 0);
+        $var = new Twig_Node_Expression_AssignName('macro', 0);
+        $node = new Twig_Node_Import($macro, $var, 0);
+
+        $tests[] = array($node, '$context[\'macro\'] = $this->env->loadTemplate("foo.twig", true);');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/IncludeTest.php b/test/Twig/Tests/Node/IncludeTest.php
new file mode 100644 (file)
index 0000000..439c69c
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_IncludeTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Include::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo.twig', 0);
+        $node = new Twig_Node_Include($expr, null, 0);
+
+        $this->assertEquals(null, $node->variables);
+        $this->assertEquals($expr, $node->expr);
+
+        $vars = new Twig_Node_Expression_Array(array('foo' => new Twig_Node_Expression_Constant(true, 0)), 0);
+        $node = new Twig_Node_Include($expr, $vars, 0);
+        $this->assertEquals($vars, $node->variables);
+    }
+
+    /**
+     * @covers Twig_Node_Include::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $expr = new Twig_Node_Expression_Constant('foo.twig', 0);
+        $node = new Twig_Node_Include($expr, null, 0);
+        $tests[] = array($node, '$this->env->loadTemplate("foo.twig")->display($context);');
+
+        $expr = new Twig_Node_Expression_Constant('foo.twig', 0);
+        $vars = new Twig_Node_Expression_Array(array('foo' => new Twig_Node_Expression_Constant(true, 0)), 0);
+        $node = new Twig_Node_Include($expr, $vars, 0);
+        $tests[] = array($node, '$this->env->loadTemplate("foo.twig")->display(array("foo" => true));');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/MacroTest.php b/test/Twig/Tests/Node/MacroTest.php
new file mode 100644 (file)
index 0000000..959d016
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_MacroTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Macro::__construct
+     */
+    public function testConstructor()
+    {
+        $body = new Twig_Node_Text('foo', 0);
+        $arguments = new Twig_Node(array(new Twig_Node_Expression_Name('foo', 0)), array(), 0);
+        $node = new Twig_Node_Macro('foo', $body, $arguments, 0);
+
+        $this->assertEquals($body, $node->body);
+        $this->assertEquals($arguments, $node->arguments);
+        $this->assertEquals('foo', $node['name']);
+    }
+
+    /**
+     * @covers Twig_Node_Macro::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $body = new Twig_Node_Text('foo', 0);
+        $arguments = new Twig_Node(array(new Twig_Node_Expression_Name('foo', 0)), array(), 0);
+        $node = new Twig_Node_Macro('foo', $body, $arguments, 0);
+
+        return array(
+            array($node, <<<EOF
+public function getfoo(\$foo = null)
+{
+    \$context = array(
+        "foo" => \$foo,
+    );
+
+    echo "foo";
+}
+EOF
+            ),
+        );
+    }
+}
diff --git a/test/Twig/Tests/Node/ModuleTest.php b/test/Twig/Tests/Node/ModuleTest.php
new file mode 100644 (file)
index 0000000..cea65ac
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_ModuleTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Module::__construct
+     */
+    public function testConstructor()
+    {
+        $body = new Twig_Node_Text('foo', 0);
+        $extends = 'layout.twig';
+        $blocks = new Twig_Node();
+        $macros = new Twig_Node();
+        $filename = 'foo.twig';
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename);
+
+        $this->assertEquals($body, $node->body);
+        $this->assertEquals($blocks, $node->blocks);
+        $this->assertEquals($macros, $node->macros);
+        $this->assertEquals($filename, $node['filename']);
+        $this->assertEquals($extends, $node['extends']);
+    }
+
+    /**
+     * @covers Twig_Node_Module::compile
+     * @covers Twig_Node_Module::compileTemplate
+     * @covers Twig_Node_Module::compileMacros
+     * @covers Twig_Node_Module::compileClassHeader
+     * @covers Twig_Node_Module::compileDisplayHeader
+     * @covers Twig_Node_Module::compileDisplayBody
+     * @covers Twig_Node_Module::compileDisplayFooter
+     * @covers Twig_Node_Module::compileGetName
+     * @covers Twig_Node_Module::compileClassFooter
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $twig = new Twig_Environment(new Twig_Loader_String());
+
+        $tests = array();
+
+        $body = new Twig_Node_Text('foo', 0);
+        $extends = null;
+        $blocks = new Twig_Node();
+        $macros = new Twig_Node();
+        $filename = 'foo.twig';
+
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename);
+        $tests[] = array($node, <<<EOF
+<?php
+
+/* foo.twig */
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
+{
+    public function display(array \$context)
+    {
+        echo "foo";
+    }
+
+    public function getName()
+    {
+        return "foo.twig";
+    }
+
+}
+
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34_Macro extends Twig_Macro
+{
+}
+EOF
+        , $twig);
+
+        $import = new Twig_Node_Import(new Twig_Node_Expression_Constant('foo.twig', 0), new Twig_Node_Expression_AssignName('macro', 0), 0);
+
+        $body = new Twig_Node(array($import, new Twig_Node_Text('foo', 0)));
+        $extends = 'layout.twig';
+        $blocks = new Twig_Node();
+        $macros = new Twig_Node();
+        $filename = 'foo.twig';
+
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename);
+        $tests[] = array($node, <<<EOF
+<?php
+
+\$this->loadTemplate("layout.twig");
+
+/* foo.twig */
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends __TwigTemplate_d8fb9d03f55738ff78518e1bc2741faf
+{
+    public function display(array \$context)
+    {
+        \$context['macro'] = \$this->env->loadTemplate("foo.twig", true);
+
+        parent::display(\$context);
+    }
+
+    public function getName()
+    {
+        return "foo.twig";
+    }
+
+}
+
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34_Macro extends Twig_Macro
+{
+}
+EOF
+        , $twig);
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/ParentTest.php b/test/Twig/Tests/Node/ParentTest.php
new file mode 100644 (file)
index 0000000..51f5419
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_ParentTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Parent::__construct
+     */
+    public function testConstructor()
+    {
+        $node = new Twig_Node_Parent('foo', 0);
+
+        $this->assertEquals('foo', $node['name']);
+    }
+
+    /**
+     * @covers Twig_Node_Parent::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+        $tests[] = array(new Twig_Node_Parent('foo', 0), 'parent::block_foo($context);');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/PrintTest.php b/test/Twig/Tests/Node/PrintTest.php
new file mode 100644 (file)
index 0000000..53ef15e
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_PrintTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Print::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $node = new Twig_Node_Print($expr, 0);
+
+        $this->assertEquals($expr, $node->expr);
+    }
+
+    /**
+     * @covers Twig_Node_Print::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+        $tests[] = array(new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 0), 0), 'echo "foo";');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/SandboxTest.php b/test/Twig/Tests/Node/SandboxTest.php
new file mode 100644 (file)
index 0000000..d3598a9
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_SandboxTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Sandbox::__construct
+     */
+    public function testConstructor()
+    {
+        $body = new Twig_Node_Text('foo', 0);
+        $node = new Twig_Node_Sandbox($body, 0);
+
+        $this->assertEquals($body, $node->body);
+    }
+
+    /**
+     * @covers Twig_Node_Sandbox::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $body = new Twig_Node_Text('foo', 0);
+        $node = new Twig_Node_Sandbox($body, 0);
+
+        $tests[] = array($node, <<<EOF
+\$sandbox = \$this->env->getExtension('sandbox');
+if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {
+    \$sandbox->enableSandbox();
+}
+echo "foo";
+if (!\$alreadySandboxed) {
+    \$sandbox->disableSandbox();
+}
+EOF
+        );
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/SandboxedModuleTest.php b/test/Twig/Tests/Node/SandboxedModuleTest.php
new file mode 100644 (file)
index 0000000..3ef84c1
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_SandboxedModuleTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_SandboxedModule::__construct
+     */
+    public function testConstructor()
+    {
+        $body = new Twig_Node_Text('foo', 0);
+        $extends = 'layout.twig';
+        $blocks = new Twig_Node();
+        $macros = new Twig_Node();
+        $filename = 'foo.twig';
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename);
+        $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'));
+
+        $this->assertEquals($body, $node->body);
+        $this->assertEquals($blocks, $node->blocks);
+        $this->assertEquals($macros, $node->macros);
+        $this->assertEquals($filename, $node['filename']);
+        $this->assertEquals($extends, $node['extends']);
+    }
+
+    /**
+     * @covers Twig_Node_SandboxedModule::compile
+     * @covers Twig_Node_SandboxedModule::compileDisplayBody
+     * @covers Twig_Node_SandboxedModule::compileDisplayFooter
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $twig = new Twig_Environment(new Twig_Loader_String());
+
+        $tests = array();
+
+        $body = new Twig_Node_Text('foo', 0);
+        $extends = null;
+        $blocks = new Twig_Node();
+        $macros = new Twig_Node();
+        $filename = 'foo.twig';
+
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename);
+        $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'));
+
+        $tests[] = array($node, <<<EOF
+<?php
+
+/* foo.twig */
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
+{
+    public function display(array \$context)
+    {
+        \$this->checkSecurity();
+        echo "foo";
+    }
+
+    protected function checkSecurity() {
+        \$this->env->getExtension('sandbox')->checkSecurity(
+            array('upper'),
+            array('for')
+        );
+    }
+
+    public function getName()
+    {
+        return "foo.twig";
+    }
+
+}
+
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34_Macro extends Twig_Macro
+{
+}
+EOF
+        , $twig);
+
+        $body = new Twig_Node_Text('foo', 0);
+        $extends = 'layout.twig';
+        $blocks = new Twig_Node();
+        $macros = new Twig_Node();
+        $filename = 'foo.twig';
+
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename);
+        $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'));
+
+        $tests[] = array($node, <<<EOF
+<?php
+
+\$this->loadTemplate("layout.twig");
+
+/* foo.twig */
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends __TwigTemplate_d8fb9d03f55738ff78518e1bc2741faf
+{
+    public function display(array \$context)
+    {
+
+        parent::display(\$context);
+    }
+
+    protected function checkSecurity() {
+        \$this->env->getExtension('sandbox')->checkSecurity(
+            array('upper'),
+            array('for')
+        );
+
+        parent::checkSecurity();
+    }
+
+    public function getName()
+    {
+        return "foo.twig";
+    }
+
+}
+
+class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34_Macro extends Twig_Macro
+{
+}
+EOF
+        , $twig);
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/SandboxedPrintTest.php b/test/Twig/Tests/Node/SandboxedPrintTest.php
new file mode 100644 (file)
index 0000000..ee205a6
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_SandboxedPrintTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_SandboxedPrint::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $node = new Twig_Node_Print($expr, 0);
+        $node = new Twig_Node_SandboxedPrint($node);
+
+        $this->assertEquals($expr, $node->expr);
+    }
+
+    /**
+     * @covers Twig_Node_SandboxedPrint::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $node = new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 0), 0);
+        $tests[] = array(new Twig_Node_SandboxedPrint($node), <<<EOF
+if (\$this->env->hasExtension('sandbox') && is_object("foo")) {
+    \$this->env->getExtension('sandbox')->checkMethodAllowed("foo", '__toString');
+}
+echo "foo";
+EOF
+        );
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/SetTest.php b/test/Twig/Tests/Node/SetTest.php
new file mode 100644 (file)
index 0000000..dff9bde
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_SetTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Set::__construct
+     */
+    public function testConstructor()
+    {
+        $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 0)), array(), 0);
+        $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 0)), array(), 0);
+        $node = new Twig_Node_Set(false, $names, $values, 0);
+
+        $this->assertEquals($names, $node->names);
+        $this->assertEquals($values, $node->values);
+        $this->assertEquals(false, $node['capture']);
+    }
+
+    /**
+     * @covers Twig_Node_Set::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 0)), array(), 0);
+        $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 0)), array(), 0);
+        $node = new Twig_Node_Set(false, $names, $values, 0);
+        $tests[] = array($node, '$context[\'foo\'] = "foo";');
+
+        $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 0)), array(), 0);
+        $values = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 0), 0)), array(), 0);
+        $node = new Twig_Node_Set(true, $names, $values, 0);
+        $tests[] = array($node, <<<EOF
+ob_start();
+echo "foo";
+\$context['foo'] = ob_get_clean();
+EOF
+        );
+
+        $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 0), new Twig_Node_Expression_AssignName('bar', 0)), array(), 0);
+        $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 0), new Twig_Node_Expression_Name('bar', 0)), array(), 0);
+        $node = new Twig_Node_Set(false, $names, $values, 0);
+        $tests[] = array($node, <<<EOF
+list(\$context['foo'], \$context['bar']) = array("foo", \$this->getContext(\$context, 'bar'));
+EOF
+        );
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/TestCase.php b/test/Twig/Tests/Node/TestCase.php
new file mode 100644 (file)
index 0000000..c5b974f
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+abstract class Twig_Tests_Node_TestCase extends PHPUnit_Framework_TestCase
+{
+    abstract public function getTests();
+
+    /**
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        $this->assertNodeCompilation($source, $node, $environment);
+    }
+
+    public function assertNodeCompilation($source, Twig_Node $node, Twig_Environment $environment = null)
+    {
+        $compiler = $this->getCompiler($environment);
+        $compiler->compile($node);
+
+        $this->assertEquals($source, trim($compiler->getSource()));
+    }
+
+    protected function getCompiler(Twig_Environment $environment = null)
+    {
+        return new Twig_Compiler(null === $environment ? $this->getEnvironment() : $environment);
+    }
+
+    protected function getEnvironment()
+    {
+        return new Twig_Environment();
+    }
+}
diff --git a/test/Twig/Tests/Node/TextTest.php b/test/Twig/Tests/Node/TextTest.php
new file mode 100644 (file)
index 0000000..8550d6f
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_TextTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Text::__construct
+     */
+    public function testConstructor()
+    {
+        $node = new Twig_Node_Text('foo', 0);
+
+        $this->assertEquals('foo', $node['data']);
+    }
+
+    /**
+     * @covers Twig_Node_Text::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+        $tests[] = array(new Twig_Node_Text('foo', 0), 'echo "foo";');
+
+        return $tests;
+    }
+}
diff --git a/test/Twig/Tests/Node/TransTest.php b/test/Twig/Tests/Node/TransTest.php
new file mode 100644 (file)
index 0000000..3182fd9
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once dirname(__FILE__).'/TestCase.php';
+
+class Twig_Tests_Node_TransTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Trans::__construct
+     */
+    public function testConstructor()
+    {
+        $count = new Twig_Node_Expression_Constant(12, 0);
+        $body = new Twig_Node(array(
+            new Twig_Node_Text('Hello', 0),
+        ), array(), 0);
+        $plural = new Twig_Node(array(
+            new Twig_Node_Text('Hey ', 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('name', 0), 0),
+            new Twig_Node_Text(', I have ', 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('count', 0), 0),
+            new Twig_Node_Text(' apples', 0),
+        ), array(), 0);
+        $node = new Twig_Node_Trans($count, $body, $plural, 0);
+
+        $this->assertEquals($body, $node->body);
+        $this->assertEquals($count, $node->count);
+        $this->assertEquals($plural, $node->plural);
+    }
+
+    /**
+     * @covers Twig_Node_Trans::compile
+     * @covers Twig_Node_Trans::compileString
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+
+        $body = new Twig_Node(array(
+            new Twig_Node_Expression_Constant('Hello', 0),
+        ), array(), 0);
+        $node = new Twig_Node_Trans(null, $body, null, 0);
+
+        try {
+            $node->compile($this->getCompiler());
+            $this->fail();
+        } catch (Exception $e) {
+            $this->assertEquals('Twig_SyntaxError', get_class($e));
+        }
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $body = new Twig_Node(array(
+            new Twig_Node_Text('Hello', 0),
+        ), array(), 0);
+        $node = new Twig_Node_Trans(null, $body, null, 0);
+        $tests[] = array($node, 'echo gettext("Hello");');
+
+        $body = new Twig_Node(array(
+            new Twig_Node_Text('J\'ai ', 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 0), 0),
+            new Twig_Node_Text(' pommes', 0),
+        ), array(), 0);
+        $node = new Twig_Node_Trans(null, $body, null, 0);
+        $tests[] = array($node, 'echo strtr(gettext("J\'ai %foo% pommes"), array("%foo%" => $this->getContext($context, \'foo\'), ));');
+
+        $count = new Twig_Node_Expression_Constant(12, 0);
+        $body = new Twig_Node(array(
+            new Twig_Node_Text('Hey ', 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('name', 0), 0),
+            new Twig_Node_Text(', I have one apple', 0),
+        ), array(), 0);
+        $plural = new Twig_Node(array(
+            new Twig_Node_Text('Hey ', 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('name', 0), 0),
+            new Twig_Node_Text(', I have ', 0),
+            new Twig_Node_Print(new Twig_Node_Expression_Name('count', 0), 0),
+            new Twig_Node_Text(' apples', 0),
+        ), array(), 0);
+        $node = new Twig_Node_Trans($count, $body, $plural, 0);
+        $tests[] = array($node, 'echo strtr(ngettext("Hey %name%, I have one apple", "Hey %name%, I have %count% apples", abs(12)), array("%name%" => $this->getContext($context, \'name\'), "%name%" => $this->getContext($context, \'name\'), "%count%" => abs(12), ));');
+
+        return $tests;
+    }
+}
index 091c9e5..0c9c07b 100644 (file)
 
 class Twig_Tests_IntegrationTest extends PHPUnit_Framework_TestCase
 {
-    static protected $fixturesDir;
-
-    public function setUp()
+    public function getTests()
     {
-        self::$fixturesDir = realpath(dirname(__FILE__).'/../../fixtures/');
-    }
+        $fixturesDir = realpath(dirname(__FILE__).'/../../fixtures/');
+        $tests = array();
 
-    public function testIntegration()
-    {
-        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(self::$fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
+        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
             if (!preg_match('/\.test$/', $file)) {
                 continue;
             }
@@ -28,7 +24,7 @@ class Twig_Tests_IntegrationTest extends PHPUnit_Framework_TestCase
             $test = file_get_contents($file->getRealpath());
 
             if (!preg_match('/--TEST--\s*(.*?)\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) {
-                throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace(self::$fixturesDir.'/', '', $file)));
+                throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file)));
             }
 
             $message = $match[1];
@@ -38,34 +34,44 @@ class Twig_Tests_IntegrationTest extends PHPUnit_Framework_TestCase
                 $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2];
             }
 
-            $loader = new Twig_Loader_Array($templates);
-            $twig = new Twig_Environment($loader, array('trim_blocks' => true, 'cache' => false));
-            $twig->addExtension(new Twig_Extension_Escaper());
-            $twig->addExtension(new TestExtension());
+            $tests[] = array(str_replace($fixturesDir.'/', '', $file), $test, $message, $templates);
+        }
 
-            try {
-                $template = $twig->loadTemplate('index.twig');
-            } catch (Twig_SyntaxError $e) {
-                $e->setFilename(str_replace(self::$fixturesDir.'/', '', $file));
+        return $tests;
+    }
 
-                throw $e;
-            } catch (Exception $e) {
-                throw new Twig_Error($e->getMessage().' (in '.str_replace(self::$fixturesDir, '', $file).')');
-            }
+    /**
+     * @dataProvider getTests
+     */
+    public function testIntegration($file, $test, $message, $templates)
+    {
+        $loader = new Twig_Loader_Array($templates);
+        $twig = new Twig_Environment($loader, array('trim_blocks' => true, 'cache' => false));
+        $twig->addExtension(new Twig_Extension_Escaper());
+        $twig->addExtension(new TestExtension());
+
+        try {
+            $template = $twig->loadTemplate('index.twig');
+        } catch (Twig_SyntaxError $e) {
+            $e->setFilename($file);
+
+            throw $e;
+        } catch (Exception $e) {
+            throw new Twig_Error($e->getMessage().' (in '.$file.')');
+        }
 
-            preg_match_all('/--DATA--(.*?)--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $matches, PREG_SET_ORDER);
-            foreach ($matches as $match) {
-                $output = trim($template->render(eval($match[1].';')), "\n ");
-                $expected = trim($match[2], "\n ");
+        preg_match_all('/--DATA--(.*?)--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $matches, PREG_SET_ORDER);
+        foreach ($matches as $match) {
+            $output = trim($template->render(eval($match[1].';')), "\n ");
+            $expected = trim($match[2], "\n ");
 
-                $this->assertEquals($expected, $output, $message.' (in '.str_replace(self::$fixturesDir, '', $file).')');
-                if ($output != $expected)  {
-                    echo 'Compiled template that failed:';
+            $this->assertEquals($expected, $output, $message.' (in '.$file.')');
+            if ($output != $expected)  {
+                echo 'Compiled template that failed:';
 
-                    foreach (array_keys($templates) as $name)  {
-                        $source = $loader->getSource($name);
-                        echo $twig->compile($twig->parse($twig->tokenize($source, $name)));
-                    }
+                foreach (array_keys($templates) as $name)  {
+                    $source = $loader->getSource($name);
+                    echo $twig->compile($twig->parse($twig->tokenize($source, $name)));
                 }
             }
         }
index c7a5790..75512ef 100644 (file)
@@ -1,7 +1,7 @@
 --TEST--
 "filter" tags accept multiple chained filters
 --TEMPLATE--
-{% filter lower|capitalize %}
+{% filter lower|title %}
   {{ var }}
 {% endfilter %}
 --DATA--
index d26bf6e..7e4e4eb 100644 (file)
@@ -1,7 +1,7 @@
 --TEST--
 "filter" tags can be nested at will
 --TEMPLATE--
-{% filter capitalize %}
+{% filter lower|title %}
   {{ var }}
   {% filter upper %}
     {{ var }}
@@ -12,5 +12,5 @@
 return array('var' => 'var')
 --EXPECT--
   Var
-      VAR
+      Var
     Var