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 | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 21 | from jinja2.runtime import LoopContext, concat |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 22 | |
| 23 | |
Armin Ronacher | bcb7c53 | 2008-04-11 16:30:34 +0200 | [diff] [blame] | 24 | def 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 31 | class ContextStack(object): |
| 32 | """Simple compile time context implementation.""" |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 33 | undefined = object() |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 34 | |
| 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 Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 46 | def get(self, key, default=None): |
| 47 | try: |
| 48 | return self[key] |
| 49 | except KeyError: |
| 50 | return default |
| 51 | |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 52 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 63 | def __getitem__(self, key): |
| 64 | for level in reversed(self.stack): |
| 65 | if key in level: |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 66 | rv = level[key] |
| 67 | if rv is self.undefined: |
| 68 | raise KeyError(key) |
| 69 | return rv |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 70 | 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 Hack | b40b880 | 2008-04-08 23:01:58 +0200 | [diff] [blame] | 80 | class Optimizer(NodeTransformer): |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 81 | |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 82 | def __init__(self, environment): |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 83 | self.environment = environment |
Christoph Hack | 9d99e47 | 2008-04-08 20:21:11 +0200 | [diff] [blame] | 84 | |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 85 | def visit_Block(self, node, context): |
| 86 | return self.generic_visit(node, context.blank()) |
| 87 | |
Armin Ronacher | 07a21ba | 2008-04-23 22:28:42 +0200 | [diff] [blame] | 88 | def scoped_section(self, node, context): |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 89 | context.push() |
| 90 | try: |
| 91 | return self.generic_visit(node, context) |
| 92 | finally: |
| 93 | context.pop() |
Armin Ronacher | 07a21ba | 2008-04-23 22:28:42 +0200 | [diff] [blame] | 94 | visit_For = visit_Macro = scoped_section |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 95 | |
Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 96 | 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 Ronacher | de6bf71 | 2008-04-26 01:44:14 +0200 | [diff] [blame] | 114 | data = node.filter.as_const(concat(buffer)) |
Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 115 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 122 | def visit_If(self, node, context): |
Christoph Hack | ca0666d | 2008-04-08 23:17:27 +0200 | [diff] [blame] | 123 | try: |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 124 | 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 Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 132 | if node.ctx != 'load': |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 133 | # something overwrote the variable, we can no longer use |
| 134 | # the constant from the context |
| 135 | context.undef(node.name) |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 136 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 143 | |
| 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 Hack | ca0666d | 2008-04-08 23:17:27 +0200 | [diff] [blame] | 148 | except nodes.Impossible: |
| 149 | return node |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 150 | |
| 151 | result = [] |
| 152 | lineno = node.lineno |
| 153 | def walk(target, value): |
| 154 | if isinstance(target, nodes.Name): |
Armin Ronacher | 4dfc975 | 2008-04-09 15:03:29 +0200 | [diff] [blame] | 155 | const = nodes.Const.from_untrusted(value, lineno=lineno) |
| 156 | result.append(nodes.Assign(target, const, lineno=lineno)) |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 157 | context[target.name] = value |
| 158 | elif isinstance(target, nodes.Tuple): |
| 159 | try: |
| 160 | value = tuple(value) |
| 161 | except TypeError: |
| 162 | raise nodes.Impossible() |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 163 | if len(target.items) != len(value): |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 164 | raise nodes.Impossible() |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 165 | for name, val in zip(target.items, value): |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 166 | 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 Ronacher | 0611e49 | 2008-04-25 23:44:14 +0200 | [diff] [blame] | 176 | 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 Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 187 | def fold(self, node, context): |
| 188 | """Do constant folding.""" |
| 189 | node = self.generic_visit(node, context) |
| 190 | try: |
Armin Ronacher | 4dfc975 | 2008-04-09 15:03:29 +0200 | [diff] [blame] | 191 | return nodes.Const.from_untrusted(node.as_const(), |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 192 | lineno=node.lineno, |
| 193 | environment=self.environment) |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 194 | except nodes.Impossible: |
| 195 | return node |
Armin Ronacher | fed44b5 | 2008-04-13 19:42:53 +0200 | [diff] [blame] | 196 | |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 197 | visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ |
| 198 | visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 199 | visit_Not = visit_Compare = visit_Subscript = visit_Call = \ |
Armin Ronacher | fed44b5 | 2008-04-13 19:42:53 +0200 | [diff] [blame] | 200 | visit_Filter = visit_Test = visit_CondExpr = fold |
Armin Ronacher | 4dfc975 | 2008-04-09 15:03:29 +0200 | [diff] [blame] | 201 | del fold |