blob: 95d2f35c91182dd8b9e7348b6c7650147334b96d [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
Christoph Hack65642a52008-04-08 14:46:56 +020032def generate(node, environment, filename, stream=None):
Armin Ronachere791c2a2008-04-07 18:39:54 +020033 is_child = node.find(nodes.Extends) is not None
Christoph Hack65642a52008-04-08 14:46:56 +020034 generator = CodeGenerator(environment, is_child, filename, stream)
Armin Ronachere791c2a2008-04-07 18:39:54 +020035 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
Christoph Hack65642a52008-04-08 14:46:56 +020059 # filters that are declared locally
60 self.declared_filter = set()
61 self.undeclared_filter = set()
62
Armin Ronachere791c2a2008-04-07 18:39:54 +020063 def add_special(self, name):
64 """Register a special name like `loop`."""
65 self.undeclared.discard(name)
66 self.declared.add(name)
67
68 def is_declared(self, name):
69 """Check if a name is declared in this or an outer scope."""
70 return name in self.declared or name in self.declared_locally or \
71 name in self.declared_parameter
72
73 def find_shadowed(self):
74 """Find all the shadowed names."""
75 return self.declared & (self.declared_locally | self.declared_parameter)
76
77
78class Frame(object):
79
80 def __init__(self, parent=None):
81 self.identifiers = Identifiers()
Armin Ronacher8efc5222008-04-08 14:47:40 +020082 self.toplevel = False
Armin Ronachere791c2a2008-04-07 18:39:54 +020083 self.parent = parent
Armin Ronacher8efc5222008-04-08 14:47:40 +020084 self.block = parent and parent.block or None
Armin Ronachere791c2a2008-04-07 18:39:54 +020085 if parent is not None:
86 self.identifiers.declared.update(
87 parent.identifiers.declared |
88 parent.identifiers.undeclared |
89 parent.identifiers.declared_locally |
90 parent.identifiers.declared_parameter
91 )
92
Armin Ronacher8efc5222008-04-08 14:47:40 +020093 def copy(self):
94 """Create a copy of the current one."""
95 rv = copy(self)
96 rv.identifiers = copy(self)
97 return rv
98
Armin Ronachere791c2a2008-04-07 18:39:54 +020099 def inspect(self, nodes):
100 """Walk the node and check for identifiers."""
101 visitor = FrameIdentifierVisitor(self.identifiers)
102 for node in nodes:
103 visitor.visit(node)
104
105 def inner(self):
106 """Return an inner frame."""
107 return Frame(self)
108
109
110class FrameIdentifierVisitor(NodeVisitor):
111 """A visitor for `Frame.inspect`."""
112
113 def __init__(self, identifiers):
114 self.identifiers = identifiers
115
116 def visit_Name(self, node):
117 """All assignments to names go through this function."""
118 if node.ctx in ('store', 'param'):
119 self.identifiers.declared_locally.add(node.name)
120 elif node.ctx == 'load':
121 if not self.identifiers.is_declared(node.name):
122 self.identifiers.undeclared.add(node.name)
123
Christoph Hack65642a52008-04-08 14:46:56 +0200124 def visit_FilterCall(self, node):
125 if not node.name in self.identifiers.declared_filter:
126 self.identifiers.undeclared_filter.add(node.name)
127 self.identifiers.declared_filter.add(node.name)
128
Armin Ronachere791c2a2008-04-07 18:39:54 +0200129 def visit_Macro(self, node):
130 """Macros set local."""
131 self.identifiers.declared_locally.add(node.name)
132
133 # stop traversing at instructions that have their own scope.
Armin Ronacher8efc5222008-04-08 14:47:40 +0200134 visit_Block = visit_CallBlock = visit_FilterBlock = \
Armin Ronachere791c2a2008-04-07 18:39:54 +0200135 visit_For = lambda s, n: None
136
137
138class CodeGenerator(NodeVisitor):
139
Christoph Hack65642a52008-04-08 14:46:56 +0200140 def __init__(self, environment, is_child, filename, stream=None):
Armin Ronachere791c2a2008-04-07 18:39:54 +0200141 if stream is None:
142 stream = StringIO()
Christoph Hack65642a52008-04-08 14:46:56 +0200143 self.environment = environment
Armin Ronachere791c2a2008-04-07 18:39:54 +0200144 self.is_child = is_child
145 self.filename = filename
146 self.stream = stream
147 self.blocks = {}
148 self.indentation = 0
149 self.new_lines = 0
150 self.last_identifier = 0
151 self._last_line = 0
152 self._first_write = True
153
154 def temporary_identifier(self):
155 self.last_identifier += 1
156 return 't%d' % self.last_identifier
157
158 def indent(self):
159 self.indentation += 1
160
Armin Ronacher8efc5222008-04-08 14:47:40 +0200161 def outdent(self, step=1):
162 self.indentation -= step
Armin Ronachere791c2a2008-04-07 18:39:54 +0200163
164 def blockvisit(self, nodes, frame, force_generator=False):
165 self.indent()
166 if force_generator:
167 self.writeline('if 0: yield None')
168 for node in nodes:
169 self.visit(node, frame)
170 self.outdent()
171
172 def write(self, x):
173 if self.new_lines:
174 if not self._first_write:
175 self.stream.write('\n' * self.new_lines)
176 self._first_write = False
177 self.stream.write(' ' * self.indentation)
178 self.new_lines = 0
179 self.stream.write(x)
180
181 def writeline(self, x, node=None, extra=0):
182 self.newline(node, extra)
183 self.write(x)
184
185 def newline(self, node=None, extra=0):
186 self.new_lines = max(self.new_lines, 1 + extra)
187 if node is not None and node.lineno != self._last_line:
188 self.write('# line: %s' % node.lineno)
189 self.new_lines = 1
190 self._last_line = node.lineno
191
Armin Ronacher8efc5222008-04-08 14:47:40 +0200192 def signature(self, node, frame, have_comma=True):
193 have_comma = have_comma and [True] or []
194 def touch_comma():
195 if have_comma:
196 self.write(', ')
197 else:
198 have_comma.append(True)
199
200 for arg in node.args:
201 touch_comma()
202 self.visit(arg, frame)
203 for kwarg in node.kwargs:
204 touch_comma()
205 self.visit(kwarg, frame)
206 if node.dyn_args:
207 touch_comma()
208 self.visit(node.dyn_args, frame)
209 if node.dyn_kwargs:
210 touch_comma()
211 self.visit(node.dyn_kwargs, frame)
212
Armin Ronachere791c2a2008-04-07 18:39:54 +0200213 def pull_locals(self, frame, no_indent=False):
214 if not no_indent:
215 self.indent()
216 for name in frame.identifiers.undeclared:
217 self.writeline('l_%s = context[%r]' % (name, name))
Christoph Hack65642a52008-04-08 14:46:56 +0200218 for name in frame.identifiers.undeclared_filter:
219 self.writeline('f_%s = context[%r]' % (name, name))
Armin Ronachere791c2a2008-04-07 18:39:54 +0200220 if not no_indent:
221 self.outdent()
222
223 # -- Visitors
224
225 def visit_Template(self, node, frame=None):
226 assert frame is None, 'no root frame allowed'
227 self.writeline('from jinja2.runtime import *')
228 self.writeline('filename = %r' % self.filename)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200229 self.writeline('template_context = TemplateContext(global_context, '
Armin Ronachere791c2a2008-04-07 18:39:54 +0200230 'make_undefined, filename)')
231
Armin Ronacher8efc5222008-04-08 14:47:40 +0200232 # generate the root render function.
233 self.writeline('def root(context=template_context):', extra=1)
234 self.indent()
235 self.writeline('parent_root = None')
236 self.outdent()
Armin Ronachere791c2a2008-04-07 18:39:54 +0200237 frame = Frame()
238 frame.inspect(node.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200239 frame.toplevel = True
Armin Ronachere791c2a2008-04-07 18:39:54 +0200240 self.pull_locals(frame)
241 self.blockvisit(node.body, frame, True)
242
Armin Ronacher8efc5222008-04-08 14:47:40 +0200243 # make sure that the parent root is called.
Armin Ronachere791c2a2008-04-07 18:39:54 +0200244 self.indent()
Armin Ronacher8efc5222008-04-08 14:47:40 +0200245 self.writeline('if parent_root is not None:')
246 self.indent()
247 self.writeline('for event in parent_root(context):')
248 self.indent()
249 self.writeline('yield event')
250 self.outdent(3)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200251
252 # at this point we now have the blocks collected and can visit them too.
253 for name, block in self.blocks.iteritems():
254 block_frame = Frame()
255 block_frame.inspect(block.body)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200256 block_frame.block = name
257 self.writeline('def block_%s(context):' % name, block, 1)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200258 self.pull_locals(block_frame)
259 self.blockvisit(block.body, block_frame, True)
260
261 def visit_Block(self, node, frame):
262 """Call a block and register it for the template."""
263 if node.name in self.blocks:
264 raise TemplateAssertionError("the block '%s' was already defined" %
265 node.name, node.lineno,
266 self.filename)
267 self.blocks[node.name] = node
Armin Ronacher8efc5222008-04-08 14:47:40 +0200268 self.writeline('for event in block_%s(context):' % node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200269 self.indent()
270 self.writeline('yield event')
271 self.outdent()
272
273 def visit_Extends(self, node, frame):
274 """Calls the extender."""
Armin Ronacher8efc5222008-04-08 14:47:40 +0200275 if not frame.toplevel:
276 raise TemplateAssertionError('cannot use extend from a non '
277 'top-level scope', node.lineno,
278 self.filename)
279 self.writeline('if parent_root is not None:')
280 self.indent()
281 self.writeline('raise TemplateRuntimeError(%r)' %
282 'extended multiple times')
283 self.outdent()
284 self.writeline('parent_root = extends(', node, 1)
285 self.visit(node.template, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200286 self.write(', globals())')
287
288 def visit_For(self, node, frame):
289 loop_frame = frame.inner()
290 loop_frame.inspect(node.iter_child_nodes())
Armin Ronachere791c2a2008-04-07 18:39:54 +0200291 extended_loop = bool(node.else_) or \
292 'loop' in loop_frame.identifiers.undeclared
Armin Ronacher8efc5222008-04-08 14:47:40 +0200293 loop_frame.identifiers.add_special('loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200294
295 # make sure we "backup" overridden, local identifiers
296 # TODO: we should probably optimize this and check if the
297 # identifier is in use afterwards.
298 aliases = {}
299 for name in loop_frame.identifiers.find_shadowed():
300 aliases[name] = ident = self.temporary_identifier()
301 self.writeline('%s = l_%s' % (ident, name))
302
303 self.pull_locals(loop_frame, True)
304
305 self.newline(node)
306 if node.else_:
307 self.writeline('l_loop = None')
308 self.write('for ')
309 self.visit(node.target, loop_frame)
310 self.write(extended_loop and ', l_loop in looper(' or ' in ')
311 self.visit(node.iter, loop_frame)
312 self.write(extended_loop and '):' or ':')
313 self.blockvisit(node.body, loop_frame)
314
315 if node.else_:
316 self.writeline('if l_loop is None:')
317 self.blockvisit(node.else_, loop_frame)
318
Armin Ronacher8efc5222008-04-08 14:47:40 +0200319 # reset the aliases and clean up
320 delete = set('l_' + x for x in loop_frame.identifiers.declared_locally
321 | loop_frame.identifiers.declared_parameter)
322 if extended_loop:
323 delete.add('l_loop')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200324 for name, alias in aliases.iteritems():
Armin Ronacher8efc5222008-04-08 14:47:40 +0200325 self.writeline('l_%s = %s' % (name, alias))
326 delete.add(alias)
327 delete.discard('l_' + name)
328 self.writeline('del %s' % ', '.join(delete))
Armin Ronachere791c2a2008-04-07 18:39:54 +0200329
330 def visit_If(self, node, frame):
331 self.writeline('if ', node)
332 self.visit(node.test, frame)
333 self.write(':')
334 self.blockvisit(node.body, frame)
335 if node.else_:
336 self.writeline('else:')
337 self.blockvisit(node.else_, frame)
338
Armin Ronacher8efc5222008-04-08 14:47:40 +0200339 def visit_Macro(self, node, frame):
340 macro_frame = frame.inner()
341 macro_frame.inspect(node.body)
342 args = ['l_' + x.name for x in node.args]
343 if 'arguments' in macro_frame.identifiers.undeclared:
344 accesses_arguments = True
345 args.append('l_arguments')
346 else:
347 accesses_arguments = False
348 self.writeline('def macro(%s):' % ', '.join(args), node)
349 self.indent()
350 self.writeline('if 0: yield None')
351 self.outdent()
352 self.blockvisit(node.body, frame)
353 self.newline()
354 if frame.toplevel:
355 self.write('context[%r] = ' % node.name)
356 arg_tuple = ', '.join(repr(x.name) for x in node.args)
357 if len(node.args) == 1:
358 arg_tuple += ','
359 self.write('l_%s = Macro(macro, %r, (%s), %s)' % (
360 node.name, node.name,
361 arg_tuple, accesses_arguments
362 ))
363
Armin Ronachere791c2a2008-04-07 18:39:54 +0200364 def visit_ExprStmt(self, node, frame):
365 self.newline(node)
366 self.visit(node, frame)
367
368 def visit_Output(self, node, frame):
369 self.newline(node)
370
371 # try to evaluate as many chunks as possible into a static
372 # string at compile time.
373 body = []
374 for child in node.nodes:
375 try:
376 const = unicode(child.as_const())
377 except:
378 body.append(child)
379 continue
380 if body and isinstance(body[-1], list):
381 body[-1].append(const)
382 else:
383 body.append([const])
384
385 # if we have less than 3 nodes we just yield them
386 if len(body) < 3:
387 for item in body:
388 if isinstance(item, list):
389 self.writeline('yield %s' % repr(u''.join(item)))
390 else:
391 self.newline(item)
392 self.write('yield unicode(')
393 self.visit(item, frame)
394 self.write(')')
395
396 # otherwise we create a format string as this is faster in that case
397 else:
398 format = []
399 arguments = []
400 for item in body:
401 if isinstance(item, list):
402 format.append(u''.join(item).replace('%', '%%'))
403 else:
404 format.append('%s')
405 arguments.append(item)
406 self.writeline('yield %r %% (' % u''.join(format))
407 idx = -1
408 for idx, argument in enumerate(arguments):
409 if idx:
410 self.write(', ')
411 self.visit(argument, frame)
412 self.write(idx == 0 and ',)' or ')')
413
Armin Ronacher8efc5222008-04-08 14:47:40 +0200414 def visit_Assign(self, node, frame):
415 self.newline(node)
416 # toplevel assignments however go into the local namespace and
417 # the current template's context. We create a copy of the frame
418 # here and add a set so that the Name visitor can add the assigned
419 # names here.
420 if frame.toplevel:
421 assignment_frame = frame.copy()
422 assignment_frame.assigned_names = set()
423 else:
424 assignment_frame = frame
425 self.visit(node.target, assignment_frame)
426 self.write(' = ')
427 self.visit(node.node, frame)
428 if frame.toplevel:
429 for name in assignment_frame.assigned_names:
430 self.writeline('context[%r] = l_%s' % (name, name))
431
Armin Ronachere791c2a2008-04-07 18:39:54 +0200432 def visit_Name(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200433 if frame.toplevel and node.ctx == 'store':
434 frame.assigned_names.add(node.name)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200435 self.write('l_' + node.name)
436
437 def visit_Const(self, node, frame):
438 val = node.value
439 if isinstance(val, float):
440 # XXX: add checks for infinity and nan
441 self.write(str(val))
442 else:
443 self.write(repr(val))
444
Armin Ronacher8efc5222008-04-08 14:47:40 +0200445 def visit_Tuple(self, node, frame):
446 self.write('(')
447 idx = -1
448 for idx, item in enumerate(node.items):
449 if idx:
450 self.write(', ')
451 self.visit(item, frame)
452 self.write(idx == 0 and ',)' or ')')
453
Armin Ronachere791c2a2008-04-07 18:39:54 +0200454 def binop(operator):
455 def visitor(self, node, frame):
456 self.write('(')
457 self.visit(node.left, frame)
458 self.write(' %s ' % operator)
459 self.visit(node.right, frame)
460 self.write(')')
461 return visitor
462
463 def uaop(operator):
464 def visitor(self, node, frame):
465 self.write('(' + operator)
466 self.visit(node.node)
467 self.write(')')
468 return visitor
469
470 visit_Add = binop('+')
471 visit_Sub = binop('-')
472 visit_Mul = binop('*')
473 visit_Div = binop('/')
474 visit_FloorDiv = binop('//')
475 visit_Pow = binop('**')
476 visit_Mod = binop('%')
477 visit_And = binop('and')
478 visit_Or = binop('or')
479 visit_Pos = uaop('+')
480 visit_Neg = uaop('-')
481 visit_Not = uaop('not ')
482 del binop, uaop
483
484 def visit_Compare(self, node, frame):
485 self.visit(node.expr, frame)
486 for op in node.ops:
487 self.visit(op, frame)
488
489 def visit_Operand(self, node, frame):
490 self.write(' %s ' % operators[node.op])
491 self.visit(node.expr, frame)
492
493 def visit_Subscript(self, node, frame):
Armin Ronacher8efc5222008-04-08 14:47:40 +0200494 if isinstance(node.arg, nodes.Slice):
495 self.visit(node.node, frame)
496 self.write('[')
497 self.visit(node.arg, frame)
498 self.write(']')
499 return
500 try:
501 const = node.arg.as_const()
502 have_const = True
503 except nodes.Impossible:
504 have_const = False
505 if have_const:
506 if isinstance(const, (int, long, float)):
507 self.visit(node.node, frame)
508 self.write('[%s]' % const)
509 return
510 self.write('subscribe(')
Armin Ronachere791c2a2008-04-07 18:39:54 +0200511 self.visit(node.node, frame)
512 self.write(', ')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200513 if have_const:
514 self.write(repr(const))
515 else:
516 self.visit(node.arg, frame)
517 self.write(', make_undefined)')
518
519 def visit_Slice(self, node, frame):
520 if node.start is not None:
521 self.visit(node.start, frame)
522 self.write(':')
523 if node.stop is not None:
524 self.visit(node.stop, frame)
525 if node.step is not None:
526 self.write(':')
527 self.visit(node.step, frame)
528
529 def visit_Filter(self, node, frame):
530 for filter in node.filters:
Christoph Hack65642a52008-04-08 14:46:56 +0200531 self.write('f_%s(' % filter.name)
Armin Ronacher8efc5222008-04-08 14:47:40 +0200532 self.visit(node.node, frame)
533 for filter in reversed(node.filters):
534 self.signature(filter, frame)
535 self.write(')')
536
537 def visit_Test(self, node, frame):
538 self.write('context.tests[%r](')
539 self.visit(node.node, frame)
540 self.signature(node, frame)
Armin Ronachere791c2a2008-04-07 18:39:54 +0200541 self.write(')')
Armin Ronacher8efc5222008-04-08 14:47:40 +0200542
543 def visit_Call(self, node, frame):
544 self.visit(node.node, frame)
545 self.write('(')
546 self.signature(node, frame, False)
547 self.write(')')
548
549 def visit_Keyword(self, node, frame):
550 self.visit(node.key, frame)
551 self.write('=')
552 self.visit(node.value, frame)