From 180a1bd140f1ac0915c552f871844d9977420b34 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 9 Apr 2008 12:14:24 +0200 Subject: [PATCH] tuple unpacking at compile time is handled properly now --HG-- branch : trunk --- jinja2/compiler.py | 4 ++- jinja2/optimizer.py | 70 +++++++++++++++++++++++++++++++++++++-------- jinja2/runtime.py | 32 ++++++++++++++++++++- test_optimizer.py | 6 ++-- 4 files changed, 95 insertions(+), 17 deletions(-) diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 02fc1e8..4f5ff0b 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -315,8 +315,10 @@ class CodeGenerator(NodeVisitor): self.writeline('l_loop = None') self.write('for ') self.visit(node.target, loop_frame) - self.write(extended_loop and ', l_loop in looper(' or ' in ') + self.write(extended_loop and ', l_loop in LoopContext(' or ' in ') self.visit(node.iter, loop_frame) + if 'loop' in aliases: + self.write(', ' + aliases['loop']) self.write(extended_loop and '):' or ':') self.blockvisit(node.body, loop_frame) diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py index c508727..191ec58 100644 --- a/jinja2/optimizer.py +++ b/jinja2/optimizer.py @@ -22,7 +22,7 @@ from copy import deepcopy from jinja2 import nodes from jinja2.visitor import NodeVisitor, NodeTransformer -from jinja2.runtime import subscribe +from jinja2.runtime import subscribe, LoopContext class ContextStack(object): @@ -39,6 +39,12 @@ class ContextStack(object): def pop(self): self.stack.pop() + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + def __getitem__(self, key): for level in reversed(self.stack): if key in level: @@ -58,6 +64,9 @@ class Optimizer(NodeTransformer): def __init__(self, environment): self.environment = environment + def visit_Block(self, node, context): + return self.generic_visit(node, context.blank()) + def visit_Filter(self, node, context): """Try to evaluate filters if possible.""" # XXX: nonconstant arguments? not-called visitors? generic visit! @@ -77,18 +86,44 @@ class Optimizer(NodeTransformer): iterable = iter(self.visit(node.iter, context).as_const()) except (nodes.Impossible, TypeError): return self.generic_visit(node, context) + + parent = context.get('loop') context.push() result = [] - # XXX: tuple unpacking (for key, value in foo) - target = node.target.name iterated = False - for item in iterable: - context[target] = item - result.extend(self.visit(n, context) for n in deepcopy(node.body)) - iterated = True - if not iterated and node.else_: - result.extend(self.visit(n, context) for n in deepcopy(node.else_)) - context.pop() + + def assign(target, value): + if isinstance(target, nodes.Name): + context[target.name] = value + elif isinstance(target, nodes.Tuple): + try: + value = tuple(value) + except TypeError: + raise nodes.Impossible() + if len(target.items) != len(value): + raise nodes.Impossible() + for name, val in zip(target.items, value): + assign(name, val) + 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 + assign(node.target, item) + result.extend(self.visit(n, context) + for n in deepcopy(node.body)) + iterated = True + if not iterated and node.else_: + result.extend(self.visit(n, context) + for n in deepcopy(node.else_)) + except nodes.Impossible: + return node + finally: + context.pop() return result def visit_If(self, node, context): @@ -127,9 +162,9 @@ class Optimizer(NodeTransformer): value = tuple(value) except TypeError: raise nodes.Impossible() - if len(target) != len(value): + if len(target.items) != len(value): raise nodes.Impossible() - for name, val in zip(target, value): + for name, val in zip(target.items, value): walk(name, val) else: raise AssertionError('unexpected assignable node') @@ -140,6 +175,17 @@ class Optimizer(NodeTransformer): return node return result + def fold(self, node, context): + """Do constant folding.""" + node = self.generic_visit(node, context) + try: + return nodes.Const(node.as_const(), lineno=node.lineno) + except nodes.Impossible: + return node + visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ + visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ + visit_Not = visit_Compare = fold + def visit_Subscript(self, node, context): if node.ctx == 'load': try: diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 5a9764e..a79fac3 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -14,7 +14,7 @@ except ImportError: defaultdict = None -__all__ = ['extends', 'subscribe', 'TemplateContext', 'Macro'] +__all__ = ['extends', 'subscribe', 'LoopContext', 'TemplateContext', 'Macro'] def extends(template, namespace): @@ -74,6 +74,36 @@ class TemplateContext(dict): return self.undefined_factory(key) +class LoopContext(object): + """Helper for extended iteration.""" + + def __init__(self, iterable, parent=None): + self._iterable = iterable + 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) + + @property + def length(self): + if not hasattr(self, '_length'): + try: + length = len(self._iterable) + except TypeError: + length = len(tuple(self._iterable)) + self._length = length + return self._length + + class Macro(object): """ Wraps a macor diff --git a/test_optimizer.py b/test_optimizer.py index ddb0fa0..ce0cb75 100644 --- a/test_optimizer.py +++ b/test_optimizer.py @@ -17,10 +17,10 @@ ast = env.parse(""" {{ readstatus(forum.id) }} {{ forum.id|e }} {{ forum.name|e }} {% endfor %} - {% navigation = [('#foo', 'Foo'), ('#bar', 'Bar')] %} + {% navigation = [('#foo', 'Foo'), ('#bar', 'Bar'), ('#baz', 42 * 2 + 23)] %} """) -- 2.26.2