merged

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 02fc1e8..4f5ff0b 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -315,8 +315,10 @@
             self.writeline('l_loop = None')
         self.write('for ')
         self.visit(node.target, loop_frame)
-        self.write(extended_loop and ', l_loop in looper(' or ' in ')
+        self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
         self.visit(node.iter, loop_frame)
+        if 'loop' in aliases:
+            self.write(', ' + aliases['loop'])
         self.write(extended_loop and '):' or ':')
         self.blockvisit(node.body, loop_frame)
 
diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py
index 391ddbd..041b5f5 100644
--- a/jinja2/optimizer.py
+++ b/jinja2/optimizer.py
@@ -22,7 +22,7 @@
 from copy import deepcopy
 from jinja2 import nodes
 from jinja2.visitor import NodeVisitor, NodeTransformer
-from jinja2.runtime import subscribe
+from jinja2.runtime import subscribe, LoopContext
 
 
 class ContextStack(object):
@@ -39,6 +39,12 @@
     def pop(self):
         self.stack.pop()
 
+    def get(self, key, default=None):
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
     def __getitem__(self, key):
         for level in reversed(self.stack):
             if key in level:
@@ -58,6 +64,9 @@
     def __init__(self, environment):
         self.environment = environment
 
+    def visit_Block(self, node, context):
+        return self.generic_visit(node, context.blank())
+
     def visit_Filter(self, node, context):
         """Try to evaluate filters if possible."""
         # XXX: nonconstant arguments?  not-called visitors?  generic visit!
@@ -77,18 +86,44 @@
             iterable = iter(self.visit(node.iter, context).as_const())
         except (nodes.Impossible, TypeError):
             return self.generic_visit(node, context)
+
+        parent = context.get('loop')
         context.push()
         result = []
-        # XXX: tuple unpacking (for key, value in foo)
-        target = node.target.name
         iterated = False
-        for item in iterable:
-            context[target] = item
-            result.extend(self.visit(n, context) for n in deepcopy(node.body))
-            iterated = True
-        if not iterated and node.else_:
-            result.extend(self.visit(n, context) for n in deepcopy(node.else_))
-        context.pop()
+
+        def assign(target, value):
+            if isinstance(target, nodes.Name):
+                context[target.name] = value
+            elif isinstance(target, nodes.Tuple):
+                try:
+                    value = tuple(value)
+                except TypeError:
+                    raise nodes.Impossible()
+                if len(target.items) != len(value):
+                    raise nodes.Impossible()
+                for name, val in zip(target.items, value):
+                    assign(name, val)
+            else:
+                raise AssertionError('unexpected assignable node')
+
+        # XXX: not covered cases:
+        #       - item is accessed by dynamic part in the iteration
+        try:
+            try:
+                for loop, item in LoopContext(iterable, parent):
+                    context['loop'] = loop
+                    assign(node.target, item)
+                    result.extend(self.visit(n, context)
+                                  for n in deepcopy(node.body))
+                    iterated = True
+                if not iterated and node.else_:
+                    result.extend(self.visit(n, context)
+                                  for n in deepcopy(node.else_))
+            except nodes.Impossible:
+                return node
+        finally:
+            context.pop()
         return result
 
     def visit_If(self, node, context):
@@ -127,9 +162,9 @@
                     value = tuple(value)
                 except TypeError:
                     raise nodes.Impossible()
-                if len(target) != len(value):
+                if len(target.items) != len(value):
                     raise nodes.Impossible()
-                for name, val in zip(target, value):
+                for name, val in zip(target.items, value):
                     walk(name, val)
             else:
                 raise AssertionError('unexpected assignable node')
@@ -140,6 +175,17 @@
             return node
         return result
 
+    def fold(self, node, context):
+        """Do constant folding."""
+        node = self.generic_visit(node, context)
+        try:
+            return nodes.Const(node.as_const(), lineno=node.lineno)
+        except nodes.Impossible:
+            return node
+    visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
+    visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
+    visit_Not = visit_Compare = fold
+
     def visit_Subscript(self, node, context):
         if node.ctx == 'load':
             try:
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 5a9764e..a79fac3 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -14,7 +14,7 @@
     defaultdict = None
 
 
-__all__ = ['extends', 'subscribe', 'TemplateContext', 'Macro']
+__all__ = ['extends', 'subscribe', 'LoopContext', 'TemplateContext', 'Macro']
 
 
 def extends(template, namespace):
@@ -74,6 +74,36 @@
             return self.undefined_factory(key)
 
 
+class LoopContext(object):
+    """Helper for extended iteration."""
+
+    def __init__(self, iterable, parent=None):
+        self._iterable = iterable
+        self.index0 = 0
+        self.parent = parent
+
+    def __iter__(self):
+        for item in self._iterable:
+            yield self, item
+            self.index0 += 1
+
+    first = property(lambda x: x.index0 == 0)
+    last = property(lambda x: x.revindex0 == 0)
+    index = property(lambda x: x.index0 + 1)
+    revindex = property(lambda x: x.length)
+    revindex0 = property(lambda x: x.length - 1)
+
+    @property
+    def length(self):
+        if not hasattr(self, '_length'):
+            try:
+                length = len(self._iterable)
+            except TypeError:
+                length = len(tuple(self._iterable))
+            self._length = length
+        return self._length
+
+
 class Macro(object):
     """
     Wraps a macor
diff --git a/test_optimizer.py b/test_optimizer.py
index ddb0fa0..ce0cb75 100644
--- a/test_optimizer.py
+++ b/test_optimizer.py
@@ -17,10 +17,10 @@
         {{ readstatus(forum.id) }} {{ forum.id|e }} {{ forum.name|e }}
     {% endfor %}
 
-    {% navigation = [('#foo', 'Foo'), ('#bar', 'Bar')] %}
+    {% navigation = [('#foo', 'Foo'), ('#bar', 'Bar'), ('#baz', 42 * 2 + 23)] %}
     <ul>
-    {% for item in navigation %}
-        <li><a href="{{ item[0] }}">{{ item[1] }}</a></li>
+    {% for key, value in navigation %}
+        <li>{{ loop.index }}: <a href="{{ key|e }}">{{ value|e }}</a></li>
     {% endfor %}
     </ul>
 """)