X-Git-Url: http://nsz.repo.hu/git/?a=blobdiff_plain;f=scripts%2Fjinja2%2Fcompiler.py;h=a52b1c7bc3420ef6a9add9a6bd1cb4d8cf1af22a;hb=4d6c06673e8fc5d144bfe16397697ff2812fc22e;hp=5074a342ecbd11dd492636762ea1ac3117b7856f;hpb=279822e9e2a4f26deaf67b5fe8423b27837a75d7;p=libfirm diff --git a/scripts/jinja2/compiler.py b/scripts/jinja2/compiler.py index 5074a342e..a52b1c7bc 100644 --- a/scripts/jinja2/compiler.py +++ b/scripts/jinja2/compiler.py @@ -5,15 +5,16 @@ Compiles nodes into python code. - :copyright: Copyright 2008 by Armin Ronacher. - :license: BSD. + :copyright: (c) 2010 by the Jinja Team. + :license: BSD, see LICENSE for more details. """ from cStringIO import StringIO from itertools import chain +from copy import deepcopy from jinja2 import nodes from jinja2.visitor import NodeVisitor, NodeTransformer from jinja2.exceptions import TemplateAssertionError -from jinja2.utils import Markup, concat, escape, is_python_keyword +from jinja2.utils import Markup, concat, escape, is_python_keyword, next operators = { @@ -35,6 +36,23 @@ else: have_condexpr = True +# what method to iterate over items do we want to use for dict iteration +# in generated code? on 2.x let's go with iteritems, on 3.x with items +if hasattr(dict, 'iteritems'): + dict_item_iter = 'iteritems' +else: + dict_item_iter = 'items' + + +# does if 0: dummy(x) get us x into the scope? +def unoptimize_before_dead_code(): + x = 42 + def f(): + if 0: dummy(x) + return f +unoptimize_before_dead_code = bool(unoptimize_before_dead_code().func_closure) + + def generate(node, environment, name, filename, stream=None): """Generate the python source for a node tree.""" if not isinstance(node, nodes.Template): @@ -115,13 +133,8 @@ class Identifiers(object): return False return name in self.declared - def find_shadowed(self, extra=()): - """Find all the shadowed names. extra is an iterable of variables - that may be defined with `add_special` which may occour scoped. - """ - return (self.declared | self.outer_undeclared) & \ - (self.declared_locally | self.declared_parameter) | \ - set(x for x in extra if self.is_declared(x)) + def copy(self): + return deepcopy(self) class Frame(object): @@ -151,15 +164,17 @@ class Frame(object): # the name of the block we're in, otherwise None. self.block = parent and parent.block or None + # a set of actually assigned names + self.assigned_names = set() + # the parent of this frame self.parent = parent if parent is not None: self.identifiers.declared.update( parent.identifiers.declared | - parent.identifiers.declared_locally | parent.identifiers.declared_parameter | - parent.identifiers.undeclared + parent.assigned_names ) self.identifiers.outer_undeclared.update( parent.identifiers.undeclared - @@ -184,6 +199,15 @@ class Frame(object): for node in nodes: visitor.visit(node) + def find_shadowed(self, extra=()): + """Find all the shadowed names. extra is an iterable of variables + that may be defined with `add_special` which may occour scoped. + """ + i = self.identifiers + return (i.declared | i.outer_undeclared) & \ + (i.declared_locally | i.declared_parameter) | \ + set(x for x in extra if i.is_declared(x)) + def inner(self): """Return an inner frame.""" return Frame(self) @@ -262,6 +286,37 @@ class FrameIdentifierVisitor(NodeVisitor): self.identifiers.is_declared(node.name, self.hard_scope): self.identifiers.undeclared.add(node.name) + def visit_If(self, node): + self.visit(node.test) + real_identifiers = self.identifiers + + old_names = real_identifiers.declared | \ + real_identifiers.declared_locally | \ + real_identifiers.declared_parameter + + def inner_visit(nodes): + if not nodes: + return set() + self.identifiers = real_identifiers.copy() + for subnode in nodes: + self.visit(subnode) + rv = self.identifiers.declared_locally - old_names + # we have to remember the undeclared variables of this branch + # because we will have to pull them. + real_identifiers.undeclared.update(self.identifiers.undeclared) + self.identifiers = real_identifiers + return rv + + body = inner_visit(node.body) + else_ = inner_visit(node.else_ or ()) + + # the differences between the two branches are also pulled as + # undeclared variables + real_identifiers.undeclared.update(body.symmetric_difference(else_)) + + # remember those that are declared. + real_identifiers.declared_locally.update(body | else_) + def visit_Macro(self, node): self.identifiers.declared_locally.add(node.name) @@ -289,12 +344,14 @@ class FrameIdentifierVisitor(NodeVisitor): self.visit(node.iter) def visit_CallBlock(self, node): - for child in node.iter_child_nodes(exclude=('body',)): - self.visit(child) + self.visit(node.call) def visit_FilterBlock(self, node): self.visit(node.filter) + def visit_Scope(self, node): + """Stop visiting at scopes.""" + def visit_Block(self, node): """Stop visiting at blocks.""" @@ -315,6 +372,7 @@ class CodeGenerator(NodeVisitor): self.name = name self.filename = filename self.stream = stream + self.created_block_context = False # aliases for imports self.import_aliases = {} @@ -520,6 +578,18 @@ class CodeGenerator(NodeVisitor): self.writeline('%s = environment.%s[%r]' % (mapping[name], dependency, name)) + def unoptimize_scope(self, frame): + """Disable Python optimizations for the frame.""" + # XXX: this is not that nice but it has no real overhead. It + # mainly works because python finds the locals before dead code + # is removed. If that breaks we have to add a dummy function + # that just accepts the arguments and does nothing. + if frame.identifiers.declared: + self.writeline('%sdummy(%s)' % ( + unoptimize_before_dead_code and 'if 0: ' or '', + ', '.join('l_' + name for name in frame.identifiers.declared) + )) + def push_scope(self, frame, extra_vars=()): """This function returns all the shadowed variables in a dict in the form name: alias and will write the required assignments @@ -528,10 +598,10 @@ class CodeGenerator(NodeVisitor): This also predefines locally declared variables from the loop body because under some circumstances it may be the case that - `extra_vars` is passed to `Identifiers.find_shadowed`. + `extra_vars` is passed to `Frame.find_shadowed`. """ aliases = {} - for name in frame.identifiers.find_shadowed(extra_vars): + for name in frame.find_shadowed(extra_vars): aliases[name] = ident = self.temporary_identifier() self.writeline('%s = l_%s' % (ident, name)) to_declare = set() @@ -551,7 +621,10 @@ class CodeGenerator(NodeVisitor): if name not in aliases: to_delete.add('l_' + name) if to_delete: - self.writeline('del ' + ', '.join(to_delete)) + # we cannot use the del statement here because enclosed + # scopes can trigger a SyntaxError: + # a = 42; b = lambda: a; del a + self.writeline(' = '.join(to_delete) + ' = missing') def function_scoping(self, node, frame, children=None, find_special=True): @@ -576,7 +649,9 @@ class CodeGenerator(NodeVisitor): # variables that are undeclared (accessed before declaration) and # declared locally *and* part of an outside scope raise a template # assertion error. Reason: we can't generate reasonable code from - # it without aliasing all the variables. XXX: alias them ^^ + # it without aliasing all the variables. + # this could be fixed in Python 3 where we have the nonlocal + # keyword or if we switch to bytecode generation overriden_closure_vars = ( func_frame.identifiers.undeclared & func_frame.identifiers.declared & @@ -585,7 +660,7 @@ class CodeGenerator(NodeVisitor): ) if overriden_closure_vars: self.fail('It\'s not possible to set and access variables ' - 'derived from an outer scope! (affects: %s' % + 'derived from an outer scope! (affects: %s)' % ', '.join(sorted(overriden_closure_vars)), node.lineno) # remove variables from a closure from the frame's undeclared @@ -626,6 +701,15 @@ class CodeGenerator(NodeVisitor): # macros are delayed, they never require output checks frame.require_output_check = False args = frame.arguments + # XXX: this is an ugly fix for the loop nesting bug + # (tests.test_old_bugs.test_loop_call_bug). This works around + # a identifier nesting problem we have in general. It's just more + # likely to happen in loops which is why we work around it. The + # real solution would be "nonlocal" all the identifiers that are + # leaking into a new python frame and might be used both unassigned + # and assigned. + if 'loop' in frame.identifiers.declared: + args = args + ['l_loop=l_loop'] self.writeline('def macro(%s):' % ', '.join(args), node) self.indent() self.buffer(frame) @@ -656,7 +740,7 @@ class CodeGenerator(NodeVisitor): """Return a human readable position for the node.""" rv = 'line %d' % node.lineno if self.name is not None: - rv += ' in' + repr(self.name) + rv += ' in ' + repr(self.name) return rv # -- Statement Visitors @@ -666,6 +750,8 @@ class CodeGenerator(NodeVisitor): from jinja2.runtime import __all__ as exported self.writeline('from __future__ import division') self.writeline('from jinja2.runtime import ' + ', '.join(exported)) + if not unoptimize_before_dead_code: + self.writeline('dummy = lambda *x: None') # do we have an extends tag at all? If not, we can save some # overhead by just not processing any inheritance code. @@ -764,8 +850,9 @@ class CodeGenerator(NodeVisitor): self.writeline('if parent_template is None:') self.indent() level += 1 - self.writeline('for event in context.blocks[%r][0](context):' % - node.name, node) + context = node.scoped and 'context.derived(locals())' or 'context' + self.writeline('for event in context.blocks[%r][0](%s):' % ( + node.name, context), node) self.indent() self.simple_write('event', frame) self.outdent(level) @@ -801,7 +888,7 @@ class CodeGenerator(NodeVisitor): self.visit(node.template, frame) self.write(', %r)' % self.name) self.writeline('for name, parent_block in parent_template.' - 'blocks.iteritems():') + 'blocks.%s():' % dict_item_iter) self.indent() self.writeline('context.blocks.setdefault(name, []).' 'append(parent_block)') @@ -819,23 +906,50 @@ class CodeGenerator(NodeVisitor): def visit_Include(self, node, frame): """Handles includes.""" if node.with_context: - self.writeline('template = environment.get_template(', node) - self.visit(node.template, frame) - self.write(', %r)' % self.name) + self.unoptimize_scope(frame) + if node.ignore_missing: + self.writeline('try:') + self.indent() + + func_name = 'get_or_select_template' + if isinstance(node.template, nodes.Const): + if isinstance(node.template.value, basestring): + func_name = 'get_template' + elif isinstance(node.template.value, (tuple, list)): + func_name = 'select_template' + elif isinstance(node.template, (nodes.Tuple, nodes.List)): + func_name = 'select_template' + + self.writeline('template = environment.%s(' % func_name, node) + self.visit(node.template, frame) + self.write(', %r)' % self.name) + if node.ignore_missing: + self.outdent() + self.writeline('except TemplateNotFound:') + self.indent() + self.writeline('pass') + self.outdent() + self.writeline('else:') + self.indent() + + if node.with_context: self.writeline('for event in template.root_render_func(' 'template.new_context(context.parent, True, ' 'locals())):') else: - self.writeline('for event in environment.get_template(', node) - self.visit(node.template, frame) - self.write(', %r).module._body_stream:' % - self.name) + self.writeline('for event in template.module._body_stream:') + self.indent() self.simple_write('event', frame) self.outdent() + if node.ignore_missing: + self.outdent() + def visit_Import(self, node, frame): """Visit regular imports.""" + if node.with_context: + self.unoptimize_scope(frame) self.writeline('l_%s = ' % node.target, node) if frame.toplevel: self.write('context.vars[%r] = ' % node.target) @@ -848,6 +962,7 @@ class CodeGenerator(NodeVisitor): self.write('module') if frame.toplevel and not node.target.startswith('_'): self.writeline('context.exported_vars.discard(%r)' % node.target) + frame.assigned_names.add(node.target) def visit_FromImport(self, node, frame): """Visit named imports.""" @@ -884,6 +999,7 @@ class CodeGenerator(NodeVisitor): var_names.append(alias) if not alias.startswith('_'): discarded_names.append(alias) + frame.assigned_names.add(alias) if var_names: if len(var_names) == 1: @@ -1012,7 +1128,8 @@ class CodeGenerator(NodeVisitor): self.outdent() # reset the aliases if there are any. - self.pop_scope(aliases, loop_frame) + if not node.recursive: + self.pop_scope(aliases, loop_frame) # if the node was recursive we have to return the buffer contents # and start the iteration code @@ -1048,6 +1165,7 @@ class CodeGenerator(NodeVisitor): self.writeline('context.vars[%r] = ' % node.name) self.write('l_%s = ' % node.name) self.macro_def(node, macro_frame) + frame.assigned_names.add(node.name) def visit_CallBlock(self, node, frame): children = node.iter_child_nodes(exclude=('call',)) @@ -1145,7 +1263,7 @@ class CodeGenerator(NodeVisitor): if self.environment.autoescape: self.write('escape(') else: - self.write('unicode(') + self.write('to_string(') if self.environment.finalize is not None: self.write('environment.finalize(') close += 1 @@ -1197,7 +1315,7 @@ class CodeGenerator(NodeVisitor): # names here. if frame.toplevel: assignment_frame = frame.copy() - assignment_frame.assigned_names = set() + assignment_frame.toplevel_assignments = set() else: assignment_frame = frame self.visit(node.target, assignment_frame) @@ -1206,14 +1324,14 @@ class CodeGenerator(NodeVisitor): # make sure toplevel assignments are added to the context. if frame.toplevel: - public_names = [x for x in assignment_frame.assigned_names + public_names = [x for x in assignment_frame.toplevel_assignments if not x.startswith('_')] - if len(assignment_frame.assigned_names) == 1: - name = iter(assignment_frame.assigned_names).next() + if len(assignment_frame.toplevel_assignments) == 1: + name = next(iter(assignment_frame.toplevel_assignments)) self.writeline('context.vars[%r] = l_%s' % (name, name)) else: self.writeline('context.vars.update({') - for idx, name in enumerate(assignment_frame.assigned_names): + for idx, name in enumerate(assignment_frame.toplevel_assignments): if idx: self.write(', ') self.write('%r: l_%s' % (name, name)) @@ -1230,8 +1348,9 @@ class CodeGenerator(NodeVisitor): def visit_Name(self, node, frame): if node.ctx == 'store' and frame.toplevel: - frame.assigned_names.add(node.name) + frame.toplevel_assignments.add(node.name) self.write('l_' + node.name) + frame.assigned_names.add(node.name) def visit_Const(self, node, frame): val = node.value @@ -1441,3 +1560,11 @@ class CodeGenerator(NodeVisitor): def visit_Break(self, node, frame): self.writeline('break', node) + + def visit_Scope(self, node, frame): + scope_frame = frame.inner() + scope_frame.inspect(node.iter_child_nodes()) + aliases = self.push_scope(scope_frame) + self.pull_locals(scope_frame) + self.blockvisit(node.body, scope_frame) + self.pop_scope(aliases, scope_frame)