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): |
Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 86 | block_context = context.blank() |
| 87 | for name in 'super', 'self': |
| 88 | block_context.undef(name) |
| 89 | return self.generic_visit(node, block_context) |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 90 | |
Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 91 | 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 Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 119 | context.push() |
| 120 | try: |
| 121 | return self.generic_visit(node, context) |
| 122 | finally: |
| 123 | context.pop() |
Armin Ronacher | 00d5d21 | 2008-04-13 01:10:18 +0200 | [diff] [blame] | 124 | |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 125 | def visit_If(self, node, context): |
Christoph Hack | ca0666d | 2008-04-08 23:17:27 +0200 | [diff] [blame] | 126 | try: |
Armin Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 127 | val = self.visit(node.test, context).as_const() |
| 128 | except nodes.Impossible: |
| 129 | return self.generic_visit(node, context) |
| 130 | if val: |
Armin Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 131 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 138 | |
| 139 | def visit_Name(self, node, context): |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 140 | if node.ctx != 'load': |
Armin Ronacher | 8edbe49 | 2008-04-10 20:43:43 +0200 | [diff] [blame] | 141 | # something overwrote the variable, we can no longer use |
| 142 | # the constant from the context |
| 143 | context.undef(node.name) |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 144 | 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 Ronacher | 81b8817 | 2008-04-09 00:40:05 +0200 | [diff] [blame] | 151 | |
Armin Ronacher | 0611e49 | 2008-04-25 23:44:14 +0200 | [diff] [blame] | 152 | 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 Ronacher | c9705c2 | 2008-04-27 21:28:03 +0200 | [diff] [blame] | 160 | if isinstance(name, tuple): |
| 161 | context.undef(name[1]) |
| 162 | else: |
| 163 | context.undef(name) |
Armin Ronacher | 0611e49 | 2008-04-25 23:44:14 +0200 | [diff] [blame] | 164 | return rv |
| 165 | |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 166 | def fold(self, node, context): |
| 167 | """Do constant folding.""" |
| 168 | node = self.generic_visit(node, context) |
| 169 | try: |
Armin Ronacher | 4dfc975 | 2008-04-09 15:03:29 +0200 | [diff] [blame] | 170 | return nodes.Const.from_untrusted(node.as_const(), |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 171 | lineno=node.lineno, |
| 172 | environment=self.environment) |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 173 | except nodes.Impossible: |
| 174 | return node |
Armin Ronacher | fed44b5 | 2008-04-13 19:42:53 +0200 | [diff] [blame] | 175 | |
Armin Ronacher | 180a1bd | 2008-04-09 12:14:24 +0200 | [diff] [blame] | 176 | visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ |
| 177 | visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ |
Armin Ronacher | d55ab53 | 2008-04-09 16:13:39 +0200 | [diff] [blame] | 178 | visit_Not = visit_Compare = visit_Subscript = visit_Call = \ |
Armin Ronacher | fed44b5 | 2008-04-13 19:42:53 +0200 | [diff] [blame] | 179 | visit_Filter = visit_Test = visit_CondExpr = fold |
Armin Ronacher | 4dfc975 | 2008-04-09 15:03:29 +0200 | [diff] [blame] | 180 | del fold |