made a huge optimization for loops (again)
authorfabien <fabien@93ef8e89-cb99-4229-a87c-7fa0fa45744b>
Sat, 7 Nov 2009 22:18:56 +0000 (22:18 +0000)
committerfabien <fabien@93ef8e89-cb99-4229-a87c-7fa0fa45744b>
Sat, 7 Nov 2009 22:18:56 +0000 (22:18 +0000)
git-svn-id: http://svn.twig-project.org/trunk@118 93ef8e89-cb99-4229-a87c-7fa0fa45744b

doc/02-Twig-for-Template-Designers.markdown
lib/Twig/Extension/Core.php
lib/Twig/Node/For.php
lib/Twig/runtime.php
lib/Twig/runtime_for.php [deleted file]

index 544f8d4..014a2e5 100644 (file)
@@ -390,7 +390,11 @@ provided in a variable called `users`:
       {% endfor %}
     </ul>
 
-Inside of a for loop block you can access some special variables:
+>**NOTE**
+>A sequence can be either an array or an object implementing the `Iterator`
+>interface.
+
+Inside of a `for` loop block you can access some special variables:
 
 | Variable              | Description
 | --------------------- | -------------------------------------------------------------
@@ -402,7 +406,8 @@ Inside of a for loop block you can access some special variables:
 | `loop.last`           | True if last iteration
 | `loop.length`         | The number of items in the sequence
 
-Unlike in PHP it's not possible to `break` or `continue` in a loop.
+>**NOTE**
+>Unlike in PHP it's not possible to `break` or `continue` in a loop.
 
 If no iteration took place because the sequence was empty, you can render a
 replacement block by using `else`:
@@ -412,10 +417,35 @@ replacement block by using `else`:
       {% for user in users %}
         <li>{{ user.username|e }}</li>
       {% else %}
-        <li><em>no users found</em></li>
+        <li><em>no user found</em></li>
       {% endif %}
     </ul>
 
+By default, a loop iterates over the values of the sequence. You can iterate
+on keys by using the `keys` filter:
+
+    [twig]
+    <h1>Members</h1>
+    <ul>
+      {% for key in users|keys %}
+        <li>{{ key }}</li>
+      {% endfor %}
+    </ul>
+
+You can also access both keys and values:
+
+    [twig]
+    <h1>Members</h1>
+    <ul>
+      {% for key, value in users %}
+        <li>{{ key }}: {{ user.username|e }}</li>
+      {% endfor %}
+    </ul>
+
+>**NOTE**
+>On Twig before 0.9.3, you need to use the `items` filter to access both the
+>keys and values (`{% for key, value in users|items %}`).
+
 ### If
 
 The `if` statement in Twig is comparable with the if statements of PHP. In the
@@ -801,16 +831,6 @@ iterate over the keys of an array:
         ...
     {% endfor %}
 
-### `items`
-
-The `items` filter is mainly useful when using the `for` tag to iterate over
-both the keys and the values of an array:
-
-    [twig]
-    {% for key, value in array|items %}
-        ...
-    {% endfor %}
-
 ### `escape`, `e`
 
 The `escape` filter converts the characters `&`, `<`, `>`, `'`, and `"` in
