blob: bdf195f1a221cfdd478443100598e4b0b7f2829b [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
13from operator import xor
14from cStringIO import StringIO
15from jinja2 import nodes
16from jinja2.visitor import NodeVisitor, NodeTransformer
17from jinja2.exceptions import TemplateAssertionError
18
19
20operators = {
21 'eq': '==',
22 'ne': '!=',
23 'gt': '>',
24 'gteq': '>=',
25 'lt': '<',
26 'lteq': '<=',
27 'in': 'in',
28 'notin': 'not in'
29}
30
31
32def generate(node, filename, stream=None):
33 is_child = node.find(nodes.Extends) is not None
34 generator = CodeGenerator(is_child, filename, stream)
35 generator.visit(node)
36 if stream is None:
37 return generator.stream.getvalue()
38
39
40class Identifiers(object):
41 """Tracks the status of identifiers in frames."""
42
43 def __init__(self):
44 # variables that are known to be declared (probably from outer
45 # frames or because they are special for the frame)
46 self.declared = set()
47
48 # names that are accessed without being explicitly declared by
49 # this one or any of the outer scopes. Names can appear both in
50 # declared and undeclared.
51 self.undeclared = set()
52
53 # names that are declared locally
54 self.declared_locally = set()
55
56 # names that are declared by parameters
57 self.declared_parameter = set()
58
59 def add_special(self, name):
60 """Register a special name like `loop`."""
61 self.undeclared.discard(name)
62 self.declared.add(name)
63
Armin Ronacher4f62a9f2008-04-08 18:09:13 +020064 def is_declared(self, name, local_only=False):
Armin Ronachere791c2a2008-04-07 18:39:54 +020065 """Check if a name is declared in this or an outer scope."""
Armin Ronacher4f62a9f2008-04-08 18:09:13 +020066 if name in self.declared_locally or name in self.declared_parameter:
67 return True
68 if local_only:
69 return False
70 return name in self.declared
Armin Ronachere791c2a2008-04-07 18:39:54 +020071
72 def find_shadowed(self):
73 """Find all the shadowed names."""
74 return self.declared & (self.declared_locally | self.declared_parameter)
75
76
77class Frame(object):
78
79 def __init__(self, parent=None):
80 self.identifiers = Identifiers()
Armin Ronacher8efc5222008-04-08 14:47:40 +020081 self.toplevel = False
Armin Ronachere791c2a2008-04-07 18:39:54 +020082 self.parent = parent
Armin Ronacher8efc5222008-04-08 14:47:40 +020083 self.block = parent and parent.block or None
Armin Ronachere791c2a2008-04-07 18:39:54 +020084 if parent is not None:
85 self.identifiers.declared.update(
86 parent.identifiers.declared |
Armin Ronachere791c2a2008-04-07 18:39:54 +020087 parent.identifiers.declared_locally |
88 parent.identifiers.declared_parameter
89 )
90
Armin Ronacher8efc5222008-04-08 14:47:40 +020091 def copy(self):
92 """Create a copy of the current one."""
93 rv = copy(self)
94 rv.identifiers = copy(self)
95 return rv
96
Armin Ronacher4f62a9f2008-04-08 18:09:13 +020097 def inspect(self, nodes, hard_scope=False):
98 """Walk the node and check for identifiers. If the scope
99 is hard (eg: enforce on a python level) overrides from outer
100 scopes are tracked differently.
101 """
102 visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200103 for node in nodes:
104 visitor.visit(node)
105
106 def inner(self):
107 """Return an inner frame."""
108 return Frame(self)
109
110
111class FrameIdentifierVisitor(NodeVisitor):
112 """A visitor for `Frame.inspect`."""
113
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200114 def __init__(self, identifiers, hard_scope):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200115 self.identifiers = identifiers
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200116 self.hard_scope = hard_scope
Armin Ronachere791c2a2008-04-07 18:39:54 +0200117
118 def visit_Name(self, node):
119 """All assignments to names go through this function."""
120 if node.ctx in ('store', 'param'):
121 self.identifiers.declared_locally.add(node.name)
122 elif node.ctx == 'load':
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200123 if not self.identifiers.is_declared(node.name, self.hard_scope):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200124 self.identifiers.undeclared.add(node.name)
125
126 def visit_Macro(self, node):
127 """Macros set local."""
128 self.identifiers.declared_locally.add(node.name)
129
130 # stop traversing at instructions that have their own scope.
Armin Ronacher8efc5222008-04-08 14:47:40 +0200131 visit_Block = visit_CallBlock = visit_FilterBlock = \
Armin Ronachere791c2a2008-04-07 18:39:54 +0200132 visit_For = lambda s, n: None
133
134
135class CodeGenerator(NodeVisitor):
136
137 def __init__(self, is_child, filename, stream=None):
138 if stream is None:
139 stream = StringIO()
140 self.is_child = is_child
141 self.filename = filename
142 self.stream = stream
143 self.blocks = {}
144 self.indentation = 0
145 self.new_lines = 0
146 self.last_identifier = 0
147 self._last_line = 0
148 self._first_write = True
149
150 def temporary_identifier(self):
151 self.last_identifier += 1
152 return 't%d' % self.last_identifier
153
154 def indent(self):
155 self.indentation += 1
156
Armin Ronacher8efc5222008-04-08 14:47:40 +0200157 def outdent(self, step=1):
158 self.indentation -= step
Armin Ronachere791c2a2008-04-07 18:39:54 +0200159
160 def blockvisit(self, nodes, frame, force_generator=False):
161 self.indent()
162 if force_generator:
163 self.writeline('if 0: yield None')
164 for node in nodes:
165 self.visit(node, frame)
166 self.outdent()
167
168 def write(self, x):
169 if self.new_lines:
170 if not self._first_write:
171 self.stream.write('\n' * self.new_lines)
172 self._first_write = False
173 self.stream.write(' ' * self.indentation)
174 self.new_lines = 0
175 self.stream.write(x)
176
177 def writeline(self, x, node=None, extra=0):
178 self.newline(node, extra)
179 self.write(x)
180
181 def newline(self, node=None, extra=0):
182 self.new_lines = max(self.new_lines, 1 + extra)
183 if node is not None and node.lineno != self._last_line:
184 self.write('# line: %s' % node.lineno)
185 self.new_lines = 1
186 self._last_line = node.lineno
187
Armin Ronacher8efc5222008-04-08 14:47:40 +0200188 def signature(self, node, frame, have_comma=True):
189 have_comma = have_comma and [True] or []
190 def touch_comma():
191 if have_comma:
192 self.write(', ')
193 else:
194 have_comma.append(True)
195
196 for arg in node.args:
197 touch_comma()
198 self.visit(arg, frame)
199 for kwarg in node.kwargs:
200 touch_comma()
201 self.visit(kwarg, frame)
202 if node.dyn_args:
203 touch_comma()
204 self.visit(node.dyn_args, frame)
205 if node.dyn_kwargs:
206 touch_comma()
207 self.visit(node.dyn_kwargs, frame)
208
Armin Ronachere791c2a2008-04-07 18:39:54 +0200209 def pull_locals(self, frame, no_indent=False):
210 if not no_indent:
211 self.indent()
212 for name in frame.identifiers.undeclared:
213 self.writeline('l_%s = context[%r]' % (name, name))
214 if not no_indent:
215 self.outdent()
216
217 # -- Visitors
218
219 def visit_Template(self, node, frame=None):
220 assert frame is None, 'no root frame allowed'
221 self.writeline('from jinja2.runtime import *')
222 self.writeline('filename = %r' % self.filename)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200223 self.writeline('template_context = TemplateContext(global_context, '
Armin Ronachere791c2a2008-04-07 18:39:54 +0200224 'make_undefined, filename)')
225
Armin Ronacher8efc5222008-04-08 14:47:40 +0200226 # generate the root render function.
227 self.writeline('def root(context=template_context):', extra=1)
228 self.indent()
229 self.writeline('parent_root = None')
230 self.outdent()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200231 frame = Frame()
232 frame.inspect(node.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200233 frame.toplevel = True
Armin Ronachere791c2a2008-04-07 18:39:54 +0200234 self.pull_locals(frame)
235 self.blockvisit(node.body, frame, True)
236
Armin Ronacher8efc5222008-04-08 14:47:40 +0200237 # make sure that the parent root is called.
Armin Ronachere791c2a2008-04-07 18:39:54 +0200238 self.indent()
Armin Ronacher8efc5222008-04-08 14:47:40 +0200239 self.writeline('if parent_root is not None:')
240 self.indent()
241 self.writeline('for event in parent_root(context):')
242 self.indent()
243 self.writeline('yield event')
244 self.outdent(3)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200245
246 # at this point we now have the blocks collected and can visit them too.
247 for name, block in self.blocks.iteritems():
248 block_frame = Frame()
249 block_frame.inspect(block.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200250 block_frame.block = name
251 self.writeline('def block_%s(context):' % name, block, 1)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200252 self.pull_locals(block_frame)
253 self.blockvisit(block.body, block_frame, True)
254
255 def visit_Block(self, node, frame):
256 """Call a block and register it for the template."""
257 if node.name in self.blocks:
258 raise TemplateAssertionError("the block '%s' was already defined" %
259 node.name, node.lineno,
260 self.filename)
261 self.blocks[node.name] = node
Armin Ronacher8efc5222008-04-08 14:47:40 +0200262 self.writeline('for event in block_%s(context):' % node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200263 self.indent()
264 self.writeline('yield event')
265 self.outdent()
266
267 def visit_Extends(self, node, frame):
268 """Calls the extender."""
Armin Ronacher8efc5222008-04-08 14:47:40 +0200269 if not frame.toplevel:
270 raise TemplateAssertionError('cannot use extend from a non '
271 'top-level scope', node.lineno,
272 self.filename)
273 self.writeline('if parent_root is not None:')
274 self.indent()
275 self.writeline('raise TemplateRuntimeError(%r)' %
276 'extended multiple times')
277 self.outdent()
278 self.writeline('parent_root = extends(', node, 1)
279 self.visit(node.template, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200280 self.write(', globals())')
281
282 def visit_For(self, node, frame):
283 loop_frame = frame.inner()
284 loop_frame.inspect(node.iter_child_nodes())
Armin Ronachere791c2a2008-04-07 18:39:54 +0200285 extended_loop = bool(node.else_) or \
286 'loop' in loop_frame.identifiers.undeclared
Armin Ronacher8efc5222008-04-08 14:47:40 +0200287 loop_frame.identifiers.add_special('loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200288
289 # make sure we "backup" overridden, local identifiers
290 # TODO: we should probably optimize this and check if the
291 # identifier is in use afterwards.
292 aliases = {}
293 for name in loop_frame.identifiers.find_shadowed():
294 aliases[name] = ident = self.temporary_identifier()
295 self.writeline('%s = l_%s' % (ident, name))
296
297 self.pull_locals(loop_frame, True)
298
299 self.newline(node)
300 if node.else_:
301 self.writeline('l_loop = None')
302 self.write('for ')
303 self.visit(node.target, loop_frame)
304 self.write(extended_loop and ', l_loop in looper(' or ' in ')
305 self.visit(node.iter, loop_frame)
306 self.write(extended_loop and '):' or ':')
307 self.blockvisit(node.body, loop_frame)
308
309 if node.else_:
310 self.writeline('if l_loop is None:')
311 self.blockvisit(node.else_, loop_frame)
312
Armin Ronacher8efc5222008-04-08 14:47:40 +0200313 # reset the aliases and clean up
314 delete = set('l_' + x for x in loop_frame.identifiers.declared_locally
315 | loop_frame.identifiers.declared_parameter)
316 if extended_loop:
317 delete.add('l_loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200318 for name, alias in aliases.iteritems():
Armin Ronacher8efc5222008-04-08 14:47:40 +0200319 self.writeline('l_%s = %s' % (name, alias))
320 delete.add(alias)
321 delete.discard('l_' + name)
322 self.writeline('del %s' % ', '.join(delete))
Armin Ronachere791c2a2008-04-07 18:39:54 +0200323
324 def visit_If(self, node, frame):
325 self.writeline('if ', node)
326 self.visit(node.test, frame)
327 self.write(':')
328 self.blockvisit(node.body, frame)
329 if node.else_:
330 self.writeline('else:')
331 self.blockvisit(node.else_, frame)
332
Armin Ronacher8efc5222008-04-08 14:47:40 +0200333 def visit_Macro(self, node, frame):
334 macro_frame = frame.inner()
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200335 macro_frame.inspect(node.iter_child_nodes(), hard_scope=True)
336
337 # variables that are undeclared (accessed before declaration) and
338 # declared locally *and* part of an outside scope raise a template
339 # assertion error. Reason: we can't generate reasonable code from
340 # it without aliasing all the variables. XXX: alias them ^^
341 overriden_closure_vars = (
342 macro_frame.identifiers.undeclared &
343 macro_frame.identifiers.declared &
344 (macro_frame.identifiers.declared_locally |
345 macro_frame.identifiers.declared_parameter)
346 )
347 if overriden_closure_vars:
348 vars = ', '.join(sorted(overriden_closure_vars))
349 raise TemplateAssertionError('It\'s not possible to set and '
350 'access variables derived from '
351 'an outer scope! (affects: %s' %
352 vars, node.lineno, self.filename)
353
354 # remove variables from a closure from the frame's undeclared
355 # identifiers.
356 macro_frame.identifiers.undeclared -= (
357 macro_frame.identifiers.undeclared &
358 macro_frame.identifiers.declared
359 )
360
Armin Ronacher8efc5222008-04-08 14:47:40 +0200361 args = ['l_' + x.name for x in node.args]
362 if 'arguments' in macro_frame.identifiers.undeclared:
363 accesses_arguments = True
364 args.append('l_arguments')
365 else:
366 accesses_arguments = False
367 self.writeline('def macro(%s):' % ', '.join(args), node)
368 self.indent()
369 self.writeline('if 0: yield None')
370 self.outdent()
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200371 self.pull_locals(macro_frame)
372 self.blockvisit(node.body, macro_frame)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200373 self.newline()
374 if frame.toplevel:
375 self.write('context[%r] = ' % node.name)
376 arg_tuple = ', '.join(repr(x.name) for x in node.args)
377 if len(node.args) == 1:
378 arg_tuple += ','
Armin Ronacher4f62a9f2008-04-08 18:09:13 +0200379 self.write('l_%s = Macro(macro, %r, (%s), (' % (node.name, node.name,
380 arg_tuple))
381 for arg in node.defaults:
382 self.visit(arg)
383 self.write(', ')
Armin Ronacher9706fab2008-04-08 18:49:56 +0200384 self.write('), %r, make_undefined)' % accesses_arguments)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200385
Armin Ronachere791c2a2008-04-07 18:39:54 +0200386 def visit_ExprStmt(self, node, frame):
387 self.newline(node)
388 self.visit(node, frame)
389
390 def visit_Output(self, node, frame):
391 self.newline(node)
392
393 # try to evaluate as many chunks as possible into a static
394 # string at compile time.
395 body = []
396 for child in node.nodes:
397 try:
398 const = unicode(child.as_const())
399 except:
400 body.append(child)
401 continue
402 if body and isinstance(body[-1], list):
403 body[-1].append(const)
404 else:
405 body.append([const])
406
407 # if we have less than 3 nodes we just yield them
408 if len(body) < 3:
409 for item in body:
410 if isinstance(item, list):
411 self.writeline('yield %s' % repr(u''.join(item)))
412 else:
413 self.newline(item)
414 self.write('yield unicode(')
415 self.visit(item, frame)
416 self.write(')')
417
418 # otherwise we create a format string as this is faster in that case
419 else:
420 format = []
421 arguments = []
422 for item in body:
423 if isinstance(item, list):
424 format.append(u''.join(item).replace('%', '%%'))
425 else:
426 format.append('%s')
427 arguments.append(item)
428 self.writeline('yield %r %% (' % u''.join(format))
429 idx = -1
430 for idx, argument in enumerate(arguments):
431 if idx:
432 self.write(', ')
433 self.visit(argument, frame)
434 self.write(idx == 0 and ',)' or ')')
435
Armin Ronacher8efc5222008-04-08 14:47:40 +0200436 def visit_Assign(self, node, frame):
437 self.newline(node)
438 # toplevel assignments however go into the local namespace and
439 # the current template's context. We create a copy of the frame
440 # here and add a set so that the Name visitor can add the assigned
441 # names here.
442 if frame.toplevel:
443 assignment_frame = frame.copy()
444 assignment_frame.assigned_names = set()
445 else:
446 assignment_frame = frame
447 self.visit(node.target, assignment_frame)
448 self.write(' = ')
449 self.visit(node.node, frame)
Armin Ronacher9706fab2008-04-08 18:49:56 +0200450
451 # make sure toplevel assignments are added to the context.
Armin Ronacher8efc5222008-04-08 14:47:40 +0200452 if frame.toplevel:
453 for name in assignment_frame.assigned_names:
454 self.writeline('context[%r] = l_%s' % (name, name))
455
Armin Ronachere791c2a2008-04-07 18:39:54 +0200456 def visit_Name(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200457 if frame.toplevel and node.ctx == 'store':
458 frame.assigned_names.add(node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200459 self.write('l_' + node.name)
460
461 def visit_Const(self, node, frame):
462 val = node.value
463 if isinstance(val, float):
464 # XXX: add checks for infinity and nan
465 self.write(str(val))
466 else:
467 self.write(repr(val))
468
Armin Ronacher8efc5222008-04-08 14:47:40 +0200469 def visit_Tuple(self, node, frame):
470 self.write('(')
471 idx = -1
472 for idx, item in enumerate(node.items):
473 if idx:
474 self.write(', ')
475 self.visit(item, frame)
476 self.write(idx == 0 and ',)' or ')')
477
Armin Ronachere791c2a2008-04-07 18:39:54 +0200478 def binop(operator):
479 def visitor(self, node, frame):
480 self.write('(')
481 self.visit(node.left, frame)
482 self.write(' %s ' % operator)
483 self.visit(node.right, frame)
484 self.write(')')
485 return visitor
486
487 def uaop(operator):
488 def visitor(self, node, frame):
489 self.write('(' + operator)
490 self.visit(node.node)
491 self.write(')')
492 return visitor
493
494 visit_Add = binop('+')
495 visit_Sub = binop('-')
496 visit_Mul = binop('*')
497 visit_Div = binop('/')
498 visit_FloorDiv = binop('//')
499 visit_Pow = binop('**')
500 visit_Mod = binop('%')
501 visit_And = binop('and')
502 visit_Or = binop('or')
503 visit_Pos = uaop('+')
504 visit_Neg = uaop('-')
505 visit_Not = uaop('not ')
506 del binop, uaop
507
508 def visit_Compare(self, node, frame):
509 self.visit(node.expr, frame)
510 for op in node.ops:
511 self.visit(op, frame)
512
513 def visit_Operand(self, node, frame):
514 self.write(' %s ' % operators[node.op])
515 self.visit(node.expr, frame)
516
517 def visit_Subscript(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200518 if isinstance(node.arg, nodes.Slice):
519 self.visit(node.node, frame)
520 self.write('[')
521 self.visit(node.arg, frame)
522 self.write(']')
523 return
524 try:
525 const = node.arg.as_const()
526 have_const = True
527 except nodes.Impossible:
528 have_const = False
529 if have_const:
530 if isinstance(const, (int, long, float)):
531 self.visit(node.node, frame)
532 self.write('[%s]' % const)
533 return
534 self.write('subscribe(')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200535 self.visit(node.node, frame)
536 self.write(', ')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200537 if have_const:
538 self.write(repr(const))
539 else:
540 self.visit(node.arg, frame)
541 self.write(', make_undefined)')
542
543 def visit_Slice(self, node, frame):
544 if node.start is not None:
545 self.visit(node.start, frame)
546 self.write(':')
547 if node.stop is not None:
548 self.visit(node.stop, frame)
549 if node.step is not None:
550 self.write(':')
551 self.visit(node.step, frame)
552
553 def visit_Filter(self, node, frame):
554 for filter in node.filters:
555 self.write('context.filters[%r](' % filter.name)
556 self.visit(node.node, frame)
557 for filter in reversed(node.filters):
558 self.signature(filter, frame)
559 self.write(')')
560
561 def visit_Test(self, node, frame):
562 self.write('context.tests[%r](')
563 self.visit(node.node, frame)
564 self.signature(node, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200565 self.write(')')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200566
567 def visit_Call(self, node, frame):
568 self.visit(node.node, frame)
569 self.write('(')
570 self.signature(node, frame, False)
571 self.write(')')
572
573 def visit_Keyword(self, node, frame):
574 self.visit(node.key, frame)
575 self.write('=')
576 self.visit(node.value, frame)