blob: 784c3a8c5d59f9ee75ecbc159e8de56ac0b21ec8 [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):
Armin Ronacherc9705c22008-04-27 21:28:03 +020086 block_context = context.blank()
87 for name in 'super', 'self':
88 block_context.undef(name)
89 return self.generic_visit(node, block_context)
Armin Ronacher180a1bd2008-04-09 12:14:24 +020090
Armin Ronacherc9705c22008-04-27 21:28:03 +020091 def visit_For(self, node, context):
92 context.push()
93 context.undef('loop')
94 try:
95 return self.generic_visit(node, context)
96 finally:
97 context.pop()
98
99 def visit_Macro(self, node, context):
100 context.push()
101 for name in 'varargs', 'kwargs', 'caller':
102 context.undef(name)
103 try:
104 return self.generic_visit(node, context)
105 finally:
106 context.pop()
107
108 def visit_CallBlock(self, node, context):
109 context.push()
110 for name in 'varargs', 'kwargs':
111 context.undef(name)
112 try:
113 return self.generic_visit(node, context)
114 finally:
115 context.pop()
116
117 def visit_FilterBlock(self, node, context):
118 """Try to filter a block at compile time."""
Armin Ronacher8edbe492008-04-10 20:43:43 +0200119 context.push()
120 try:
121 return self.generic_visit(node, context)
122 finally:
123 context.pop()
Armin Ronacher00d5d212008-04-13 01:10:18 +0200124
Armin Ronacher81b88172008-04-09 00:40:05 +0200125 def visit_If(self, node, context):
Christoph Hackca0666d2008-04-08 23:17:27 +0200126 try:
Armin Ronacher81b88172008-04-09 00:40:05 +0200127 val = self.visit(node.test, context).as_const()
128 except nodes.Impossible:
129 return self.generic_visit(node, context)
130 if val:
Armin Ronacherc9705c22008-04-27 21:28:03 +0200131 body = node.body
132 else:
133 body = node.else_
134 result = []
135 for node in body:
136 result.extend(self.visit_list(node, context))
137 return result
Armin Ronacher81b88172008-04-09 00:40:05 +0200138
139 def visit_Name(self, node, context):
Armin Ronacherd55ab532008-04-09 16:13:39 +0200140 if node.ctx != 'load':
Armin Ronacher8edbe492008-04-10 20:43:43 +0200141 # something overwrote the variable, we can no longer use
142 # the constant from the context
143 context.undef(node.name)
Armin Ronacherd55ab532008-04-09 16:13:39 +0200144 return node
145 try:
146 return nodes.Const.from_untrusted(context[node.name],
147 lineno=node.lineno,
148 environment=self.environment)
149 except (KeyError, nodes.Impossible):
150 return node
Armin Ronacher81b88172008-04-09 00:40:05 +0200151
Armin Ronacher0611e492008-04-25 23:44:14 +0200152 def visit_Import(self, node, context):
153 rv = self.generic_visit(node, context)
154 context.undef(node.target)
155 return rv
156
157 def visit_FromImport(self, node, context):
158 rv = self.generic_visit(node, context)
159 for name in node.names:
Armin Ronacherc9705c22008-04-27 21:28:03 +0200160 if isinstance(name, tuple):
161 context.undef(name[1])
162 else:
163 context.undef(name)
Armin Ronacher0611e492008-04-25 23:44:14 +0200164 return rv
165
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200166 def fold(self, node, context):
167 """Do constant folding."""
168 node = self.generic_visit(node, context)
169 try:
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200170 return nodes.Const.from_untrusted(node.as_const(),
Armin Ronacherd55ab532008-04-09 16:13:39 +0200171 lineno=node.lineno,
172 environment=self.environment)
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200173 except nodes.Impossible:
174 return node
Armin Ronacherfed44b52008-04-13 19:42:53 +0200175
Armin Ronacher180a1bd2008-04-09 12:14:24 +0200176 visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
177 visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
Armin Ronacherd55ab532008-04-09 16:13:39 +0200178 visit_Not = visit_Compare = visit_Subscript = visit_Call = \
Armin Ronacherfed44b52008-04-13 19:42:53 +0200179 visit_Filter = visit_Test = visit_CondExpr = fold
Armin Ronacher4dfc9752008-04-09 15:03:29 +0200180 del fold