index b0b625a..e93e08f 100644 (file)
@@ -18,7 +18,6 @@ class Twig_Extension_Core extends Twig_Extension
   public function initRuntime()
   {
     require_once dirname(__FILE__).'/../runtime.php';
-    require_once dirname(__FILE__).'/../runtime_for.php';
   }
 
   /**
index 184e470..fb3f127 100644 (file)
@@ -58,13 +58,38 @@ 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());
+    }
+
     $var = rand(1, 999999);
     $compiler
       ->write("\$seq$var = twig_iterator_to_array(")
       ->subcompile($this->seq)
       ->raw(");\n")
       ->write("\$context['loop']['length'] = count(\$seq$var);\n")
-      ->write("for (\$i$var = 0; \$i$var < \$context['loop']['length']; \$i$var++)\n")
+
+      ->write("\$context['loop'] = array(\n")
+      ->write("  'parent'    => \$context['_parent'],\n")
+      ->write("  'length'    => \$context['loop']['length'],\n")
+      ->write("  'index0'    => 0,\n")
+      ->write("  'index'     => 1,\n")
+      ->write("  'revindex0' => \$context['loop']['length'] - 1,\n")
+      ->write("  'revindex'  => \$context['loop']['length'],\n")
+      ->write("  'first'     => true,\n")
+      ->write("  'last'      => false,\n")
+      ->write(");\n")
+
+      ->write("foreach (\$seq$var as \$context[")
+      ->repr($loopVars[0])
+      ->raw("] => \$context[")
+      ->repr($loopVars[1])
+      ->raw("])\n")
       ->write("{\n")
       ->indent()
     ;
@@ -74,29 +99,16 @@ class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
       $compiler->write("\$context['_iterated'] = true;\n");
     }
 
-    $compiler->write("twig_set_loop_context(\$context, \$seq$var, \$i$var, ");
-
-    if ($this->isMultitarget)
-    {
-      $compiler->raw('array(');
-      foreach ($this->item as $idx => $node)
-      {
-        if ($idx)
-        {
-          $compiler->raw(', ');
-        }
-        $compiler->repr($node->getName());
-      }
-      $compiler->raw(')');
-    }
-    else
-    {
-      $compiler->repr($this->item->getName());
-    }
-
     $compiler
-      ->raw(");\n")
       ->subcompile($this->body)
+
+      ->write("++\$context['loop']['index0'];\n")
+      ->write("++\$context['loop']['index'];\n")
+      ->write("--\$context['loop']['revindex0'];\n")
+      ->write("--\$context['loop']['revindex'];\n")
+      ->write("\$context['loop']['first'] = false;\n")
+      ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n")
+
       ->outdent()
       ->write("}\n")
     ;
index c41f60f..6bb32cc 100644 (file)
@@ -188,3 +188,26 @@ else
     return ucfirst(strtolower($string));
   }
 }
+
+function twig_iterator_to_array($seq)
+{
+  if (is_array($seq))
+  {
+    return $seq;
+  }
+  elseif (is_object($seq) && $seq instanceof Iterator)
+  {
+    return iterator_to_array($seq);
+  }
+  else
+  {
+    return array();
+  }
+}
+
+// only for backward compatibility
+function twig_get_array_items_filter($array)
+{
+  // noop
+  return $array;
+}
diff --git a/lib/Twig/runtime_for.php b/lib/Twig/runtime_for.php
deleted file mode 100644 (file)
index c526153..0000000
+++ /dev/null
@@ -1,66 +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.
- */
-
-function twig_iterator_to_array($seq)
-{
-  if (is_array($seq))
-  {
-    return array_values($seq);
-  }
-  elseif (is_object($seq) && $seq instanceof Iterator)
-  {
-    return iterator_to_array($seq, false);
-  }
-  else
-  {
-    return array();
-  }
-}
-
-function twig_set_loop_context(&$context, $seq, $idx, $target)
-{
-  if (is_array($target))
-  {
-    $context[$target[0]] = $seq[$idx][0];
-    $context[$target[1]] = $seq[$idx][1];
-  }
-  else
-  {
-    $context[$target] = $seq[$idx];
-  }
-
-  $context['loop'] = array(
-    'parent'    => $context['_parent'],
-    'length'    => $context['loop']['length'],
-    'index0'    => $idx,
-    'index'     => $idx + 1,
-    'revindex0' => $context['loop']['length'] - $idx - 1,
-    'revindex'  => $context['loop']['length'] - $idx,
-    'first'     => $idx == 0,
-    'last'      => $idx + 1 == $context['loop']['length'],
-  );
-}
-
-function twig_get_array_items_filter($array)
-{
-  if (!is_array($array) && (!is_object($array) || !$array instanceof Iterator))
-  {
-    return false;
-  }
-
-  $result = array();
-  foreach ($array as $key => $value)
-  {
-    $result[] = array($key, $value);
-  }
-
-  return $result;
-}