blob: f52b77f8e5b0c84b7218a73a06780cedb6732a4b [file] [log] [blame]
Christoph Hack9d99e472008-04-08 20:21:11 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.optimizer
4 ~~~~~~~~~~~~~~~~
5
Armin Ronacher07a21ba2008-04-23 22:28:42 +02006 The jinja optimizer is currently trying to constant fold a few expressions
7 and modify the AST in place so that it should be easier to evaluate it.
Christoph Hack9d99e472008-04-08 20:21:11 +02008
Armin Ronacher07a21ba2008-04-23 22:28:42 +02009 Because the AST does not contain all the scoping information and the
10 compiler has to find that out, we cannot do all the optimizations we
11 want. For example loop unrolling doesn't work because unrolled loops would
12 have a different scoping.
Christoph Hack9d99e472008-04-08 20:21:11 +020013
Armin Ronacher07a21ba2008-04-23 22:28:42 +020014 The solution would be a second syntax tree that has the scoping rules stored.
Christoph Hack9d99e472008-04-08 20:21:11 +020015
Armin Ronacher2e9396b2008-04-16 14:21:57 +020016 :copyright: Copyright 2008 by Christoph Hack, Armin Ronacher.
Christoph Hack9d99e472008-04-08 20:21:11 +020017 :license: GNU GPL.
18"""
Christoph Hack9d99e472008-04-08 20:21:11 +020019from jinja2 import nodes
20from jinja2.visitor import NodeVisitor, NodeTransformer
Armin Ronacherde6bf712008-04-26 01:44:14 +020021from jinja2.runtime import LoopContext, concat
Christoph Hack9d99e472008-04-08 20:21:11 +020022
23
Armin Ronacherbcb7c532008-04-11 16:30:34 +020024def optimize(node, environment, context_hint=None):
25 """The context hint can be used to perform an static optimization
26 based on the context given."""
27 optimizer = Optimizer(environment)
28 return optimizer.visit(node, ContextStack(context_hint))
29
30
Armin Ronacher81b88172008-04-09 00:40:05 +020031class ContextStack(object):
32 """Simple compile time context implementation."""
Armin Ronacher8edbe492008-04-10 20:43:43 +020033 undefined = object()
Armin Ronacher81b88172008-04-09 00:40:05 +020034
35 def __init__(self, initial=None):
36 self.stack = [{}]
37 if initial is not None:
38 self.stack.insert(0, initial)
39
40 def push(self):
41 self.stack.append({})
42
43 def pop(self):
44 self.stack.pop()
45
Armin Ronacher180a1bd2008-04-09 12:14:24 +020046 def get(self, key, default=None):
47 try:
48 return self[key]
49 except KeyError:
50 return default
51
Armin Ronacher8edbe492008-04-10 20:43:43 +020052 def undef(self, name):
53 if name in self:
54 self[name] = self.undefined
55
56 def __contains__(self, key):
57 try:
58 self[key]
59 except KeyError:
60 return False
61 return True
62
Armin Ronacher81b88172008-04-09 00:40:05 +020063 def __getitem__(self, key):
64 for level in reversed(self.stack):
65 if key in level:
Armin Ronacher8edbe492008-04-10 20:43:43 +020066 rv = level[key]
67 if rv is self.undefined:
68 raise KeyError(key)
69 return rv
Armin Ronacher81b88172008-04-09 00:40:05 +020070 raise KeyError(key)
71
72 def __setitem__(self, key, value):
73 self.stack[-1][key] = value
74
75 def blank(self):
76 """Return a new context with nothing but the root scope."""
77 return ContextStack(self.stack[0])
78
79
Christoph Hackb40b8802008-04-08 23:01:58 +020080class Optimizer(NodeTransformer):
Christoph Hack9d99e472008-04-08 20:21:11 +020081
Armin Ronacher81b88172008-04-09 00:40:05 +020082 def __init__(self, environment):
Christoph Hack9d99e472008-04-08 20:21:11 +020083 self.environment = environment
Christoph Hack9d99e472008-04-08 20:21:11 +020084
Armin Ronacher180a1bd2008-04-09 12:14:24 +020085 def visit_Block(self, node, context):
86 return self.generic_visit(node, context.blank())
87
Armin Ronacher07a21ba2008-04-23 22:28:42 +020088 def scoped_section(self, node, context):
Armin Ronacher8edbe492008-04-10 20:43:43 +020089 context.push()
90 try:
91 return self.generic_visit(node, context)
92 finally:
93 context.pop()
Armin Ronacher07a21ba2008-04-23 22:28:42 +020094 visit_For = visit_Macro = scoped_section
Armin Ronacher8edbe492008-04-10 20:43:43 +020095
Armin Ronacher00d5d212008-04-13 01:10:18 +020096 def visit_FilterBlock(self, node, context):
97 """Try to filter a block at compile time."""
98 node = self.generic_visit(node, context)
99 context.push()
100
101 # check if we can evaluate the wrapper body into a string
102 # at compile time
103 buffer = []
104 for child in node.body:
105 if not isinstance(child, nodes.Output):
106 return node
107 for item in child.optimized_nodes():
108 if isinstance(item, nodes.Node):
109 return node
110 buffer.append(item)
111
112 # now check if we can evaluate the filter at compile time.
113 try:
Armin Ronacherde6bf712008-04-26 01:44:14 +0200114 data = node.filter.as_const(concat(buffer))
Armin Ronacher00d5d212008-04-13 01:10:18 +0200115 except nodes.Impossible:
116 return node
117
118 context.pop()
119 const = nodes.Const(data, lineno=node.lineno)
120 return nodes.Output([const], lineno=node.lineno)
121
Armin Ronacher81b88172008-04-09 00:40:05 +0200122 def visit_If(self, node, context):
Christoph Hackca0666d2008-04-08 23:17:27 +0200123 try:
Armin Ronacher81b88172008-04-09 00:40:05 +0200124 val = self.visit(node.test, context).as_const()
125 except nodes.Impossible:
126 return self.generic_visit(node, context)
127 if val:
128 return node.body
129 return node.else_
130
131 def visit_Name(self, node, context):
Armin Ronacherd55ab532008-04-09 16:13:39 +0200132 if node.ctx != 'load':
Armin Ronacher8edbe492008-04-10 20:43:43 +0200133 # something overwrote the variable, we can no longer use
134 # the constant from the context
135 context.undef(node.name)
Armin Ronacherd55ab532008-04-09 16:13:39 +0200136 return node
137 try:
138 return nodes.Const.from_untrusted(context[node.name],
139 lineno=node.lineno,
140 environment=self.environment)
141 except (KeyError, nodes.Impossible):
142 return node
Armin Ronacher81b88172008-04-09 00:40:05 +0200143
144 def visit_Assign(self, node, context):
145 try:
146 target = node.target = self.generic_visit(node.target, context)
147 value = self.generic_visit(node.node, context).as_const()
Christoph Hackca0666d2008-04-08 23:17:27 +0200148 except nodes.Impossible:
149 return node
Armin Ronacher81b88172008-04-09 00:40:05 +0200150
151 result = []
152 lineno = node.lineno
153 def walk(target, value):
154 if isinstance(target, nodes.Name):
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200155 const = nodes.Const.from_untrusted(value, lineno=lineno)
156 result.append(nodes.Assign(target, const, lineno=lineno))
Armin Ronacher81b88172008-04-09 00:40:05 +0200157 context[target.name] = value
158 elif isinstance(target, nodes.Tuple):
159 try:
160 value = tuple(value)
161 except TypeError:
162 raise nodes.Impossible()
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200163 if len(target.items) != len(value):
Armin Ronacher81b88172008-04-09 00:40:05 +0200164 raise nodes.Impossible()
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200165 for name, val in zip(target.items, value):
Armin Ronacher81b88172008-04-09 00:40:05 +0200166 walk(name, val)
167 else:
168 raise AssertionError('unexpected assignable node')
169
170 try:
171 walk(target, value)
172 except nodes.Impossible:
173 return node
174 return result
175
Armin Ronacher0611e492008-04-25 23:44:14 +0200176 def visit_Import(self, node, context):
177 rv = self.generic_visit(node, context)
178 context.undef(node.target)
179 return rv
180
181 def visit_FromImport(self, node, context):
182 rv = self.generic_visit(node, context)
183 for name in node.names:
184 context.undef(name)
185 return rv
186
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200187 def fold(self, node, context):
188 """Do constant folding."""
189 node = self.generic_visit(node, context)
190 try:
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200191 return nodes.Const.from_untrusted(node.as_const(),
Armin Ronacherd55ab532008-04-09 16:13:39 +0200192 lineno=node.lineno,
193 environment=self.environment)
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200194 except nodes.Impossible:
195 return node
Armin Ronacherfed44b52008-04-13 19:42:53 +0200196
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200197 visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
198 visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
Armin Ronacherd55ab532008-04-09 16:13:39 +0200199 visit_Not = visit_Compare = visit_Subscript = visit_Call = \
Armin Ronacherfed44b52008-04-13 19:42:53 +0200200 visit_Filter = visit_Test = visit_CondExpr = fold
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200201 del fold