loops and `tests` tests pass now
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 18 Apr 2008 09:30:37 +0000 (11:30 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 18 Apr 2008 09:30:37 +0000 (11:30 +0200)
--HG--
branch : trunk

jinja2/compiler.py
jinja2/parser.py
jinja2/runtime.py
tests/test_forloop.py

index b7aae4608b10b46bb3cfee29634b61999bbaf236..871728f48d1a82b2b0d844ed111c60ed29c2f575 100644 (file)
@@ -76,6 +76,9 @@ class Identifiers(object):
         # frames or because they are special for the frame)
         self.declared = set()
 
+        # undeclared variables from outer scopes
+        self.outer_undeclared = set()
+
         # names that are accessed without being explicitly declared by
         # this one or any of the outer scopes.  Names can appear both in
         # declared and undeclared.
@@ -106,7 +109,8 @@ class Identifiers(object):
 
     def find_shadowed(self):
         """Find all the shadowed names."""
-        return self.declared & (self.declared_locally | self.declared_parameter)
+        return (self.declared | self.outer_undeclared) & \
+               (self.declared_locally | self.declared_parameter)
 
 
 class Frame(object):
@@ -145,6 +149,10 @@ class Frame(object):
                 parent.identifiers.declared_locally |
                 parent.identifiers.declared_parameter
             )
+            self.identifiers.outer_undeclared.update(
+                parent.identifiers.undeclared -
+                self.identifiers.declared
+            )
             self.buffer = parent.buffer
             self.name_overrides = parent.name_overrides.copy()
 
index 84a317c66de7bc11a8ee10f52b34d04595d191ca..8b82a4d077752cd726816917dcc7164b1bbf2213 100644 (file)
@@ -626,6 +626,8 @@ class Parser(object):
         else:
             negated = False
         name = self.stream.expect('name').value
+        dyn_args = dyn_kwargs = None
+        kwargs = []
         if self.stream.current.type is 'lparen':
             args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
         elif self.stream.current.type in ('name', 'string', 'integer',
@@ -634,8 +636,6 @@ class Parser(object):
             args = [self.parse_expression()]
         else:
             args = []
-            kwargs = []
-            dyn_args = dyn_kwargs = None
         node = nodes.Test(node, name, args, kwargs, dyn_args,
                           dyn_kwargs, lineno=token.lineno)
         if negated:
index 1f1d7bd7bb68c41cf6830277f3fe4ad916878dc0..e1bf486d097b110f42106067e4ab1e048ada1e37 100644 (file)
@@ -142,8 +142,8 @@ class LoopContextBase(object):
     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)
+    revindex = property(lambda x: x.length - x.index0)
+    revindex0 = property(lambda x: x.length - x.index)
 
     def __len__(self):
         return self.length
@@ -191,7 +191,8 @@ class StaticLoopContext(LoopContextBase):
     """The static loop context is used in the optimizer to "freeze" the
     status of an iteration.  The only reason for this object is if the
     loop object is accessed in a non static way (eg: becomes part of a
-    function call)."""
+    function call).
+    """
 
     def __init__(self, index0, length):
         self.index0 = index0
index ed1cc2ed0820b9a6698e68b6dad4ec0ea7933c6a..dd254945bd6090c52d05b1d8877b7f2f745acb05 100644 (file)
@@ -6,6 +6,8 @@
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+from py.test import raises
+
 
 SIMPLE = '''{% for item in seq %}{{ item }}{% endfor %}'''
 ELSE = '''{% for item in seq %}XXX{% else %}...{% endfor %}'''
@@ -13,9 +15,9 @@ EMPTYBLOCKS = '''<{% for item in seq %}{% else %}{% endfor %}>'''
 CONTEXTVARS = '''{% for item in seq %}\
 {{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
    loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
-   loop.even }}|{{ loop.odd }}|{{ loop.length }}###{% endfor %}'''
-CYCLING = '''{% for item in seq %}{% cycle '<1>', '<2>' %}{% endfor %}\
-{% for item in seq %}{% cycle through %}{% endfor %}'''
+   loop.length }}###{% endfor %}'''
+CYCLING = '''{% for item in seq %}{{ loop.cycle('<1>', '<2>') }}{% endfor %}\
+{% for item in seq %}{{ loop.cycle(*through) }}{% endfor %}'''
 SCOPE = '''{% for item in seq %}{% endfor %}{{ item }}'''
 VARLEN = '''{% for item in iter %}{{ item }}{% endfor %}'''
 NONITER = '''{% for item in none %}...{% endfor %}'''
@@ -40,9 +42,9 @@ def test_context_vars(env):
     tmpl = env.from_string(CONTEXTVARS)
     one, two, _ = tmpl.render(seq=[0, 1]).split('###')
     (one_index, one_index0, one_revindex, one_revindex0, one_first,
-     one_last, one_even, one_odd, one_length) = one.split('|')
+     one_last, one_length) = one.split('|')
     (two_index, two_index0, two_revindex, two_revindex0, two_first,
-     two_last, two_even, two_odd, two_length) = two.split('|')
+     two_last, two_length) = two.split('|')
 
     assert int(one_index) == 1 and int(two_index) == 2
     assert int(one_index0) == 0 and int(two_index0) == 1
@@ -50,8 +52,6 @@ def test_context_vars(env):
     assert int(one_revindex0) == 1 and int(two_revindex0) == 0
     assert one_first == 'True' and two_first == 'False'
     assert one_last == 'False' and two_last == 'True'
-    assert one_even == 'False' and two_even == 'True'
-    assert one_odd == 'True' and two_odd == 'False'
     assert one_length == two_length == '2'
 
 
@@ -78,4 +78,4 @@ def test_varlen(env):
 
 def test_noniter(env):
     tmpl = env.from_string(NONITER)
-    assert not tmpl.render()
+    raises(TypeError, tmpl.render)