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
82 return nodes.Const.from_untrusted(x, lineno=node.lineno)
83 except nodes.Impossible:
84 return self.generic_visit(node)
86 def visit_For(self, node, context):
87 """Loop unrolling for iterable constant values."""
89 iterable = self.visit(node.iter, context).as_const()
90 # we only unroll them if they have a length and are iterable
93 except (nodes.Impossible, TypeError):
94 return self.generic_visit(node, context)
96 parent = context.get('loop')
101 def assign(target, value):
102 if isinstance(target, nodes.Name):
103 context[target.name] = value
104 elif isinstance(target, nodes.Tuple):
108 raise nodes.Impossible()
109 if len(target.items) != len(value):
110 raise nodes.Impossible()
111 for name, val in zip(target.items, value):
114 raise AssertionError('unexpected assignable node')
118 for loop, item in LoopContext(iterable, parent, True):
119 context['loop'] = loop.make_static()
120 assign(node.target, item)
121 result.extend(self.visit(n, context)
122 for n in deepcopy(node.body))
124 if not iterated and node.else_:
125 result.extend(self.visit(n, context)
126 for n in deepcopy(node.else_))
127 except nodes.Impossible:
133 def visit_If(self, node, context):
135 val = self.visit(node.test, context).as_const()
136 except nodes.Impossible:
137 return self.generic_visit(node, context)
142 def visit_Name(self, node, context):
143 if node.ctx == 'load':
145 return nodes.Const.from_untrusted(context[node.name],
147 except (KeyError, nodes.Impossible):
151 def visit_Assign(self, node, context):
153 target = node.target = self.generic_visit(node.target, context)
154 value = self.generic_visit(node.node, context).as_const()
155 except nodes.Impossible:
160 def walk(target, value):
161 if isinstance(target, nodes.Name):
162 const = nodes.Const.from_untrusted(value, lineno=lineno)
163 result.append(nodes.Assign(target, const, lineno=lineno))
164 context[target.name] = value
165 elif isinstance(target, nodes.Tuple):
169 raise nodes.Impossible()
170 if len(target.items) != len(value):
171 raise nodes.Impossible()
172 for name, val in zip(target.items, value):
175 raise AssertionError('unexpected assignable node')
179 except nodes.Impossible:
183 def fold(self, node, context):
184 """Do constant folding."""
185 node = self.generic_visit(node, context)
187 return nodes.Const.from_untrusted(node.as_const(),
189 except nodes.Impossible:
191 visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
192 visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
193 visit_Not = visit_Compare = visit_Subscribt = visit_Call = fold
197 def optimize(node, environment, context_hint=None):
198 """The context hint can be used to perform an static optimization
199 based on the context given."""
200 optimizer = Optimizer(environment)
201 return optimizer.visit(node, ContextStack(context_hint))