With number of course. Jinja2.pdf not Jinja.pdf
[jinja2.git] / jinja2 / parser.py
index d365d4c0d18acdd8298a76fb5e59f40656e1c795..d44229ad0f09a562bbf67de4a5daf5d5730456ab 100644 (file)
@@ -5,13 +5,16 @@
 
     Implements the template parser.
 
-    :copyright: 2008 by Armin Ronacher.
+    :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
 from jinja2 import nodes
 from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
+from jinja2.utils import next
+from jinja2.lexer import describe_token, describe_token_expr
 
 
+#: statements that callinto 
 _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
                                  'macro', 'include', 'from', 'import',
                                  'set'])
@@ -23,17 +26,20 @@ class Parser(object):
     extensions and can be used to parse expressions or statements.
     """
 
-    def __init__(self, environment, source, name=None, filename=None):
+    def __init__(self, environment, source, name=None, filename=None,
+                 state=None):
         self.environment = environment
-        self.stream = environment._tokenize(source, name, filename)
+        self.stream = environment._tokenize(source, name, filename, state)
         self.name = name
         self.filename = filename
         self.closed = False
         self.extensions = {}
-        for extension in environment.extensions.itervalues():
+        for extension in environment.iter_extensions():
             for tag in extension.tags:
                 self.extensions[tag] = extension.parse
         self._last_identifier = 0
+        self._tag_stack = []
+        self._end_token_stack = []
 
     def fail(self, msg, lineno=None, exc=TemplateSyntaxError):
         """Convenience method that raises `exc` with the message, passed
@@ -44,6 +50,51 @@ class Parser(object):
             lineno = self.stream.current.lineno
         raise exc(msg, lineno, self.name, self.filename)
 
+    def _fail_ut_eof(self, name, end_token_stack, lineno):
+        expected = []
+        for exprs in end_token_stack:
+            expected.extend(map(describe_token_expr, exprs))
+        if end_token_stack:
+            currently_looking = ' or '.join(
+                "'%s'" % describe_token_expr(expr)
+                for expr in end_token_stack[-1])
+        else:
+            currently_looking = None
+
+        if name is None:
+            message = ['Unexpected end of template.']
+        else:
+            message = ['Encountered unknown tag \'%s\'.' % name]
+
+        if currently_looking:
+            if name is not None and name in expected:
+                message.append('You probably made a nesting mistake. Jinja '
+                               'is expecting this tag, but currently looking '
+                               'for %s.' % currently_looking)
+            else:
+                message.append('Jinja was looking for the following tags: '
+                               '%s.' % currently_looking)
+
+        if self._tag_stack:
+            message.append('The innermost block that needs to be '
+                           'closed is \'%s\'.' % self._tag_stack[-1])
+
+        self.fail(' '.join(message), lineno)
+
+    def fail_unknown_tag(self, name, lineno=None):
+        """Called if the parser encounters an unknown tag.  Tries to fail
+        with a human readable error message that could help to identify
+        the problem.
+        """
+        return self._fail_ut_eof(name, self._end_token_stack, lineno)
+
+    def fail_eof(self, end_tokens=None, lineno=None):
+        """Like fail_unknown_tag but for end of template situations."""
+        stack = list(self._end_token_stack)
+        if end_tokens is not None:
+            stack.append(end_tokens)
+        return self._fail_ut_eof(None, stack, lineno)
+
     def is_tuple_end(self, extra_end_rules=None):
         """Are we at the end of a tuple?"""
         if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
@@ -62,18 +113,30 @@ class Parser(object):
     def parse_statement(self):
         """Parse a single statement."""
         token = self.stream.current
-        if token.type is not 'name':
+        if token.type != 'name':
             self.fail('tag name expected', token.lineno)
