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")