blob: 283d1fae5637493aec61f722a995bec87b4edfd9 [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 Ronacher7ceced52008-05-03 10:15:31 +020021from jinja2.runtime import LoopContext
22from jinja2.utils import concat
Christoph Hack9d99e472008-04-08 20:21:11 +020023
24
Armin Ronacherbcb7c532008-04-11 16:30:34 +020025def optimize(node, environment, context_hint=None):
26 """The context hint can be used to perform an static optimization
27 based on the context given."""
28 optimizer = Optimizer(environment)
29 return optimizer.visit(node, ContextStack(context_hint))
30
31
Armin Ronacher81b88172008-04-09 00:40:05 +020032class ContextStack(object):
33 """Simple compile time context implementation."""
Armin Ronacher8edbe492008-04-10 20:43:43 +020034 undefined = object()
Armin Ronacher81b88172008-04-09 00:40:05 +020035
36 def __init__(self, initial=None):
37 self.stack = [{}]
38 if initial is not None:
39 self.stack.insert(0, initial)
40
41 def push(self):
42 self.stack.append({})
43
44 def pop(self):
45 self.stack.pop()
46
Armin Ronacher180a1bd2008-04-09 12:14:24 +020047 def get(self, key, default=None):
48 try:
49 return self[key]
50 except KeyError:
51 return default
52
Armin Ronacher8edbe492008-04-10 20:43:43 +020053 def undef(self, name):
54 if name in self:
55 self[name] = self.undefined
56
57 def __contains__(self, key):
58 try:
59 self[key]
60 except KeyError:
61 return False
62 return True
63
Armin Ronacher81b88172008-04-09 00:40:05 +020064 def __getitem__(self, key):
65 for level in reversed(self.stack):
66 if key in level:
Armin Ronacher8edbe492008-04-10 20:43:43 +020067 rv = level[key]
68 if rv is self.undefined:
69 raise KeyError(key)
70 return rv
Armin Ronacher81b88172008-04-09 00:40:05 +020071 raise KeyError(key)
72
73 def __setitem__(self, key, value):
74 self.stack[-1][key] = value
75
76 def blank(self):
77 """Return a new context with nothing but the root scope."""
78 return ContextStack(self.stack[0])
79
80
Christoph Hackb40b8802008-04-08 23:01:58 +020081class Optimizer(NodeTransformer):
Christoph Hack9d99e472008-04-08 20:21:11 +020082
Armin Ronacher81b88172008-04-09 00:40:05 +020083 def __init__(self, environment):
Christoph Hack9d99e472008-04-08 20:21:11 +020084 self.environment = environment
Christoph Hack9d99e472008-04-08 20:21:11 +020085
Armin Ronacher180a1bd2008-04-09 12:14:24 +020086 def visit_Block(self, node, context):
Armin Ronacherc9705c22008-04-27 21:28:03 +020087 block_context = context.blank()
88 for name in 'super', 'self':
89 block_context.undef(name)
90 return self.generic_visit(node, block_context)
Armin Ronacher180a1bd2008-04-09 12:14:24 +020091
Armin Ronacherc9705c22008-04-27 21:28:03 +020092 def visit_For(self, node, context):
93 context.push()
94 context.undef('loop')
95 try:
96 return self.generic_visit(node, context)
97 finally:
98 context.pop()
99
100 def visit_Macro(self, node, context):
101 context.push()
102 for name in 'varargs', 'kwargs', 'caller':
103 context.undef(name)
104 try:
105 return self.generic_visit(node, context)
106 finally:
107 context.pop()
108
109 def visit_CallBlock(self, node, context):
110 context.push()
111 for name in 'varargs', 'kwargs':
112 context.undef(name)
113 try:
114 return self.generic_visit(node, context)
115 finally:
116 context.pop()
117
118 def visit_FilterBlock(self, node, context):
119 """Try to filter a block at compile time."""
Armin Ronacher8edbe492008-04-10 20:43:43 +0200120 context.push()
121 try:
122 return self.generic_visit(node, context)
123 finally:
124 context.pop()
Armin Ronacher00d5d212008-04-13 01:10:18 +0200125
Armin Ronacher81b88172008-04-09 00:40:05 +0200126 def visit_If(self, node, context):
Christoph Hackca0666d2008-04-08 23:17:27 +0200127 try:
Armin Ronacher81b88172008-04-09 00:40:05 +0200128 val = self.visit(node.test, context).as_const()
129 except nodes.Impossible:
130 return self.generic_visit(node, context)
131 if val:
Armin Ronacherc9705c22008-04-27 21:28:03 +0200132 body = node.body
133 else:
134 body = node.else_
135 result = []
136 for node in body:
137 result.extend(self.visit_list(node, context))
138 return result
Armin Ronacher81b88172008-04-09 00:40:05 +0200139
140 def visit_Name(self, node, context):
Armin Ronacherd55ab532008-04-09 16:13:39 +0200141 if node.ctx != 'load':
Armin Ronacher8edbe492008-04-10 20:43:43 +0200142 # something overwrote the variable, we can no longer use
143 # the constant from the context
144 context.undef(node.name)
Armin Ronacherd55ab532008-04-09 16:13:39 +0200145 return node
146 try:
147 return nodes.Const.from_untrusted(context[node.name],
148 lineno=node.lineno,
149 environment=self.environment)
150 except (KeyError, nodes.Impossible):
151 return node
Armin Ronacher81b88172008-04-09 00:40:05 +0200152
Armin Ronacher0611e492008-04-25 23:44:14 +0200153 def visit_Import(self, node, context):
154 rv = self.generic_visit(node, context)
155 context.undef(node.target)
156 return rv
157
158 def visit_FromImport(self, node, context):
159 rv = self.generic_visit(node, context)
160 for name in node.names:
Armin Ronacherc9705c22008-04-27 21:28:03 +0200161 if isinstance(name, tuple):
162 context.undef(name[1])
163 else:
164 context.undef(name)
Armin Ronacher0611e492008-04-25 23:44:14 +0200165 return rv
166
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200167 def fold(self, node, context):
168 """Do constant folding."""
169 node = self.generic_visit(node, context)
170 try:
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200171 return nodes.Const.from_untrusted(node.as_const(),
Armin Ronacherd55ab532008-04-09 16:13:39 +0200172 lineno=node.lineno,
173 environment=self.environment)
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200174 except nodes.Impossible:
175 return node
Armin Ronacherfed44b52008-04-13 19:42:53 +0200176
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200177 visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
178 visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
Armin Ronacherd55ab532008-04-09 16:13:39 +0200179 visit_Not = visit_Compare = visit_Subscript = visit_Call = \
Armin Ronacherfed44b52008-04-13 19:42:53 +0200180 visit_Filter = visit_Test = visit_CondExpr = fold
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200181 del fold