Skip to content

Commit

Permalink
added optimizer
Browse files Browse the repository at this point in the history
--HG--
branch : trunk
  • Loading branch information
tux21b committed Apr 8, 2008
2 parents fdbefac + 9706fab commit 9d99e47
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 26 deletions.
19 changes: 6 additions & 13 deletions jinja2/compiler.py
Expand Up @@ -396,7 +396,7 @@ def visit_Macro(self, node, frame):
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)
Expand Down Expand Up @@ -462,6 +462,8 @@ def visit_Assign(self, node, frame):
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))
Expand Down Expand Up @@ -564,22 +566,13 @@ def visit_Slice(self, node, frame):
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(')')

Expand Down
62 changes: 62 additions & 0 deletions 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)
61 changes: 48 additions & 13 deletions jinja2/runtime.py
Expand Up @@ -14,7 +14,7 @@
defaultdict = None


__all__ = ['extends', 'subscribe', 'TemplateContext']
__all__ = ['extends', 'subscribe', 'TemplateContext', 'Macro']


def extends(template, namespace):
Expand All @@ -33,44 +33,79 @@ def subscribe(obj, argument, undefined_factory):


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))
18 changes: 18 additions & 0 deletions 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")

0 comments on commit 9d99e47

Please sign in to comment.