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