-        if token.value in _statement_keywords:
-            return getattr(self, 'parse_' + self.stream.current.value)()
-        if token.value == 'call':
-            return self.parse_call_block()
-        if token.value == 'filter':
-            return self.parse_filter_block()
-        ext = self.extensions.get(token.value)
-        if ext is not None:
-            return ext(self)
-        self.fail('unknown tag %r' % token.value, token.lineno)
+        self._tag_stack.append(token.value)
+        pop_tag = True
+        try:
+            if token.value in _statement_keywords:
+                return getattr(self, 'parse_' + self.stream.current.value)()
+            if token.value == 'call':
+                return self.parse_call_block()
+            if token.value == 'filter':
+                return self.parse_filter_block()
+            ext = self.extensions.get(token.value)
+            if ext is not None:
+                return ext(self)
+
+            # did not work out, remove the token we pushed by accident
+            # from the stack so that the unknown tag fail function can
+            # produce a proper error message.
+            self._tag_stack.pop()
+            pop_tag = False
+            self.fail_unknown_tag(token.value, token.lineno)
+        finally:
+            if pop_tag:
+                self._tag_stack.pop()
 
     def parse_statements(self, end_tokens, drop_needle=False):
         """Parse multiple statements into a list until one of the end tokens
@@ -93,13 +156,18 @@ class Parser(object):
         self.stream.expect('block_end')
         result = self.subparse(end_tokens)
 
+        # we reached the end of the template too early, the subparser
+        # does not check for this, so we do that now
+        if self.stream.current.type == 'eof':
+            self.fail_eof(end_tokens)
+
         if drop_needle:
-            self.stream.next()
+            next(self.stream)
         return result
 
     def parse_set(self):
         """Parse an assign statement."""
-        lineno = self.stream.next().lineno
+        lineno = next(self.stream).lineno
         target = self.parse_assign_target()
         self.stream.expect('assign')
         expr = self.parse_tuple()
@@ -117,7 +185,7 @@ class Parser(object):
             test = self.parse_expression()
         recursive = self.stream.skip_if('name:recursive')
         body = self.parse_statements(('name:endfor', 'name:else'))
-        if self.stream.next().value == 'endfor':
+        if next(self.stream).value == 'endfor':
             else_ = []
         else:
             else_ = self.parse_statements(('name:endfor',), drop_needle=True)
@@ -131,7 +199,7 @@ class Parser(object):
             node.test = self.parse_tuple(with_condexpr=False)
             node.body = self.parse_statements(('name:elif', 'name:else',
                                                'name:endif'))
-            token = self.stream.next()
+            token = next(self.stream)
             if token.test('name:elif'):
                 new_node = nodes.If(lineno=self.stream.current.lineno)
                 node.else_ = [new_node]
@@ -146,40 +214,56 @@ class Parser(object):
         return result
 
     def parse_block(self):
-        node = nodes.Block(lineno=self.stream.next().lineno)
+        node = nodes.Block(lineno=next(self.stream).lineno)
         node.name = self.stream.expect('name').value
+        node.scoped = self.stream.skip_if('name:scoped')
+
+        # common problem people encounter when switching from django
+        # to jinja.  we do not support hyphens in block names, so let's
+        # raise a nicer error message in that case.
+        if self.stream.current.type == 'sub':
+            self.fail('Block names in Jinja have to be valid Python '
+                      'identifiers and may not contain hypens, use an '
+                      'underscore instead.')
+
         node.body = self.parse_statements(('name:endblock',), drop_needle=True)
         self.stream.skip_if('name:' + node.name)
         return node
 
     def parse_extends(self):
-        node = nodes.Extends(lineno=self.stream.next().lineno)
+        node = nodes.Extends(lineno=next(self.stream).lineno)
         node.template = self.parse_expression()
         return node
 
     def parse_import_context(self, node, default):
         if self.stream.current.test_any('name:with', 'name:without') and \
            self.stream.look().test('name:context'):
-            node.with_context = self.stream.next().value == 'with'
+            node.with_context = next(self.stream).value == 'with'
             self.stream.skip()
         else:
             node.with_context = default
         return node
 
     def parse_include(self):
-        node = nodes.Include(lineno=self.stream.next().lineno)
+        node = nodes.Include(lineno=next(self.stream).lineno)
         node.template = self.parse_expression()
+        if self.stream.current.test('name:ignore') and \
+           self.stream.look().test('name:missing'):
+            node.ignore_missing = True
+            self.stream.skip(2)
+        else:
+            node.ignore_missing = False
         return self.parse_import_context(node, True)
 
     def parse_import(self):
-        node = nodes.Import(lineno=self.stream.next().lineno)
+        node = nodes.Import(lineno=next(self.stream).lineno)
         node.template = self.parse_expression()
         self.stream.expect('name:as')
         node.target = self.parse_assign_target(name_only=True).name
         return self.parse_import_context(node, False)
 
     def parse_from(self):
-        node = nodes.FromImport(lineno=self.stream.next().lineno)
+        node = nodes.FromImport(lineno=next(self.stream).lineno)
         node.template = self.parse_expression()
         self.stream.expect('name:import')
         node.names = []
@@ -187,7 +271,7 @@ class Parser(object):
         def parse_context():
             if self.stream.current.value in ('with', 'without') and \
                self.stream.look().test('name:context'):
-                node.with_context = self.stream.next().value == 'with'
+                node.with_context = next(self.stream).value == 'with'
                 self.stream.skip()
                 return True
             return False
@@ -195,7 +279,7 @@ class Parser(object):
         while 1:
             if node.names:
                 self.stream.expect('comma')
-            if self.stream.current.type is 'name':
+            if self.stream.current.type == 'name':
                 if parse_context():
                     break
                 target = self.parse_assign_target(name_only=True)
@@ -208,7 +292,7 @@ class Parser(object):
                     node.names.append((target.name, alias.name))
                 else:
                     node.names.append(target.name)
-                if parse_context() or self.stream.current.type is not 'comma':
+                if parse_context() or self.stream.current.type != 'comma':
                     break
             else:
                 break
@@ -221,7 +305,7 @@ class Parser(object):
         node.args = args = []
         node.defaults = defaults = []
         self.stream.expect('lparen')
-        while self.stream.current.type is not 'rparen':
+        while self.stream.current.type != 'rparen':
             if args:
                 self.stream.expect('comma')
             arg = self.parse_assign_target(name_only=True)
@@ -232,8 +316,8 @@ class Parser(object):
         self.stream.expect('rparen')
 
     def parse_call_block(self):
-        node = nodes.CallBlock(lineno=self.stream.next().lineno)
-        if self.stream.current.type is 'lparen':
+        node = nodes.CallBlock(lineno=next(self.stream).lineno)
+        if self.stream.current.type == 'lparen':
             self.parse_signature(node)
         else:
             node.args = []
@@ -246,14 +330,14 @@ class Parser(object):
         return node
 
     def parse_filter_block(self):
-        node = nodes.FilterBlock(lineno=self.stream.next().lineno)
+        node = nodes.FilterBlock(lineno=next(self.stream).lineno)
         node.filter = self.parse_filter(None, start_inline=True)
         node.body = self.parse_statements(('name:endfilter',),
                                           drop_needle=True)
         return node
 
     def parse_macro(self):
-        node = nodes.Macro(lineno=self.stream.next().lineno)
+        node = nodes.Macro(lineno=next(self.stream).lineno)
         node.name = self.parse_assign_target(name_only=True).name
         self.parse_signature(node)
         node.body = self.parse_statements(('name:endmacro',),
@@ -261,9 +345,9 @@ class Parser(object):
         return node
 
     def parse_print(self):
-        node = nodes.Output(lineno=self.stream.next().lineno)
+        node = nodes.Output(lineno=next(self.stream).lineno)
         node.nodes = []
-        while self.stream.current.type is not 'block_end':
+        while self.stream.current.type != 'block_end':
             if node.nodes:
                 self.stream.expect('comma')
             node.nodes.append(self.parse_expression())
@@ -286,7 +370,7 @@ class Parser(object):
                 target = self.parse_tuple(simplified=True,
                                           extra_end_rules=extra_end_rules)
             else:
-                target = self.parse_primary(with_postfix=False)
+                target = self.parse_primary()
             target.set_ctx('store')
         if not target.can_assign():
             self.fail('can\'t assign to %r' % target.__class__.
@@ -326,13 +410,19 @@ class Parser(object):
 
     def parse_and(self):
         lineno = self.stream.current.lineno
-        left = self.parse_compare()
+        left = self.parse_not()
         while self.stream.skip_if('name:and'):
-            right = self.parse_compare()
+            right = self.parse_not()
             left = nodes.And(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
         return left
 
+    def parse_not(self):
+        if self.stream.current.test('name:not'):
+            lineno = next(self.stream).lineno
+            return nodes.Not(self.parse_not(), lineno=lineno)
+        return self.parse_compare()
+
     def parse_compare(self):
         lineno = self.stream.current.lineno
         expr = self.parse_add()
@@ -340,7 +430,7 @@ class Parser(object):
         while 1:
             token_type = self.stream.current.type
             if token_type in _compare_operators:
-                self.stream.next()
+                next(self.stream)
                 ops.append(nodes.Operand(token_type, self.parse_add()))
             elif self.stream.skip_if('name:in'):
                 ops.append(nodes.Operand('in', self.parse_add()))
@@ -358,8 +448,8 @@ class Parser(object):
     def parse_add(self):
         lineno = self.stream.current.lineno
         left = self.parse_sub()
-        while self.stream.current.type is 'add':
-            self.stream.next()
+        while self.stream.current.type == 'add':
+            next(self.stream)
             right = self.parse_sub()
             left = nodes.Add(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -368,8 +458,8 @@ class Parser(object):
     def parse_sub(self):
         lineno = self.stream.current.lineno
         left = self.parse_concat()
-        while self.stream.current.type is 'sub':
-            self.stream.next()
+        while self.stream.current.type == 'sub':
+            next(self.stream)
             right = self.parse_concat()
             left = nodes.Sub(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -378,8 +468,8 @@ class Parser(object):
     def parse_concat(self):
         lineno = self.stream.current.lineno
         args = [self.parse_mul()]
-        while self.stream.current.type is 'tilde':
-            self.stream.next()
+        while self.stream.current.type == 'tilde':
+            next(self.stream)
             args.append(self.parse_mul())
         if len(args) == 1:
             return args[0]
@@ -388,8 +478,8 @@ class Parser(object):
     def parse_mul(self):
         lineno = self.stream.current.lineno
         left = self.parse_div()
-        while self.stream.current.type is 'mul':
-            self.stream.next()
+        while self.stream.current.type == 'mul':
+            next(self.stream)
             right = self.parse_div()
             left = nodes.Mul(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -398,8 +488,8 @@ class Parser(object):
     def parse_div(self):
         lineno = self.stream.current.lineno
         left = self.parse_floordiv()
-        while self.stream.current.type is 'div':
-            self.stream.next()
+        while self.stream.current.type == 'div':
+            next(self.stream)
             right = self.parse_floordiv()
             left = nodes.Div(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -408,8 +498,8 @@ class Parser(object):
     def parse_floordiv(self):
         lineno = self.stream.current.lineno
         left = self.parse_mod()
-        while self.stream.current.type is 'floordiv':
-            self.stream.next()
+        while self.stream.current.type == 'floordiv':
+            next(self.stream)
             right = self.parse_mod()
             left = nodes.FloorDiv(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -418,8 +508,8 @@ class Parser(object):
     def parse_mod(self):
         lineno = self.stream.current.lineno
         left = self.parse_pow()
-        while self.stream.current.type is 'mod':
-            self.stream.next()
+        while self.stream.current.type == 'mod':
+            next(self.stream)
             right = self.parse_pow()
             left = nodes.Mod(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
@@ -428,33 +518,32 @@ class Parser(object):
     def parse_pow(self):
         lineno = self.stream.current.lineno
         left = self.parse_unary()
-        while self.stream.current.type is 'pow':
-            self.stream.next()
+        while self.stream.current.type == 'pow':
+            next(self.stream)
             right = self.parse_unary()
             left = nodes.Pow(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
         return left
 
-    def parse_unary(self):
+    def parse_unary(self, with_filter=True):
         token_type = self.stream.current.type
         lineno = self.stream.current.lineno
-        if token_type is 'name' and self.stream.current.value == 'not':
-            self.stream.next()
-            node = self.parse_unary()
-            return nodes.Not(node, lineno=lineno)
-        if token_type is 'sub':
-            self.stream.next()
-            node = self.parse_unary()
-            return nodes.Neg(node, lineno=lineno)
-        if token_type is 'add':
-            self.stream.next()
-            node = self.parse_unary()
-            return nodes.Pos(node, lineno=lineno)
-        return self.parse_primary()
-
-    def parse_primary(self, with_postfix=True):
+        if token_type == 'sub':
+            next(self.stream)
+            node = nodes.Neg(self.parse_unary(False), lineno=lineno)
+        elif token_type == 'add':
+            next(self.stream)
+            node = nodes.Pos(self.parse_unary(False), lineno=lineno)
+        else:
+            node = self.parse_primary()
+        node = self.parse_postfix(node)
+        if with_filter:
+            node = self.parse_filter_expr(node)
+        return node
+
+    def parse_primary(self):
         token = self.stream.current
-        if token.type is 'name':
+        if token.type == 'name':
             if token.value in ('true', 'false', 'True', 'False'):
                 node = nodes.Const(token.value in ('true', 'True'),
                                    lineno=token.lineno)
@@ -462,34 +551,32 @@ class Parser(object):
                 node = nodes.Const(None, lineno=token.lineno)
             else:
                 node = nodes.Name(token.value, 'load', lineno=token.lineno)
-            self.stream.next()
-        elif token.type is 'string':
-            self.stream.next()
+            next(self.stream)
+        elif token.type == 'string':
+            next(self.stream)
             buf = [token.value]
             lineno = token.lineno
-            while self.stream.current.type is 'string':
+            while self.stream.current.type == 'string':
                 buf.append(self.stream.current.value)
-                self.stream.next()
+                next(self.stream)
             node = nodes.Const(''.join(buf), lineno=lineno)
         elif token.type in ('integer', 'float'):
-            self.stream.next()
+            next(self.stream)
             node = nodes.Const(token.value, lineno=token.lineno)
-        elif token.type is 'lparen':
-            self.stream.next()
-            node = self.parse_tuple()
+        elif token.type == 'lparen':
+            next(self.stream)
+            node = self.parse_tuple(explicit_parentheses=True)
             self.stream.expect('rparen')
-        elif token.type is 'lbracket':
+        elif token.type == 'lbracket':
             node = self.parse_list()
-        elif token.type is 'lbrace':
+        elif token.type == 'lbrace':
             node = self.parse_dict()
         else:
-            self.fail("unexpected token '%s'" % (token,), token.lineno)
-        if with_postfix:
-            node = self.parse_postfix(node)
+            self.fail("unexpected '%s'" % describe_token(token), token.lineno)
         return node
 
     def parse_tuple(self, simplified=False, with_condexpr=True,
-                    extra_end_rules=None):
+                    extra_end_rules=None, explicit_parentheses=False):
         """Works like `parse_expression` but if multiple expressions are
         delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
         This method could also return a regular expression instead of a tuple
