blob: 0c4edfceaa28d7d509c2c005a593ba0746417c9c [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 Ronacher82b3f3d2008-03-31 20:01:08 +020011from jinja2 import nodes
12from jinja2.exceptions import TemplateSyntaxError
Armin Ronacher07bc6842008-03-31 14:18:49 +020013
14
15__all__ = ['Parser']
16
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020017_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'include'])
18_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
19_tuple_edge_tokens = set(['rparen', 'block_end', 'variable_end', 'in',
20 'semicolon', 'recursive'])
21_statement_end_tokens = set(['elif', 'else', 'endblock', 'endfilter',
22 'endfor', 'endif', 'endmacro',
23 'endcall', 'block_end'])
24
Armin Ronacher07bc6842008-03-31 14:18:49 +020025
26class Parser(object):
27 """
28 The template parser class.
29
30 Transforms sourcecode into an abstract syntax tree.
31 """
32
33 def __init__(self, environment, source, filename=None):
34 self.environment = environment
35 if isinstance(source, str):
36 source = source.decode(environment.template_charset, 'ignore')
37 if isinstance(filename, unicode):
38 filename = filename.encode('utf-8')
39 self.source = source
40 self.filename = filename
41 self.closed = False
42 self.blocks = set()
43 self.no_variable_block = self.environment.lexer.no_variable_block
44 self.stream = environment.lexer.tokenize(source, filename)
45
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020046 def end_statement(self):
47 """Make sure that the statement ends properly."""
48 if self.stream.current.type is 'semicolon':
49 self.stream.next()
50 elif self.stream.current.type not in _statement_end_tokens:
51 raise TemplateSyntaxError('ambigous end of statement',
52 self.stream.current.lineno,
53 self.filename)
54
55 def parse_statement(self):
56 """Parse a single statement."""
57 token_type = self.stream.current.type
58 if token_type in _statement_keywords:
59 return getattr(self, 'parse_' + token_type)()
60 elif token_type is 'call':
61 self.stream.next()
62 return self.parse_call_block()
63 lineno = self.stream.current.lineno
64 expr = self.parse_expression()
65 if self.stream.current.type == 'assign':
66 return self.parse_assign(expr)
67 self.end_statement()
68 return nodes.ExprStmt(expr, lineno=lineno)
69
70 def parse_assign(self, target):
71 """Parse an assign statement."""
72 lineno = self.stream.expect('assign').lineno
73 if not target.can_assign():
74 raise TemplateSyntaxError("can't assign to '%s'" %
75 target, target.lineno,
76 self.filename)
77 expr = self.parse_tuple()
78 self.end_statement()
79 nodes.set_ctx(target, 'store')
80 return nodes.Assign(target, expr, lineno=lineno)
81
82 def parse_statements(self, end_tokens, drop_needle=False):
83 """
84 Parse multiple statements into a list until one of the end tokens
85 is reached. This is used to parse the body of statements as it
86 also parses template data if appropriate.
87 """
88 # the first token may be a colon for python compatibility
89 if self.stream.current.type is 'colon':
90 self.stream.next()
91
92 if self.stream.current.type is 'block_end':
93 self.stream.next()
94 result = self.subparse(end_tokens)
95 else:
96 result = []
97 while self.stream.current.type not in end_tokens:
98 result.append(self.parse_statement())
99 if drop_needle:
100 self.stream.next()
101 return result
102
103 def parse_for(self):
104 """Parse a for loop."""
105 lineno = self.stream.expect('for').lineno
106 target = self.parse_tuple(simplified=True)
107 nodes.set_ctx(target, 'store')
108 self.stream.expect('in')
109 iter = self.parse_tuple()
110 if self.stream.current.type is 'recursive':
111 self.stream.next()
112 recursive = True
113 else:
114 recursive = False
115 body = self.parse_statements(('endfor', 'else'))
116 token_type = self.stream.current.type
117 self.stream.next()
118 if token_type is 'endfor':
119 else_ = []
120 else:
121 else_ = self.parse_statements(('endfor',), drop_needle=True)
122 return nodes.For(target, iter, body, else_, False, lineno=lineno)
123
124 def parse_if(self):
Armin Ronacher07bc6842008-03-31 14:18:49 +0200125 pass
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200126
127 def parse_block(self):
128 pass
129
130 def parse_extends(self):
131 pass
132
133 def parse_include(self):
134 pass
135
136 def parse_call_block(self):
137 pass
138
139 def parse_expression(self):
140 """Parse an expression."""
141 return self.parse_condexpr()
142
143 def parse_condexpr(self):
144 lineno = self.stream.current.lineno
145 expr1 = self.parse_or()
146 while self.stream.current.type is 'if':
147 self.stream.next()
148 expr2 = self.parse_or()
149 self.stream.expect('else')
150 expr3 = self.parse_condexpr()
151 expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
152 lineno = self.stream.current.lineno
153 return expr1
154
155 def parse_or(self):
156 lineno = self.stream.current.lineno
157 left = self.parse_and()
158 while self.stream.current.type is 'or':
159 self.stream.next()
160 right = self.parse_and()
161 left = nodes.Or(left, right, lineno=lineno)
162 lineno = self.stream.current.lineno
163 return left
164
165 def parse_and(self):
166 lineno = self.stream.current.lineno
167 left = self.parse_compare()
168 while self.stream.current.type is 'and':
169 self.stream.next()
170 right = self.parse_compare()
171 left = nodes.And(left, right, lineno=lineno)
172 lineno = self.stream.current.lineno
173 return left
174
175 def parse_compare(self):
176 lineno = self.stream.current.lineno
177 expr = self.parse_add()
178 ops = []
179 while 1:
180 token_type = self.stream.current.type
181 if token_type in _compare_operators:
182 self.stream.next()
183 ops.append(nodes.Operand(token_type, self.parse_add()))
184 elif token_type is 'not' and self.stream.look().type is 'in':
185 self.stream.skip(2)
186 ops.append(nodes.Operand('notin', self.parse_add()))
187 else:
188 break
189 lineno = self.stream.current.lineno
190 if not ops:
191 return expr
192 return nodes.Compare(expr, ops, lineno=lineno)
193
194 def parse_add(self):
195 lineno = self.stream.current.lineno
196 left = self.parse_sub()
197 while self.stream.current.type is 'add':
198 self.stream.next()
199 right = self.parse_sub()
200 left = nodes.Add(left, right, lineno=lineno)
201 lineno = self.stream.current.lineno
202 return left
203
204 def parse_sub(self):
205 lineno = self.stream.current.lineno
206 left = self.parse_concat()
207 while self.stream.current.type is 'sub':
208 self.stream.next()
209 right = self.parse_concat()
210 left = nodes.Sub(left, right, lineno=lineno)
211 lineno = self.stream.current.lineno
212 return left
213
214 def parse_concat(self):
215 lineno = self.stream.current.lineno
216 args = [self.parse_mul()]
217 while self.stream.current.type is 'tilde':
218 self.stream.next()
219 args.append(self.parse_mul())
220 if len(args) == 1:
221 return args[0]
222 return nodes.Concat(args, lineno=lineno)
223
224 def parse_mul(self):
225 lineno = self.stream.current.lineno
226 left = self.parse_div()
227 while self.stream.current.type is 'mul':
228 self.stream.next()
229 right = self.parse_div()
230 left = nodes.Mul(left, right, lineno=lineno)
231 lineno = self.stream.current.lineno
232 return left
233
234 def parse_div(self):
235 lineno = self.stream.current.lineno
236 left = self.parse_floordiv()
237 while self.stream.current.type is 'div':
238 self.stream.next()
239 right = self.parse_floordiv()
240 left = nodes.Floor(left, right, lineno=lineno)
241 lineno = self.stream.current.lineno
242 return left
243
244 def parse_floordiv(self):
245 lineno = self.stream.current.lineno
246 left = self.parse_mod()
247 while self.stream.current.type is 'floordiv':
248 self.stream.next()
249 right = self.parse_mod()
250 left = nodes.FloorDiv(left, right, lineno=lineno)
251 lineno = self.stream.current.lineno
252 return left
253
254 def parse_mod(self):
255 lineno = self.stream.current.lineno
256 left = self.parse_pow()
257 while self.stream.current.type is 'mod':
258 self.stream.next()
259 right = self.parse_pow()
260 left = nodes.Mod(left, right, lineno=lineno)
261 lineno = self.stream.current.lineno
262 return left
263
264 def parse_pow(self):
265 lineno = self.stream.current.lineno
266 left = self.parse_unary()
267 while self.stream.current.type is 'pow':
268 self.stream.next()
269 right = self.parse_unary()
270 left = nodes.Pow(left, right, lineno=lineno)
271 lineno = self.stream.current.lineno
272 return left
273
274 def parse_unary(self):
275 token_type = self.stream.current.type
276 lineno = self.stream.current.lineno
277 if token_type is 'not':
278 self.stream.next()
279 node = self.parse_unary()
280 return nodes.Neg(node, lineno=lineno)
281 if token_type is 'sub':
282 self.stream.next()
283 node = self.parse_unary()
284 return nodes.Sub(node, lineno=lineno)
285 if token_type is 'add':
286 self.stream.next()
287 node = self.parse_unary()
288 return nodes.Pos(node, lineno=lineno)
289 return self.parse_primary()
290
291 def parse_primary(self, parse_postfix=True):
292 token = self.stream.current
293 if token.type is 'name':
294 if token.value in ('true', 'false'):
295 node = nodes.Const(token.value == 'true', lineno=token.lineno)
296 elif token.value == 'none':
297 node = nodes.Const(None, lineno=token.lineno)
298 else:
299 node = nodes.Name(token.value, 'load', lineno=token.lineno)
300 self.stream.next()
301 elif token.type in ('integer', 'float', 'string'):
302 self.stream.next()
303 node = nodes.Const(token.value, lineno=token.lineno)
304 elif token.type is 'lparen':
305 self.stream.next()
306 node = self.parse_tuple()
307 self.stream.expect('rparen')
308 elif token.type is 'lbracket':
309 node = self.parse_list()
310 elif token.type is 'lbrace':
311 node = self.parse_dict()
312 else:
313 raise TemplateSyntaxError("unexpected token '%s'" %
314 (token,), token.lineno,
315 self.filename)
316 if parse_postfix:
317 node = self.parse_postfix(node)
318 return node
319
320 def parse_tuple(self, enforce=False, simplified=False):
321 """
322 Parse multiple expressions into a tuple. This can also return
323 just one expression which is not a tuple. If you want to enforce
324 a tuple, pass it enforce=True (currently unused).
325 """
326 lineno = self.stream.current.lineno
327 parse = simplified and self.parse_primary or self.parse_expression
328 args = []
329 is_tuple = False
330 while 1:
331 if args:
332 self.stream.expect('comma')
333 if self.stream.current.type in _tuple_edge_tokens:
334 break
335 args.append(parse())
336 if self.stream.current.type is not 'comma':
337 break
338 is_tuple = True
339 lineno = self.stream.current.lineno
340 if not is_tuple and args:
341 if enforce:
342 raise TemplateSyntaxError('tuple expected', lineno,
343 self.filename)
344 return args[0]
345 return nodes.Tuple(args, 'load', lineno=lineno)
346
347 def parse_list(self):
348 token = self.stream.expect('lbracket')
349 items = []
350 while self.stream.current.type is not 'rbracket':
351 if items:
352 self.stream.expect('comma')
353 if self.stream.current.type == 'rbracket':
354 break
355 items.append(self.parse_expression())
356 self.stream.expect('rbracket')
357 return nodes.List(items, lineno=token.lineno)
358
359 def parse_dict(self):
360 token = self.stream.expect('lbrace')
361 items = []
362 while self.stream.current.type is not 'rbrace':
363 if items:
364 self.stream.expect('comma')
365 if self.stream.current.type == 'rbrace':
366 break
367 key = self.parse_expression()
368 self.stream.expect('colon')
369 value = self.parse_expression()
370 items.append(nodes.Pair(key, value, lineno=key.lineno))
371 self.stream.expect('rbrace')
372 return nodes.Dict(items, token.lineno, self.filename)
373
374 def parse_postfix(self, node):
375 while 1:
376 token_type = self.stream.current.type
377 if token_type is 'dot' or token_type is 'lbracket':
378 node = self.parse_subscript(node)
379 elif token_type is 'lparen':
380 node = self.parse_call(node)
381 elif token_type is 'pipe':
382 node = self.parse_filter(node)
383 elif token_type is 'is':
384 node = self.parse_test(node)
385 else:
386 break
387 return node
388
389 def parse_subscript(self, node):
390 token = self.stream.next()
391 if token.type is 'dot':
392 if token.type not in ('name', 'integer'):
393 raise TemplateSyntaxError('expected name or number',
394 token.lineno, self.filename)
395 arg = nodes.Const(token.value, lineno=token.lineno)
396 self.stream.next()
397 elif token.type is 'lbracket':
398 args = []
399 while self.stream.current.type is not 'rbracket':
400 if args:
401 self.stream.expect('comma')
402 args.append(self.parse_subscribed())
403 self.stream.expect('rbracket')
404 if len(args) == 1:
405 arg = args[0]
406 else:
407 arg = nodes.Tuple(args, lineno, self.filename)
408 else:
409 raise TemplateSyntaxError('expected subscript expression',
410 self.lineno, self.filename)
411 return nodes.Subscript(node, arg, 'load', lineno=token.lineno)
412
413 def parse_subscribed(self):
414 lineno = self.stream.current.lineno
415
416 if self.stream.current.type is 'colon':
417 self.stream.next()
418 args = [None]
419 else:
420 node = self.parse_expression()
421 if self.stream.current.type is not 'colon':
422 return node
423 self.stream.next()
424 args = [node]
425
426 if self.stream.current.type is 'colon':
427 args.append(None)
428 elif self.stream.current.type not in ('rbracket', 'comma'):
429 args.append(self.parse_expression())
430 else:
431 args.append(None)
432
433 if self.stream.current.type is 'colon':
434 self.stream.next()
435 if self.stream.current.type not in ('rbracket', 'comma'):
436 args.append(self.parse_expression())
437 else:
438 args.append(None)
439 else:
440 args.append(None)
441
442 return nodes.Slice(lineno=lineno, *args)
443
444 def parse_call(self, node):
445 token = self.stream.expect('lparen')
446 args = []
447 kwargs = []
448 dyn_args = dyn_kwargs = None
449 require_comma = False
450
451 def ensure(expr):
452 if not expr:
453 raise TemplateSyntaxError('invalid syntax for function '
454 'call expression', token.lineno,
455 self.filename)
456
457 while self.stream.current.type is not 'rparen':
458 if require_comma:
459 self.stream.expect('comma')
460 # support for trailing comma
461 if self.stream.current.type is 'rparen':
462 break
463 if self.stream.current.type is 'mul':
464 ensure(dyn_args is None and dyn_kwargs is None)
465 self.stream.next()
466 dyn_args = self.parse_expression()
467 elif self.stream.current.type is 'pow':
468 ensure(dyn_kwargs is None)
469 self.stream.next()
470 dyn_kwargs = self.parse_expression()
471 else:
472 ensure(dyn_args is None and dyn_kwargs is None)
473 if self.stream.current.type is 'name' and \
474 self.stream.look().type is 'assign':
475 key = self.stream.current.value
476 self.stream.skip(2)
477 kwargs.append(nodes.Pair(key, self.parse_expression(),
478 lineno=key.lineno))
479 else:
480 ensure(not kwargs)
481 args.append(self.parse_expression())
482
483 require_comma = True
484 self.stream.expect('rparen')
485
486 if node is None:
487 return args, kwargs, dyn_args, dyn_kwargs
488 return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
489 lineno=token.lineno)
490
491 def parse_filter(self, node):
492 lineno = self.stream.current.type
493 filters = []
494 while self.stream.current.type == 'pipe':
495 self.stream.next()
496 token = self.stream.expect('name')
497 if self.stream.current.type is 'lparen':
498 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
499 else:
500 args = []
501 kwargs = []
502 dyn_args = dyn_kwargs = None
503 filters.append(nodes.FilterCall(token.value, args, kwargs,
504 dyn_args, dyn_kwargs,
505 lineno=token.lineno))
506 return nodes.Filter(node, filters)
507
508 def parse_test(self, node):
509 token = self.stream.expect('is')
510 if self.stream.current.type is 'not':
511 self.stream.next()
512 negated = True
513 else:
514 negated = False
515 name = self.stream.expect('name').value
516 if self.stream.current.type is 'lparen':
517 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
518 elif self.stream.current.type in ('name', 'string', 'integer',
519 'float', 'lparen', 'lbracket',
520 'lbrace', 'regex'):
521 args = [self.parse_expression()]
522 else:
523 args = []
524 kwargs = []
525 dyn_args = dyn_kwargs = None
526 node = nodes.Test(node, name, args, kwargs, dyn_args,
527 dyn_kwargs, lineno=token.lineno)
528 if negated:
529 node = nodes.NotExpression(node, lineno=token.lineno)
530 return node
531
532 def subparse(self, end_tokens=None):
533 body = []
534 data_buffer = []
535 add_data = data_buffer.append
536
537 def flush_data():
538 if data_buffer:
539 lineno = data_buffer[0].lineno
540 body.append(nodes.Output(data_buffer[:], lineno=lineno))
541 del data_buffer[:]
542
543 while self.stream:
544 token = self.stream.current
545 if token.type is 'data':
546 add_data(nodes.Const(token.value, lineno=token.lineno))
547 self.stream.next()
548 elif token.type is 'variable_begin':
549 self.stream.next()
550 add_data(self.parse_tuple())
551 self.stream.expect('variable_end')
552 elif token.type is 'block_begin':
553 flush_data()
554 self.stream.next()
555 if end_tokens is not None and \
556 self.stream.current.type in end_tokens:
557 return body
558 while self.stream.current.type is not 'block_end':
559 body.append(self.parse_statement())
560 self.stream.expect('block_end')
561 else:
562 raise AssertionError('internal parsing error')
563
564 flush_data()
565 return body
566
567 def parse(self):
568 """Parse the whole template into a `Template` node."""
569 return nodes.Template(self.subparse(), lineno=1)