blob: 5658ca9a10c633fe59eab7b650c7dbc8e26207bd [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.parser
4 ~~~~~~~~~~~~~
5
6 Implements the template parser.
7
8 :copyright: 2008 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10"""
Armin Ronacherf59bac22008-04-20 13:11:43 +020011from operator import itemgetter
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020012from jinja2 import nodes
13from jinja2.exceptions import TemplateSyntaxError
Armin Ronacher07bc6842008-03-31 14:18:49 +020014
15
Armin Ronachere791c2a2008-04-07 18:39:54 +020016_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
Armin Ronacherf59bac22008-04-20 13:11:43 +020017 'macro', 'include'])
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020018_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
Armin Ronacher2b60fe52008-04-21 08:23:59 +020019statement_end_tokens = set(['variable_end', 'block_end', 'in'])
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020020
Armin Ronacher07bc6842008-03-31 14:18:49 +020021
22class Parser(object):
Armin Ronacherd55ab532008-04-09 16:13:39 +020023 """The template parser class.
Armin Ronacher07bc6842008-03-31 14:18:49 +020024
25 Transforms sourcecode into an abstract syntax tree.
26 """
27
28 def __init__(self, environment, source, filename=None):
29 self.environment = environment
Armin Ronacher07bc6842008-03-31 14:18:49 +020030 if isinstance(filename, unicode):
31 filename = filename.encode('utf-8')
Armin Ronacherbf7c4ad2008-04-12 12:02:36 +020032 self.source = unicode(source)
Armin Ronacher07bc6842008-03-31 14:18:49 +020033 self.filename = filename
34 self.closed = False
Armin Ronacher07bc6842008-03-31 14:18:49 +020035 self.stream = environment.lexer.tokenize(source, filename)
Armin Ronacher05530932008-04-20 13:27:49 +020036 self.extensions = {}
37 for extension in environment.extensions:
38 for tag in extension.tags:
39 self.extensions[tag] = extension.parse
Armin Ronacher07bc6842008-03-31 14:18:49 +020040
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020041 def parse_statement(self):
42 """Parse a single statement."""
43 token_type = self.stream.current.type
44 if token_type in _statement_keywords:
45 return getattr(self, 'parse_' + token_type)()
46 elif token_type is 'call':
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020047 return self.parse_call_block()
Armin Ronacherfa865fb2008-04-12 22:11:53 +020048 elif token_type is 'filter':
49 return self.parse_filter_block()
Armin Ronacherf59bac22008-04-20 13:11:43 +020050 elif token_type is 'name':
51 ext = self.extensions.get(self.stream.current.value)
52 if ext is not None:
53 return ext(self)
Armin Ronachere791c2a2008-04-07 18:39:54 +020054 lineno = self.stream.current
Armin Ronacher8efc5222008-04-08 14:47:40 +020055 expr = self.parse_tuple()
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020056 if self.stream.current.type == 'assign':
Armin Ronachere791c2a2008-04-07 18:39:54 +020057 result = self.parse_assign(expr)
58 else:
59 result = nodes.ExprStmt(expr, lineno=lineno)
Armin Ronachere791c2a2008-04-07 18:39:54 +020060 return result
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020061
62 def parse_assign(self, target):
63 """Parse an assign statement."""
64 lineno = self.stream.expect('assign').lineno
65 if not target.can_assign():
66 raise TemplateSyntaxError("can't assign to '%s'" %
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020067 target.__class__.__name__.lower(),
68 target.lineno, self.filename)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020069 expr = self.parse_tuple()
Armin Ronachere791c2a2008-04-07 18:39:54 +020070 target.set_ctx('store')
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020071 return nodes.Assign(target, expr, lineno=lineno)
72
73 def parse_statements(self, end_tokens, drop_needle=False):
Armin Ronacherf59bac22008-04-20 13:11:43 +020074 """Parse multiple statements into a list until one of the end tokens
75 is reached. This is used to parse the body of statements as it also
76 parses template data if appropriate.
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020077 """
78 # the first token may be a colon for python compatibility
79 if self.stream.current.type is 'colon':
80 self.stream.next()
81
Armin Ronacher2b60fe52008-04-21 08:23:59 +020082 # in the future it would be possible to add whole code sections
83 # by adding some sort of end of statement token and parsing those here.
84 self.stream.expect('block_end')
85 result = self.subparse(end_tokens)
86
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020087 if drop_needle:
88 self.stream.next()
89 return result
90
91 def parse_for(self):
92 """Parse a for loop."""
93 lineno = self.stream.expect('for').lineno
94 target = self.parse_tuple(simplified=True)
Armin Ronachere791c2a2008-04-07 18:39:54 +020095 if not target.can_assign():
96 raise TemplateSyntaxError("can't assign to '%s'" %
Armin Ronacher4f7d2d52008-04-22 10:40:26 +020097 target.__class__.__name__.lower(),
98 target.lineno, self.filename)
Armin Ronachere791c2a2008-04-07 18:39:54 +020099 target.set_ctx('store')
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200100 self.stream.expect('in')
Armin Ronacher3d8b7842008-04-13 13:16:50 +0200101 iter = self.parse_tuple(no_condexpr=True)
102 test = None
103 if self.stream.current.type is 'if':
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200104 self.stream.next()
Armin Ronacher3d8b7842008-04-13 13:16:50 +0200105 test = self.parse_expression()
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200106 body = self.parse_statements(('endfor', 'else'))
Armin Ronachere791c2a2008-04-07 18:39:54 +0200107 if self.stream.next().type is 'endfor':
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200108 else_ = []
109 else:
110 else_ = self.parse_statements(('endfor',), drop_needle=True)
Armin Ronacher3d8b7842008-04-13 13:16:50 +0200111 return nodes.For(target, iter, body, else_, test, lineno=lineno)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200112
113 def parse_if(self):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200114 """Parse an if construct."""
115 node = result = nodes.If(lineno=self.stream.expect('if').lineno)
116 while 1:
117 # TODO: exclude conditional expressions here
118 node.test = self.parse_tuple()
119 node.body = self.parse_statements(('elif', 'else', 'endif'))
120 token_type = self.stream.next().type
121 if token_type is 'elif':
122 new_node = nodes.If(lineno=self.stream.current.lineno)
123 node.else_ = [new_node]
124 node = new_node
125 continue
126 elif token_type is 'else':
127 node.else_ = self.parse_statements(('endif',),
128 drop_needle=True)
129 else:
130 node.else_ = []
131 break
132 return result
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200133
134 def parse_block(self):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200135 node = nodes.Block(lineno=self.stream.expect('block').lineno)
136 node.name = self.stream.expect('name').value
137 node.body = self.parse_statements(('endblock',), drop_needle=True)
138 return node
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200139
140 def parse_extends(self):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200141 node = nodes.Extends(lineno=self.stream.expect('extends').lineno)
142 node.template = self.parse_expression()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200143 return node
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200144
145 def parse_include(self):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200146 node = nodes.Include(lineno=self.stream.expect('include').lineno)
147 expr = self.parse_expression()
148 if self.stream.current.type is 'assign':
149 self.stream.next()
Armin Ronacher7c0116f2008-04-12 00:06:19 +0200150 if not isinstance(expr, nodes.Name):
151 raise TemplateSyntaxError('must assign imported template to '
152 'variable or current scope',
153 expr.lineno, self.filename)
154 if not expr.can_assign():
Armin Ronachere791c2a2008-04-07 18:39:54 +0200155 raise TemplateSyntaxError('can\'t assign imported template '
Armin Ronacher7c0116f2008-04-12 00:06:19 +0200156 'to %r' % expr, expr.lineno,
Armin Ronachere791c2a2008-04-07 18:39:54 +0200157 self.filename)
Armin Ronacher7c0116f2008-04-12 00:06:19 +0200158 node.target = expr.name
Armin Ronachere791c2a2008-04-07 18:39:54 +0200159 node.template = self.parse_expression()
160 else:
161 node.target = None
162 node.template = expr
Armin Ronachere791c2a2008-04-07 18:39:54 +0200163 return node
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200164
Armin Ronacher71082072008-04-12 14:19:36 +0200165 def parse_signature(self, node):
166 node.args = args = []
167 node.defaults = defaults = []
168 self.stream.expect('lparen')
169 while self.stream.current.type is not 'rparen':
170 if args:
171 self.stream.expect('comma')
172 token = self.stream.expect('name')
173 arg = nodes.Name(token.value, 'param', lineno=token.lineno)
174 if not arg.can_assign():
175 raise TemplateSyntaxError("can't assign to '%s'" %
176 arg.name, arg.lineno,
177 self.filename)
178 if self.stream.current.type is 'assign':
179 self.stream.next()
180 defaults.append(self.parse_expression())
181 args.append(arg)
182 self.stream.expect('rparen')
183
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200184 def parse_call_block(self):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200185 node = nodes.CallBlock(lineno=self.stream.expect('call').lineno)
Armin Ronacher71082072008-04-12 14:19:36 +0200186 if self.stream.current.type is 'lparen':
187 self.parse_signature(node)
188
Armin Ronacher8edbe492008-04-10 20:43:43 +0200189 node.call = self.parse_expression()
190 if not isinstance(node.call, nodes.Call):
191 raise TemplateSyntaxError('expected call', node.lineno,
192 self.filename)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200193 node.body = self.parse_statements(('endcall',), drop_needle=True)
194 return node
195
Armin Ronacherfa865fb2008-04-12 22:11:53 +0200196 def parse_filter_block(self):
197 node = nodes.FilterBlock(lineno=self.stream.expect('filter').lineno)
198 node.filter = self.parse_filter(None, start_inline=True)
199 node.body = self.parse_statements(('endfilter',), drop_needle=True)
200 return node
201
Armin Ronachere791c2a2008-04-07 18:39:54 +0200202 def parse_macro(self):
203 node = nodes.Macro(lineno=self.stream.expect('macro').lineno)
204 node.name = self.stream.expect('name').value
Armin Ronacherf059ec12008-04-11 22:21:00 +0200205 # make sure that assignments to that name are allowed
206 if not nodes.Name(node.name, 'store').can_assign():
207 raise TemplateSyntaxError('can\'t assign macro to %r' %
208 node.target, node.lineno,
209 self.filename)
Armin Ronacher71082072008-04-12 14:19:36 +0200210 self.parse_signature(node)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200211 node.body = self.parse_statements(('endmacro',), drop_needle=True)
212 return node
213
214 def parse_print(self):
215 node = nodes.Output(lineno=self.stream.expect('print').lineno)
216 node.nodes = []
Armin Ronacherf59bac22008-04-20 13:11:43 +0200217 while self.stream.current.type not in statement_end_tokens:
Armin Ronachere791c2a2008-04-07 18:39:54 +0200218 if node.nodes:
219 self.stream.expect('comma')
220 node.nodes.append(self.parse_expression())
Armin Ronachere791c2a2008-04-07 18:39:54 +0200221 return node
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200222
Armin Ronacher3d8b7842008-04-13 13:16:50 +0200223 def parse_expression(self, no_condexpr=False):
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200224 """Parse an expression."""
Armin Ronacher3d8b7842008-04-13 13:16:50 +0200225 if no_condexpr:
226 return self.parse_or()
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200227 return self.parse_condexpr()
228
229 def parse_condexpr(self):
230 lineno = self.stream.current.lineno
231 expr1 = self.parse_or()
232 while self.stream.current.type is 'if':
233 self.stream.next()
234 expr2 = self.parse_or()
235 self.stream.expect('else')
236 expr3 = self.parse_condexpr()
237 expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
238 lineno = self.stream.current.lineno
239 return expr1
240
241 def parse_or(self):
242 lineno = self.stream.current.lineno
243 left = self.parse_and()
244 while self.stream.current.type is 'or':
245 self.stream.next()
246 right = self.parse_and()
247 left = nodes.Or(left, right, lineno=lineno)
248 lineno = self.stream.current.lineno
249 return left
250
251 def parse_and(self):
252 lineno = self.stream.current.lineno
253 left = self.parse_compare()
254 while self.stream.current.type is 'and':
255 self.stream.next()
256 right = self.parse_compare()
257 left = nodes.And(left, right, lineno=lineno)
258 lineno = self.stream.current.lineno
259 return left
260
261 def parse_compare(self):
262 lineno = self.stream.current.lineno
263 expr = self.parse_add()
264 ops = []
265 while 1:
266 token_type = self.stream.current.type
267 if token_type in _compare_operators:
268 self.stream.next()
269 ops.append(nodes.Operand(token_type, self.parse_add()))
270 elif token_type is 'not' and self.stream.look().type is 'in':
271 self.stream.skip(2)
272 ops.append(nodes.Operand('notin', self.parse_add()))
273 else:
274 break
275 lineno = self.stream.current.lineno
276 if not ops:
277 return expr
278 return nodes.Compare(expr, ops, lineno=lineno)
279
280 def parse_add(self):
281 lineno = self.stream.current.lineno
282 left = self.parse_sub()
283 while self.stream.current.type is 'add':
284 self.stream.next()
285 right = self.parse_sub()
286 left = nodes.Add(left, right, lineno=lineno)
287 lineno = self.stream.current.lineno
288 return left
289
290 def parse_sub(self):
291 lineno = self.stream.current.lineno
292 left = self.parse_concat()
293 while self.stream.current.type is 'sub':
294 self.stream.next()
295 right = self.parse_concat()
296 left = nodes.Sub(left, right, lineno=lineno)
297 lineno = self.stream.current.lineno
298 return left
299
300 def parse_concat(self):
301 lineno = self.stream.current.lineno
302 args = [self.parse_mul()]
303 while self.stream.current.type is 'tilde':
304 self.stream.next()
305 args.append(self.parse_mul())
306 if len(args) == 1:
307 return args[0]
308 return nodes.Concat(args, lineno=lineno)
309
310 def parse_mul(self):
311 lineno = self.stream.current.lineno
312 left = self.parse_div()
313 while self.stream.current.type is 'mul':
314 self.stream.next()
315 right = self.parse_div()
316 left = nodes.Mul(left, right, lineno=lineno)
317 lineno = self.stream.current.lineno
318 return left
319
320 def parse_div(self):
321 lineno = self.stream.current.lineno
322 left = self.parse_floordiv()
323 while self.stream.current.type is 'div':
324 self.stream.next()
325 right = self.parse_floordiv()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200326 left = nodes.Div(left, right, lineno=lineno)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200327 lineno = self.stream.current.lineno
328 return left
329
330 def parse_floordiv(self):
331 lineno = self.stream.current.lineno
332 left = self.parse_mod()
333 while self.stream.current.type is 'floordiv':
334 self.stream.next()
335 right = self.parse_mod()
336 left = nodes.FloorDiv(left, right, lineno=lineno)
337 lineno = self.stream.current.lineno
338 return left
339
340 def parse_mod(self):
341 lineno = self.stream.current.lineno
342 left = self.parse_pow()
343 while self.stream.current.type is 'mod':
344 self.stream.next()
345 right = self.parse_pow()
346 left = nodes.Mod(left, right, lineno=lineno)
347 lineno = self.stream.current.lineno
348 return left
349
350 def parse_pow(self):
351 lineno = self.stream.current.lineno
352 left = self.parse_unary()
353 while self.stream.current.type is 'pow':
354 self.stream.next()
355 right = self.parse_unary()
356 left = nodes.Pow(left, right, lineno=lineno)
357 lineno = self.stream.current.lineno
358 return left
359
360 def parse_unary(self):
361 token_type = self.stream.current.type
362 lineno = self.stream.current.lineno
363 if token_type is 'not':
364 self.stream.next()
365 node = self.parse_unary()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200366 return nodes.Not(node, lineno=lineno)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200367 if token_type is 'sub':
368 self.stream.next()
369 node = self.parse_unary()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200370 return nodes.Neg(node, lineno=lineno)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200371 if token_type is 'add':
372 self.stream.next()
373 node = self.parse_unary()
374 return nodes.Pos(node, lineno=lineno)
375 return self.parse_primary()
376
377 def parse_primary(self, parse_postfix=True):
378 token = self.stream.current
379 if token.type is 'name':
380 if token.value in ('true', 'false'):
381 node = nodes.Const(token.value == 'true', lineno=token.lineno)
382 elif token.value == 'none':
383 node = nodes.Const(None, lineno=token.lineno)
384 else:
385 node = nodes.Name(token.value, 'load', lineno=token.lineno)
386 self.stream.next()
387 elif token.type in ('integer', 'float', 'string'):
388 self.stream.next()
389 node = nodes.Const(token.value, lineno=token.lineno)
390 elif token.type is 'lparen':
391 self.stream.next()
392 node = self.parse_tuple()
393 self.stream.expect('rparen')
394 elif token.type is 'lbracket':
395 node = self.parse_list()
396 elif token.type is 'lbrace':
397 node = self.parse_dict()
398 else:
399 raise TemplateSyntaxError("unexpected token '%s'" %
400 (token,), token.lineno,
401 self.filename)
402 if parse_postfix:
403 node = self.parse_postfix(node)
404 return node
405
Armin Ronacher3d8b7842008-04-13 13:16:50 +0200406 def parse_tuple(self, enforce=False, simplified=False, no_condexpr=False):
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200407 """
408 Parse multiple expressions into a tuple. This can also return
409 just one expression which is not a tuple. If you want to enforce
410 a tuple, pass it enforce=True (currently unused).
411 """
412 lineno = self.stream.current.lineno
Armin Ronacher3d8b7842008-04-13 13:16:50 +0200413 if simplified:
414 parse = self.parse_primary
415 elif no_condexpr:
416 parse = lambda: self.parse_expression(no_condexpr=True)
417 else:
418 parse = self.parse_expression
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200419 args = []
420 is_tuple = False
421 while 1:
422 if args:
423 self.stream.expect('comma')
Armin Ronacherf59bac22008-04-20 13:11:43 +0200424 if self.stream.current.type in statement_end_tokens:
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200425 break
426 args.append(parse())
427 if self.stream.current.type is not 'comma':
428 break
429 is_tuple = True
430 lineno = self.stream.current.lineno
431 if not is_tuple and args:
432 if enforce:
433 raise TemplateSyntaxError('tuple expected', lineno,
434 self.filename)
435 return args[0]
436 return nodes.Tuple(args, 'load', lineno=lineno)
437
438 def parse_list(self):
439 token = self.stream.expect('lbracket')
440 items = []
441 while self.stream.current.type is not 'rbracket':
442 if items:
443 self.stream.expect('comma')
444 if self.stream.current.type == 'rbracket':
445 break
446 items.append(self.parse_expression())
447 self.stream.expect('rbracket')
448 return nodes.List(items, lineno=token.lineno)
449
450 def parse_dict(self):
451 token = self.stream.expect('lbrace')
452 items = []
453 while self.stream.current.type is not 'rbrace':
454 if items:
455 self.stream.expect('comma')
456 if self.stream.current.type == 'rbrace':
457 break
458 key = self.parse_expression()
459 self.stream.expect('colon')
460 value = self.parse_expression()
461 items.append(nodes.Pair(key, value, lineno=key.lineno))
462 self.stream.expect('rbrace')
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200463 return nodes.Dict(items, lineno=token.lineno)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200464
465 def parse_postfix(self, node):
466 while 1:
467 token_type = self.stream.current.type
468 if token_type is 'dot' or token_type is 'lbracket':
469 node = self.parse_subscript(node)
470 elif token_type is 'lparen':
471 node = self.parse_call(node)
472 elif token_type is 'pipe':
473 node = self.parse_filter(node)
474 elif token_type is 'is':
475 node = self.parse_test(node)
476 else:
477 break
478 return node
479
480 def parse_subscript(self, node):
481 token = self.stream.next()
482 if token.type is 'dot':
Armin Ronachere791c2a2008-04-07 18:39:54 +0200483 attr_token = self.stream.current
484 if attr_token.type not in ('name', 'integer'):
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200485 raise TemplateSyntaxError('expected name or number',
Armin Ronachere791c2a2008-04-07 18:39:54 +0200486 attr_token.lineno, self.filename)
487 arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200488 self.stream.next()
489 elif token.type is 'lbracket':
490 args = []
491 while self.stream.current.type is not 'rbracket':
492 if args:
493 self.stream.expect('comma')
494 args.append(self.parse_subscribed())
495 self.stream.expect('rbracket')
496 if len(args) == 1:
497 arg = args[0]
498 else:
499 arg = nodes.Tuple(args, lineno, self.filename)
500 else:
501 raise TemplateSyntaxError('expected subscript expression',
502 self.lineno, self.filename)
503 return nodes.Subscript(node, arg, 'load', lineno=token.lineno)
504
505 def parse_subscribed(self):
506 lineno = self.stream.current.lineno
507
508 if self.stream.current.type is 'colon':
509 self.stream.next()
510 args = [None]
511 else:
512 node = self.parse_expression()
513 if self.stream.current.type is not 'colon':
514 return node
515 self.stream.next()
516 args = [node]
517
518 if self.stream.current.type is 'colon':
519 args.append(None)
520 elif self.stream.current.type not in ('rbracket', 'comma'):
521 args.append(self.parse_expression())
522 else:
523 args.append(None)
524
525 if self.stream.current.type is 'colon':
526 self.stream.next()
527 if self.stream.current.type not in ('rbracket', 'comma'):
528 args.append(self.parse_expression())
529 else:
530 args.append(None)
531 else:
532 args.append(None)
533
534 return nodes.Slice(lineno=lineno, *args)
535
536 def parse_call(self, node):
537 token = self.stream.expect('lparen')
538 args = []
539 kwargs = []
540 dyn_args = dyn_kwargs = None
541 require_comma = False
542
543 def ensure(expr):
544 if not expr:
545 raise TemplateSyntaxError('invalid syntax for function '
546 'call expression', token.lineno,
547 self.filename)
548
549 while self.stream.current.type is not 'rparen':
550 if require_comma:
551 self.stream.expect('comma')
552 # support for trailing comma
553 if self.stream.current.type is 'rparen':
554 break
555 if self.stream.current.type is 'mul':
556 ensure(dyn_args is None and dyn_kwargs is None)
557 self.stream.next()
558 dyn_args = self.parse_expression()
559 elif self.stream.current.type is 'pow':
560 ensure(dyn_kwargs is None)
561 self.stream.next()
562 dyn_kwargs = self.parse_expression()
563 else:
564 ensure(dyn_args is None and dyn_kwargs is None)
565 if self.stream.current.type is 'name' and \
566 self.stream.look().type is 'assign':
567 key = self.stream.current.value
568 self.stream.skip(2)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200569 kwargs.append(nodes.Keyword(key, self.parse_expression(),
570 lineno=key.lineno))
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200571 else:
572 ensure(not kwargs)
573 args.append(self.parse_expression())
574
575 require_comma = True
576 self.stream.expect('rparen')
577
578 if node is None:
579 return args, kwargs, dyn_args, dyn_kwargs
580 return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
581 lineno=token.lineno)
582
Armin Ronacherfa865fb2008-04-12 22:11:53 +0200583 def parse_filter(self, node, start_inline=False):
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200584 lineno = self.stream.current.type
Armin Ronacherfa865fb2008-04-12 22:11:53 +0200585 while self.stream.current.type == 'pipe' or start_inline:
586 if not start_inline:
587 self.stream.next()
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200588 token = self.stream.expect('name')
589 if self.stream.current.type is 'lparen':
590 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
591 else:
592 args = []
593 kwargs = []
594 dyn_args = dyn_kwargs = None
Armin Ronacherd55ab532008-04-09 16:13:39 +0200595 node = nodes.Filter(node, token.value, args, kwargs, dyn_args,
596 dyn_kwargs, lineno=token.lineno)
Armin Ronacherfa865fb2008-04-12 22:11:53 +0200597 start_inline = False
Armin Ronacherd55ab532008-04-09 16:13:39 +0200598 return node
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200599
600 def parse_test(self, node):
601 token = self.stream.expect('is')
602 if self.stream.current.type is 'not':
603 self.stream.next()
604 negated = True
605 else:
606 negated = False
607 name = self.stream.expect('name').value
Armin Ronacher10f3ba22008-04-18 11:30:37 +0200608 dyn_args = dyn_kwargs = None
609 kwargs = []
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200610 if self.stream.current.type is 'lparen':
611 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
612 elif self.stream.current.type in ('name', 'string', 'integer',
613 'float', 'lparen', 'lbracket',
614 'lbrace', 'regex'):
615 args = [self.parse_expression()]
616 else:
617 args = []
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200618 node = nodes.Test(node, name, args, kwargs, dyn_args,
619 dyn_kwargs, lineno=token.lineno)
620 if negated:
Armin Ronacher9a822052008-04-17 18:44:07 +0200621 node = nodes.Not(node, lineno=token.lineno)
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200622 return node
623
624 def subparse(self, end_tokens=None):
625 body = []
626 data_buffer = []
627 add_data = data_buffer.append
628
629 def flush_data():
630 if data_buffer:
631 lineno = data_buffer[0].lineno
632 body.append(nodes.Output(data_buffer[:], lineno=lineno))
633 del data_buffer[:]
634
635 while self.stream:
636 token = self.stream.current
637 if token.type is 'data':
Armin Ronacherfa865fb2008-04-12 22:11:53 +0200638 if token.value:
639 add_data(nodes.Const(token.value, lineno=token.lineno))
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200640 self.stream.next()
641 elif token.type is 'variable_begin':
642 self.stream.next()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200643 want_comma = False
Armin Ronacherf59bac22008-04-20 13:11:43 +0200644 while not self.stream.current.test_many(statement_end_tokens):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200645 if want_comma:
646 self.stream.expect('comma')
647 add_data(self.parse_expression())
648 want_comma = True
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200649 self.stream.expect('variable_end')
650 elif token.type is 'block_begin':
651 flush_data()
652 self.stream.next()
653 if end_tokens is not None and \
Armin Ronacherf59bac22008-04-20 13:11:43 +0200654 self.stream.current.test_many(end_tokens):
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200655 return body
656 while self.stream.current.type is not 'block_end':
657 body.append(self.parse_statement())
658 self.stream.expect('block_end')
659 else:
660 raise AssertionError('internal parsing error')
661
662 flush_data()
663 return body
664
665 def parse(self):
666 """Parse the whole template into a `Template` node."""
Armin Ronacherd55ab532008-04-09 16:13:39 +0200667 result = nodes.Template(self.subparse(), lineno=1)
668 result.set_environment(self.environment)
669 return result