Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | """ |
| 3 | jinja2.optimizer |
| 4 | ~~~~~~~~~~~~~~~~ |
| 5 | |
Armin Ronacher | 07a21ba | 2008-04-23 22:28:42 +0200 | [diff] [blame] | 6 | 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 Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 8 | |
Armin Ronacher | 07a21ba | 2008-04-23 22:28:42 +0200 | [diff] [blame] | 9 | 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 Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 13 | |
Armin Ronacher | 07a21ba | 2008-04-23 22:28:42 +0200 | [diff] [blame] | 14 | The solution would be a second syntax tree that has the scoping rules stored. |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 15 | |
Armin Ronacher | 2e9396b | 2008-04-16 14:21:57 +0200 | [diff] [blame] | 16 | :copyright: Copyright 2008 by Christoph Hack, Armin Ronacher. |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 17 | :license: GNU GPL. |
| 18 | """ |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 19 | from jinja2 import nodes |
| 20 | from jinja2.visitor import NodeVisitor, NodeTransformer |
Armin Ronacher | 7ceced5 | 2008-05-03 10:15:31 +0200 | [diff] [blame] | 21 | from jinja2.runtime import LoopContext |
| 22 | from jinja2.utils import concat |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 23 | |
| 24 | |
Armin Ronacher | bcb7c53 | 2008-04-11 16:30:34 +0200 | [diff] [blame] | 25 | def 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 32 | class ContextStack(object): |
| 33 | """Simple compile time context implementation.""" |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 34 | undefined = object() |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 35 | |
| 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 Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 47 | def get(self, key, default=None): |
| 48 | try: |
| 49 | return self[key] |
| 50 | except KeyError: |
| 51 | return default |
| 52 | |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 53 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 64 | def __getitem__(self, key): |
| 65 | for level in reversed(self.stack): |
| 66 | if key in level: |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 67 | rv = level[key] |
| 68 | if rv is self.undefined: |
| 69 | raise KeyError(key) |
| 70 | return rv |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 71 | 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 Hack | b40b880 | 2008-04-08 23:01:58 +0200 | [diff] [blame] | 81 | class Optimizer(NodeTransformer): |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 82 | |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 83 | def __init__(self, environment): |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 84 | self.environment = environment |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 85 | |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 86 | def visit_Block(self, node, context): |
Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 87 | block_context = context.blank() |
| 88 | for name in 'super', 'self': |
| 89 | block_context.undef(name) |
| 90 | return self.generic_visit(node, block_context) |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 91 | |
Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 92 | 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 Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 120 | context.push() |
| 121 | try: |
| 122 | return self.generic_visit(node, context) |
| 123 | finally: |
| 124 | context.pop() |
Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 125 | |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 126 | def visit_If(self, node, context): |
Christoph Hack | ca0666d | 2008-04-08 23:17:27 +0200 | [diff] [blame] | 127 | try: |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 128 | val = self.visit(node.test, context).as_const() |
| 129 | except nodes.Impossible: |
| 130 | return self.generic_visit(node, context) |
| 131 | if val: |
Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 132 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 139 | |
| 140 | def visit_Name(self, node, context): |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 141 | if node.ctx != 'load': |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 142 | # something overwrote the variable, we can no longer use |
| 143 | # the constant from the context |
| 144 | context.undef(node.name) |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 145 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 152 | |
Armin Ronacher | 0611e49 | 2008-04-25 23:44:14 +0200 | [diff] [blame] | 153 | 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 Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 161 | if isinstance(name, tuple): |
| 162 | context.undef(name[1]) |
| 163 | else: |
| 164 | context.undef(name) |
Armin Ronacher | 0611e49 | 2008-04-25 23:44:14 +0200 | [diff] [blame] | 165 | return rv |
| 166 | |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 167 | def fold(self, node, context): |
| 168 | """Do constant folding.""" |
| 169 | node = self.generic_visit(node, context) |
| 170 | try: |
Armin Ronacher | 4dfc975 | 2008-04-09 15:03:29 +0200 | [diff] [blame] | 171 | return nodes.Const.from_untrusted(node.as_const(), |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 172 | lineno=node.lineno, |
| 173 | environment=self.environment) |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 174 | except nodes.Impossible: |
| 175 | return node |
Armin Ronacher | fed44b5 | 2008-04-13 19:42:53 +0200 | [diff] [blame] | 176 | |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 177 | visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ |
| 178 | visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 179 | visit_Not = visit_Compare = visit_Subscript = visit_Call = \ |
Armin Ronacher | fed44b5 | 2008-04-13 19:42:53 +0200 | [diff] [blame] | 180 | visit_Filter = visit_Test = visit_CondExpr = fold |
Armin Ronacher | 4dfc975 | 2008-04-09 15:03:29 +0200 | [diff] [blame] | 181 | del fold |