From d1d2f3db005e9635c582d3c920d781931eb67a81 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 9 Apr 2008 14:02:55 +0200 Subject: [PATCH] improved loop unrolling --HG-- branch : trunk --- jinja2/optimizer.py | 11 +++--- jinja2/runtime.py | 95 +++++++++++++++++++++++++++++++++++++++------ test_optimizer.py | 2 +- 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py index 191ec58..ee2969c 100644 --- a/jinja2/optimizer.py +++ b/jinja2/optimizer.py @@ -83,7 +83,10 @@ class Optimizer(NodeTransformer): def visit_For(self, node, context): """Loop unrolling for iterable constant values.""" try: - iterable = iter(self.visit(node.iter, context).as_const()) + iterable = self.visit(node.iter, context).as_const() + # we only unroll them if they have a length and are iterable + iter(iterable) + len(iterable) except (nodes.Impossible, TypeError): return self.generic_visit(node, context) @@ -107,12 +110,10 @@ class Optimizer(NodeTransformer): else: raise AssertionError('unexpected assignable node') - # XXX: not covered cases: - # - item is accessed by dynamic part in the iteration try: try: - for loop, item in LoopContext(iterable, parent): - context['loop'] = loop + for loop, item in LoopContext(iterable, parent, True): + context['loop'] = loop.make_static() assign(node.target, item) result.extend(self.visit(n, context) for n in deepcopy(node.body)) diff --git a/jinja2/runtime.py b/jinja2/runtime.py index a79fac3..fd22395 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -14,7 +14,10 @@ except ImportError: defaultdict = None -__all__ = ['extends', 'subscribe', 'LoopContext', 'TemplateContext', 'Macro'] +# contains only the variables the template will import automatically, not the +# objects injected by the evaluation loop (such as undefined objects) +__all__ = ['extends', 'subscribe', 'LoopContext', 'StaticLoopContext', + 'TemplateContext', 'Macro'] def extends(template, namespace): @@ -74,36 +77,74 @@ class TemplateContext(dict): return self.undefined_factory(key) -class LoopContext(object): +class LoopContextBase(object): """Helper for extended iteration.""" def __init__(self, iterable, parent=None): self._iterable = iterable + self._length = None self.index0 = 0 self.parent = parent - def __iter__(self): - for item in self._iterable: - yield self, item - self.index0 += 1 - first = property(lambda x: x.index0 == 0) last = property(lambda x: x.revindex0 == 0) index = property(lambda x: x.index0 + 1) revindex = property(lambda x: x.length) revindex0 = property(lambda x: x.length - 1) + length = property(lambda x: len(x)) + + +class LoopContext(LoopContextBase): + + def __init__(self, iterable, parent=None, enforce_length=False): + self._iterable = iterable + self._length = None + self.index0 = 0 + self.parent = parent + if enforce_length: + len(self) + + def make_static(self): + """Return a static loop context for the optimizer.""" + parent = None + if self.parent is not None: + parent = self.parent.make_static() + return StaticLoopContext(self.index0, self.length, parent) + + def __iter__(self): + for item in self._iterable: + yield self, item + self.index0 += 1 - @property - def length(self): - if not hasattr(self, '_length'): + def __len__(self): + if self._length is None: try: length = len(self._iterable) except TypeError: - length = len(tuple(self._iterable)) + self._iterable = tuple(self._iterable) + length = self.index0 + len(tuple(self._iterable)) self._length = length return self._length +class StaticLoopContext(LoopContextBase): + + def __init__(self, index0, length, parent): + self.index0 = index0 + self.parent = parent + self._length = length + + def __repr__(self): + return 'StaticLoopContext(%r, %r, %r)' % ( + self.index0, + self._length, + self.parent + ) + + def make_static(self): + return self + + class Macro(object): """ Wraps a macor @@ -139,3 +180,35 @@ class Macro(object): if self.catch_all: arguments['l_arguments'] = kwargs return u''.join(self.func(**arguments)) + + +class Undefined(object): + """The default undefined behavior.""" + + def __init__(self, name=None, attr=None): + if attr is None: + self._undefined_hint = '%r is undefined' % attr + elif name is None: + self._undefined_hint = 'attribute %r is undefined' % name + else: + self._undefined_hint = 'attribute %r of %r is undefined' \ + % (attr, name) + + def fail(self, *args, **kwargs): + raise TypeError(self._undefined_hint) + __getattr__ = __getitem__ = __add__ = __mul__ = __div__ = \ + __realdiv__ = __floordiv__ = __mod__ = __pos__ = __neg__ = fail + del fail + + def __unicode__(self): + return '' + + def __repr__(self): + return 'Undefined' + + def __len__(self): + return 0 + + def __iter__(self): + if 0: + yield None diff --git a/test_optimizer.py b/test_optimizer.py index ce0cb75..36f2011 100644 --- a/test_optimizer.py +++ b/test_optimizer.py @@ -20,7 +20,7 @@ ast = env.parse(""" {% navigation = [('#foo', 'Foo'), ('#bar', 'Bar'), ('#baz', 42 * 2 + 23)] %} """) -- 2.26.2