blob: dd7aa8263e2748b7865ce62459e4b49ba5f78201 [file] [log] [blame]
# -*- coding: utf-8 -*-
"""
jinja.parser
~~~~~~~~~~~~
Implements the template parser.
The Jinja template parser is not a real parser but a combination of the
python compiler package and some postprocessing. The tokens yielded by
the lexer are used to separate template data and expressions. The
expression tokens are then converted into strings again and processed
by the python parser.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from jinja import nodes
from jinja.datastructure import StateTest
from jinja.exceptions import TemplateSyntaxError
from jinja.utils import set
__all__ = ['Parser']
# general callback functions for the parser
end_of_block = StateTest.expect_token('block_end',
msg='expected end of block tag')
end_of_variable = StateTest.expect_token('variable_end',
msg='expected end of variable')
end_of_comment = StateTest.expect_token('comment_end',
msg='expected end of comment')
# internal tag callbacks
switch_for = StateTest.expect_token('else', 'endfor')
end_of_for = StateTest.expect_token('endfor')
switch_if = StateTest.expect_token('else', 'elif', 'endif')
end_of_if = StateTest.expect_token('endif')
end_of_filter = StateTest.expect_token('endfilter')
end_of_macro = StateTest.expect_token('endmacro')
end_of_call = StateTest.expect_token('endcall')
end_of_block_tag = StateTest.expect_token('endblock')
end_of_trans = StateTest.expect_token('endtrans')
# this ends a tuple
tuple_edge_tokens = set(['rparen', 'block_end', 'variable_end', 'in',
'recursive'])
class Parser(object):
"""
The template parser class.
Transforms sourcecode into an abstract syntax tree.
"""
def __init__(self, environment, source, filename=None):
self.environment = environment
if isinstance(source, str):
source = source.decode(environment.template_charset, 'ignore')
if isinstance(filename, unicode):
filename = filename.encode('utf-8')
self.source = source
self.filename = filename
self.closed = False
#: set for blocks in order to keep them unique
self.blocks = set()
#: mapping of directives that require special treatment
self.directives = {
# "fake" directives that just trigger errors
'raw': self.parse_raw_directive,
'extends': self.parse_extends_directive,
# real directives
'for': self.parse_for_loop,
'if': self.parse_if_condition,
'cycle': self.parse_cycle_directive,
'call': self.parse_call_directive,
'set': self.parse_set_directive,
'filter': self.parse_filter_directive,
'print': self.parse_print_directive,
'macro': self.parse_macro_directive,
'block': self.parse_block_directive,
'include': self.parse_include_directive,
'trans': self.parse_trans_directive
}
#: set of directives that are only available in a certain
#: context.
self.context_directives = set([
'elif', 'else', 'endblock', 'endfilter', 'endfor', 'endif',
'endmacro', 'endraw', 'endtrans', 'pluralize'
])
#: get the `no_variable_block` flag
self.no_variable_block = self.environment.lexer.no_variable_block
self.stream = environment.lexer.tokenize(source, filename)
def parse_raw_directive(self):
"""
Handle fake raw directive. (real raw directives are handled by
the lexer. But if there are arguments to raw or the end tag
is missing the parser tries to resolve this directive. In that
case present the user a useful error message.
"""
if self.stream:
raise TemplateSyntaxError('raw directive does not support '
'any arguments.', lineno,
self.filename)
raise TemplateSyntaxError('missing end tag for raw directive.',
lineno, self.filename)
def parse_extends_directive(self):
"""
Handle the extends directive used for inheritance.
"""
raise TemplateSyntaxError('mispositioned extends tag. extends must '
'be the first tag of a template.',
self.stream.lineno, self.filename)
def parse_for_loop(self):
"""
Handle a for directive and return a ForLoop node
"""
token = self.stream.expect('for')
item = self.parse_tuple_expression(simplified=True)
if not item.allows_assignments():
raise TemplateSyntaxError('cannot assign to expression',
token.lineno, self.filename)
self.stream.expect('in')
seq = self.parse_tuple_expression()
if self.stream.current.type == 'recursive':
self.stream.next()
recursive = True
else:
recursive = False
self.stream.expect('block_end')
body = self.subparse(switch_for)
# do we have an else section?
if self.stream.current.type == 'else':
self.stream.next()
self.stream.expect('block_end')
else_ = self.subparse(end_of_for, True)
else:
self.stream.next()
else_ = None
self.stream.expect('block_end')
return nodes.ForLoop(item, seq, body, else_, recursive,
token.lineno, self.filename)
def parse_if_condition(self):
"""
Handle if/else blocks.
"""
token = self.stream.expect('if')
expr = self.parse_expression()
self.stream.expect('block_end')
tests = [(expr, self.subparse(switch_if))]
else_ = None
# do we have an else section?
while True:
if self.stream.current.type == 'else':
self.stream.next()
self.stream.expect('block_end')
else_ = self.subparse(end_of_if, True)
elif self.stream.current.type == 'elif':
self.stream.next()
expr = self.parse_expression()
self.stream.expect('block_end')
tests.append((expr, self.subparse(switch_if)))
continue
else:
self.stream.next()
break
self.stream.expect('block_end')
return nodes.IfCondition(tests, else_, token.lineno, self.filename)
def parse_cycle_directive(self):
"""
Handle {% cycle foo, bar, baz %}.
"""
token = self.stream.expect('cycle')
expr = self.parse_tuple_expression()
self.stream.expect('block_end')
return nodes.Cycle(expr, token.lineno, self.filename)
def parse_set_directive(self):
"""
Handle {% set foo = 'value of foo' %}.
"""
token = self.stream.expect('set')
name = self.stream.expect('name')
self.test_name(name.value)
self.stream.expect('assign')
value = self.parse_expression()
if self.stream.current.type == 'bang':
self.stream.next()
scope_local = False
else:
scope_local = True
self.stream.expect('block_end')
return nodes.Set(name.value, value, scope_local,
token.lineno, self.filename)
def parse_filter_directive(self):
"""
Handle {% filter foo|bar %} directives.
"""
token = self.stream.expect('filter')
filters = []
while self.stream.current.type != 'block_end':
if filters:
self.stream.expect('pipe')
token = self.stream.expect('name')
args = []
if self.stream.current.type == 'lparen':
self.stream.next()
while self.stream.current.type != 'rparen':
if args:
self.stream.expect('comma')
args.append(self.parse_expression())
self.stream.expect('rparen')
filters.append((token.value, args))
self.stream.expect('block_end')
body = self.subparse(end_of_filter, True)
self.stream.expect('block_end')
return nodes.Filter(body, filters, token.lineno, self.filename)
def parse_print_directive(self):
"""
Handle {% print foo %}.
"""
token = self.stream.expect('print')
expr = self.parse_tuple_expression()
node = nodes.Print(expr, token.lineno, self.filename)
self.stream.expect('block_end')
return node
def parse_macro_directive(self):
"""
Handle {% macro foo bar, baz %} as well as
{% macro foo(bar, baz) %}.
"""
token = self.stream.expect('macro')
macro_name = self.stream.expect('name')
self.test_name(macro_name.value)
if self.stream.current.type == 'lparen':
self.stream.next()
needle_token = 'rparen'
else:
needle_token = 'block_end'
args = []
while self.stream.current.type != needle_token:
if args:
self.stream.expect('comma')
name = self.stream.expect('name').value
self.test_name(name)
if self.stream.current.type == 'assign':
self.stream.next()
default = self.parse_expression()
else:
default = None
args.append((name, default))
self.stream.next()
if needle_token == 'rparen':
self.stream.expect('block_end')
body = self.subparse(end_of_macro, True)
self.stream.expect('block_end')
return nodes.Macro(macro_name.value, args, body, token.lineno,
self.filename)
def parse_call_directive(self):
"""
Handle {% call foo() %}...{% endcall %}
"""
token = self.stream.expect('call')
expr = self.parse_call_expression()
self.stream.expect('block_end')
body = self.subparse(end_of_call, True)
self.stream.expect('block_end')
return nodes.Call(expr, body, token.lineno, self.filename)
def parse_block_directive(self):
"""
Handle block directives used for inheritance.
"""
token = self.stream.expect('block')
name = self.stream.expect('name').value
# check if this block does not exist by now.
if name in self.blocks:
raise TemplateSyntaxError('block %r defined twice' %
name, token.lineno,
self.filename)
self.blocks.add(name)
if self.stream.current.type != 'block_end':
lineno = self.stream.lineno
body = nodes.NodeList([self.parse_variable_tag()], lineno,
self.filename)
self.stream.expect('block_end')
else:
# otherwise parse the body and attach it to the block
self.stream.expect('block_end')
body = self.subparse(end_of_block_tag, True)
self.stream.expect('block_end')
return nodes.Block(name, body, token.lineno, self.filename)
def parse_include_directive(self):
"""
Handle the include directive used for template inclusion.
"""
token = self.stream.expect('include')
template = self.stream.expect('string').value
self.stream.expect('block_end')
return nodes.Include(template, token.lineno, self.filename)
def parse_trans_directive(self):
"""
Handle translatable sections.
"""
trans_token = self.stream.expect('trans')
# string based translations {% trans "foo" %}
if self.stream.current.type == 'string':
text = self.stream.expect('string')
self.stream.expect('block_end')
return nodes.Trans(text.value, None, None, None,
trans_token.lineno, self.filename)
# block based translations
replacements = {}
plural_var = None
while self.stream.current.type != 'block_end':
if replacements:
self.stream.expect('comma')
name = self.stream.expect('name')
if self.stream.current.type == 'assign':
self.stream.next()
value = self.parse_expression()
else:
value = nodes.NameExpression(name.value, name.lineno,
self.filename)
if name.value in replacements:
raise TemplateSyntaxError('translation variable %r '
'is defined twice' % name.value,
name.lineno, self.filename)
replacements[name.value] = value
if plural_var is None:
plural_var = name.value
self.stream.expect('block_end')
def process_variable():
var_name = self.stream.expect('name')
if var_name.value not in replacements:
raise TemplateSyntaxError('unregistered translation variable'
" '%s'." % var_name.value,
var_name.lineno, self.filename)
buf.append('%%(%s)s' % var_name.value)
buf = singular = []
plural = None
while True:
token = self.stream.current
if token.type == 'data':
buf.append(token.value.replace('%', '%%'))
self.stream.next()
elif token.type == 'variable_begin':
self.stream.next()
process_variable()
self.stream.expect('variable_end')
elif token.type == 'block_begin':
self.stream.next()
if plural is None and self.stream.current.type == 'pluralize':
self.stream.next()
if self.stream.current.type == 'name':
plural_var = self.stream.expect('name').value
plural = buf = []
elif self.stream.current.type == 'endtrans':
self.stream.next()
self.stream.expect('block_end')
break
else:
if self.no_variable_block:
process_variable()
else:
raise TemplateSyntaxError('blocks are not allowed '
'in trans tags',
self.stream.lineno,
self.filename)
self.stream.expect('block_end')
else:
assert False, 'something very strange happened'
singular = u''.join(singular)
if plural is not None:
plural = u''.join(plural)
return nodes.Trans(singular, plural, plural_var, replacements,
trans_token.lineno, self.filename)
def parse_expression(self):
"""
Parse one expression from the stream.
"""
return self.parse_conditional_expression()
def parse_subscribed_expression(self):
"""
Like parse_expression but parses slices too. Because this
parsing function requires a border the two tokens rbracket
and comma mark the end of the expression in some situations.
"""
lineno = self.stream.lineno
if self.stream.current.type == 'colon':
self.stream.next()
args = [None]
else:
node = self.parse_expression()
if self.stream.current.type != 'colon':
return node
self.stream.next()
args = [node]
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 == 'colon':
self.stream.next()
if self.stream.current.type not in ('rbracket', 'comma'):
args.append(self.parse_expression())
else:
args.append(None)
else:
args.append(None)
return nodes.SliceExpression(*(args + [lineno, self.filename]))
def parse_conditional_expression(self):
"""
Parse a conditional expression (foo if bar else baz)
"""
lineno = self.stream.lineno
expr1 = self.parse_or_expression()
while self.stream.current.type == 'if':
self.stream.next()
expr2 = self.parse_or_expression()
self.stream.expect('else')
expr3 = self.parse_conditional_expression()
expr1 = nodes.ConditionalExpression(expr2, expr1, expr3,
lineno, self.filename)
lineno = self.stream.lineno
return expr1
def parse_or_expression(self):
"""
Parse something like {{ foo or bar }}.
"""
lineno = self.stream.lineno
left = self.parse_and_expression()
while self.stream.current.type == 'or':
self.stream.next()
right = self.parse_and_expression()
left = nodes.OrExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_and_expression(self):
"""
Parse something like {{ foo and bar }}.
"""
lineno = self.stream.lineno
left = self.parse_compare_expression()
while self.stream.current.type == 'and':
self.stream.next()
right = self.parse_compare_expression()
left = nodes.AndExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_compare_expression(self):
"""
Parse something like {{ foo == bar }}.
"""
known_operators = set(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
lineno = self.stream.lineno
expr = self.parse_add_expression()
ops = []
while True:
if self.stream.current.type in known_operators:
op = self.stream.current.type
self.stream.next()
ops.append([op, self.parse_add_expression()])
elif self.stream.current.type == 'not' and \
self.stream.look().type == 'in':
self.stream.skip(2)
ops.append(['not in', self.parse_add_expression()])
else:
break
if not ops:
return expr
return nodes.CompareExpression(expr, ops, lineno, self.filename)
def parse_add_expression(self):
"""
Parse something like {{ foo + bar }}.
"""
lineno = self.stream.lineno
left = self.parse_sub_expression()
while self.stream.current.type == 'add':
self.stream.next()
right = self.parse_sub_expression()
left = nodes.AddExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_sub_expression(self):
"""
Parse something like {{ foo - bar }}.
"""
lineno = self.stream.lineno
left = self.parse_concat_expression()
while self.stream.current.type == 'sub':
self.stream.next()
right = self.parse_concat_expression()
left = nodes.SubExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_concat_expression(self):
"""
Parse something like {{ foo ~ bar }}.
"""
lineno = self.stream.lineno
args = [self.parse_mul_expression()]
while self.stream.current.type == 'tilde':
self.stream.next()
args.append(self.parse_mul_expression())
if len(args) == 1:
return args[0]
return nodes.ConcatExpression(args, lineno, self.filename)
def parse_mul_expression(self):
"""
Parse something like {{ foo * bar }}.
"""
lineno = self.stream.lineno
left = self.parse_div_expression()
while self.stream.current.type == 'mul':
self.stream.next()
right = self.parse_div_expression()
left = nodes.MulExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_div_expression(self):
"""
Parse something like {{ foo / bar }}.
"""
lineno = self.stream.lineno
left = self.parse_floor_div_expression()
while self.stream.current.type == 'div':
self.stream.next()
right = self.parse_floor_div_expression()
left = nodes.DivExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_floor_div_expression(self):
"""
Parse something like {{ foo // bar }}.
"""
lineno = self.stream.lineno
left = self.parse_mod_expression()
while self.stream.current.type == 'floordiv':
self.stream.next()
right = self.parse_mod_expression()
left = nodes.FloorDivExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_mod_expression(self):
"""
Parse something like {{ foo % bar }}.
"""
lineno = self.stream.lineno
left = self.parse_pow_expression()
while self.stream.current.type == 'mod':
self.stream.next()
right = self.parse_pow_expression()
left = nodes.ModExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_pow_expression(self):
"""
Parse something like {{ foo ** bar }}.
"""
lineno = self.stream.lineno
left = self.parse_unary_expression()
while self.stream.current.type == 'pow':
self.stream.next()
right = self.parse_unary_expression()
left = nodes.PowExpression(left, right, lineno, self.filename)
lineno = self.stream.lineno
return left
def parse_unary_expression(self):
"""
Parse all kinds of unary expressions.
"""
if self.stream.current.type == 'not':
return self.parse_not_expression()
elif self.stream.current.type == 'sub':
return self.parse_neg_expression()
elif self.stream.current.type == 'add':
return self.parse_pos_expression()
return self.parse_primary_expression()
def parse_not_expression(self):
"""
Parse something like {{ not foo }}.
"""
token = self.stream.expect('not')
node = self.parse_unary_expression()
return nodes.NotExpression(node, token.lineno, self.filename)
def parse_neg_expression(self):
"""
Parse something like {{ -foo }}.
"""
token = self.stream.expect('sub')
node = self.parse_unary_expression()
return nodes.NegExpression(node, token.lineno, self.filename)
def parse_pos_expression(self):
"""
Parse something like {{ +foo }}.
"""
token = self.stream.expect('add')
node = self.parse_unary_expression()
return nodes.PosExpression(node, token.lineno, self.filename)
def parse_primary_expression(self, parse_postfix=True):
"""
Parse a primary expression such as a name or literal.
"""
current = self.stream.current
if current.type == 'name':
if current.value in ('true', 'false'):
node = self.parse_bool_expression()
elif current.value == 'none':
node = self.parse_none_expression()
elif current.value == 'undefined':
node = self.parse_undefined_expression()
elif current.value == '_':
node = self.parse_gettext_call()
else:
node = self.parse_name_expression()
elif current.type in ('integer', 'float'):
node = self.parse_number_expression()
elif current.type == 'string':
node = self.parse_string_expression()
elif current.type == 'regex':
node = self.parse_regex_expression()
elif current.type == 'lparen':
node = self.parse_paren_expression()
elif current.type == 'lbracket':
node = self.parse_list_expression()
elif current.type == 'lbrace':
node = self.parse_dict_expression()
elif current.type == 'at':
node = self.parse_set_expression()
else:
raise TemplateSyntaxError("unexpected token '%s'" %
self.stream.current,
self.stream.current.lineno,
self.filename)
if parse_postfix:
node = self.parse_postfix_expression(node)
return node
def parse_tuple_expression(self, enforce=False, simplified=False):
"""
Parse multiple expressions into a tuple. This can also return
just one expression which is not a tuple. If you want to enforce
a tuple, pass it enforce=True.
"""
lineno = self.stream.lineno
if simplified:
parse = self.parse_primary_expression
else:
parse = self.parse_expression
args = []
is_tuple = False
while True:
if args:
self.stream.expect('comma')
if self.stream.current.type in tuple_edge_tokens:
break
args.append(parse())
if self.stream.current.type == 'comma':
is_tuple = True
else:
break
if not is_tuple and args:
if enforce:
raise TemplateSyntaxError('tuple expected', lineno,
self.filename)
return args[0]
return nodes.TupleExpression(args, lineno, self.filename)
def parse_bool_expression(self):
"""
Parse a boolean literal.
"""
token = self.stream.expect('name')
if token.value == 'true':
value = True
elif token.value == 'false':
value = False
else:
raise TemplateSyntaxError("expected boolean literal",
token.lineno, self.filename)
return nodes.ConstantExpression(value, token.lineno, self.filename)
def parse_none_expression(self):
"""
Parse a none literal.
"""
token = self.stream.expect('name', 'none')
return nodes.ConstantExpression(None, token.lineno, self.filename)
def parse_undefined_expression(self):
"""
Parse an undefined literal.
"""
token = self.stream.expect('name', 'undefined')
return nodes.UndefinedExpression(token.lineno, self.filename)
def parse_gettext_call(self):
"""
parse {{ _('foo') }}.
"""
# XXX: check if only one argument was passed and if
# it is a string literal. Maybe that should become a special
# expression anyway.
token = self.stream.expect('name', '_')
node = nodes.NameExpression(token.value, token.lineno, self.filename)
return self.parse_call_expression(node)
def parse_name_expression(self):
"""
Parse any name.
"""
token = self.stream.expect('name')
self.test_name(token.value)
return nodes.NameExpression(token.value, token.lineno, self.filename)
def parse_number_expression(self):
"""
Parse a number literal.
"""
token = self.stream.current
if token.type not in ('integer', 'float'):
raise TemplateSyntaxError('integer or float literal expected',
token.lineno, self.filename)
self.stream.next()
return nodes.ConstantExpression(token.value, token.lineno, self.filename)
def parse_string_expression(self):
"""
Parse a string literal.
"""
token = self.stream.expect('string')
return nodes.ConstantExpression(token.value, token.lineno, self.filename)
def parse_regex_expression(self):
"""
Parse a regex literal.
"""
token = self.stream.expect('regex')
return nodes.RegexExpression(token.value, token.lineno, self.filename)
def parse_paren_expression(self):
"""
Parse a parenthized expression.
"""
self.stream.expect('lparen')
try:
return self.parse_tuple_expression()
finally:
self.stream.expect('rparen')
def parse_list_expression(self):
"""
Parse something like {{ [1, 2, "three"] }}
"""
token = self.stream.expect('lbracket')
items = []
while self.stream.current.type != 'rbracket':
if items:
self.stream.expect('comma')
if self.stream.current.type == 'rbracket':
break
items.append(self.parse_expression())
self.stream.expect('rbracket')
return nodes.ListExpression(items, token.lineno, self.filename)
def parse_dict_expression(self):
"""
Parse something like {{ {1: 2, 3: 4} }}
"""
token = self.stream.expect('lbrace')
items = []
while self.stream.current.type != 'rbrace':
if items:
self.stream.expect('comma')
if self.stream.current.type == 'rbrace':
break
key = self.parse_expression()
self.stream.expect('colon')
value = self.parse_expression()
items.append((key, value))
self.stream.expect('rbrace')
return nodes.DictExpression(items, token.lineno, self.filename)
def parse_set_expression(self):
"""
Parse something like {{ @(1, 2, 3) }}.
"""
token = self.stream.expect('at')
self.stream.expect('lparen')
items = []
while self.stream.current.type != 'rparen':
if items:
self.stream.expect('comma')
if self.stream.current.type == 'rparen':
break
items.append(self.parse_expression())
self.stream.expect('rparen')
return nodes.SetExpression(items, token.lineno, self.filename)
def parse_postfix_expression(self, node):
"""
Parse a postfix expression such as a filter statement or a
function call.
"""
while True:
current = self.stream.current.type
if current == 'dot' or current == 'lbracket':
node = self.parse_subscript_expression(node)
elif current == 'lparen':
node = self.parse_call_expression(node)
elif current == 'pipe':
node = self.parse_filter_expression(node)
elif current == 'is':
node = self.parse_test_expression(node)
else:
break
return node
def parse_subscript_expression(self, node):
"""
Parse a subscript statement. Gets attributes and items from an
object.
"""
lineno = self.stream.lineno
if self.stream.current.type == 'dot':
self.stream.next()
token = self.stream.current
if token.type in ('name', 'integer'):
arg = nodes.ConstantExpression(token.value, token.lineno,
self.filename)
else:
raise TemplateSyntaxError('expected name or number',
token.lineno, self.filename)
self.stream.next()
elif self.stream.current.type == 'lbracket':
self.stream.next()
args = []
while self.stream.current.type != 'rbracket':
if args:
self.stream.expect('comma')
args.append(self.parse_subscribed_expression())
self.stream.expect('rbracket')
if len(args) == 1:
arg = args[0]
else:
arg = nodes.TupleExpression(args, lineno, self.filename)
else:
raise TemplateSyntaxError('expected subscript expression',
self.lineno, self.filename)
return nodes.SubscriptExpression(node, arg, lineno, self.filename)
def parse_call_expression(self, node=None):
"""
Parse a call.
"""
if node is None:
node = self.parse_primary_expression(parse_postfix=False)
token = self.stream.expect('lparen')
args = []
kwargs = []
dyn_args = None
dyn_kwargs = None
require_comma = False
def ensure(expr):
if not expr:
raise TemplateSyntaxError('invalid syntax for function '
'call expression', token.lineno,
self.filename)
while self.stream.current.type != 'rparen':
if require_comma:
self.stream.expect('comma')
# support for trailing comma
if self.stream.current.type == 'rparen':
break
if self.stream.current.type == 'mul':
ensure(dyn_args is None and dyn_kwargs is None)
self.stream.next()
dyn_args = self.parse_expression()
elif self.stream.current.type == 'pow':
ensure(dyn_kwargs is None)
self.stream.next()
dyn_kwargs = self.parse_expression()
else:
ensure(dyn_args is None and dyn_kwargs is None)
if self.stream.current.type == 'name' and \
self.stream.look().type == 'assign':
key = self.stream.current.value
self.stream.skip(2)
kwargs.append((key, self.parse_expression()))
else:
ensure(not kwargs)
args.append(self.parse_expression())
require_comma = True
self.stream.expect('rparen')
return nodes.CallExpression(node, args, kwargs, dyn_args,
dyn_kwargs, token.lineno,
self.filename)
def parse_filter_expression(self, node):
"""
Parse filter calls.
"""
lineno = self.stream.lineno
filters = []
while self.stream.current.type == 'pipe':
self.stream.next()
token = self.stream.expect('name')
args = []
if self.stream.current.type == 'lparen':
self.stream.next()
while self.stream.current.type != 'rparen':
if args:
self.stream.expect('comma')
args.append(self.parse_expression())
self.stream.expect('rparen')
filters.append((token.value, args))
return nodes.FilterExpression(node, filters, lineno, self.filename)
def parse_test_expression(self, node):
"""
Parse test calls.
"""
token = self.stream.expect('is')
if self.stream.current.type == 'not':
self.stream.next()
negated = True
else:
negated = False
name = self.stream.expect('name').value
args = []
if self.stream.current.type == 'lparen':
self.stream.next()
while self.stream.current.type != 'rparen':
if args:
self.stream.expect('comma')
args.append(self.parse_expression())
self.stream.expect('rparen')
elif self.stream.current.type in ('name', 'string', 'integer',
'float', 'lparen', 'lbracket',
'lbrace', 'regex'):
args.append(self.parse_expression())
node = nodes.TestExpression(node, name, args, token.lineno,
self.filename)
if negated:
node = nodes.NotExpression(node, token.lineno, self.filename)
return node
def test_name(self, name):
"""
Test if a name is not a special constant
"""
if name in ('true', 'false', 'none', 'undefined', '_'):
raise TemplateSyntaxError('expected name not special constant',
self.stream.lineno, self.filename)
def subparse(self, test, drop_needle=False):
"""
Helper function used to parse the sourcecode until the test
function which is passed a tuple in the form (lineno, token, data)
returns True. In that case the current token is pushed back to
the stream and the generator ends.
The test function is only called for the first token after a
block tag. Variable tags are *not* aliases for {% print %} in
that case.
If drop_needle is True the needle_token is removed from the
stream.
"""
if self.closed:
raise RuntimeError('parser is closed')
result = []
buffer = []
next = self.stream.next
lineno = self.stream.lineno
def assemble_list():
push_buffer()
return nodes.NodeList(result, lineno, self.filename)
def push_variable():
buffer.append((True, self.parse_tuple_expression()))
def push_data():
buffer.append((False, self.stream.expect('data')))
def push_buffer():
if not buffer:
return
template = []
variables = []
for is_var, data in buffer:
if is_var:
template.append('%s')
variables.append(data)
else:
template.append(data.value.replace('%', '%%'))
result.append(nodes.Text(u''.join(template), variables,
buffer[0][1].lineno, self.filename))
del buffer[:]
def push_node(node):
push_buffer()
result.append(node)
while self.stream:
token_type = self.stream.current.type
if token_type == 'variable_begin':
next()
push_variable()
self.stream.expect('variable_end')
elif token_type == 'block_begin':
next()
if test is not None and test(self.stream.current):
if drop_needle:
next()
return assemble_list()
handler = self.directives.get(self.stream.current.type)
if handler is None:
if self.no_variable_block:
push_variable()
self.stream.expect('block_end')
elif self.stream.current.type in self.context_directives:
raise TemplateSyntaxError('unexpected directive %r.' %
self.stream.current.type,
lineno, self.filename)
else:
raise TemplateSyntaxError('unknown directive %r.' %
self.stream.current.type,
lineno, self.filename)
else:
node = handler()
if node is not None:
push_node(node)
elif token_type == 'data':
push_data()
# this should be unreachable code
else:
assert False, "unexpected token %r" % self.stream.current
if test is not None:
msg = isinstance(test, StateTest) and ': ' + test.msg or ''
raise TemplateSyntaxError('unexpected end of stream' + msg,
self.stream.lineno, self.filename)
return assemble_list()
def parse(self):
"""
Parse the template and return a Template node. This also does some
post processing sanitizing and parses for an extends tag.
"""
if self.closed:
raise RuntimeError('parser is closed')
try:
# get the leading whitespace, if we are not in a child
# template we push that back to the stream later.
leading_whitespace = self.stream.read_whitespace()
# parse an optional extends which *must* be the first node
# of a template.
if self.stream.current.type == 'block_begin' and \
self.stream.look().type == 'extends':
self.stream.skip(2)
extends = self.stream.expect('string').value
self.stream.expect('block_end')
else:
extends = None
if leading_whitespace:
self.stream.shift(leading_whitespace)
body = self.subparse(None)
def walk(nodelist, stack):
for node in nodelist:
if extends is not None and \
node.__class__ is nodes.Block and \
stack[-1] is not body:
for n in stack:
if n.__class__ is nodes.Block:
break
else:
raise TemplateSyntaxError('misplaced block %r, '
'blocks in child '
'templates must be '
'either top level or '
'located in a block '
'tag.' % node.name,
node.lineno,
self.filename)
stack.append(node)
walk(node.get_child_nodes(), stack)
stack.pop()
walk([body], [body])
return nodes.Template(extends, body, 1, self.filename)
finally:
self.close()
def close(self):
"""Clean up soon."""
self.closed = True
self.stream = self.directives = self.stream = self.blocks = \
self.environment = None