added optimizer

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index c26c1b2..f173685 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -396,7 +396,7 @@
         for arg in node.defaults:
             self.visit(arg)
             self.write(', ')
-        self.write('), %r)' % accesses_arguments)
+        self.write('), %r, make_undefined)' % accesses_arguments)
 
     def visit_ExprStmt(self, node, frame):
         self.newline(node)
@@ -462,6 +462,8 @@
         self.visit(node.target, assignment_frame)
         self.write(' = ')
         self.visit(node.node, frame)
+
+        # make sure toplevel assignments are added to the context.
         if frame.toplevel:
             for name in assignment_frame.assigned_names:
                 self.writeline('context[%r] = l_%s' % (name, name))
@@ -564,22 +566,13 @@
             self.visit(node.step, frame)
 
     def visit_Filter(self, node, frame):
-        value = node.node
-        flen = len(node.filters)
-        if isinstance(value, nodes.Const):
-            # try to optimize filters on constant values
-            for filter in reversed(node.filters):
-                value = nodes.Const(self.environment.filters \
-                    .get(filter.name)(self.environment, value.value))
-                print value
-                flen -= 1
-        for filter in node.filters[:flen]:
+        for filter in node.filters:
             if filter.name in frame.identifiers.declared_filter:
                 self.write('f_%s(' % filter.name)
             else:
                 self.write('context.filter[%r](' % filter.name)
-        self.visit(value, frame)
-        for filter in reversed(node.filters[:flen]):
+        self.visit(node.node, frame)
+        for filter in reversed(node.filters):
             self.signature(filter, frame)
             self.write(')')
 
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/jinja2/runtime.py b/jinja2/runtime.py
index 7af5c4a..c1c34e1 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -14,7 +14,7 @@
     defaultdict = None
 
 
-__all__ = ['extends', 'subscribe', 'TemplateContext']
+__all__ = ['extends', 'subscribe', 'TemplateContext', 'Macro']
 
 
 def extends(template, namespace):
@@ -33,44 +33,79 @@
 
 
 class TemplateContext(dict):
+    """
+    Holds the variables of the local template or of the global one.  It's
+    not save to use this class outside of the compiled code.  For example
+    update and other methods will not work as they seem (they don't update
+    the exported variables for example).
+    """
 
     def __init__(self, globals, undefined_factory, filename):
-        dict.__init__(self)
-        self.globals = globals
+        dict.__init__(self, globals)
+        self.exported = set()
         self.undefined_factory = undefined_factory
         self.filename = filename
         self.filters = {}
         self.tests = {}
 
+    def __setitem__(self, key, value):
+        """If we set items to the dict we track the variables set so
+        that includes can access the exported variables."""
+        dict.__setitem__(self, key, value)
+        self.exported.add(key)
+
+    def __delitem__(self, key):
+        """On delete we no longer export it."""
+        dict.__delitem__(self, key)
+        self.exported.dicard(key)
+
+    def get_exported(self):
+        """Get a dict of all exported variables."""
+        return dict((k, self[k]) for k in self.exported)
+
     # if there is a default dict, dict has a __missing__ method we can use.
     if defaultdict is None:
         def __getitem__(self, name):
             if name in self:
                 return self[name]
-            elif name in self.globals:
-                return self.globals[name]
             return self.undefined_factory(name)
     else:
         def __missing__(self, key):
-            try:
-                return self.globals[key]
-            except:
-                return self.undefined_factory(key)
+            return self.undefined_factory(key)
 
 
 class Macro(object):
+    """
+    Wraps a macor
+    """
 
-    def __init__(self, func, name, arguments, defaults, catch_all):
+    def __init__(self, func, name, arguments, defaults, catch_all, \
+                 undefined_factory):
         self.func = func
         self.name = name
         self.arguments = arguments
         self.defaults = defaults
         self.catch_all = catch_all
+        self.undefined_factory = undefined_factory
 
     def __call__(self, *args, **kwargs):
-        if len(args) > len(self.arguments):
+        arg_count = len(self.arguments)
+        if len(args) > arg_count:
             raise TypeError('macro %r takes not more than %d argument(s).' %
                             (self.name, len(self.arguments)))
         arguments = {}
-        # XXX: assemble arguments
-        return u''.join(self.func(*args, **kwargs))
+        for idx, name in enumerate(self.arguments):
+            try:
+                value = args[idx]
+            except IndexError:
+                try:
+                    value = kwargs.pop(name)
+                except KeyError:
+                    try:
+                        value = self.defaults[idx - arg_count]
+                    except IndexError:
+                        value = self.undefined_factory(name)
+            arguments['l_' + name] = arg
+        if self.catch_all:
+            arguments['l_arguments'] = kwargs
+        return u''.join(self.func(**arguments))
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")