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_end_tokens = set(['variable_end', 'block_end', 'in'])
17 _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
18 'macro', 'include', 'from', 'import'])
19 _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
20 _tuple_edge_tokens = set(['rparen']) | statement_end_tokens
24 """The template parser class.
26 Transforms sourcecode into an abstract syntax tree.
29 def __init__(self, environment, source, filename=None):
30 self.environment = environment
31 if isinstance(filename, unicode):
32 filename = filename.encode('utf-8')
33 self.source = unicode(source)
34 self.filename = filename
36 self.stream = environment.lexer.tokenize(source, filename)
38 for extension in environment.extensions:
39 for tag in extension.tags:
40 self.extensions[tag] = extension.parse
42 def parse_statement(self):
43 """Parse a single statement."""
44 token_type = self.stream.current.type
45 if token_type in _statement_keywords:
46 return getattr(self, 'parse_' + token_type)()
47 elif token_type is 'call':
48 return self.parse_call_block()
49 elif token_type is 'filter':
50 return self.parse_filter_block()
51 elif token_type is 'name':
52 ext = self.extensions.get(self.stream.current.value)
55 lineno = self.stream.current
56 expr = self.parse_tuple()
57 if self.stream.current.type == 'assign':
58 result = self.parse_assign(expr)
60 result = nodes.ExprStmt(expr, lineno=lineno)
63 def parse_assign(self, target):
64 """Parse an assign statement."""
65 lineno = self.stream.expect('assign').lineno
66 if not target.can_assign():
67 raise TemplateSyntaxError("can't assign to '%s'" %
68 target.__class__.__name__.lower(),
69 target.lineno, self.filename)
70 expr = self.parse_tuple()
71 target.set_ctx('store')
72 return nodes.Assign(target, expr, lineno=lineno)
74 def parse_statements(self, end_tokens, drop_needle=False):
75 """Parse multiple statements into a list until one of the end tokens
76 is reached. This is used to parse the body of statements as it also
77 parses template data if appropriate.
79 # the first token may be a colon for python compatibility
80 if self.stream.current.type is 'colon':
83 # in the future it would be possible to add whole code sections
84 # by adding some sort of end of statement token and parsing those here.
85 self.stream.expect('block_end')
86 result = self.subparse(end_tokens)
93 """Parse a for loop."""
94 lineno = self.stream.expect('for').lineno
95 target = self.parse_tuple(simplified=True)
96 if not target.can_assign():
97 raise TemplateSyntaxError("can't assign to '%s'" %
98 target.__class__.__name__.lower(),
99 target.lineno, self.filename)
100 target.set_ctx('store')
101 self.stream.expect('in')
102 iter = self.parse_tuple(no_condexpr=True)
104 if self.stream.current.type is 'if':
106 test = self.parse_expression()
107 body = self.parse_statements(('endfor', 'else'))
108 if self.stream.next().type is 'endfor':
111 else_ = self.parse_statements(('endfor',), drop_needle=True)
112 return nodes.For(target, iter, body, else_, test, lineno=lineno)
115 """Parse an if construct."""
116 node = result = nodes.If(lineno=self.stream.expect('if').lineno)
118 # TODO: exclude conditional expressions here
119 node.test = self.parse_tuple()
120 node.body = self.parse_statements(('elif', 'else', 'endif'))
121 token_type = self.stream.next().type
122 if token_type is 'elif':
123 new_node = nodes.If(lineno=self.stream.current.lineno)
124 node.else_ = [new_node]
127 elif token_type is 'else':
128 node.else_ = self.parse_statements(('endif',),
135 def parse_block(self):
136 node = nodes.Block(lineno=self.stream.expect('block').lineno)
137 node.name = self.stream.expect('name').value
138 node.body = self.parse_statements(('endblock',), drop_needle=True)
141 def parse_extends(self):
142 node = nodes.Extends(lineno=self.stream.expect('extends').lineno)
143 node.template = self.parse_expression()
146 def parse_include(self):
147 node = nodes.Include(lineno=self.stream.expect('include').lineno)
148 node.template = self.parse_expression()
151 def parse_import(self):
152 node = nodes.Import(lineno=self.stream.expect('import').lineno)
153 node.template = self.parse_expression()
154 self.stream.expect('name:as')
155 node.target = self.stream.expect('name').value
156 if not nodes.Name(node.target, 'store').can_assign():
157 raise TemplateSyntaxError('can\'t assign imported template '
158 'to %r' % node.target, node.lineno,
162 def parse_from(self):
163 node = nodes.FromImport(lineno=self.stream.expect('from').lineno)
164 node.template = self.parse_expression()
165 self.stream.expect('import')
169 self.stream.expect('comma')
170 if self.stream.current.type is 'name':
171 target = nodes.Name(self.stream.current.value, 'store')
172 if not target.can_assign():
173 raise TemplateSyntaxError('can\'t import object named %r'
174 % target.name, target.lineno,
176 elif target.name.startswith('__'):
177 raise TemplateAssertionError('names starting with two '
178 'underscores can not be '
179 'imported', target.lineno,
182 if self.stream.current.test('name:as'):
184 alias = self.stream.expect('name')
185 if not nodes.Name(alias.value, 'store').can_assign():
186 raise TemplateSyntaxError('can\'t name imported '
187 'object %r.' % alias.value,
188 alias.lineno, self.filename)
189 node.names.append((target.name, alias.value))
191 node.names.append(target.name)
192 if self.stream.current.type is not 'comma':
196 if self.stream.current.type is 'comma':
200 def parse_signature(self, node):
201 node.args = args = []
202 node.defaults = defaults = []
203 self.stream.expect('lparen')
204 while self.stream.current.type is not 'rparen':
206 self.stream.expect('comma')
207 token = self.stream.expect('name')
208 arg = nodes.Name(token.value, 'param', lineno=token.lineno)
209 if not arg.can_assign():
210 raise TemplateSyntaxError("can't assign to '%s'" %
211 arg.name, arg.lineno,
213 if self.stream.current.type is 'assign':
215 defaults.append(self.parse_expression())
217 self.stream.expect('rparen')
219 def parse_call_block(self):
220 node = nodes.CallBlock(lineno=self.stream.expect('call').lineno)
221 if self.stream.current.type is 'lparen':
222 self.parse_signature(node)
224 node.call = self.parse_expression()
225 if not isinstance(node.call, nodes.Call):
226 raise TemplateSyntaxError('expected call', node.lineno,
228 node.body = self.parse_statements(('endcall',), drop_needle=True)
231 def parse_filter_block(self):
232 node = nodes.FilterBlock(lineno=self.stream.expect('filter').lineno)
233 node.filter = self.parse_filter(None, start_inline=True)
234 node.body = self.parse_statements(('endfilter',), drop_needle=True)
237 def parse_macro(self):
238 node = nodes.Macro(lineno=self.stream.expect('macro').lineno)
239 node.name = self.stream.expect('name').value
240 # make sure that assignments to that name are allowed
241 if not nodes.Name(node.name, 'store').can_assign():
242 raise TemplateSyntaxError('can\'t assign macro to %r' %
243 node.target, node.lineno,
245 self.parse_signature(node)
246 node.body = self.parse_statements(('endmacro',), drop_needle=True)
249 def parse_print(self):
250 node = nodes.Output(lineno=self.stream.expect('print').lineno)
252 while self.stream.current.type not in statement_end_tokens:
254 self.stream.expect('comma')
255 node.nodes.append(self.parse_expression())
258 def parse_expression(self, no_condexpr=False):
259 """Parse an expression."""
261 return self.parse_or()
262 return self.parse_condexpr()
264 def parse_condexpr(self):
265 lineno = self.stream.current.lineno
266 expr1 = self.parse_or()
267 while self.stream.current.type is 'if':
269 expr2 = self.parse_or()
270 self.stream.expect('else')
271 expr3 = self.parse_condexpr()
272 expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
273 lineno = self.stream.current.lineno
277 lineno = self.stream.current.lineno
278 left = self.parse_and()
279 while self.stream.current.type is 'or':
281 right = self.parse_and()
282 left = nodes.Or(left, right, lineno=lineno)
283 lineno = self.stream.current.lineno
287 lineno = self.stream.current.lineno
288 left = self.parse_compare()
289 while self.stream.current.type is 'and':
291 right = self.parse_compare()
292 left = nodes.And(left, right, lineno=lineno)
293 lineno = self.stream.current.lineno
296 def parse_compare(self):
297 lineno = self.stream.current.lineno
298 expr = self.parse_add()
301 token_type = self.stream.current.type
302 if token_type in _compare_operators:
304 ops.append(nodes.Operand(token_type, self.parse_add()))
305 elif token_type is 'not' and self.stream.look().type is 'in':
307 ops.append(nodes.Operand('notin', self.parse_add()))
310 lineno = self.stream.current.lineno
313 return nodes.Compare(expr, ops, lineno=lineno)
316 lineno = self.stream.current.lineno
317 left = self.parse_sub()
318 while self.stream.current.type is 'add':
320 right = self.parse_sub()
321 left = nodes.Add(left, right, lineno=lineno)
322 lineno = self.stream.current.lineno
326 lineno = self.stream.current.lineno
327 left = self.parse_concat()
328 while self.stream.current.type is 'sub':
330 right = self.parse_concat()
331 left = nodes.Sub(left, right, lineno=lineno)
332 lineno = self.stream.current.lineno
335 def parse_concat(self):
336 lineno = self.stream.current.lineno
337 args = [self.parse_mul()]
338 while self.stream.current.type is 'tilde':
340 args.append(self.parse_mul())
343 return nodes.Concat(args, lineno=lineno)
346 lineno = self.stream.current.lineno
347 left = self.parse_div()
348 while self.stream.current.type is 'mul':
350 right = self.parse_div()
351 left = nodes.Mul(left, right, lineno=lineno)
352 lineno = self.stream.current.lineno
356 lineno = self.stream.current.lineno
357 left = self.parse_floordiv()
358 while self.stream.current.type is 'div':
360 right = self.parse_floordiv()
361 left = nodes.Div(left, right, lineno=lineno)
362 lineno = self.stream.current.lineno
365 def parse_floordiv(self):
366 lineno = self.stream.current.lineno
367 left = self.parse_mod()
368 while self.stream.current.type is 'floordiv':
370 right = self.parse_mod()
371 left = nodes.FloorDiv(left, right, lineno=lineno)
372 lineno = self.stream.current.lineno
376 lineno = self.stream.current.lineno
377 left = self.parse_pow()
378 while self.stream.current.type is 'mod':
380 right = self.parse_pow()
381 left = nodes.Mod(left, right, lineno=lineno)
382 lineno = self.stream.current.lineno
386 lineno = self.stream.current.lineno
387 left = self.parse_unary()
388 while self.stream.current.type is 'pow':
390 right = self.parse_unary()
391 left = nodes.Pow(left, right, lineno=lineno)
392 lineno = self.stream.current.lineno
395 def parse_unary(self):
396 token_type = self.stream.current.type
397 lineno = self.stream.current.lineno
398 if token_type is 'not':
400 node = self.parse_unary()
401 return nodes.Not(node, lineno=lineno)
402 if token_type is 'sub':
404 node = self.parse_unary()
405 return nodes.Neg(node, lineno=lineno)
406 if token_type is 'add':
408 node = self.parse_unary()
409 return nodes.Pos(node, lineno=lineno)
410 return self.parse_primary()
412 def parse_primary(self, parse_postfix=True):
413 token = self.stream.current
414 if token.type is 'name':
415 if token.value in ('true', 'false'):
416 node = nodes.Const(token.value == 'true', lineno=token.lineno)
417 elif token.value == 'none':
418 node = nodes.Const(None, lineno=token.lineno)
420 node = nodes.Name(token.value, 'load', lineno=token.lineno)
422 elif token.type in ('integer', 'float', 'string'):
424 node = nodes.Const(token.value, lineno=token.lineno)
425 elif token.type is 'lparen':
427 node = self.parse_tuple()
428 self.stream.expect('rparen')
429 elif token.type is 'lbracket':
430 node = self.parse_list()
431 elif token.type is 'lbrace':
432 node = self.parse_dict()
434 raise TemplateSyntaxError("unexpected token '%s'" %
435 (token,), token.lineno,
438 node = self.parse_postfix(node)
441 def parse_tuple(self, enforce=False, simplified=False, no_condexpr=False):
443 Parse multiple expressions into a tuple. This can also return
444 just one expression which is not a tuple. If you want to enforce
445 a tuple, pass it enforce=True (currently unused).
447 lineno = self.stream.current.lineno
449 parse = self.parse_primary
451 parse = lambda: self.parse_expression(no_condexpr=True)
453 parse = self.parse_expression
458 self.stream.expect('comma')
459 if self.stream.current.type in _tuple_edge_tokens:
462 if self.stream.current.type is 'comma':
466 lineno = self.stream.current.lineno
467 if not is_tuple and args:
469 raise TemplateSyntaxError('tuple expected', lineno,
472 return nodes.Tuple(args, 'load', lineno=lineno)
474 def parse_list(self):
475 token = self.stream.expect('lbracket')
477 while self.stream.current.type is not 'rbracket':
479 self.stream.expect('comma')
480 if self.stream.current.type == 'rbracket':
482 items.append(self.parse_expression())
483 self.stream.expect('rbracket')
484 return nodes.List(items, lineno=token.lineno)
486 def parse_dict(self):
487 token = self.stream.expect('lbrace')
489 while self.stream.current.type is not 'rbrace':
491 self.stream.expect('comma')
492 if self.stream.current.type == 'rbrace':
494 key = self.parse_expression()
495 self.stream.expect('colon')
496 value = self.parse_expression()
497 items.append(nodes.Pair(key, value, lineno=key.lineno))
498 self.stream.expect('rbrace')
499 return nodes.Dict(items, lineno=token.lineno)
501 def parse_postfix(self, node):
503 token_type = self.stream.current.type
504 if token_type is 'dot' or token_type is 'lbracket':
505 node = self.parse_subscript(node)
506 elif token_type is 'lparen':
507 node = self.parse_call(node)
508 elif token_type is 'pipe':
509 node = self.parse_filter(node)
510 elif token_type is 'is':
511 node = self.parse_test(node)
516 def parse_subscript(self, node):
517 token = self.stream.next()
518 if token.type is 'dot':
519 attr_token = self.stream.current
520 if attr_token.type not in ('name', 'integer'):
521 raise TemplateSyntaxError('expected name or number',
522 attr_token.lineno, self.filename)
523 arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
525 elif token.type is 'lbracket':
527 while self.stream.current.type is not 'rbracket':
529 self.stream.expect('comma')
530 args.append(self.parse_subscribed())
531 self.stream.expect('rbracket')
535 arg = nodes.Tuple(args, lineno, self.filename)
537 raise TemplateSyntaxError('expected subscript expression',
538 self.lineno, self.filename)
539 return nodes.Subscript(node, arg, 'load', lineno=token.lineno)
541 def parse_subscribed(self):
542 lineno = self.stream.current.lineno
544 if self.stream.current.type is 'colon':
548 node = self.parse_expression()
549 if self.stream.current.type is not 'colon':
554 if self.stream.current.type is 'colon':
556 elif self.stream.current.type not in ('rbracket', 'comma'):
557 args.append(self.parse_expression())
561 if self.stream.current.type is 'colon':
563 if self.stream.current.type not in ('rbracket', 'comma'):
564 args.append(self.parse_expression())
570 return nodes.Slice(lineno=lineno, *args)
572 def parse_call(self, node):
573 token = self.stream.expect('lparen')
576 dyn_args = dyn_kwargs = None
577 require_comma = False
581 raise TemplateSyntaxError('invalid syntax for function '
582 'call expression', token.lineno,
585 while self.stream.current.type is not 'rparen':
587 self.stream.expect('comma')
588 # support for trailing comma
589 if self.stream.current.type is 'rparen':
591 if self.stream.current.type is 'mul':
592 ensure(dyn_args is None and dyn_kwargs is None)
594 dyn_args = self.parse_expression()
595 elif self.stream.current.type is 'pow':
596 ensure(dyn_kwargs is None)
598 dyn_kwargs = self.parse_expression()
600 ensure(dyn_args is None and dyn_kwargs is None)
601 if self.stream.current.type is 'name' and \
602 self.stream.look().type is 'assign':
603 key = self.stream.current.value
605 value = self.parse_expression()
606 kwargs.append(nodes.Keyword(key, value,
607 lineno=value.lineno))
610 args.append(self.parse_expression())
613 self.stream.expect('rparen')
616 return args, kwargs, dyn_args, dyn_kwargs
617 return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
620 def parse_filter(self, node, start_inline=False):
621 lineno = self.stream.current.type
622 while self.stream.current.type == 'pipe' or start_inline:
625 token = self.stream.expect('name')
626 if self.stream.current.type is 'lparen':
627 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
631 dyn_args = dyn_kwargs = None
632 node = nodes.Filter(node, token.value, args, kwargs, dyn_args,
633 dyn_kwargs, lineno=token.lineno)
637 def parse_test(self, node):
638 token = self.stream.expect('is')
639 if self.stream.current.type is 'not':
644 name = self.stream.expect('name').value
645 dyn_args = dyn_kwargs = None
647 if self.stream.current.type is 'lparen':
648 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
649 elif self.stream.current.type in ('name', 'string', 'integer',
650 'float', 'lparen', 'lbracket',
652 args = [self.parse_expression()]
655 node = nodes.Test(node, name, args, kwargs, dyn_args,
656 dyn_kwargs, lineno=token.lineno)
658 node = nodes.Not(node, lineno=token.lineno)
661 def subparse(self, end_tokens=None):
664 add_data = data_buffer.append
668 lineno = data_buffer[0].lineno
669 body.append(nodes.Output(data_buffer[:], lineno=lineno))
673 token = self.stream.current
674 if token.type is 'data':
676 add_data(nodes.Const(token.value, lineno=token.lineno))
678 elif token.type is 'variable_begin':
680 add_data(self.parse_tuple())
681 self.stream.expect('variable_end')
682 elif token.type is 'block_begin':
685 if end_tokens is not None and \
686 self.stream.current.test_many(end_tokens):
688 while self.stream.current.type is not 'block_end':
689 body.append(self.parse_statement())
690 self.stream.expect('block_end')
692 raise AssertionError('internal parsing error')
698 """Parse the whole template into a `Template` node."""
699 result = nodes.Template(self.subparse(), lineno=1)
700 result.set_environment(self.environment)