added optimizer

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index bdf195f..f173685 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -29,9 +29,9 @@
 }
 
 
-def generate(node, filename, stream=None):
+def generate(node, environment, filename, stream=None):
     is_child = node.find(nodes.Extends) is not None
-    generator = CodeGenerator(is_child, filename, stream)
+    generator = CodeGenerator(environment, is_child, filename, stream)
     generator.visit(node)
     if stream is None:
         return generator.stream.getvalue()
@@ -56,6 +56,10 @@
         # names that are declared by parameters
         self.declared_parameter = set()
 
+        # filters that are declared locally
+        self.declared_filter = set()
+        self.undeclared_filter = dict()
+
     def add_special(self, name):
         """Register a special name like `loop`."""
         self.undeclared.discard(name)
@@ -123,6 +127,13 @@
             if not self.identifiers.is_declared(node.name, self.hard_scope):
                 self.identifiers.undeclared.add(node.name)
 
+    def visit_FilterCall(self, node):
+        if not node.name in self.identifiers.declared_filter:
+            uf = self.identifiers.undeclared_filter.get(node.name, 0) + 1
+            if uf > 1:
+                self.identifiers.declared_filter.add(node.name)
+            self.identifiers.undeclared_filter[node.name] = uf
+
     def visit_Macro(self, node):
         """Macros set local."""
         self.identifiers.declared_locally.add(node.name)
@@ -134,9 +145,10 @@
 
 class CodeGenerator(NodeVisitor):
 
-    def __init__(self, is_child, filename, stream=None):
+    def __init__(self, environment, is_child, filename, stream=None):
         if stream is None:
             stream = StringIO()
+        self.environment = environment
         self.is_child = is_child
         self.filename = filename
         self.stream = stream
@@ -211,6 +223,9 @@
             self.indent()
         for name in frame.identifiers.undeclared:
             self.writeline('l_%s = context[%r]' % (name, name))
+        for name, count in frame.identifiers.undeclared_filter.iteritems():
+            if count > 1:
+                self.writeline('f_%s = context[%r]' % (name, name))
         if not no_indent:
             self.outdent()
 
@@ -552,7 +567,10 @@
 
     def visit_Filter(self, node, frame):
         for filter in node.filters:
-            self.write('context.filters[%r](' % filter.name)
+            if filter.name in frame.identifiers.declared_filter:
+                self.write('f_%s(' % filter.name)
+            else:
+                self.write('context.filter[%r](' % filter.name)
         self.visit(node.node, frame)
         for filter in reversed(node.filters):
             self.signature(filter, frame)
diff --git a/jinja2/defaults.py b/jinja2/defaults.py
index 37473ea..84c1b08 100644
--- a/jinja2/defaults.py
+++ b/jinja2/defaults.py
@@ -8,7 +8,7 @@
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
-from jinja.filters import FILTERS as DEFAULT_FILTERS
+from jinja2.filters import FILTERS as DEFAULT_FILTERS
 from jinja.tests import TESTS as DEFAULT_TESTS
 DEFAULT_NAMESPACE = {}
 
diff --git a/jinja2/filters.py b/jinja2/filters.py
index 6098b6e..4518822 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -15,6 +15,7 @@
 except ImportError:
     itemgetter = lambda a: lambda b: b[a]
 from urllib import urlencode, quote
+from jinja.utils import escape
 
 
 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
@@ -94,18 +95,16 @@
     Convert a value to uppercase.
     """
     return s.upper()
-do_upper = stringfilter(do_upper)
 
 
-def do_lower(s):
+def do_lower(env, s):
     """
     Convert a value to lowercase.
     """
     return s.lower()
-do_lower = stringfilter(do_lower)
 
 
-def do_escape(attribute=False):
+def do_escape(env, s, attribute=False):
     """
     XML escape ``&``, ``<``, and ``>`` in a string of data. If the
     optional parameter is `true` this filter will also convert
@@ -114,20 +113,12 @@
 
     This method will have no effect it the value is already escaped.
     """
-    #: because filters are cached we can make a local alias to
-    #: speed things up a bit
-    e = escape
-    def wrapped(env, context, s):
-        if isinstance(s, TemplateData):
-            return s
-        elif hasattr(s, '__html__'):
-            return s.__html__()
-        #: small speedup, do not convert to unicode if we already
-        #: have an unicode object.
-        if s.__class__ is not unicode:
-            s = env.to_unicode(s)
-        return e(s, attribute)
-    return wrapped
+    # XXX: Does this still exists?
+    #if isinstance(s, TemplateData):
+    #    return s
+    if hasattr(s, '__html__'):
+        return s.__html__()
+    return escape(unicode(s), attribute)
 
 
 def do_xmlattr(autospace=False):
diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py
new file mode 100644
index 0000000..8793a88
--- /dev/null
+++ b/jinja2/optimizer.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.optimizer
+    ~~~~~~~~~~~~~~~~
+
+    This module tries to optimize template trees by:
+
+        * eliminating constant nodes
+        * evaluating filters and macros on constant nodes
+        * unroll loops on constant values
+        * replace variables which are already known (because the doesn't
+          change often and you want to prerender a template) with constants
+
+    After the optimation you will get a new, simplier template which can
+    be saved again for later rendering. But even if you don't want to
+    prerender a template, this module might speed up your templates a bit
+    if you are using a lot of constants.
+
+    :copyright: Copyright 2008 by Christoph Hack.
+    :license: GNU GPL.
+"""
+from copy import copy
+from random import randrange
+from operator import xor
+from cStringIO import StringIO
+from jinja2 import nodes
+from jinja2.visitor import NodeVisitor, NodeTransformer
+from jinja2.exceptions import TemplateAssertionError
+
+
+class Optimizer(NodeVisitor):
+
+    def __init__(self, environment, context={}):
+        self.environment = environment
+        self.context = context
+
+    def visit_Output(self, node):
+        node.nodes = [self.visit(n) for n in node.nodes]
+        return node
+
+    def visit_Filter(self, node):
+        """Try to evaluate filters if possible."""
+        value = self.visit(node.node)
+        if isinstance(value, nodes.Const):
+            x = value.value
+            for filter in reversed(node.filters):
+                # XXX: call filters with arguments
+                x = self.environment.filters[filter.name](self.environment, x)
+                # XXX: don't optimize context dependent filters
+            return nodes.Const(x)
+        return node
+
+    def generic_visit(self, node, *args, **kwargs):
+        NodeVisitor.generic_visit(self, node, *args, **kwargs)
+        return node
+
+
+def optimize(ast, env, clone=True):
+    optimizer = Optimizer(env)
+    if clone:
+        ast = copy(ast)
+    return optimizer.visit(ast)
diff --git a/test.py b/test.py
index 3aa44e1..67f19b6 100644
--- a/test.py
+++ b/test.py
@@ -14,4 +14,4 @@
 """)
 print ast
 print
-print generate(ast, "foo.html")
+print generate(ast, env, "foo.html")
diff --git a/test_optimizer.py b/test_optimizer.py
new file mode 100644
index 0000000..d632777
--- /dev/null
+++ b/test_optimizer.py
@@ -0,0 +1,18 @@
+from jinja2 import Environment
+from jinja2.compiler import generate
+from jinja2.optimizer import optimize
+
+
+env = Environment()
+ast = env.parse("""
+    Hi {{ "<blub>"|e }},
+    how are you?
+""")
+print ast
+print
+print generate(ast, env, "foo.html")
+print
+ast = optimize(ast, env)
+print ast
+print
+print generate(ast, env, "foo.html")