Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/jinja2/parser.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author | bcclaywell |
---|---|
date | Mon, 12 Oct 2015 17:43:33 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d67268158946 |
---|---|
1 # -*- coding: utf-8 -*- | |
2 """ | |
3 jinja2.parser | |
4 ~~~~~~~~~~~~~ | |
5 | |
6 Implements the template parser. | |
7 | |
8 :copyright: (c) 2010 by the Jinja Team. | |
9 :license: BSD, see LICENSE for more details. | |
10 """ | |
11 from jinja2 import nodes | |
12 from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError | |
13 from jinja2.lexer import describe_token, describe_token_expr | |
14 from jinja2._compat import imap | |
15 | |
16 | |
17 _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', | |
18 'macro', 'include', 'from', 'import', | |
19 'set']) | |
20 _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) | |
21 | |
22 | |
23 class Parser(object): | |
24 """This is the central parsing class Jinja2 uses. It's passed to | |
25 extensions and can be used to parse expressions or statements. | |
26 """ | |
27 | |
28 def __init__(self, environment, source, name=None, filename=None, | |
29 state=None): | |
30 self.environment = environment | |
31 self.stream = environment._tokenize(source, name, filename, state) | |
32 self.name = name | |
33 self.filename = filename | |
34 self.closed = False | |
35 self.extensions = {} | |
36 for extension in environment.iter_extensions(): | |
37 for tag in extension.tags: | |
38 self.extensions[tag] = extension.parse | |
39 self._last_identifier = 0 | |
40 self._tag_stack = [] | |
41 self._end_token_stack = [] | |
42 | |
43 def fail(self, msg, lineno=None, exc=TemplateSyntaxError): | |
44 """Convenience method that raises `exc` with the message, passed | |
45 line number or last line number as well as the current name and | |
46 filename. | |
47 """ | |
48 if lineno is None: | |
49 lineno = self.stream.current.lineno | |
50 raise exc(msg, lineno, self.name, self.filename) | |
51 | |
52 def _fail_ut_eof(self, name, end_token_stack, lineno): | |
53 expected = [] | |
54 for exprs in end_token_stack: | |
55 expected.extend(imap(describe_token_expr, exprs)) | |
56 if end_token_stack: | |
57 currently_looking = ' or '.join( | |
58 "'%s'" % describe_token_expr(expr) | |
59 for expr in end_token_stack[-1]) | |
60 else: | |
61 currently_looking = None | |
62 | |
63 if name is None: | |
64 message = ['Unexpected end of template.'] | |
65 else: | |
66 message = ['Encountered unknown tag \'%s\'.' % name] | |
67 | |
68 if currently_looking: | |
69 if name is not None and name in expected: | |
70 message.append('You probably made a nesting mistake. Jinja ' | |
71 'is expecting this tag, but currently looking ' | |
72 'for %s.' % currently_looking) | |
73 else: | |
74 message.append('Jinja was looking for the following tags: ' | |
75 '%s.' % currently_looking) | |
76 | |
77 if self._tag_stack: | |
78 message.append('The innermost block that needs to be ' | |
79 'closed is \'%s\'.' % self._tag_stack[-1]) | |
80 | |
81 self.fail(' '.join(message), lineno) | |
82 | |
83 def fail_unknown_tag(self, name, lineno=None): | |
84 """Called if the parser encounters an unknown tag. Tries to fail | |
85 with a human readable error message that could help to identify | |
86 the problem. | |
87 """ | |
88 return self._fail_ut_eof(name, self._end_token_stack, lineno) | |
89 | |
90 def fail_eof(self, end_tokens=None, lineno=None): | |
91 """Like fail_unknown_tag but for end of template situations.""" | |
92 stack = list(self._end_token_stack) | |
93 if end_tokens is not None: | |
94 stack.append(end_tokens) | |
95 return self._fail_ut_eof(None, stack, lineno) | |
96 | |
97 def is_tuple_end(self, extra_end_rules=None): | |
98 """Are we at the end of a tuple?""" | |
99 if self.stream.current.type in ('variable_end', 'block_end', 'rparen'): | |
100 return True | |
101 elif extra_end_rules is not None: | |
102 return self.stream.current.test_any(extra_end_rules) | |
103 return False | |
104 | |
105 def free_identifier(self, lineno=None): | |
106 """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" | |
107 self._last_identifier += 1 | |
108 rv = object.__new__(nodes.InternalName) | |
109 nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno) | |
110 return rv | |
111 | |
112 def parse_statement(self): | |
113 """Parse a single statement.""" | |
114 token = self.stream.current | |
115 if token.type != 'name': | |
116 self.fail('tag name expected', token.lineno) | |
117 self._tag_stack.append(token.value) | |
118 pop_tag = True | |
119 try: | |
120 if token.value in _statement_keywords: | |
121 return getattr(self, 'parse_' + self.stream.current.value)() | |
122 if token.value == 'call': | |
123 return self.parse_call_block() | |
124 if token.value == 'filter': | |
125 return self.parse_filter_block() | |
126 ext = self.extensions.get(token.value) | |
127 if ext is not None: | |
128 return ext(self) | |
129 | |
130 # did not work out, remove the token we pushed by accident | |
131 # from the stack so that the unknown tag fail function can | |
132 # produce a proper error message. | |
133 self._tag_stack.pop() | |
134 pop_tag = False | |
135 self.fail_unknown_tag(token.value, token.lineno) | |
136 finally: | |
137 if pop_tag: | |
138 self._tag_stack.pop() | |
139 | |
140 def parse_statements(self, end_tokens, drop_needle=False): | |
141 """Parse multiple statements into a list until one of the end tokens | |
142 is reached. This is used to parse the body of statements as it also | |
143 parses template data if appropriate. The parser checks first if the | |
144 current token is a colon and skips it if there is one. Then it checks | |
145 for the block end and parses until if one of the `end_tokens` is | |
146 reached. Per default the active token in the stream at the end of | |
147 the call is the matched end token. If this is not wanted `drop_needle` | |
148 can be set to `True` and the end token is removed. | |
149 """ | |
150 # the first token may be a colon for python compatibility | |
151 self.stream.skip_if('colon') | |
152 | |
153 # in the future it would be possible to add whole code sections | |
154 # by adding some sort of end of statement token and parsing those here. | |
155 self.stream.expect('block_end') | |
156 result = self.subparse(end_tokens) | |
157 | |
158 # we reached the end of the template too early, the subparser | |
159 # does not check for this, so we do that now | |
160 if self.stream.current.type == 'eof': | |
161 self.fail_eof(end_tokens) | |
162 | |
163 if drop_needle: | |
164 next(self.stream) | |
165 return result | |
166 | |
167 def parse_set(self): | |
168 """Parse an assign statement.""" | |
169 lineno = next(self.stream).lineno | |
170 target = self.parse_assign_target() | |
171 if self.stream.skip_if('assign'): | |
172 expr = self.parse_tuple() | |
173 return nodes.Assign(target, expr, lineno=lineno) | |
174 body = self.parse_statements(('name:endset',), | |
175 drop_needle=True) | |
176 return nodes.AssignBlock(target, body, lineno=lineno) | |
177 | |
178 def parse_for(self): | |
179 """Parse a for loop.""" | |
180 lineno = self.stream.expect('name:for').lineno | |
181 target = self.parse_assign_target(extra_end_rules=('name:in',)) | |
182 self.stream.expect('name:in') | |
183 iter = self.parse_tuple(with_condexpr=False, | |
184 extra_end_rules=('name:recursive',)) | |
185 test = None | |
186 if self.stream.skip_if('name:if'): | |
187 test = self.parse_expression() | |
188 recursive = self.stream.skip_if('name:recursive') | |
189 body = self.parse_statements(('name:endfor', 'name:else')) | |
190 if next(self.stream).value == 'endfor': | |
191 else_ = [] | |
192 else: | |
193 else_ = self.parse_statements(('name:endfor',), drop_needle=True) | |
194 return nodes.For(target, iter, body, else_, test, | |
195 recursive, lineno=lineno) | |
196 | |
197 def parse_if(self): | |
198 """Parse an if construct.""" | |
199 node = result = nodes.If(lineno=self.stream.expect('name:if').lineno) | |
200 while 1: | |
201 node.test = self.parse_tuple(with_condexpr=False) | |
202 node.body = self.parse_statements(('name:elif', 'name:else', | |
203 'name:endif')) | |
204 token = next(self.stream) | |
205 if token.test('name:elif'): | |
206 new_node = nodes.If(lineno=self.stream.current.lineno) | |
207 node.else_ = [new_node] | |
208 node = new_node | |
209 continue | |
210 elif token.test('name:else'): | |
211 node.else_ = self.parse_statements(('name:endif',), | |
212 drop_needle=True) | |
213 else: | |
214 node.else_ = [] | |
215 break | |
216 return result | |
217 | |
218 def parse_block(self): | |
219 node = nodes.Block(lineno=next(self.stream).lineno) | |
220 node.name = self.stream.expect('name').value | |
221 node.scoped = self.stream.skip_if('name:scoped') | |
222 | |
223 # common problem people encounter when switching from django | |
224 # to jinja. we do not support hyphens in block names, so let's | |
225 # raise a nicer error message in that case. | |
226 if self.stream.current.type == 'sub': | |
227 self.fail('Block names in Jinja have to be valid Python ' | |
228 'identifiers and may not contain hyphens, use an ' | |
229 'underscore instead.') | |
230 | |
231 node.body = self.parse_statements(('name:endblock',), drop_needle=True) | |
232 self.stream.skip_if('name:' + node.name) | |
233 return node | |
234 | |
235 def parse_extends(self): | |
236 node = nodes.Extends(lineno=next(self.stream).lineno) | |
237 node.template = self.parse_expression() | |
238 return node | |
239 | |
240 def parse_import_context(self, node, default): | |
241 if self.stream.current.test_any('name:with', 'name:without') and \ | |
242 self.stream.look().test('name:context'): | |
243 node.with_context = next(self.stream).value == 'with' | |
244 self.stream.skip() | |
245 else: | |
246 node.with_context = default | |
247 return node | |
248 | |
249 def parse_include(self): | |
250 node = nodes.Include(lineno=next(self.stream).lineno) | |
251 node.template = self.parse_expression() | |
252 if self.stream.current.test('name:ignore') and \ | |
253 self.stream.look().test('name:missing'): | |
254 node.ignore_missing = True | |
255 self.stream.skip(2) | |
256 else: | |
257 node.ignore_missing = False | |
258 return self.parse_import_context(node, True) | |
259 | |
260 def parse_import(self): | |
261 node = nodes.Import(lineno=next(self.stream).lineno) | |
262 node.template = self.parse_expression() | |
263 self.stream.expect('name:as') | |
264 node.target = self.parse_assign_target(name_only=True).name | |
265 return self.parse_import_context(node, False) | |
266 | |
267 def parse_from(self): | |
268 node = nodes.FromImport(lineno=next(self.stream).lineno) | |
269 node.template = self.parse_expression() | |
270 self.stream.expect('name:import') | |
271 node.names = [] | |
272 | |
273 def parse_context(): | |
274 if self.stream.current.value in ('with', 'without') and \ | |
275 self.stream.look().test('name:context'): | |
276 node.with_context = next(self.stream).value == 'with' | |
277 self.stream.skip() | |
278 return True | |
279 return False | |
280 | |
281 while 1: | |
282 if node.names: | |
283 self.stream.expect('comma') | |
284 if self.stream.current.type == 'name': | |
285 if parse_context(): | |
286 break | |
287 target = self.parse_assign_target(name_only=True) | |
288 if target.name.startswith('_'): | |
289 self.fail('names starting with an underline can not ' | |
290 'be imported', target.lineno, | |
291 exc=TemplateAssertionError) | |
292 if self.stream.skip_if('name:as'): | |
293 alias = self.parse_assign_target(name_only=True) | |
294 node.names.append((target.name, alias.name)) | |
295 else: | |
296 node.names.append(target.name) | |
297 if parse_context() or self.stream.current.type != 'comma': | |
298 break | |
299 else: | |
300 break | |
301 if not hasattr(node, 'with_context'): | |
302 node.with_context = False | |
303 self.stream.skip_if('comma') | |
304 return node | |
305 | |
306 def parse_signature(self, node): | |
307 node.args = args = [] | |
308 node.defaults = defaults = [] | |
309 self.stream.expect('lparen') | |
310 while self.stream.current.type != 'rparen': | |
311 if args: | |
312 self.stream.expect('comma') | |
313 arg = self.parse_assign_target(name_only=True) | |
314 arg.set_ctx('param') | |
315 if self.stream.skip_if('assign'): | |
316 defaults.append(self.parse_expression()) | |
317 elif defaults: | |
318 self.fail('non-default argument follows default argument') | |
319 args.append(arg) | |
320 self.stream.expect('rparen') | |
321 | |
322 def parse_call_block(self): | |
323 node = nodes.CallBlock(lineno=next(self.stream).lineno) | |
324 if self.stream.current.type == 'lparen': | |
325 self.parse_signature(node) | |
326 else: | |
327 node.args = [] | |
328 node.defaults = [] | |
329 | |
330 node.call = self.parse_expression() | |
331 if not isinstance(node.call, nodes.Call): | |
332 self.fail('expected call', node.lineno) | |
333 node.body = self.parse_statements(('name:endcall',), drop_needle=True) | |
334 return node | |
335 | |
336 def parse_filter_block(self): | |
337 node = nodes.FilterBlock(lineno=next(self.stream).lineno) | |
338 node.filter = self.parse_filter(None, start_inline=True) | |
339 node.body = self.parse_statements(('name:endfilter',), | |
340 drop_needle=True) | |
341 return node | |
342 | |
343 def parse_macro(self): | |
344 node = nodes.Macro(lineno=next(self.stream).lineno) | |
345 node.name = self.parse_assign_target(name_only=True).name | |
346 self.parse_signature(node) | |
347 node.body = self.parse_statements(('name:endmacro',), | |
348 drop_needle=True) | |
349 return node | |
350 | |
351 def parse_print(self): | |
352 node = nodes.Output(lineno=next(self.stream).lineno) | |
353 node.nodes = [] | |
354 while self.stream.current.type != 'block_end': | |
355 if node.nodes: | |
356 self.stream.expect('comma') | |
357 node.nodes.append(self.parse_expression()) | |
358 return node | |
359 | |
360 def parse_assign_target(self, with_tuple=True, name_only=False, | |
361 extra_end_rules=None): | |
362 """Parse an assignment target. As Jinja2 allows assignments to | |
363 tuples, this function can parse all allowed assignment targets. Per | |
364 default assignments to tuples are parsed, that can be disable however | |
365 by setting `with_tuple` to `False`. If only assignments to names are | |
366 wanted `name_only` can be set to `True`. The `extra_end_rules` | |
367 parameter is forwarded to the tuple parsing function. | |
368 """ | |
369 if name_only: | |
370 token = self.stream.expect('name') | |
371 target = nodes.Name(token.value, 'store', lineno=token.lineno) | |
372 else: | |
373 if with_tuple: | |
374 target = self.parse_tuple(simplified=True, | |
375 extra_end_rules=extra_end_rules) | |
376 else: | |
377 target = self.parse_primary() | |
378 target.set_ctx('store') | |
379 if not target.can_assign(): | |
380 self.fail('can\'t assign to %r' % target.__class__. | |
381 __name__.lower(), target.lineno) | |
382 return target | |
383 | |
384 def parse_expression(self, with_condexpr=True): | |
385 """Parse an expression. Per default all expressions are parsed, if | |
386 the optional `with_condexpr` parameter is set to `False` conditional | |
387 expressions are not parsed. | |
388 """ | |
389 if with_condexpr: | |
390 return self.parse_condexpr() | |
391 return self.parse_or() | |
392 | |
393 def parse_condexpr(self): | |
394 lineno = self.stream.current.lineno | |
395 expr1 = self.parse_or() | |
396 while self.stream.skip_if('name:if'): | |
397 expr2 = self.parse_or() | |
398 if self.stream.skip_if('name:else'): | |
399 expr3 = self.parse_condexpr() | |
400 else: | |
401 expr3 = None | |
402 expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno) | |
403 lineno = self.stream.current.lineno | |
404 return expr1 | |
405 | |
406 def parse_or(self): | |
407 lineno = self.stream.current.lineno | |
408 left = self.parse_and() | |
409 while self.stream.skip_if('name:or'): | |
410 right = self.parse_and() | |
411 left = nodes.Or(left, right, lineno=lineno) | |
412 lineno = self.stream.current.lineno | |
413 return left | |
414 | |
415 def parse_and(self): | |
416 lineno = self.stream.current.lineno | |
417 left = self.parse_not() | |
418 while self.stream.skip_if('name:and'): | |
419 right = self.parse_not() | |
420 left = nodes.And(left, right, lineno=lineno) | |
421 lineno = self.stream.current.lineno | |
422 return left | |
423 | |
424 def parse_not(self): | |
425 if self.stream.current.test('name:not'): | |
426 lineno = next(self.stream).lineno | |
427 return nodes.Not(self.parse_not(), lineno=lineno) | |
428 return self.parse_compare() | |
429 | |
430 def parse_compare(self): | |
431 lineno = self.stream.current.lineno | |
432 expr = self.parse_add() | |
433 ops = [] | |
434 while 1: | |
435 token_type = self.stream.current.type | |
436 if token_type in _compare_operators: | |
437 next(self.stream) | |
438 ops.append(nodes.Operand(token_type, self.parse_add())) | |
439 elif self.stream.skip_if('name:in'): | |
440 ops.append(nodes.Operand('in', self.parse_add())) | |
441 elif (self.stream.current.test('name:not') and | |
442 self.stream.look().test('name:in')): | |
443 self.stream.skip(2) | |
444 ops.append(nodes.Operand('notin', self.parse_add())) | |
445 else: | |
446 break | |
447 lineno = self.stream.current.lineno | |
448 if not ops: | |
449 return expr | |
450 return nodes.Compare(expr, ops, lineno=lineno) | |
451 | |
452 def parse_add(self): | |
453 lineno = self.stream.current.lineno | |
454 left = self.parse_sub() | |
455 while self.stream.current.type == 'add': | |
456 next(self.stream) | |
457 right = self.parse_sub() | |
458 left = nodes.Add(left, right, lineno=lineno) | |
459 lineno = self.stream.current.lineno | |
460 return left | |
461 | |
462 def parse_sub(self): | |
463 lineno = self.stream.current.lineno | |
464 left = self.parse_concat() | |
465 while self.stream.current.type == 'sub': | |
466 next(self.stream) | |
467 right = self.parse_concat() | |
468 left = nodes.Sub(left, right, lineno=lineno) | |
469 lineno = self.stream.current.lineno | |
470 return left | |
471 | |
472 def parse_concat(self): | |
473 lineno = self.stream.current.lineno | |
474 args = [self.parse_mul()] | |
475 while self.stream.current.type == 'tilde': | |
476 next(self.stream) | |
477 args.append(self.parse_mul()) | |
478 if len(args) == 1: | |
479 return args[0] | |
480 return nodes.Concat(args, lineno=lineno) | |
481 | |
482 def parse_mul(self): | |
483 lineno = self.stream.current.lineno | |
484 left = self.parse_div() | |
485 while self.stream.current.type == 'mul': | |
486 next(self.stream) | |
487 right = self.parse_div() | |
488 left = nodes.Mul(left, right, lineno=lineno) | |
489 lineno = self.stream.current.lineno | |
490 return left | |
491 | |
492 def parse_div(self): | |
493 lineno = self.stream.current.lineno | |
494 left = self.parse_floordiv() | |
495 while self.stream.current.type == 'div': | |
496 next(self.stream) | |
497 right = self.parse_floordiv() | |
498 left = nodes.Div(left, right, lineno=lineno) | |
499 lineno = self.stream.current.lineno | |
500 return left | |
501 | |
502 def parse_floordiv(self): | |
503 lineno = self.stream.current.lineno | |
504 left = self.parse_mod() | |
505 while self.stream.current.type == 'floordiv': | |
506 next(self.stream) | |
507 right = self.parse_mod() | |
508 left = nodes.FloorDiv(left, right, lineno=lineno) | |
509 lineno = self.stream.current.lineno | |
510 return left | |
511 | |
512 def parse_mod(self): | |
513 lineno = self.stream.current.lineno | |
514 left = self.parse_pow() | |
515 while self.stream.current.type == 'mod': | |
516 next(self.stream) | |
517 right = self.parse_pow() | |
518 left = nodes.Mod(left, right, lineno=lineno) | |
519 lineno = self.stream.current.lineno | |
520 return left | |
521 | |
522 def parse_pow(self): | |
523 lineno = self.stream.current.lineno | |
524 left = self.parse_unary() | |
525 while self.stream.current.type == 'pow': | |
526 next(self.stream) | |
527 right = self.parse_unary() | |
528 left = nodes.Pow(left, right, lineno=lineno) | |
529 lineno = self.stream.current.lineno | |
530 return left | |
531 | |
532 def parse_unary(self, with_filter=True): | |
533 token_type = self.stream.current.type | |
534 lineno = self.stream.current.lineno | |
535 if token_type == 'sub': | |
536 next(self.stream) | |
537 node = nodes.Neg(self.parse_unary(False), lineno=lineno) | |
538 elif token_type == 'add': | |
539 next(self.stream) | |
540 node = nodes.Pos(self.parse_unary(False), lineno=lineno) | |
541 else: | |
542 node = self.parse_primary() | |
543 node = self.parse_postfix(node) | |
544 if with_filter: | |
545 node = self.parse_filter_expr(node) | |
546 return node | |
547 | |
548 def parse_primary(self): | |
549 token = self.stream.current | |
550 if token.type == 'name': | |
551 if token.value in ('true', 'false', 'True', 'False'): | |
552 node = nodes.Const(token.value in ('true', 'True'), | |
553 lineno=token.lineno) | |
554 elif token.value in ('none', 'None'): | |
555 node = nodes.Const(None, lineno=token.lineno) | |
556 else: | |
557 node = nodes.Name(token.value, 'load', lineno=token.lineno) | |
558 next(self.stream) | |
559 elif token.type == 'string': | |
560 next(self.stream) | |
561 buf = [token.value] | |
562 lineno = token.lineno | |
563 while self.stream.current.type == 'string': | |
564 buf.append(self.stream.current.value) | |
565 next(self.stream) | |
566 node = nodes.Const(''.join(buf), lineno=lineno) | |
567 elif token.type in ('integer', 'float'): | |
568 next(self.stream) | |
569 node = nodes.Const(token.value, lineno=token.lineno) | |
570 elif token.type == 'lparen': | |
571 next(self.stream) | |
572 node = self.parse_tuple(explicit_parentheses=True) | |
573 self.stream.expect('rparen') | |
574 elif token.type == 'lbracket': | |
575 node = self.parse_list() | |
576 elif token.type == 'lbrace': | |
577 node = self.parse_dict() | |
578 else: | |
579 self.fail("unexpected '%s'" % describe_token(token), token.lineno) | |
580 return node | |
581 | |
582 def parse_tuple(self, simplified=False, with_condexpr=True, | |
583 extra_end_rules=None, explicit_parentheses=False): | |
584 """Works like `parse_expression` but if multiple expressions are | |
585 delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created. | |
586 This method could also return a regular expression instead of a tuple | |
587 if no commas where found. | |
588 | |
589 The default parsing mode is a full tuple. If `simplified` is `True` | |
590 only names and literals are parsed. The `no_condexpr` parameter is | |
591 forwarded to :meth:`parse_expression`. | |
592 | |
593 Because tuples do not require delimiters and may end in a bogus comma | |
594 an extra hint is needed that marks the end of a tuple. For example | |
595 for loops support tuples between `for` and `in`. In that case the | |
596 `extra_end_rules` is set to ``['name:in']``. | |
597 | |
598 `explicit_parentheses` is true if the parsing was triggered by an | |
599 expression in parentheses. This is used to figure out if an empty | |
600 tuple is a valid expression or not. | |
601 """ | |
602 lineno = self.stream.current.lineno | |
603 if simplified: | |
604 parse = self.parse_primary | |
605 elif with_condexpr: | |
606 parse = self.parse_expression | |
607 else: | |
608 parse = lambda: self.parse_expression(with_condexpr=False) | |
609 args = [] | |
610 is_tuple = False | |
611 while 1: | |
612 if args: | |
613 self.stream.expect('comma') | |
614 if self.is_tuple_end(extra_end_rules): | |
615 break | |
616 args.append(parse()) | |
617 if self.stream.current.type == 'comma': | |
618 is_tuple = True | |
619 else: | |
620 break | |
621 lineno = self.stream.current.lineno | |
622 | |
623 if not is_tuple: | |
624 if args: | |
625 return args[0] | |
626 | |
627 # if we don't have explicit parentheses, an empty tuple is | |
628 # not a valid expression. This would mean nothing (literally | |
629 # nothing) in the spot of an expression would be an empty | |
630 # tuple. | |
631 if not explicit_parentheses: | |
632 self.fail('Expected an expression, got \'%s\'' % | |
633 describe_token(self.stream.current)) | |
634 | |
635 return nodes.Tuple(args, 'load', lineno=lineno) | |
636 | |
637 def parse_list(self): | |
638 token = self.stream.expect('lbracket') | |
639 items = [] | |
640 while self.stream.current.type != 'rbracket': | |
641 if items: | |
642 self.stream.expect('comma') | |
643 if self.stream.current.type == 'rbracket': | |
644 break | |
645 items.append(self.parse_expression()) | |
646 self.stream.expect('rbracket') | |
647 return nodes.List(items, lineno=token.lineno) | |
648 | |
649 def parse_dict(self): | |
650 token = self.stream.expect('lbrace') | |
651 items = [] | |
652 while self.stream.current.type != 'rbrace': | |
653 if items: | |
654 self.stream.expect('comma') | |
655 if self.stream.current.type == 'rbrace': | |
656 break | |
657 key = self.parse_expression() | |
658 self.stream.expect('colon') | |
659 value = self.parse_expression() | |
660 items.append(nodes.Pair(key, value, lineno=key.lineno)) | |
661 self.stream.expect('rbrace') | |
662 return nodes.Dict(items, lineno=token.lineno) | |
663 | |
664 def parse_postfix(self, node): | |
665 while 1: | |
666 token_type = self.stream.current.type | |
667 if token_type == 'dot' or token_type == 'lbracket': | |
668 node = self.parse_subscript(node) | |
669 # calls are valid both after postfix expressions (getattr | |
670 # and getitem) as well as filters and tests | |
671 elif token_type == 'lparen': | |
672 node = self.parse_call(node) | |
673 else: | |
674 break | |
675 return node | |
676 | |
677 def parse_filter_expr(self, node): | |
678 while 1: | |
679 token_type = self.stream.current.type | |
680 if token_type == 'pipe': | |
681 node = self.parse_filter(node) | |
682 elif token_type == 'name' and self.stream.current.value == 'is': | |
683 node = self.parse_test(node) | |
684 # calls are valid both after postfix expressions (getattr | |
685 # and getitem) as well as filters and tests | |
686 elif token_type == 'lparen': | |
687 node = self.parse_call(node) | |
688 else: | |
689 break | |
690 return node | |
691 | |
692 def parse_subscript(self, node): | |
693 token = next(self.stream) | |
694 if token.type == 'dot': | |
695 attr_token = self.stream.current | |
696 next(self.stream) | |
697 if attr_token.type == 'name': | |
698 return nodes.Getattr(node, attr_token.value, 'load', | |
699 lineno=token.lineno) | |
700 elif attr_token.type != 'integer': | |
701 self.fail('expected name or number', attr_token.lineno) | |
702 arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) | |
703 return nodes.Getitem(node, arg, 'load', lineno=token.lineno) | |
704 if token.type == 'lbracket': | |
705 args = [] | |
706 while self.stream.current.type != 'rbracket': | |
707 if args: | |
708 self.stream.expect('comma') | |
709 args.append(self.parse_subscribed()) | |
710 self.stream.expect('rbracket') | |
711 if len(args) == 1: | |
712 arg = args[0] | |
713 else: | |
714 arg = nodes.Tuple(args, 'load', lineno=token.lineno) | |
715 return nodes.Getitem(node, arg, 'load', lineno=token.lineno) | |
716 self.fail('expected subscript expression', self.lineno) | |
717 | |
718 def parse_subscribed(self): | |
719 lineno = self.stream.current.lineno | |
720 | |
721 if self.stream.current.type == 'colon': | |
722 next(self.stream) | |
723 args = [None] | |
724 else: | |
725 node = self.parse_expression() | |
726 if self.stream.current.type != 'colon': | |
727 return node | |
728 next(self.stream) | |
729 args = [node] | |
730 | |
731 if self.stream.current.type == 'colon': | |
732 args.append(None) | |
733 elif self.stream.current.type not in ('rbracket', 'comma'): | |
734 args.append(self.parse_expression()) | |
735 else: | |
736 args.append(None) | |
737 | |
738 if self.stream.current.type == 'colon': | |
739 next(self.stream) | |
740 if self.stream.current.type not in ('rbracket', 'comma'): | |
741 args.append(self.parse_expression()) | |
742 else: | |
743 args.append(None) | |
744 else: | |
745 args.append(None) | |
746 | |
747 return nodes.Slice(lineno=lineno, *args) | |
748 | |
749 def parse_call(self, node): | |
750 token = self.stream.expect('lparen') | |
751 args = [] | |
752 kwargs = [] | |
753 dyn_args = dyn_kwargs = None | |
754 require_comma = False | |
755 | |
756 def ensure(expr): | |
757 if not expr: | |
758 self.fail('invalid syntax for function call expression', | |
759 token.lineno) | |
760 | |
761 while self.stream.current.type != 'rparen': | |
762 if require_comma: | |
763 self.stream.expect('comma') | |
764 # support for trailing comma | |
765 if self.stream.current.type == 'rparen': | |
766 break | |
767 if self.stream.current.type == 'mul': | |
768 ensure(dyn_args is None and dyn_kwargs is None) | |
769 next(self.stream) | |
770 dyn_args = self.parse_expression() | |
771 elif self.stream.current.type == 'pow': | |
772 ensure(dyn_kwargs is None) | |
773 next(self.stream) | |
774 dyn_kwargs = self.parse_expression() | |
775 else: | |
776 ensure(dyn_args is None and dyn_kwargs is None) | |
777 if self.stream.current.type == 'name' and \ | |
778 self.stream.look().type == 'assign': | |
779 key = self.stream.current.value | |
780 self.stream.skip(2) | |
781 value = self.parse_expression() | |
782 kwargs.append(nodes.Keyword(key, value, | |
783 lineno=value.lineno)) | |
784 else: | |
785 ensure(not kwargs) | |
786 args.append(self.parse_expression()) | |
787 | |
788 require_comma = True | |
789 self.stream.expect('rparen') | |
790 | |
791 if node is None: | |
792 return args, kwargs, dyn_args, dyn_kwargs | |
793 return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, | |
794 lineno=token.lineno) | |
795 | |
796 def parse_filter(self, node, start_inline=False): | |
797 while self.stream.current.type == 'pipe' or start_inline: | |
798 if not start_inline: | |
799 next(self.stream) | |
800 token = self.stream.expect('name') | |
801 name = token.value | |
802 while self.stream.current.type == 'dot': | |
803 next(self.stream) | |
804 name += '.' + self.stream.expect('name').value | |
805 if self.stream.current.type == 'lparen': | |
806 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) | |
807 else: | |
808 args = [] | |
809 kwargs = [] | |
810 dyn_args = dyn_kwargs = None | |
811 node = nodes.Filter(node, name, args, kwargs, dyn_args, | |
812 dyn_kwargs, lineno=token.lineno) | |
813 start_inline = False | |
814 return node | |
815 | |
816 def parse_test(self, node): | |
817 token = next(self.stream) | |
818 if self.stream.current.test('name:not'): | |
819 next(self.stream) | |
820 negated = True | |
821 else: | |
822 negated = False | |
823 name = self.stream.expect('name').value | |
824 while self.stream.current.type == 'dot': | |
825 next(self.stream) | |
826 name += '.' + self.stream.expect('name').value | |
827 dyn_args = dyn_kwargs = None | |
828 kwargs = [] | |
829 if self.stream.current.type == 'lparen': | |
830 args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) | |
831 elif (self.stream.current.type in ('name', 'string', 'integer', | |
832 'float', 'lparen', 'lbracket', | |
833 'lbrace') and not | |
834 self.stream.current.test_any('name:else', 'name:or', | |
835 'name:and')): | |
836 if self.stream.current.test('name:is'): | |
837 self.fail('You cannot chain multiple tests with is') | |
838 args = [self.parse_expression()] | |
839 else: | |
840 args = [] | |
841 node = nodes.Test(node, name, args, kwargs, dyn_args, | |
842 dyn_kwargs, lineno=token.lineno) | |
843 if negated: | |
844 node = nodes.Not(node, lineno=token.lineno) | |
845 return node | |
846 | |
847 def subparse(self, end_tokens=None): | |
848 body = [] | |
849 data_buffer = [] | |
850 add_data = data_buffer.append | |
851 | |
852 if end_tokens is not None: | |
853 self._end_token_stack.append(end_tokens) | |
854 | |
855 def flush_data(): | |
856 if data_buffer: | |
857 lineno = data_buffer[0].lineno | |
858 body.append(nodes.Output(data_buffer[:], lineno=lineno)) | |
859 del data_buffer[:] | |
860 | |
861 try: | |
862 while self.stream: | |
863 token = self.stream.current | |
864 if token.type == 'data': | |
865 if token.value: | |
866 add_data(nodes.TemplateData(token.value, | |
867 lineno=token.lineno)) | |
868 next(self.stream) | |
869 elif token.type == 'variable_begin': | |
870 next(self.stream) | |
871 add_data(self.parse_tuple(with_condexpr=True)) | |
872 self.stream.expect('variable_end') | |
873 elif token.type == 'block_begin': | |
874 flush_data() | |
875 next(self.stream) | |
876 if end_tokens is not None and \ | |
877 self.stream.current.test_any(*end_tokens): | |
878 return body | |
879 rv = self.parse_statement() | |
880 if isinstance(rv, list): | |
881 body.extend(rv) | |
882 else: | |
883 body.append(rv) | |
884 self.stream.expect('block_end') | |
885 else: | |
886 raise AssertionError('internal parsing error') | |
887 | |
888 flush_data() | |
889 finally: | |
890 if end_tokens is not None: | |
891 self._end_token_stack.pop() | |
892 | |
893 return body | |
894 | |
895 def parse(self): | |
896 """Parse the whole template into a `Template` node.""" | |
897 result = nodes.Template(self.subparse(), lineno=1) | |
898 result.set_environment(self.environment) | |
899 return result |