1 # -*- coding: utf-8 -*-
6 Implements the template parser.
8 :copyright: 2008 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
11 from operator import itemgetter
12 from jinja2 import nodes
13 from jinja2.exceptions import TemplateSyntaxError
16 _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
18 _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
19 statement_end_tokens = set(['variable_end', 'block_end', 'in'])
23 """The template parser class.
25 Transforms sourcecode into an abstract syntax tree.
28 def __init__(self, environment, source, filename=None):
29 self.environment = environment
30 if isinstance(filename, unicode):
31 filename = filename.encode('utf-8')
32 self.source = unicode(source)
33 self.filename = filename
35 self.stream = environment.lexer.tokenize(source, filename)
37 for extension in environment.extensions:
38 for tag in extension.tags:
39 self.extensions[tag] = extension.parse
41 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':
47 return self.parse_call_block()
48 elif token_type is 'filter':
49 return self.parse_filter_block()
50 elif token_type is 'name':
51 ext = self.extensions.get(self.stream.current.value)
54 lineno = self.stream.current
55 expr = self.parse_tuple()
56 if self.stream.current.type == 'assign':
57 result = self.parse_assign(expr)
59 result = nodes.ExprStmt(expr, lineno=lineno)
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'" %
67 target, target.lineno,
69 expr = self.parse_tuple()
70 target.set_ctx('store')
71 return nodes.Assign(target, expr, lineno=lineno)
73 def parse_statements(self, end_tokens, drop_needle=False):
74 """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.
78 # the first token may be a colon for python compatibility
79 if self.stream.current.type is 'colon':
82 # 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)
92 """Parse a for loop."""
93 lineno = self.stream.expect('for').lineno
94 target = self.parse_tuple(simplified=True)
95 if not target.can_assign():
96 raise TemplateSyntaxError("can't assign to '%s'" %
97 target, target.lineno,
99 target.set_ctx('store')
100 self.stream.expect('in')
101 iter = self.parse_tuple(no_condexpr=True)
103 if self.stream.current.type is 'if':
105 test = self.parse_expression()
106 body = self.parse_statements(('endfor', 'else'))
107 if self.stream.next().type is 'endfor':
110 else_ = self.parse_statements(('endfor',), drop_needle=True)
111 return nodes.For(target, iter, body, else_, test, lineno=lineno)
114 """Parse an if construct."""
115 node = result = nodes.If(lineno=self.stream.expect('if').lineno)
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]
126 elif token_type is 'else':
127 node.else_ = self.parse_statements(('endif',),
134 def parse_block(self):
135 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)
140 def parse_extends(self):
141 node = nodes.Extends(lineno=self.stream.expect('extends').lineno)
142 node.template = self.parse_expression()
145 def parse_include(self):
146 node = nodes.Include(lineno=self.stream.expect('include').lineno)
147 expr = self.parse_expression()
148 if self.stream.current.type is 'assign':
150 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():
155 raise TemplateSyntaxError('can\'t assign imported template '
156 'to %r' % expr, expr.lineno,
158 node.target = expr.name
159 node.template = self.parse_expression()
165 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':
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,
178 if self.stream.current.type is 'assign':
180 defaults.append(self.parse_expression())
182 self.stream.expect('rparen')
184 def parse_call_block(self):
185 node = nodes.CallBlock(lineno=self.stream.expect('call').lineno)
186 if self.stream.current.type is 'lparen':
187 self.parse_signature(node)
189 node.call = self.parse_expression()
190 if not isinstance(node.call, nodes.Call):
191 raise TemplateSyntaxError('expected call', node.lineno,
193 node.body = self.parse_statements(('endcall',), drop_needle=True)
196 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)
202 def parse_macro(self):
203 node = nodes.Macro(lineno=self.stream.expect('macro').lineno)
204 node.name = self.stream.expect('name').value
205 # 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,
210 self.parse_signature(node)
211 node.body = self.parse_statements(('endmacro',), drop_needle=True)
214 def parse_print(self):
215 node = nodes.Output(lineno=self.stream.expect('print').lineno)
217 while self.stream.current.type not in statement_end_tokens:
219 self.stream.expect('comma')
220 node.nodes.append(self.parse_expression())
223 def parse_expression(self, no_condexpr=False):
224 """Parse an expression."""
226 return self.parse_or()
227 return self.parse_condexpr()
229 def parse_condexpr(self):
230 lineno = self.stream.current.lineno
231 expr1 = self.parse_or()
232 while self.stream.current.type is 'if':
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
242 lineno = self.stream.current.lineno
243 left = self.parse_and()
244 while self.stream.current.type is 'or':
246 right = self.parse_and()
247 left = nodes.Or(left, right, lineno=lineno)
248 lineno = self.stream.current.lineno
252 lineno = self.stream.current.lineno
253 left = self.parse_compare()
254 while self.stream.current.type is 'and':
256 right = self.parse_compare()
257 left = nodes.And(left, right, lineno=lineno)
258 lineno = self.stream.current.lineno
261 def parse_compare(self):
262 lineno = self.stream.current.lineno
263 expr = self.parse_add()
266 token_type = self.stream.current.type
267 if token_type in _compare_operators:
269 ops.append(nodes.Operand(token_type, self.parse_add()))
270 elif token_type is 'not' and self.stream.look().type is 'in':
272 ops.append(nodes.Operand('notin', self.parse_add()))
275 lineno = self.stream.current.lineno
278 return nodes.Compare(expr, ops, lineno=lineno)
281 lineno = self.stream.current.lineno
282 left = self.parse_sub()
283 while self.stream.current.type is 'add':
285 right = self.parse_sub()
286 left = nodes.Add(left, right, lineno=lineno)
287 lineno = self.stream.current.lineno
291 lineno = self.stream.current.lineno
292 left = self.parse_concat()
293 while self.stream.current.type is 'sub':
295 right = self.parse_concat()
296 left = nodes.Sub(left, right, lineno=lineno)
297 lineno = self.stream.current.lineno
300 def parse_concat(self):
301 lineno = self.stream.current.lineno
302 args = [self.parse_mul()]
303 while self.stream.current.type is 'tilde':
305 args.append(self.parse_mul())
308 return nodes.Concat(args, lineno=lineno)
311 lineno = self.stream.current.lineno
312 left = self.parse_div()
313 while self.stream.current.type is 'mul':
315 right = self.parse_div()
316 left = nodes.Mul(left, right, lineno=lineno)
317 lineno = self.stream.current.lineno
321 lineno = self.stream.current.lineno
322 left = self.parse_floordiv()
323 while self.stream.current.type is 'div':
325 right = self.parse_floordiv()
326 left = nodes.Div(left, right, lineno=lineno)
327 lineno = self.stream.current.lineno
330 def parse_floordiv(self):
331 lineno = self.stream.current.lineno
332 left = self.parse_mod()
333 while self.stream.current.type is 'floordiv':
335 right = self.parse_mod()
336 left = nodes.FloorDiv(left, right, lineno=lineno)
337 lineno = self.stream.current.lineno
341 lineno = self.stream.current.lineno
342 left = self.parse_pow()
343 while self.stream.current.type is 'mod':
345 right = self.parse_pow()
346 left = nodes.Mod(left, right, lineno=lineno)
347 lineno = self.stream.current.lineno
351 lineno = self.stream.current.lineno
352 left = self.parse_unary()
353 while self.stream.current.type is 'pow':
355 right = self.parse_unary()
356 left = nodes.Pow(left, right, lineno=lineno)
357 lineno = self.stream.current.lineno
360 def parse_unary(self):
361 token_type = self.stream.current.type
362 lineno = self.stream.current.lineno
363 if token_type is 'not':
365 node = self.parse_unary()
366 return nodes.Not(node, lineno=lineno)
367 if token_type is 'sub':
369 node = self.parse_unary()
370 return nodes.Neg(node, lineno=lineno)
371 if token_type is 'add':
373 node = self.parse_unary()
374 return nodes.Pos(node, lineno=lineno)
375 return self.parse_primary()
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)
385 node = nodes.Name(token.value, 'load', lineno=token.lineno)
387 elif token.type in ('integer', 'float', 'string'):
389 node = nodes.Const(token.value, lineno=token.lineno)
390 elif token.type is 'lparen':
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()
399 raise TemplateSyntaxError("unexpected token '%s'" %
400 (token,), token.lineno,
403 node = self.parse_postfix(node)
406 def parse_tuple(self, enforce=False, simplified=False, no_condexpr=False):
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).
412 lineno = self.stream.current.lineno
414 parse = self.parse_primary
416 parse = lambda: self.parse_expression(no_condexpr=True)
418 parse = self.parse_expression
423 self.stream.expect('comma')
424 if self.stream.current.type in statement_end_tokens:
427 if self.stream.current.type is not 'comma':
430 lineno = self.stream.current.lineno
431 if not is_tuple and args:
433 raise TemplateSyntaxError('tuple expected', lineno,
436 return nodes.Tuple(args, 'load', lineno=lineno)
438 def parse_list(self):
439 token = self.stream.expect('lbracket')
441 while self.stream.current.type is not 'rbracket':
443 self.stream.expect('comma')
444 if self.stream.current.type == 'rbracket':
446 items.append(self.parse_expression())
447 self.stream.expect('rbracket')
448 return nodes.List(items, lineno=token.lineno)
450 def parse_dict(self):
451 token = self.stream.expect('lbrace')
453 while self.stream.current.type is not 'rbrace':
455 self.stream.expect('comma')
456 if self.stream.current.type == 'rbrace':
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')
463 return nodes.Dict(items, lineno=token.lineno)
465 def parse_postfix(self, node):
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)
480 def parse_subscript(self, node):
481 token = self.stream.next()
482 if token.type is 'dot':
483 attr_token = self.stream.current
484 if attr_token.type not in ('name', 'integer'):
485 raise TemplateSyntaxError('expected name or number',
486 attr_token.lineno, self.filename)
487 arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
489 elif token.type is 'lbracket':
491 while self.stream.current.type is not 'rbracket':
493 self.stream.expect('comma')
494 args.append(self.parse_subscribed())
495 self.stream.expect('rbracket')
499 arg = nodes.Tuple(args, lineno, self.filename)
501 raise TemplateSyntaxError('expected subscript expression',
502 self.lineno, self.filename)
503 return nodes.Subscript(node, arg, 'load', lineno=token.lineno)
505 def parse_subscribed(self):
506 lineno = self.stream.current.lineno
508 if self.stream.current.type is 'colon':
512 node = self.parse_expression()
513 if self.stream.current.type is not 'colon':
518 if self.stream.current.type is 'colon':
520 elif self.stream.current.type not in ('rbracket', 'comma'):
521 args.append(self.parse_expression())
525 if self.stream.current.type is 'colon':
527 if self.stream.current.type not in ('rbracket', 'comma'):
528 args.append(self.parse_expression())
534 return nodes.Slice(lineno=lineno, *args)
536 def parse_call(self, node):
537 token = self.stream.expect('lparen')
540 dyn_args = dyn_kwargs = None
541 require_comma = False
545 raise TemplateSyntaxError('invalid syntax for function '
546 'call expression', token.lineno,
549 while self.stream.current.type is not 'rparen':
551 self.stream.expect('comma')
552 # support for trailing comma
553 if self.stream.current.type is 'rparen':
555 if self.stream.current.type is 'mul':
556 ensure(dyn_args is None and dyn_kwargs is None)
558 dyn_args = self.parse_expression()
559 elif self.stream.current.type is 'pow':
560 ensure(dyn_kwargs is None)
562 dyn_kwargs = self.parse_expression()
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
569 kwargs.append(nodes.Keyword(key, self.parse_expression(),
573 args.append(self.parse_expression())
576 self.stream.expect('rparen')
579 return args, kwargs, dyn_args, dyn_kwargs
580 return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
583 def parse_filter(self, node, start_inline=False):
584 lineno = self.stream.current.type
585 while self.stream.current.type == 'pipe' or start_inline:
588 token = self.stream.expect('name')
589 if self.stream.current.type is 'lparen':
590 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
594 dyn_args = dyn_kwargs = None
595 node = nodes.Filter(node, token.value, args, kwargs, dyn_args,
596 dyn_kwargs, lineno=token.lineno)
600 def parse_test(self, node):
601 token = self.stream.expect('is')
602 if self.stream.current.type is 'not':
607 name = self.stream.expect('name').value
608 dyn_args = dyn_kwargs = None
610 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',
615 args = [self.parse_expression()]
618 node = nodes.Test(node, name, args, kwargs, dyn_args,
619 dyn_kwargs, lineno=token.lineno)
621 node = nodes.Not(node, lineno=token.lineno)
624 def subparse(self, end_tokens=None):
627 add_data = data_buffer.append
631 lineno = data_buffer[0].lineno
632 body.append(nodes.Output(data_buffer[:], lineno=lineno))
636 token = self.stream.current
637 if token.type is 'data':
639 add_data(nodes.Const(token.value, lineno=token.lineno))
641 elif token.type is 'variable_begin':
644 while not self.stream.current.test_many(statement_end_tokens):
646 self.stream.expect('comma')
647 add_data(self.parse_expression())
649 self.stream.expect('variable_end')
650 elif token.type is 'block_begin':
653 if end_tokens is not None and \
654 self.stream.current.test_many(end_tokens):
656 while self.stream.current.type is not 'block_end':
657 body.append(self.parse_statement())
658 self.stream.expect('block_end')
660 raise AssertionError('internal parsing error')
666 """Parse the whole template into a `Template` node."""
667 result = nodes.Template(self.subparse(), lineno=1)
668 result.set_environment(self.environment)