blob: 4f5ff0b5e1ceee05fccf0d9a1c9f1e83e72d3889 [file] [log] [blame]
Armin Ronachere791c2a2008-04-07 18:39:54 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.compiler
4 ~~~~~~~~~~~~~~~
5
6 Compiles nodes into python code.
7
8 :copyright: Copyright 2008 by Armin Ronacher.
9 :license: GNU GPL.
10"""
Armin Ronacher8efc5222008-04-08 14:47:40 +020011from copy import copy
Armin Ronachere791c2a2008-04-07 18:39:54 +020012from random import randrange
Armin Ronachere791c2a2008-04-07 18:39:54 +020013from cStringIO import StringIO
14from jinja2 import nodes
15from jinja2.visitor import NodeVisitor, NodeTransformer
16from jinja2.exceptions import TemplateAssertionError
17
18
19operators = {
20 'eq': '==',
21 'ne': '!=',
22 'gt': '>',
23 'gteq': '>=',
24 'lt': '<',
25 'lteq': '<=',
26 'in': 'in',
27 'notin': 'not in'
28}
29
30
Christoph Hack65642a52008-04-08 14:46:56 +020031def generate(node, environment, filename, stream=None):
Armin Ronachere791c2a2008-04-07 18:39:54 +020032 is_child = node.find(nodes.Extends) is not None
Christoph Hack65642a52008-04-08 14:46:56 +020033 generator = CodeGenerator(environment, is_child, filename, stream)
Armin Ronachere791c2a2008-04-07 18:39:54 +020034 generator.visit(node)
35 if stream is None:
36 return generator.stream.getvalue()
37
38
39class Identifiers(object):
40 """Tracks the status of identifiers in frames."""
41
42 def __init__(self):
43 # variables that are known to be declared (probably from outer
44 # frames or because they are special for the frame)
45 self.declared = set()
46
47 # names that are accessed without being explicitly declared by
48 # this one or any of the outer scopes. Names can appear both in
49 # declared and undeclared.
50 self.undeclared = set()
51
52 # names that are declared locally
53 self.declared_locally = set()
54
55 # names that are declared by parameters
56 self.declared_parameter = set()
57
Christoph Hack65642a52008-04-08 14:46:56 +020058 # filters that are declared locally
59 self.declared_filter = set()
Christoph Hackacb130e2008-04-08 15:21:53 +020060 self.undeclared_filter = dict()
Christoph Hack65642a52008-04-08 14:46:56 +020061
Armin Ronachere791c2a2008-04-07 18:39:54 +020062 def add_special(self, name):
63 """Register a special name like `loop`."""
64 self.undeclared.discard(name)
65 self.declared.add(name)
66
Armin Ronacher4f62a9f2008-04-08 18:09:13 +020067 def is_declared(self, name, local_only=False):
Armin Ronachere791c2a2008-04-07 18:39:54 +020068 """Check if a name is declared in this or an outer scope."""
Armin Ronacher4f62a9f2008-04-08 18:09:13 +020069 if name in self.declared_locally or name in self.declared_parameter:
70 return True
71 if local_only:
72 return False
73 return name in self.declared
Armin Ronachere791c2a2008-04-07 18:39:54 +020074
75 def find_shadowed(self):
76 """Find all the shadowed names."""
77 return self.declared & (self.declared_locally | self.declared_parameter)
78
79
80class Frame(object):
81
82 def __init__(self, parent=None):
83 self.identifiers = Identifiers()
Armin Ronacher8efc5222008-04-08 14:47:40 +020084 self.toplevel = False
Armin Ronachere791c2a2008-04-07 18:39:54 +020085 self.parent = parent
Armin Ronacher8efc5222008-04-08 14:47:40 +020086 self.block = parent and parent.block or None
Armin Ronachere791c2a2008-04-07 18:39:54 +020087 if parent is not None:
88 self.identifiers.declared.update(
89 parent.identifiers.declared |
Armin Ronachere791c2a2008-04-07 18:39:54 +020090 parent.identifiers.declared_locally |
91 parent.identifiers.declared_parameter
92 )
93
Armin Ronacher8efc5222008-04-08 14:47:40 +020094 def copy(self):
95 """Create a copy of the current one."""
96 rv = copy(self)
97 rv.identifiers = copy(self)
98 return rv
99
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200100 def inspect(self, nodes, hard_scope=False):
101 """Walk the node and check for identifiers. If the scope
102 is hard (eg: enforce on a python level) overrides from outer
103 scopes are tracked differently.
104 """
105 visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200106 for node in nodes:
107 visitor.visit(node)
108
109 def inner(self):
110 """Return an inner frame."""
111 return Frame(self)
112
113
114class FrameIdentifierVisitor(NodeVisitor):
115 """A visitor for `Frame.inspect`."""
116
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200117 def __init__(self, identifiers, hard_scope):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200118 self.identifiers = identifiers
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200119 self.hard_scope = hard_scope
Armin Ronachere791c2a2008-04-07 18:39:54 +0200120
121 def visit_Name(self, node):
122 """All assignments to names go through this function."""
123 if node.ctx in ('store', 'param'):
124 self.identifiers.declared_locally.add(node.name)
125 elif node.ctx == 'load':
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200126 if not self.identifiers.is_declared(node.name, self.hard_scope):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200127 self.identifiers.undeclared.add(node.name)
128
Christoph Hack65642a52008-04-08 14:46:56 +0200129 def visit_FilterCall(self, node):
130 if not node.name in self.identifiers.declared_filter:
Christoph Hackacb130e2008-04-08 15:21:53 +0200131 uf = self.identifiers.undeclared_filter.get(node.name, 0) + 1
132 if uf > 1:
133 self.identifiers.declared_filter.add(node.name)
134 self.identifiers.undeclared_filter[node.name] = uf
Christoph Hack65642a52008-04-08 14:46:56 +0200135
Armin Ronachere791c2a2008-04-07 18:39:54 +0200136 def visit_Macro(self, node):
137 """Macros set local."""
138 self.identifiers.declared_locally.add(node.name)
139
140 # stop traversing at instructions that have their own scope.
Armin Ronacher8efc5222008-04-08 14:47:40 +0200141 visit_Block = visit_CallBlock = visit_FilterBlock = \
Armin Ronachere791c2a2008-04-07 18:39:54 +0200142 visit_For = lambda s, n: None
143
144
145class CodeGenerator(NodeVisitor):
146
Christoph Hack65642a52008-04-08 14:46:56 +0200147 def __init__(self, environment, is_child, filename, stream=None):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200148 if stream is None:
149 stream = StringIO()
Christoph Hack65642a52008-04-08 14:46:56 +0200150 self.environment = environment
Armin Ronachere791c2a2008-04-07 18:39:54 +0200151 self.is_child = is_child
152 self.filename = filename
153 self.stream = stream
154 self.blocks = {}
155 self.indentation = 0
156 self.new_lines = 0
157 self.last_identifier = 0
158 self._last_line = 0
159 self._first_write = True
160
161 def temporary_identifier(self):
162 self.last_identifier += 1
163 return 't%d' % self.last_identifier
164
165 def indent(self):
166 self.indentation += 1
167
Armin Ronacher8efc5222008-04-08 14:47:40 +0200168 def outdent(self, step=1):
169 self.indentation -= step
Armin Ronachere791c2a2008-04-07 18:39:54 +0200170
171 def blockvisit(self, nodes, frame, force_generator=False):
172 self.indent()
173 if force_generator:
174 self.writeline('if 0: yield None')
175 for node in nodes:
176 self.visit(node, frame)
177 self.outdent()
178
179 def write(self, x):
180 if self.new_lines:
181 if not self._first_write:
182 self.stream.write('\n' * self.new_lines)
183 self._first_write = False
184 self.stream.write(' ' * self.indentation)
185 self.new_lines = 0
186 self.stream.write(x)
187
188 def writeline(self, x, node=None, extra=0):
189 self.newline(node, extra)
190 self.write(x)
191
192 def newline(self, node=None, extra=0):
193 self.new_lines = max(self.new_lines, 1 + extra)
194 if node is not None and node.lineno != self._last_line:
195 self.write('# line: %s' % node.lineno)
196 self.new_lines = 1
197 self._last_line = node.lineno
198
Armin Ronacher8efc5222008-04-08 14:47:40 +0200199 def signature(self, node, frame, have_comma=True):
200 have_comma = have_comma and [True] or []
201 def touch_comma():
202 if have_comma:
203 self.write(', ')
204 else:
205 have_comma.append(True)
206
207 for arg in node.args:
208 touch_comma()
209 self.visit(arg, frame)
210 for kwarg in node.kwargs:
211 touch_comma()
212 self.visit(kwarg, frame)
213 if node.dyn_args:
214 touch_comma()
215 self.visit(node.dyn_args, frame)
216 if node.dyn_kwargs:
217 touch_comma()
218 self.visit(node.dyn_kwargs, frame)
219
Armin Ronachere791c2a2008-04-07 18:39:54 +0200220 def pull_locals(self, frame, no_indent=False):
221 if not no_indent:
222 self.indent()
223 for name in frame.identifiers.undeclared:
224 self.writeline('l_%s = context[%r]' % (name, name))
Christoph Hackacb130e2008-04-08 15:21:53 +0200225 for name, count in frame.identifiers.undeclared_filter.iteritems():
226 if count > 1:
227 self.writeline('f_%s = context[%r]' % (name, name))
Armin Ronachere791c2a2008-04-07 18:39:54 +0200228 if not no_indent:
229 self.outdent()
230
231 # -- Visitors
232
233 def visit_Template(self, node, frame=None):
234 assert frame is None, 'no root frame allowed'
235 self.writeline('from jinja2.runtime import *')
236 self.writeline('filename = %r' % self.filename)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200237 self.writeline('template_context = TemplateContext(global_context, '
Armin Ronachere791c2a2008-04-07 18:39:54 +0200238 'make_undefined, filename)')
239
Armin Ronacher8efc5222008-04-08 14:47:40 +0200240 # generate the root render function.
241 self.writeline('def root(context=template_context):', extra=1)
242 self.indent()
243 self.writeline('parent_root = None')
244 self.outdent()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200245 frame = Frame()
246 frame.inspect(node.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200247 frame.toplevel = True
Armin Ronachere791c2a2008-04-07 18:39:54 +0200248 self.pull_locals(frame)
249 self.blockvisit(node.body, frame, True)
250
Armin Ronacher8efc5222008-04-08 14:47:40 +0200251 # make sure that the parent root is called.
Armin Ronachere791c2a2008-04-07 18:39:54 +0200252 self.indent()
Armin Ronacher8efc5222008-04-08 14:47:40 +0200253 self.writeline('if parent_root is not None:')
254 self.indent()
255 self.writeline('for event in parent_root(context):')
256 self.indent()
257 self.writeline('yield event')
258 self.outdent(3)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200259
260 # at this point we now have the blocks collected and can visit them too.
261 for name, block in self.blocks.iteritems():
262 block_frame = Frame()
263 block_frame.inspect(block.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200264 block_frame.block = name
265 self.writeline('def block_%s(context):' % name, block, 1)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200266 self.pull_locals(block_frame)
267 self.blockvisit(block.body, block_frame, True)
268
269 def visit_Block(self, node, frame):
270 """Call a block and register it for the template."""
271 if node.name in self.blocks:
272 raise TemplateAssertionError("the block '%s' was already defined" %
273 node.name, node.lineno,
274 self.filename)
275 self.blocks[node.name] = node
Armin Ronacher8efc5222008-04-08 14:47:40 +0200276 self.writeline('for event in block_%s(context):' % node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200277 self.indent()
278 self.writeline('yield event')
279 self.outdent()
280
281 def visit_Extends(self, node, frame):
282 """Calls the extender."""
Armin Ronacher8efc5222008-04-08 14:47:40 +0200283 if not frame.toplevel:
284 raise TemplateAssertionError('cannot use extend from a non '
285 'top-level scope', node.lineno,
286 self.filename)
287 self.writeline('if parent_root is not None:')
288 self.indent()
289 self.writeline('raise TemplateRuntimeError(%r)' %
290 'extended multiple times')
291 self.outdent()
292 self.writeline('parent_root = extends(', node, 1)
293 self.visit(node.template, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200294 self.write(', globals())')
295
296 def visit_For(self, node, frame):
297 loop_frame = frame.inner()
298 loop_frame.inspect(node.iter_child_nodes())
Armin Ronachere791c2a2008-04-07 18:39:54 +0200299 extended_loop = bool(node.else_) or \
300 'loop' in loop_frame.identifiers.undeclared
Armin Ronacher8efc5222008-04-08 14:47:40 +0200301 loop_frame.identifiers.add_special('loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200302
303 # make sure we "backup" overridden, local identifiers
304 # TODO: we should probably optimize this and check if the
305 # identifier is in use afterwards.
306 aliases = {}
307 for name in loop_frame.identifiers.find_shadowed():
308 aliases[name] = ident = self.temporary_identifier()
309 self.writeline('%s = l_%s' % (ident, name))
310
311 self.pull_locals(loop_frame, True)
312
313 self.newline(node)
314 if node.else_:
315 self.writeline('l_loop = None')
316 self.write('for ')
317 self.visit(node.target, loop_frame)
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200318 self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200319 self.visit(node.iter, loop_frame)
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200320 if 'loop' in aliases:
321 self.write(', ' + aliases['loop'])
Armin Ronachere791c2a2008-04-07 18:39:54 +0200322 self.write(extended_loop and '):' or ':')
323 self.blockvisit(node.body, loop_frame)
324
325 if node.else_:
326 self.writeline('if l_loop is None:')
327 self.blockvisit(node.else_, loop_frame)
328
Armin Ronacher8efc5222008-04-08 14:47:40 +0200329 # reset the aliases and clean up
330 delete = set('l_' + x for x in loop_frame.identifiers.declared_locally
331 | loop_frame.identifiers.declared_parameter)
332 if extended_loop:
333 delete.add('l_loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200334 for name, alias in aliases.iteritems():
Armin Ronacher8efc5222008-04-08 14:47:40 +0200335 self.writeline('l_%s = %s' % (name, alias))
336 delete.add(alias)
337 delete.discard('l_' + name)
338 self.writeline('del %s' % ', '.join(delete))
Armin Ronachere791c2a2008-04-07 18:39:54 +0200339
340 def visit_If(self, node, frame):
341 self.writeline('if ', node)
342 self.visit(node.test, frame)
343 self.write(':')
344 self.blockvisit(node.body, frame)
345 if node.else_:
346 self.writeline('else:')
347 self.blockvisit(node.else_, frame)
348
Armin Ronacher8efc5222008-04-08 14:47:40 +0200349 def visit_Macro(self, node, frame):
350 macro_frame = frame.inner()
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200351 macro_frame.inspect(node.iter_child_nodes(), hard_scope=True)
352
353 # variables that are undeclared (accessed before declaration) and
354 # declared locally *and* part of an outside scope raise a template
355 # assertion error. Reason: we can't generate reasonable code from
356 # it without aliasing all the variables. XXX: alias them ^^
357 overriden_closure_vars = (
358 macro_frame.identifiers.undeclared &
359 macro_frame.identifiers.declared &
360 (macro_frame.identifiers.declared_locally |
361 macro_frame.identifiers.declared_parameter)
362 )
363 if overriden_closure_vars:
364 vars = ', '.join(sorted(overriden_closure_vars))
365 raise TemplateAssertionError('It\'s not possible to set and '
366 'access variables derived from '
367 'an outer scope! (affects: %s' %
368 vars, node.lineno, self.filename)
369
370 # remove variables from a closure from the frame's undeclared
371 # identifiers.
372 macro_frame.identifiers.undeclared -= (
373 macro_frame.identifiers.undeclared &
374 macro_frame.identifiers.declared
375 )
376
Armin Ronacher8efc5222008-04-08 14:47:40 +0200377 args = ['l_' + x.name for x in node.args]
378 if 'arguments' in macro_frame.identifiers.undeclared:
379 accesses_arguments = True
380 args.append('l_arguments')
381 else:
382 accesses_arguments = False
383 self.writeline('def macro(%s):' % ', '.join(args), node)
384 self.indent()
385 self.writeline('if 0: yield None')
386 self.outdent()
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200387 self.pull_locals(macro_frame)
388 self.blockvisit(node.body, macro_frame)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200389 self.newline()
390 if frame.toplevel:
391 self.write('context[%r] = ' % node.name)
392 arg_tuple = ', '.join(repr(x.name) for x in node.args)
393 if len(node.args) == 1:
394 arg_tuple += ','
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200395 self.write('l_%s = Macro(macro, %r, (%s), (' % (node.name, node.name,
396 arg_tuple))
397 for arg in node.defaults:
398 self.visit(arg)
399 self.write(', ')
Armin Ronacher9706fab2008-04-08 18:49:56 +0200400 self.write('), %r, make_undefined)' % accesses_arguments)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200401
Armin Ronachere791c2a2008-04-07 18:39:54 +0200402 def visit_ExprStmt(self, node, frame):
403 self.newline(node)
404 self.visit(node, frame)
405
406 def visit_Output(self, node, frame):
407 self.newline(node)
408
409 # try to evaluate as many chunks as possible into a static
410 # string at compile time.
411 body = []
412 for child in node.nodes:
413 try:
414 const = unicode(child.as_const())
415 except:
416 body.append(child)
417 continue
418 if body and isinstance(body[-1], list):
419 body[-1].append(const)
420 else:
421 body.append([const])
422
423 # if we have less than 3 nodes we just yield them
424 if len(body) < 3:
425 for item in body:
426 if isinstance(item, list):
427 self.writeline('yield %s' % repr(u''.join(item)))
428 else:
429 self.newline(item)
430 self.write('yield unicode(')
431 self.visit(item, frame)
432 self.write(')')
433
434 # otherwise we create a format string as this is faster in that case
435 else:
436 format = []
437 arguments = []
438 for item in body:
439 if isinstance(item, list):
440 format.append(u''.join(item).replace('%', '%%'))
441 else:
442 format.append('%s')
443 arguments.append(item)
444 self.writeline('yield %r %% (' % u''.join(format))
445 idx = -1
446 for idx, argument in enumerate(arguments):
447 if idx:
448 self.write(', ')
449 self.visit(argument, frame)
450 self.write(idx == 0 and ',)' or ')')
451
Armin Ronacher8efc5222008-04-08 14:47:40 +0200452 def visit_Assign(self, node, frame):
453 self.newline(node)
454 # toplevel assignments however go into the local namespace and
455 # the current template's context. We create a copy of the frame
456 # here and add a set so that the Name visitor can add the assigned
457 # names here.
458 if frame.toplevel:
459 assignment_frame = frame.copy()
460 assignment_frame.assigned_names = set()
461 else:
462 assignment_frame = frame
463 self.visit(node.target, assignment_frame)
464 self.write(' = ')
465 self.visit(node.node, frame)
Armin Ronacher9706fab2008-04-08 18:49:56 +0200466
467 # make sure toplevel assignments are added to the context.
Armin Ronacher8efc5222008-04-08 14:47:40 +0200468 if frame.toplevel:
469 for name in assignment_frame.assigned_names:
470 self.writeline('context[%r] = l_%s' % (name, name))
471
Armin Ronachere791c2a2008-04-07 18:39:54 +0200472 def visit_Name(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200473 if frame.toplevel and node.ctx == 'store':
474 frame.assigned_names.add(node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200475 self.write('l_' + node.name)
476
477 def visit_Const(self, node, frame):
478 val = node.value
479 if isinstance(val, float):
480 # XXX: add checks for infinity and nan
481 self.write(str(val))
482 else:
483 self.write(repr(val))
484
Armin Ronacher8efc5222008-04-08 14:47:40 +0200485 def visit_Tuple(self, node, frame):
486 self.write('(')
487 idx = -1
488 for idx, item in enumerate(node.items):
489 if idx:
490 self.write(', ')
491 self.visit(item, frame)
492 self.write(idx == 0 and ',)' or ')')
493
Armin Ronachere791c2a2008-04-07 18:39:54 +0200494 def binop(operator):
495 def visitor(self, node, frame):
496 self.write('(')
497 self.visit(node.left, frame)
498 self.write(' %s ' % operator)
499 self.visit(node.right, frame)
500 self.write(')')
501 return visitor
502
503 def uaop(operator):
504 def visitor(self, node, frame):
505 self.write('(' + operator)
506 self.visit(node.node)
507 self.write(')')
508 return visitor
509
510 visit_Add = binop('+')
511 visit_Sub = binop('-')
512 visit_Mul = binop('*')
513 visit_Div = binop('/')
514 visit_FloorDiv = binop('//')
515 visit_Pow = binop('**')
516 visit_Mod = binop('%')
517 visit_And = binop('and')
518 visit_Or = binop('or')
519 visit_Pos = uaop('+')
520 visit_Neg = uaop('-')
521 visit_Not = uaop('not ')
522 del binop, uaop
523
524 def visit_Compare(self, node, frame):
525 self.visit(node.expr, frame)
526 for op in node.ops:
527 self.visit(op, frame)
528
529 def visit_Operand(self, node, frame):
530 self.write(' %s ' % operators[node.op])
531 self.visit(node.expr, frame)
532
533 def visit_Subscript(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200534 if isinstance(node.arg, nodes.Slice):
535 self.visit(node.node, frame)
536 self.write('[')
537 self.visit(node.arg, frame)
538 self.write(']')
539 return
540 try:
541 const = node.arg.as_const()
542 have_const = True
543 except nodes.Impossible:
544 have_const = False
545 if have_const:
546 if isinstance(const, (int, long, float)):
547 self.visit(node.node, frame)
548 self.write('[%s]' % const)
549 return
550 self.write('subscribe(')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200551 self.visit(node.node, frame)
552 self.write(', ')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200553 if have_const:
554 self.write(repr(const))
555 else:
556 self.visit(node.arg, frame)
557 self.write(', make_undefined)')
558
559 def visit_Slice(self, node, frame):
560 if node.start is not None:
561 self.visit(node.start, frame)
562 self.write(':')
563 if node.stop is not None:
564 self.visit(node.stop, frame)
565 if node.step is not None:
566 self.write(':')
567 self.visit(node.step, frame)
568
569 def visit_Filter(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200570 for filter in node.filters:
Christoph Hackacb130e2008-04-08 15:21:53 +0200571 if filter.name in frame.identifiers.declared_filter:
572 self.write('f_%s(' % filter.name)
573 else:
574 self.write('context.filter[%r](' % filter.name)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200575 self.visit(node.node, frame)
576 for filter in reversed(node.filters):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200577 self.signature(filter, frame)
578 self.write(')')
579
580 def visit_Test(self, node, frame):
581 self.write('context.tests[%r](')
582 self.visit(node.node, frame)
583 self.signature(node, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200584 self.write(')')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200585
586 def visit_Call(self, node, frame):
587 self.visit(node.node, frame)
588 self.write('(')
589 self.signature(node, frame, False)
590 self.write(')')
591
592 def visit_Keyword(self, node, frame):
593 self.visit(node.key, frame)
594 self.write('=')
595 self.visit(node.value, frame)