blob: 41577c8c6bbcbe14bf82488ccb91212513c4360b [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
64 def is_declared(self, name):
65 """Check if a name is declared in this or an outer scope."""
66 return name in self.declared or name in self.declared_locally or \
67 name in self.declared_parameter
68
69 def find_shadowed(self):
70 """Find all the shadowed names."""
71 return self.declared & (self.declared_locally | self.declared_parameter)
72
73
74class Frame(object):
75
76 def __init__(self, parent=None):
77 self.identifiers = Identifiers()
Armin Ronacher8efc5222008-04-08 14:47:40 +020078 self.toplevel = False
Armin Ronachere791c2a2008-04-07 18:39:54 +020079 self.parent = parent
Armin Ronacher8efc5222008-04-08 14:47:40 +020080 self.block = parent and parent.block or None
Armin Ronachere791c2a2008-04-07 18:39:54 +020081 if parent is not None:
82 self.identifiers.declared.update(
83 parent.identifiers.declared |
84 parent.identifiers.undeclared |
85 parent.identifiers.declared_locally |
86 parent.identifiers.declared_parameter
87 )
88
Armin Ronacher8efc5222008-04-08 14:47:40 +020089 def copy(self):
90 """Create a copy of the current one."""
91 rv = copy(self)
92 rv.identifiers = copy(self)
93 return rv
94
Armin Ronachere791c2a2008-04-07 18:39:54 +020095 def inspect(self, nodes):
96 """Walk the node and check for identifiers."""
97 visitor = FrameIdentifierVisitor(self.identifiers)
98 for node in nodes:
99 visitor.visit(node)
100
101 def inner(self):
102 """Return an inner frame."""
103 return Frame(self)
104
105
106class FrameIdentifierVisitor(NodeVisitor):
107 """A visitor for `Frame.inspect`."""
108
109 def __init__(self, identifiers):
110 self.identifiers = identifiers
111
112 def visit_Name(self, node):
113 """All assignments to names go through this function."""
114 if node.ctx in ('store', 'param'):
115 self.identifiers.declared_locally.add(node.name)
116 elif node.ctx == 'load':
117 if not self.identifiers.is_declared(node.name):
118 self.identifiers.undeclared.add(node.name)
119
120 def visit_Macro(self, node):
121 """Macros set local."""
122 self.identifiers.declared_locally.add(node.name)
123
124 # stop traversing at instructions that have their own scope.
Armin Ronacher8efc5222008-04-08 14:47:40 +0200125 visit_Block = visit_CallBlock = visit_FilterBlock = \
Armin Ronachere791c2a2008-04-07 18:39:54 +0200126 visit_For = lambda s, n: None
127
128
129class CodeGenerator(NodeVisitor):
130
131 def __init__(self, is_child, filename, stream=None):
132 if stream is None:
133 stream = StringIO()
134 self.is_child = is_child
135 self.filename = filename
136 self.stream = stream
137 self.blocks = {}
138 self.indentation = 0
139 self.new_lines = 0
140 self.last_identifier = 0
141 self._last_line = 0
142 self._first_write = True
143
144 def temporary_identifier(self):
145 self.last_identifier += 1
146 return 't%d' % self.last_identifier
147
148 def indent(self):
149 self.indentation += 1
150
Armin Ronacher8efc5222008-04-08 14:47:40 +0200151 def outdent(self, step=1):
152 self.indentation -= step
Armin Ronachere791c2a2008-04-07 18:39:54 +0200153
154 def blockvisit(self, nodes, frame, force_generator=False):
155 self.indent()
156 if force_generator:
157 self.writeline('if 0: yield None')
158 for node in nodes:
159 self.visit(node, frame)
160 self.outdent()
161
162 def write(self, x):
163 if self.new_lines:
164 if not self._first_write:
165 self.stream.write('\n' * self.new_lines)
166 self._first_write = False
167 self.stream.write(' ' * self.indentation)
168 self.new_lines = 0
169 self.stream.write(x)
170
171 def writeline(self, x, node=None, extra=0):
172 self.newline(node, extra)
173 self.write(x)
174
175 def newline(self, node=None, extra=0):
176 self.new_lines = max(self.new_lines, 1 + extra)
177 if node is not None and node.lineno != self._last_line:
178 self.write('# line: %s' % node.lineno)
179 self.new_lines = 1
180 self._last_line = node.lineno
181
Armin Ronacher8efc5222008-04-08 14:47:40 +0200182 def signature(self, node, frame, have_comma=True):
183 have_comma = have_comma and [True] or []
184 def touch_comma():
185 if have_comma:
186 self.write(', ')
187 else:
188 have_comma.append(True)
189
190 for arg in node.args:
191 touch_comma()
192 self.visit(arg, frame)
193 for kwarg in node.kwargs:
194 touch_comma()
195 self.visit(kwarg, frame)
196 if node.dyn_args:
197 touch_comma()
198 self.visit(node.dyn_args, frame)
199 if node.dyn_kwargs:
200 touch_comma()
201 self.visit(node.dyn_kwargs, frame)
202
Armin Ronachere791c2a2008-04-07 18:39:54 +0200203 def pull_locals(self, frame, no_indent=False):
204 if not no_indent:
205 self.indent()
206 for name in frame.identifiers.undeclared:
207 self.writeline('l_%s = context[%r]' % (name, name))
208 if not no_indent:
209 self.outdent()
210
211 # -- Visitors
212
213 def visit_Template(self, node, frame=None):
214 assert frame is None, 'no root frame allowed'
215 self.writeline('from jinja2.runtime import *')
216 self.writeline('filename = %r' % self.filename)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200217 self.writeline('template_context = TemplateContext(global_context, '
Armin Ronachere791c2a2008-04-07 18:39:54 +0200218 'make_undefined, filename)')
219
Armin Ronacher8efc5222008-04-08 14:47:40 +0200220 # generate the root render function.
221 self.writeline('def root(context=template_context):', extra=1)
222 self.indent()
223 self.writeline('parent_root = None')
224 self.outdent()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200225 frame = Frame()
226 frame.inspect(node.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200227 frame.toplevel = True
Armin Ronachere791c2a2008-04-07 18:39:54 +0200228 self.pull_locals(frame)
229 self.blockvisit(node.body, frame, True)
230
Armin Ronacher8efc5222008-04-08 14:47:40 +0200231 # make sure that the parent root is called.
Armin Ronachere791c2a2008-04-07 18:39:54 +0200232 self.indent()
Armin Ronacher8efc5222008-04-08 14:47:40 +0200233 self.writeline('if parent_root is not None:')
234 self.indent()
235 self.writeline('for event in parent_root(context):')
236 self.indent()
237 self.writeline('yield event')
238 self.outdent(3)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200239
240 # at this point we now have the blocks collected and can visit them too.
241 for name, block in self.blocks.iteritems():
242 block_frame = Frame()
243 block_frame.inspect(block.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200244 block_frame.block = name
245 self.writeline('def block_%s(context):' % name, block, 1)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200246 self.pull_locals(block_frame)
247 self.blockvisit(block.body, block_frame, True)
248
249 def visit_Block(self, node, frame):
250 """Call a block and register it for the template."""
251 if node.name in self.blocks:
252 raise TemplateAssertionError("the block '%s' was already defined" %
253 node.name, node.lineno,
254 self.filename)
255 self.blocks[node.name] = node
Armin Ronacher8efc5222008-04-08 14:47:40 +0200256 self.writeline('for event in block_%s(context):' % node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200257 self.indent()
258 self.writeline('yield event')
259 self.outdent()
260
261 def visit_Extends(self, node, frame):
262 """Calls the extender."""
Armin Ronacher8efc5222008-04-08 14:47:40 +0200263 if not frame.toplevel:
264 raise TemplateAssertionError('cannot use extend from a non '
265 'top-level scope', node.lineno,
266 self.filename)
267 self.writeline('if parent_root is not None:')
268 self.indent()
269 self.writeline('raise TemplateRuntimeError(%r)' %
270 'extended multiple times')
271 self.outdent()
272 self.writeline('parent_root = extends(', node, 1)
273 self.visit(node.template, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200274 self.write(', globals())')
275
276 def visit_For(self, node, frame):
277 loop_frame = frame.inner()
278 loop_frame.inspect(node.iter_child_nodes())
Armin Ronachere791c2a2008-04-07 18:39:54 +0200279 extended_loop = bool(node.else_) or \
280 'loop' in loop_frame.identifiers.undeclared
Armin Ronacher8efc5222008-04-08 14:47:40 +0200281 loop_frame.identifiers.add_special('loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200282
283 # make sure we "backup" overridden, local identifiers
284 # TODO: we should probably optimize this and check if the
285 # identifier is in use afterwards.
286 aliases = {}
287 for name in loop_frame.identifiers.find_shadowed():
288 aliases[name] = ident = self.temporary_identifier()
289 self.writeline('%s = l_%s' % (ident, name))
290
291 self.pull_locals(loop_frame, True)
292
293 self.newline(node)
294 if node.else_:
295 self.writeline('l_loop = None')
296 self.write('for ')
297 self.visit(node.target, loop_frame)
298 self.write(extended_loop and ', l_loop in looper(' or ' in ')
299 self.visit(node.iter, loop_frame)
300 self.write(extended_loop and '):' or ':')
301 self.blockvisit(node.body, loop_frame)
302
303 if node.else_:
304 self.writeline('if l_loop is None:')
305 self.blockvisit(node.else_, loop_frame)
306
Armin Ronacher8efc5222008-04-08 14:47:40 +0200307 # reset the aliases and clean up
308 delete = set('l_' + x for x in loop_frame.identifiers.declared_locally
309 | loop_frame.identifiers.declared_parameter)
310 if extended_loop:
311 delete.add('l_loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200312 for name, alias in aliases.iteritems():
Armin Ronacher8efc5222008-04-08 14:47:40 +0200313 self.writeline('l_%s = %s' % (name, alias))
314 delete.add(alias)
315 delete.discard('l_' + name)
316 self.writeline('del %s' % ', '.join(delete))
Armin Ronachere791c2a2008-04-07 18:39:54 +0200317
318 def visit_If(self, node, frame):
319 self.writeline('if ', node)
320 self.visit(node.test, frame)
321 self.write(':')
322 self.blockvisit(node.body, frame)
323 if node.else_:
324 self.writeline('else:')
325 self.blockvisit(node.else_, frame)
326
Armin Ronacher8efc5222008-04-08 14:47:40 +0200327 def visit_Macro(self, node, frame):
328 macro_frame = frame.inner()
329 macro_frame.inspect(node.body)
330 args = ['l_' + x.name for x in node.args]
331 if 'arguments' in macro_frame.identifiers.undeclared:
332 accesses_arguments = True
333 args.append('l_arguments')
334 else:
335 accesses_arguments = False
336 self.writeline('def macro(%s):' % ', '.join(args), node)
337 self.indent()
338 self.writeline('if 0: yield None')
339 self.outdent()
340 self.blockvisit(node.body, frame)
341 self.newline()
342 if frame.toplevel:
343 self.write('context[%r] = ' % node.name)
344 arg_tuple = ', '.join(repr(x.name) for x in node.args)
345 if len(node.args) == 1:
346 arg_tuple += ','
347 self.write('l_%s = Macro(macro, %r, (%s), %s)' % (
348 node.name, node.name,
349 arg_tuple, accesses_arguments
350 ))
351
Armin Ronachere791c2a2008-04-07 18:39:54 +0200352 def visit_ExprStmt(self, node, frame):
353 self.newline(node)
354 self.visit(node, frame)
355
356 def visit_Output(self, node, frame):
357 self.newline(node)
358
359 # try to evaluate as many chunks as possible into a static
360 # string at compile time.
361 body = []
362 for child in node.nodes:
363 try:
364 const = unicode(child.as_const())
365 except:
366 body.append(child)
367 continue
368 if body and isinstance(body[-1], list):
369 body[-1].append(const)
370 else:
371 body.append([const])
372
373 # if we have less than 3 nodes we just yield them
374 if len(body) < 3:
375 for item in body:
376 if isinstance(item, list):
377 self.writeline('yield %s' % repr(u''.join(item)))
378 else:
379 self.newline(item)
380 self.write('yield unicode(')
381 self.visit(item, frame)
382 self.write(')')
383
384 # otherwise we create a format string as this is faster in that case
385 else:
386 format = []
387 arguments = []
388 for item in body:
389 if isinstance(item, list):
390 format.append(u''.join(item).replace('%', '%%'))
391 else:
392 format.append('%s')
393 arguments.append(item)
394 self.writeline('yield %r %% (' % u''.join(format))
395 idx = -1
396 for idx, argument in enumerate(arguments):
397 if idx:
398 self.write(', ')
399 self.visit(argument, frame)
400 self.write(idx == 0 and ',)' or ')')
401
Armin Ronacher8efc5222008-04-08 14:47:40 +0200402 def visit_Assign(self, node, frame):
403 self.newline(node)
404 # toplevel assignments however go into the local namespace and
405 # the current template's context. We create a copy of the frame
406 # here and add a set so that the Name visitor can add the assigned
407 # names here.
408 if frame.toplevel:
409 assignment_frame = frame.copy()
410 assignment_frame.assigned_names = set()
411 else:
412 assignment_frame = frame
413 self.visit(node.target, assignment_frame)
414 self.write(' = ')
415 self.visit(node.node, frame)
416 if frame.toplevel:
417 for name in assignment_frame.assigned_names:
418 self.writeline('context[%r] = l_%s' % (name, name))
419
Armin Ronachere791c2a2008-04-07 18:39:54 +0200420 def visit_Name(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200421 if frame.toplevel and node.ctx == 'store':
422 frame.assigned_names.add(node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200423 self.write('l_' + node.name)
424
425 def visit_Const(self, node, frame):
426 val = node.value
427 if isinstance(val, float):
428 # XXX: add checks for infinity and nan
429 self.write(str(val))
430 else:
431 self.write(repr(val))
432
Armin Ronacher8efc5222008-04-08 14:47:40 +0200433 def visit_Tuple(self, node, frame):
434 self.write('(')
435 idx = -1
436 for idx, item in enumerate(node.items):
437 if idx:
438 self.write(', ')
439 self.visit(item, frame)
440 self.write(idx == 0 and ',)' or ')')
441
Armin Ronachere791c2a2008-04-07 18:39:54 +0200442 def binop(operator):
443 def visitor(self, node, frame):
444 self.write('(')
445 self.visit(node.left, frame)
446 self.write(' %s ' % operator)
447 self.visit(node.right, frame)
448 self.write(')')
449 return visitor
450
451 def uaop(operator):
452 def visitor(self, node, frame):
453 self.write('(' + operator)
454 self.visit(node.node)
455 self.write(')')
456 return visitor
457
458 visit_Add = binop('+')
459 visit_Sub = binop('-')
460 visit_Mul = binop('*')
461 visit_Div = binop('/')
462 visit_FloorDiv = binop('//')
463 visit_Pow = binop('**')
464 visit_Mod = binop('%')
465 visit_And = binop('and')
466 visit_Or = binop('or')
467 visit_Pos = uaop('+')
468 visit_Neg = uaop('-')
469 visit_Not = uaop('not ')
470 del binop, uaop
471
472 def visit_Compare(self, node, frame):
473 self.visit(node.expr, frame)
474 for op in node.ops:
475 self.visit(op, frame)
476
477 def visit_Operand(self, node, frame):
478 self.write(' %s ' % operators[node.op])
479 self.visit(node.expr, frame)
480
481 def visit_Subscript(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200482 if isinstance(node.arg, nodes.Slice):
483 self.visit(node.node, frame)
484 self.write('[')
485 self.visit(node.arg, frame)
486 self.write(']')
487 return
488 try:
489 const = node.arg.as_const()
490 have_const = True
491 except nodes.Impossible:
492 have_const = False
493 if have_const:
494 if isinstance(const, (int, long, float)):
495 self.visit(node.node, frame)
496 self.write('[%s]' % const)
497 return
498 self.write('subscribe(')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200499 self.visit(node.node, frame)
500 self.write(', ')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200501 if have_const:
502 self.write(repr(const))
503 else:
504 self.visit(node.arg, frame)
505 self.write(', make_undefined)')
506
507 def visit_Slice(self, node, frame):
508 if node.start is not None:
509 self.visit(node.start, frame)
510 self.write(':')
511 if node.stop is not None:
512 self.visit(node.stop, frame)
513 if node.step is not None:
514 self.write(':')
515 self.visit(node.step, frame)
516
517 def visit_Filter(self, node, frame):
518 for filter in node.filters:
519 self.write('context.filters[%r](' % filter.name)
520 self.visit(node.node, frame)
521 for filter in reversed(node.filters):
522 self.signature(filter, frame)
523 self.write(')')
524
525 def visit_Test(self, node, frame):
526 self.write('context.tests[%r](')
527 self.visit(node.node, frame)
528 self.signature(node, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200529 self.write(')')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200530
531 def visit_Call(self, node, frame):
532 self.visit(node.node, frame)
533 self.write('(')
534 self.signature(node, frame, False)
535 self.write(')')
536
537 def visit_Keyword(self, node, frame):
538 self.visit(node.key, frame)
539 self.write('=')
540 self.visit(node.value, frame)