@@ -503,10 +590,14 @@ class Parser(object):
         an extra hint is needed that marks the end of a tuple.  For example
         for loops support tuples between `for` and `in`.  In that case the
         `extra_end_rules` is set to ``['name:in']``.
+
+        `explicit_parentheses` is true if the parsing was triggered by an
+        expression in parentheses.  This is used to figure out if an empty
+        tuple is a valid expression or not.
         """
         lineno = self.stream.current.lineno
         if simplified:
-            parse = lambda: self.parse_primary(with_postfix=False)
+            parse = self.parse_primary
         elif with_condexpr:
             parse = self.parse_expression
         else:
@@ -519,19 +610,30 @@ class Parser(object):
             if self.is_tuple_end(extra_end_rules):
                 break
             args.append(parse())
-            if self.stream.current.type is 'comma':
+            if self.stream.current.type == 'comma':
                 is_tuple = True
             else:
                 break
             lineno = self.stream.current.lineno
-        if not is_tuple and args:
-            return args[0]
+
+        if not is_tuple:
+            if args:
+                return args[0]
+
+            # if we don't have explicit parentheses, an empty tuple is
+            # not a valid expression.  This would mean nothing (literally
+            # nothing) in the spot of an expression would be an empty
+            # tuple.
+            if not explicit_parentheses:
+                self.fail('Expected an expression, got \'%s\'' %
+                          describe_token(self.stream.current))
+
         return nodes.Tuple(args, 'load', lineno=lineno)
 
     def parse_list(self):
         token = self.stream.expect('lbracket')
         items = []
-        while self.stream.current.type is not 'rbracket':
+        while self.stream.current.type != 'rbracket':
             if items:
                 self.stream.expect('comma')
             if self.stream.current.type == 'rbracket':
@@ -543,7 +645,7 @@ class Parser(object):
     def parse_dict(self):
         token = self.stream.expect('lbrace')
         items = []
-        while self.stream.current.type is not 'rbrace':
+        while self.stream.current.type != 'rbrace':
             if items:
                 self.stream.expect('comma')
             if self.stream.current.type == 'rbrace':
@@ -558,34 +660,47 @@ class Parser(object):
     def parse_postfix(self, node):
         while 1:
             token_type = self.stream.current.type
-            if token_type is 'dot' or token_type is 'lbracket':
+            if token_type == 'dot' or token_type == 'lbracket':
                 node = self.parse_subscript(node)
-            elif token_type is 'lparen':
+            # calls are valid both after postfix expressions (getattr
+            # and getitem) as well as filters and tests
+            elif token_type == 'lparen':
                 node = self.parse_call(node)
-            elif token_type is 'pipe':
+            else:
+                break
+        return node
+
+    def parse_filter_expr(self, node):
+        while 1:
+            token_type = self.stream.current.type
+            if token_type == 'pipe':
                 node = self.parse_filter(node)
-            elif token_type is 'name' and self.stream.current.value == 'is':
+            elif token_type == 'name' and self.stream.current.value == 'is':
                 node = self.parse_test(node)
+            # calls are valid both after postfix expressions (getattr
+            # and getitem) as well as filters and tests
+            elif token_type == 'lparen':
+                node = self.parse_call(node)
             else:
                 break
         return node
 
     def parse_subscript(self, node):
-        token = self.stream.next()
-        if token.type is 'dot':
+        token = next(self.stream)
+        if token.type == 'dot':
             attr_token = self.stream.current
-            self.stream.next()
-            if attr_token.type is 'name':
+            next(self.stream)
+            if attr_token.type == 'name':
                 return nodes.Getattr(node, attr_token.value, 'load',
                                      lineno=token.lineno)
-            elif attr_token.type is not 'integer':
+            elif attr_token.type != 'integer':
                 self.fail('expected name or number', attr_token.lineno)
             arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
             return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
-        if token.type is 'lbracket':
+        if token.type == 'lbracket':
             priority_on_attribute = False
             args = []
-            while self.stream.current.type is not 'rbracket':
+            while self.stream.current.type != 'rbracket':
                 if args:
                     self.stream.expect('comma')
                 args.append(self.parse_subscribed())
@@ -593,32 +708,32 @@ class Parser(object):
             if len(args) == 1:
                 arg = args[0]
             else:
-                arg = nodes.Tuple(args, self.lineno, self.filename)
+                arg = nodes.Tuple(args, 'load', lineno=token.lineno)
             return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
         self.fail('expected subscript expression', self.lineno)
 
     def parse_subscribed(self):
         lineno = self.stream.current.lineno
 
-        if self.stream.current.type is 'colon':
-            self.stream.next()
+        if self.stream.current.type == 'colon':
+            next(self.stream)
             args = [None]
         else:
             node = self.parse_expression()
-            if self.stream.current.type is not 'colon':
+            if self.stream.current.type != 'colon':
                 return node
-            self.stream.next()
+            next(self.stream)
             args = [node]
 
-        if self.stream.current.type is 'colon':
+        if self.stream.current.type == 'colon':
             args.append(None)
         elif self.stream.current.type not in ('rbracket', 'comma'):
             args.append(self.parse_expression())
         else:
             args.append(None)
 
-        if self.stream.current.type is 'colon':
-            self.stream.next()
+        if self.stream.current.type == 'colon':
+            next(self.stream)
             if self.stream.current.type not in ('rbracket', 'comma'):
                 args.append(self.parse_expression())
             else:
@@ -640,24 +755,24 @@ class Parser(object):
                 self.fail('invalid syntax for function call expression',
                           token.lineno)
 
-        while self.stream.current.type is not 'rparen':
+        while self.stream.current.type != 'rparen':
             if require_comma:
                 self.stream.expect('comma')
                 # support for trailing comma
-                if self.stream.current.type is 'rparen':
+                if self.stream.current.type == 'rparen':
                     break
-            if self.stream.current.type is 'mul':
+            if self.stream.current.type == 'mul':
                 ensure(dyn_args is None and dyn_kwargs is None)
-                self.stream.next()
+                next(self.stream)
                 dyn_args = self.parse_expression()
-            elif self.stream.current.type is 'pow':
+            elif self.stream.current.type == 'pow':
                 ensure(dyn_kwargs is None)
-                self.stream.next()
+                next(self.stream)
                 dyn_kwargs = self.parse_expression()
             else:
                 ensure(dyn_args is None and dyn_kwargs is None)
-                if self.stream.current.type is 'name' and \
-                    self.stream.look().type is 'assign':
+                if self.stream.current.type == 'name' and \
+                    self.stream.look().type == 'assign':
                     key = self.stream.current.value
                     self.stream.skip(2)
                     value = self.parse_expression()
@@ -678,13 +793,13 @@ class Parser(object):
     def parse_filter(self, node, start_inline=False):
         while self.stream.current.type == 'pipe' or start_inline:
             if not start_inline:
-                self.stream.next()
+                next(self.stream)
             token = self.stream.expect('name')
             name = token.value
-            while self.stream.current.type is 'dot':
-                self.stream.next()
+            while self.stream.current.type == 'dot':
+                next(self.stream)
                 name += '.' + self.stream.expect('name').value
-            if self.stream.current.type is 'lparen':
+            if self.stream.current.type == 'lparen':
                 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
             else:
                 args = []
@@ -696,19 +811,19 @@ class Parser(object):
         return node
 
     def parse_test(self, node):
-        token = self.stream.next()
+        token = next(self.stream)
         if self.stream.current.test('name:not'):
-            self.stream.next()
+            next(self.stream)
             negated = True
         else:
             negated = False
         name = self.stream.expect('name').value
-        while self.stream.current.type is 'dot':
-            self.stream.next()
+        while self.stream.current.type == 'dot':
+            next(self.stream)
             name += '.' + self.stream.expect('name').value
         dyn_args = dyn_kwargs = None
         kwargs = []
-        if self.stream.current.type is 'lparen':
+        if self.stream.current.type == 'lparen':
             args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
         elif self.stream.current.type in ('name', 'string', 'integer',
                                           'float', 'lparen', 'lbracket',
@@ -731,39 +846,47 @@ class Parser(object):
         data_buffer = []
         add_data = data_buffer.append
 
+        if end_tokens is not None:
+            self._end_token_stack.append(end_tokens)
+
         def flush_data():
             if data_buffer:
                 lineno = data_buffer[0].lineno
                 body.append(nodes.Output(data_buffer[:], lineno=lineno))
                 del data_buffer[:]
 
-        while self.stream:
-            token = self.stream.current
-            if token.type is 'data':
-                if token.value:
-                    add_data(nodes.TemplateData(token.value,
-                                                lineno=token.lineno))
-                self.stream.next()
-            elif token.type is 'variable_begin':
-                self.stream.next()
-                add_data(self.parse_tuple(with_condexpr=True))
-                self.stream.expect('variable_end')
-            elif token.type is 'block_begin':
-                flush_data()
-                self.stream.next()
-                if end_tokens is not None and \
-                   self.stream.current.test_any(*end_tokens):
-                    return body
-                rv = self.parse_statement()
-                if isinstance(rv, list):
-                    body.extend(rv)
+        try:
+            while self.stream:
+                token = self.stream.current
+                if token.type == 'data':
+                    if token.value:
+                        add_data(nodes.TemplateData(token.value,
+                                                    lineno=token.lineno))
+                    next(self.stream)
+                elif token.type == 'variable_begin':
+                    next(self.stream)
+                    add_data(self.parse_tuple(with_condexpr=True))
+                    self.stream.expect('variable_end')
+                elif token.type == 'block_begin':
+                    flush_data()
+                    next(self.stream)
+                    if end_tokens is not None and \
+                       self.stream.current.test_any(*end_tokens):
+                        return body
+                    rv = self.parse_statement()
+                    if isinstance(rv, list):
+                        body.extend(rv)
+                    else:
+                        body.append(rv)
+                    self.stream.expect('block_end')
                 else:
-                    body.append(rv)
-                self.stream.expect('block_end')
-            else:
-                raise AssertionError('internal parsing error')
+                    raise AssertionError('internal parsing error')
+
+            flush_data()
+        finally:
+            if end_tokens is not None:
+                self._end_token_stack.pop()
 
-        flush_data()
         return body
 
     def parse(self):