blob: 330db56df53d8bdf8c48670772c6a7e1cbba6d7f [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"""
11from random import randrange
12from operator import xor
13from 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
31def generate(node, filename, stream=None):
32 is_child = node.find(nodes.Extends) is not None
33 generator = CodeGenerator(is_child, filename, stream)
34 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
58 def add_special(self, name):
59 """Register a special name like `loop`."""
60 self.undeclared.discard(name)
61 self.declared.add(name)
62
63 def is_declared(self, name):
64 """Check if a name is declared in this or an outer scope."""
65 return name in self.declared or name in self.declared_locally or \
66 name in self.declared_parameter
67
68 def find_shadowed(self):
69 """Find all the shadowed names."""
70 return self.declared & (self.declared_locally | self.declared_parameter)
71
72
73class Frame(object):
74
75 def __init__(self, parent=None):
76 self.identifiers = Identifiers()
77 self.parent = parent
78 if parent is not None:
79 self.identifiers.declared.update(
80 parent.identifiers.declared |
81 parent.identifiers.undeclared |
82 parent.identifiers.declared_locally |
83 parent.identifiers.declared_parameter
84 )
85
86 def inspect(self, nodes):
87 """Walk the node and check for identifiers."""
88 visitor = FrameIdentifierVisitor(self.identifiers)
89 for node in nodes:
90 visitor.visit(node)
91
92 def inner(self):
93 """Return an inner frame."""
94 return Frame(self)
95
96
97class FrameIdentifierVisitor(NodeVisitor):
98 """A visitor for `Frame.inspect`."""
99
100 def __init__(self, identifiers):
101 self.identifiers = identifiers
102
103 def visit_Name(self, node):
104 """All assignments to names go through this function."""
105 if node.ctx in ('store', 'param'):
106 self.identifiers.declared_locally.add(node.name)
107 elif node.ctx == 'load':
108 if not self.identifiers.is_declared(node.name):
109 self.identifiers.undeclared.add(node.name)
110
111 def visit_Macro(self, node):
112 """Macros set local."""
113 self.identifiers.declared_locally.add(node.name)
114
115 # stop traversing at instructions that have their own scope.
116 visit_Block = visit_Call = visit_FilterBlock = \
117 visit_For = lambda s, n: None
118
119
120class CodeGenerator(NodeVisitor):
121
122 def __init__(self, is_child, filename, stream=None):
123 if stream is None:
124 stream = StringIO()
125 self.is_child = is_child
126 self.filename = filename
127 self.stream = stream
128 self.blocks = {}
129 self.indentation = 0
130 self.new_lines = 0
131 self.last_identifier = 0
132 self._last_line = 0
133 self._first_write = True
134
135 def temporary_identifier(self):
136 self.last_identifier += 1
137 return 't%d' % self.last_identifier
138
139 def indent(self):
140 self.indentation += 1
141
142 def outdent(self):
143 self.indentation -= 1
144
145 def blockvisit(self, nodes, frame, force_generator=False):
146 self.indent()
147 if force_generator:
148 self.writeline('if 0: yield None')
149 for node in nodes:
150 self.visit(node, frame)
151 self.outdent()
152
153 def write(self, x):
154 if self.new_lines:
155 if not self._first_write:
156 self.stream.write('\n' * self.new_lines)
157 self._first_write = False
158 self.stream.write(' ' * self.indentation)
159 self.new_lines = 0
160 self.stream.write(x)
161
162 def writeline(self, x, node=None, extra=0):
163 self.newline(node, extra)
164 self.write(x)
165
166 def newline(self, node=None, extra=0):
167 self.new_lines = max(self.new_lines, 1 + extra)
168 if node is not None and node.lineno != self._last_line:
169 self.write('# line: %s' % node.lineno)
170 self.new_lines = 1
171 self._last_line = node.lineno
172
173 def pull_locals(self, frame, no_indent=False):
174 if not no_indent:
175 self.indent()
176 for name in frame.identifiers.undeclared:
177 self.writeline('l_%s = context[%r]' % (name, name))
178 if not no_indent:
179 self.outdent()
180
181 # -- Visitors
182
183 def visit_Template(self, node, frame=None):
184 assert frame is None, 'no root frame allowed'
185 self.writeline('from jinja2.runtime import *')
186 self.writeline('filename = %r' % self.filename)
187 self.writeline('context = TemplateContext(global_context, '
188 'make_undefined, filename)')
189
190 # generate the body render function.
191 self.writeline('def body(context=context):', extra=1)
192 frame = Frame()
193 frame.inspect(node.body)
194 self.pull_locals(frame)
195 self.blockvisit(node.body, frame, True)
196
197 # top level changes to locals are pushed back to the
198 # context of *this* template for include.
199 self.indent()
200 self.writeline('context.from_locals(locals())')
201 self.outdent()
202
203 # at this point we now have the blocks collected and can visit them too.
204 for name, block in self.blocks.iteritems():
205 block_frame = Frame()
206 block_frame.inspect(block.body)
207 self.writeline('def block_%s(context=context):' % name, block, 1)
208 self.pull_locals(block_frame)
209 self.blockvisit(block.body, block_frame, True)
210
211 def visit_Block(self, node, frame):
212 """Call a block and register it for the template."""
213 if node.name in self.blocks:
214 raise TemplateAssertionError("the block '%s' was already defined" %
215 node.name, node.lineno,
216 self.filename)
217 self.blocks[node.name] = node
218 self.writeline('for event in block_%s():' % node.name)
219 self.indent()
220 self.writeline('yield event')
221 self.outdent()
222
223 def visit_Extends(self, node, frame):
224 """Calls the extender."""
225 self.writeline('extends(', node, 1)
226 self.visit(node.template)
227 self.write(', globals())')
228
229 def visit_For(self, node, frame):
230 loop_frame = frame.inner()
231 loop_frame.inspect(node.iter_child_nodes())
232 loop_frame.identifiers.add_special('loop')
233 extended_loop = bool(node.else_) or \
234 'loop' in loop_frame.identifiers.undeclared
235
236 # make sure we "backup" overridden, local identifiers
237 # TODO: we should probably optimize this and check if the
238 # identifier is in use afterwards.
239 aliases = {}
240 for name in loop_frame.identifiers.find_shadowed():
241 aliases[name] = ident = self.temporary_identifier()
242 self.writeline('%s = l_%s' % (ident, name))
243
244 self.pull_locals(loop_frame, True)
245
246 self.newline(node)
247 if node.else_:
248 self.writeline('l_loop = None')
249 self.write('for ')
250 self.visit(node.target, loop_frame)
251 self.write(extended_loop and ', l_loop in looper(' or ' in ')
252 self.visit(node.iter, loop_frame)
253 self.write(extended_loop and '):' or ':')
254 self.blockvisit(node.body, loop_frame)
255
256 if node.else_:
257 self.writeline('if l_loop is None:')
258 self.blockvisit(node.else_, loop_frame)
259
260 # reset the aliases and clean them up
261 for name, alias in aliases.iteritems():
262 self.writeline('l_%s = %s; del %s' % (name, alias, alias))
263
264 def visit_If(self, node, frame):
265 self.writeline('if ', node)
266 self.visit(node.test, frame)
267 self.write(':')
268 self.blockvisit(node.body, frame)
269 if node.else_:
270 self.writeline('else:')
271 self.blockvisit(node.else_, frame)
272
273 def visit_ExprStmt(self, node, frame):
274 self.newline(node)
275 self.visit(node, frame)
276
277 def visit_Output(self, node, frame):
278 self.newline(node)
279
280 # try to evaluate as many chunks as possible into a static
281 # string at compile time.
282 body = []
283 for child in node.nodes:
284 try:
285 const = unicode(child.as_const())
286 except:
287 body.append(child)
288 continue
289 if body and isinstance(body[-1], list):
290 body[-1].append(const)
291 else:
292 body.append([const])
293
294 # if we have less than 3 nodes we just yield them
295 if len(body) < 3:
296 for item in body:
297 if isinstance(item, list):
298 self.writeline('yield %s' % repr(u''.join(item)))
299 else:
300 self.newline(item)
301 self.write('yield unicode(')
302 self.visit(item, frame)
303 self.write(')')
304
305 # otherwise we create a format string as this is faster in that case
306 else:
307 format = []
308 arguments = []
309 for item in body:
310 if isinstance(item, list):
311 format.append(u''.join(item).replace('%', '%%'))
312 else:
313 format.append('%s')
314 arguments.append(item)
315 self.writeline('yield %r %% (' % u''.join(format))
316 idx = -1
317 for idx, argument in enumerate(arguments):
318 if idx:
319 self.write(', ')
320 self.visit(argument, frame)
321 self.write(idx == 0 and ',)' or ')')
322
323 def visit_Name(self, node, frame):
324 # at this point we should only have locals left as the
325 # blocks, macros and template body ensure that they are set.
326 self.write('l_' + node.name)
327
328 def visit_Const(self, node, frame):
329 val = node.value
330 if isinstance(val, float):
331 # XXX: add checks for infinity and nan
332 self.write(str(val))
333 else:
334 self.write(repr(val))
335
336 def binop(operator):
337 def visitor(self, node, frame):
338 self.write('(')
339 self.visit(node.left, frame)
340 self.write(' %s ' % operator)
341 self.visit(node.right, frame)
342 self.write(')')
343 return visitor
344
345 def uaop(operator):
346 def visitor(self, node, frame):
347 self.write('(' + operator)
348 self.visit(node.node)
349 self.write(')')
350 return visitor
351
352 visit_Add = binop('+')
353 visit_Sub = binop('-')
354 visit_Mul = binop('*')
355 visit_Div = binop('/')
356 visit_FloorDiv = binop('//')
357 visit_Pow = binop('**')
358 visit_Mod = binop('%')
359 visit_And = binop('and')
360 visit_Or = binop('or')
361 visit_Pos = uaop('+')
362 visit_Neg = uaop('-')
363 visit_Not = uaop('not ')
364 del binop, uaop
365
366 def visit_Compare(self, node, frame):
367 self.visit(node.expr, frame)
368 for op in node.ops:
369 self.visit(op, frame)
370
371 def visit_Operand(self, node, frame):
372 self.write(' %s ' % operators[node.op])
373 self.visit(node.expr, frame)
374
375 def visit_Subscript(self, node, frame):
376 self.write('subscript(')
377 self.visit(node.node, frame)
378 self.write(', ')
379 self.visit(node.arg, frame)
380 self.write(')')