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'])
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
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'):
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
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()
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)
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]
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 = []
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
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)
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
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)
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 = []
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',),
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())
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__.
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()
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()))
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
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
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]
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
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
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
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
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)
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
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:
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':
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':
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())
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:
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()
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 = []
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',
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):