1 # -*- coding: utf-8 -*-
6 This module tries to optimize template trees by:
8 * eliminating constant nodes
9 * evaluating filters and macros on constant nodes
10 * unroll loops on constant values
11 * replace variables which are already known (because they doesn't
12 change often and you want to prerender a template) with constants
14 After the optimation you will get a new, simplier template which can
15 be saved again for later rendering. But even if you don't want to
16 prerender a template, this module might speed up your templates a bit
17 if you are using a lot of constants.
19 :copyright: Copyright 2008 by Christoph Hack.
22 from copy import deepcopy
23 from jinja2 import nodes
24 from jinja2.visitor import NodeVisitor, NodeTransformer
25 from jinja2.runtime import subscribe, LoopContext
28 class ContextStack(object):
29 """Simple compile time context implementation."""
31 def __init__(self, initial=None):
33 if initial is not None:
34 self.stack.insert(0, initial)
42 def get(self, key, default=None):
48 def __getitem__(self, key):
49 for level in reversed(self.stack):
54 def __setitem__(self, key, value):
55 self.stack[-1][key] = value
58 """Return a new context with nothing but the root scope."""
59 return ContextStack(self.stack[0])
62 class Optimizer(NodeTransformer):
64 def __init__(self, environment):
65 self.environment = environment
67 def visit_Block(self, node, context):
68 return self.generic_visit(node, context.blank())
70 def visit_Filter(self, node, context):
71 """Try to evaluate filters if possible."""
72 # XXX: nonconstant arguments? not-called visitors? generic visit!
74 x = self.visit(node.node, context).as_const()
75 except nodes.Impossible:
76 return self.generic_visit(node, context)
77 for filter in reversed(node.filters):
78 # XXX: call filters with arguments
79 x = self.environment.filters[filter.name](x)
80 # XXX: don't optimize context dependent filters
83 def visit_For(self, node, context):
84 """Loop unrolling for iterable constant values."""
86 iterable = iter(self.visit(node.iter, context).as_const())
87 except (nodes.Impossible, TypeError):
88 return self.generic_visit(node, context)
90 parent = context.get('loop')
95 def assign(target, value):
96 if isinstance(target, nodes.Name):
97 context[target.name] = value
98 elif isinstance(target, nodes.Tuple):
102 raise nodes.Impossible()
103 if len(target.items) != len(value):
104 raise nodes.Impossible()
105 for name, val in zip(target.items, value):
108 raise AssertionError('unexpected assignable node')
110 # XXX: not covered cases:
111 # - item is accessed by dynamic part in the iteration
114 for loop, item in LoopContext(iterable, parent):
115 context['loop'] = loop
116 assign(node.target, item)
117 result.extend(self.visit(n, context)
118 for n in deepcopy(node.body))
120 if not iterated and node.else_:
121 result.extend(self.visit(n, context)
122 for n in deepcopy(node.else_))
123 except nodes.Impossible:
129 def visit_If(self, node, context):
131 val = self.visit(node.test, context).as_const()
132 except nodes.Impossible:
133 return self.generic_visit(node, context)
138 def visit_Name(self, node, context):
139 if node.ctx == 'load':
141 return nodes.Const(context[node.name], lineno=node.lineno)
146 def visit_Assign(self, node, context):
148 target = node.target = self.generic_visit(node.target, context)
149 value = self.generic_visit(node.node, context).as_const()
150 except nodes.Impossible:
155 def walk(target, value):
156 if isinstance(target, nodes.Name):
157 const_value = nodes.Const(value, lineno=lineno)
158 result.append(nodes.Assign(target, const_value, lineno=lineno))
159 context[target.name] = value
160 elif isinstance(target, nodes.Tuple):
164 raise nodes.Impossible()
165 if len(target.items) != len(value):
166 raise nodes.Impossible()
167 for name, val in zip(target.items, value):
170 raise AssertionError('unexpected assignable node')
174 except nodes.Impossible:
178 def fold(self, node, context):
179 """Do constant folding."""
180 node = self.generic_visit(node, context)
182 return nodes.Const(node.as_const(), lineno=node.lineno)
183 except nodes.Impossible:
185 visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
186 visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
187 visit_Not = visit_Compare = fold
189 def visit_Subscript(self, node, context):
190 if node.ctx == 'load':
192 item = self.visit(node.node, context).as_const()
193 arg = self.visit(node.arg, context).as_const()
194 except nodes.Impossible:
195 return self.generic_visit(node, context)
196 return nodes.Const(subscribe(item, arg, 'load'))
197 return self.generic_visit(node, context)
200 def optimize(node, environment, context_hint=None):
201 """The context hint can be used to perform an static optimization
202 based on the context given."""
203 optimizer = Optimizer(environment)
204 return optimizer.visit(node, ContextStack(context_hint))