Compiles nodes into python code.
- :copyright: Copyright 2008 by Armin Ronacher.
- :license: GNU GPL.
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
"""
-import string
-from time import time
-from copy import copy
-from random import randrange
-from keyword import iskeyword
from cStringIO import StringIO
-from itertools import chain, takewhile
+from itertools import chain
+from copy import deepcopy
from jinja2 import nodes
+from jinja2.nodes import EvalContext
from jinja2.visitor import NodeVisitor, NodeTransformer
from jinja2.exceptions import TemplateAssertionError
-from jinja2.utils import Markup, concat
+from jinja2.utils import Markup, concat, escape, is_python_keyword, next
operators = {
else:
have_condexpr = True
-_safe_ident_chars = set(string.letters + '0123456789')
+
+# 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):
+def generate(node, environment, name, filename, stream=None,
+ defer_init=False):
"""Generate the python source for a node tree."""
if not isinstance(node, nodes.Template):
raise TypeError('Can\'t compile non template nodes')
- generator = CodeGenerator(environment, name, filename, stream)
+ generator = CodeGenerator(environment, name, filename, stream, defer_init)
generator.visit(node)
if stream is None:
return generator.stream.getvalue()
-def mask_identifier(ident):
- """Mask an identifier properly for python source code."""
- rv = ['l_']
- for char in ident:
- if char in _safe_ident_chars:
- rv.append(char)
- else:
- rv.append('_%x_' % ord(char))
- return str(''.join(rv))
-
-
-def unmask_identifier(ident):
- """Unmask an identifier."""
- if not ident.startswith('l_'):
- return ident
- rv = []
- i = iter(ident[2:])
- for c in i:
- if c == '_':
- c = unichr(int(concat(takewhile(lambda c: c != '_', i)), 16))
- rv.append(c)
- return ''.join(rv)
-
-
def has_safe_repr(value):
"""Does the node have a safe representation?"""
if value is None or value is NotImplemented or value is Ellipsis:
return False
return name in self.declared
- def find_shadowed(self):
- """Find all the shadowed names."""
- return (self.declared | self.outer_undeclared) & \
- (self.declared_locally | self.declared_parameter)
+ def copy(self):
+ return deepcopy(self)
class Frame(object):
"""Holds compile time information for us."""
- def __init__(self, parent=None):
+ def __init__(self, eval_ctx, parent=None):
+ self.eval_ctx = eval_ctx
self.identifiers = Identifiers()
# a toplevel frame is the root + soft frames such as if conditions.
# situations.
self.rootlevel = False
+ # in some dynamic inheritance situations the compiler needs to add
+ # write tests around output statements.
+ self.require_output_check = parent and parent.require_output_check
+
# inside some tags we are using a buffer rather than yield statements.
# this for example affects {% filter %} or {% macro %}. If a frame
# is buffered this variable points to the name of the list used as
# 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.declared_parameter |
+ parent.assigned_names
)
self.identifiers.outer_undeclared.update(
parent.identifiers.undeclared -
def copy(self):
"""Create a copy of the current one."""
- rv = copy(self)
- rv.identifiers = copy(self.identifiers)
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.identifiers = object.__new__(self.identifiers.__class__)
+ rv.identifiers.__dict__.update(self.identifiers.__dict__)
return rv
def inspect(self, nodes, hard_scope=False):
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)
+ return Frame(self.eval_ctx, self)
def soft(self):
"""Return a soft frame. A soft frame may not be modified as
standalone thing as it shares the resources with the frame it
was created of, but it's not a rootlevel frame any longer.
"""
- rv = copy(self)
+ rv = self.copy()
rv.rootlevel = False
return rv
+ __copy__ = copy
+
class VisitorExit(RuntimeError):
"""Exception used by the `UndeclaredNameVisitor` to signal a stop."""
def visit_Name(self, node):
"""All assignments to names go through this function."""
- if node.ctx in ('store', 'param'):
+ if node.ctx == 'store':
self.identifiers.declared_locally.add(node.name)
+ elif node.ctx == 'param':
+ self.identifiers.declared_parameter.add(node.name)
elif node.ctx == 'load' and not \
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_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_) -
+ real_identifiers.declared)
+
+ # remember those that are declared.
+ real_identifiers.declared_locally.update(body | else_)
+
def visit_Macro(self, node):
- self.generic_visit(node)
self.identifiers.declared_locally.add(node.name)
def visit_Import(self, node):
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."""
class CodeGenerator(NodeVisitor):
- def __init__(self, environment, name, filename, stream=None):
+ def __init__(self, environment, name, filename, stream=None,
+ defer_init=False):
if stream is None:
stream = StringIO()
self.environment = environment
self.name = name
self.filename = filename
self.stream = stream
+ self.created_block_context = False
+ self.defer_init = defer_init
# aliases for imports
self.import_aliases = {}
# the current indentation
self._indentation = 0
+ # -- Various compilation helpers
+
+ def fail(self, msg, lineno):
+ """Fail with a :exc:`TemplateAssertionError`."""
+ raise TemplateAssertionError(msg, lineno, self.name, self.filename)
+
def temporary_identifier(self):
"""Get a new unique identifier."""
self._last_identifier += 1
- return 't%d' % self._last_identifier
+ return 't_%d' % self._last_identifier
+
+ def buffer(self, frame):
+ """Enable buffering for the frame from that point onwards."""
+ frame.buffer = self.temporary_identifier()
+ self.writeline('%s = []' % frame.buffer)
+
+ def return_buffer_contents(self, frame):
+ """Return the buffer contents of the frame."""
+ if frame.eval_ctx.volatile:
+ self.writeline('if context.eval_ctx.autoescape:')
+ self.indent()
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+ self.writeline('return concat(%s)' % frame.buffer)
+ self.outdent()
+ elif frame.eval_ctx.autoescape:
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ else:
+ self.writeline('return concat(%s)' % frame.buffer)
def indent(self):
"""Indent by one."""
"""Outdent by step."""
self._indentation -= step
- def blockvisit(self, nodes, frame, force_generator=True):
+ def start_write(self, frame, node=None):
+ """Yield or write into the frame buffer."""
+ if frame.buffer is None:
+ self.writeline('yield ', node)
+ else:
+ self.writeline('%s.append(' % frame.buffer, node)
+
+ def end_write(self, frame):
+ """End the writing process started by `start_write`."""
+ if frame.buffer is not None:
+ self.write(')')
+
+ def simple_write(self, s, frame, node=None):
+ """Simple shortcut for start_write + write + end_write."""
+ self.start_write(frame, node)
+ self.write(s)
+ self.end_write(frame)
+
+ def blockvisit(self, nodes, frame):
"""Visit a list of nodes as block in a frame. If the current frame
is no buffer a dummy ``if 0: yield None`` is written automatically
unless the force_generator parameter is set to False.
"""
- if frame.buffer is None and force_generator:
+ if frame.buffer is None:
self.writeline('if 0: yield None')
+ else:
+ self.writeline('pass')
try:
for node in nodes:
self.visit(node, frame)
self._write_debug_info = node.lineno
self._last_line = node.lineno
- def signature(self, node, frame, have_comma=True, extra_kwargs=None):
+ def signature(self, node, frame, extra_kwargs=None):
"""Writes a function call to the stream for the current node.
- Per default it will write a leading comma but this can be
- disabled by setting have_comma to False. The extra keyword
+ A leading comma is added automatically. The extra keyword
arguments may not include python keywords otherwise a syntax
error could occour. The extra keyword arguments should be given
as python dict.
"""
- have_comma = have_comma and [True] or []
- def touch_comma():
- if have_comma:
- self.write(', ')
- else:
- have_comma.append(True)
-
# if any of the given keyword arguments is a python keyword
# we have to make sure that no invalid call is created.
kwarg_workaround = False
for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
- if iskeyword(kwarg):
+ if is_python_keyword(kwarg):
kwarg_workaround = True
break
for arg in node.args:
- touch_comma()
+ self.write(', ')
self.visit(arg, frame)
if not kwarg_workaround:
for kwarg in node.kwargs:
- touch_comma()
+ self.write(', ')
self.visit(kwarg, frame)
if extra_kwargs is not None:
for key, value in extra_kwargs.iteritems():
- touch_comma()
- self.write('%s=%s' % (key, value))
+ self.write(', %s=%s' % (key, value))
if node.dyn_args:
- touch_comma()
- self.write('*')
+ self.write(', *')
self.visit(node.dyn_args, frame)
if kwarg_workaround:
- touch_comma()
if node.dyn_kwargs is not None:
- self.write('**dict({')
+ self.write(', **dict({')
else:
- self.write('**{')
+ self.write(', **{')
for kwarg in node.kwargs:
self.write('%r: ' % kwarg.key)
self.visit(kwarg.value, frame)
self.write('}')
elif node.dyn_kwargs is not None:
- touch_comma()
- self.write('**')
+ self.write(', **')
self.visit(node.dyn_kwargs, frame)
def pull_locals(self, frame):
"""Pull all the references identifiers into the local scope."""
for name in frame.identifiers.undeclared:
- self.writeline('%s = context.resolve(%r)' % (mask_identifier(name),
- name))
+ self.writeline('l_%s = context.resolve(%r)' % (name, name))
def pull_dependencies(self, nodes):
"""Pull all the dependencies."""
self.writeline('%s = environment.%s[%r]' %
(mapping[name], dependency, name))
- def collect_shadowed(self, frame):
+ 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
into the current scope. No indentation takes place.
+
+ 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 `Frame.find_shadowed`.
"""
- # make sure we "backup" overridden, local identifiers
- # TODO: we should probably optimize this and check if the
- # identifier is in use afterwards.
aliases = {}
- for name in frame.identifiers.find_shadowed():
+ for name in frame.find_shadowed(extra_vars):
aliases[name] = ident = self.temporary_identifier()
- self.writeline('%s = %s' % (ident, mask_identifier(name)))
+ self.writeline('%s = l_%s' % (ident, name))
+ to_declare = set()
+ for name in frame.identifiers.declared_locally:
+ if name not in aliases:
+ to_declare.add('l_' + name)
+ if to_declare:
+ self.writeline(' = '.join(to_declare) + ' = missing')
return aliases
- def function_scoping(self, node, frame, children=None):
+ def pop_scope(self, aliases, frame):
+ """Restore all aliases and delete unused variables."""
+ for name, alias in aliases.iteritems():
+ self.writeline('l_%s = %s' % (name, alias))
+ to_delete = set()
+ for name in frame.identifiers.declared_locally:
+ if name not in aliases:
+ to_delete.add('l_' + name)
+ if 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):
"""In Jinja a few statements require the help of anonymous
functions. Those are currently macros and call blocks and in
the future also recursive loops. As there is currently
# 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 &
func_frame.identifiers.declared_parameter)
)
if overriden_closure_vars:
- vars = ', '.join(sorted(overriden_closure_vars))
- raise TemplateAssertionError('It\'s not possible to set and '
- 'access variables derived from '
- 'an outer scope! (affects: %s' %
- vars, node.lineno, self.name)
+ self.fail('It\'s not possible to set and access variables '
+ 'derived from an outer scope! (affects: %s)' %
+ ', '.join(sorted(overriden_closure_vars)), node.lineno)
# remove variables from a closure from the frame's undeclared
# identifiers.
func_frame.identifiers.declared
)
+ # no special variables for this scope, abort early
+ if not find_special:
+ return func_frame
+
func_frame.accesses_kwargs = False
func_frame.accesses_varargs = False
func_frame.accesses_caller = False
- func_frame.arguments = args = [mask_identifier(x.name)
- for x in node.args]
+ func_frame.arguments = args = ['l_' + x.name for x in node.args]
undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs'))
args.append('l_varargs')
return func_frame
- # -- Visitors
+ def macro_body(self, node, frame, children=None):
+ """Dump the function def of a macro or call block."""
+ frame = self.function_scoping(node, frame, children)
+ # 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)
+ self.pull_locals(frame)
+ self.blockvisit(node.body, frame)
+ self.return_buffer_contents(frame)
+ self.outdent()
+ return frame
+
+ def macro_def(self, node, frame):
+ """Dump the macro definition for the def created by macro_body."""
+ arg_tuple = ', '.join(repr(x.name) for x in node.args)
+ name = getattr(node, 'name', None)
+ if len(node.args) == 1:
+ arg_tuple += ','
+ self.write('Macro(environment, macro, %r, (%s), (' %
+ (name, arg_tuple))
+ for arg in node.defaults:
+ self.visit(arg, frame)
+ self.write(', ')
+ self.write('), %r, %r, %r)' % (
+ bool(frame.accesses_kwargs),
+ bool(frame.accesses_varargs),
+ bool(frame.accesses_caller)
+ ))
+
+ def position(self, node):
+ """Return a human readable position for the node."""
+ rv = 'line %d' % node.lineno
+ if self.name is not None:
+ rv += ' in ' + repr(self.name)
+ return rv
+
+ # -- Statement Visitors
def visit_Template(self, node, frame=None):
assert frame is None, 'no root frame allowed'
+ eval_ctx = EvalContext(self.environment, self.name)
+
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')
+
+ # if we want a deferred initialization we cannot move the
+ # environment into a local name
+ envenv = not self.defer_init and ', environment=environment' or ''
# do we have an extends tag at all? If not, we can save some
# overhead by just not processing any inheritance code.
# find all blocks
for block in node.find_all(nodes.Block):
if block.name in self.blocks:
- raise TemplateAssertionError('block %r defined twice' %
- block.name, block.lineno,
- self.name)
+ self.fail('block %r defined twice' % block.name, block.lineno)
self.blocks[block.name] = block
# find all imports and import them
self.writeline('name = %r' % self.name)
# generate the root render function.
- self.writeline('def root(context, environment=environment):', extra=1)
+ self.writeline('def root(context%s):' % envenv, extra=1)
# process the root
- frame = Frame()
+ frame = Frame(eval_ctx)
frame.inspect(node.body)
frame.toplevel = frame.rootlevel = True
+ frame.require_output_check = have_extends and not self.has_known_extends
self.indent()
if have_extends:
self.writeline('parent_template = None')
- self.pull_locals(frame)
- self.pull_dependencies(node.body)
if 'self' in find_undeclared(node.body, ('self',)):
frame.identifiers.add_special('self')
self.writeline('l_self = TemplateReference(context)')
+ self.pull_locals(frame)
+ self.pull_dependencies(node.body)
self.blockvisit(node.body, frame)
self.outdent()
# at this point we now have the blocks collected and can visit them too.
for name, block in self.blocks.iteritems():
- block_frame = Frame()
+ block_frame = Frame(eval_ctx)
block_frame.inspect(block.body)
block_frame.block = name
- self.writeline('def block_%s(context, environment=environment):'
- % name, block, 1)
+ self.writeline('def block_%s(context%s):' % (name, envenv),
+ block, 1)
self.indent()
undeclared = find_undeclared(block.body, ('self', 'super'))
if 'self' in undeclared:
self.writeline('if parent_template is None:')
self.indent()
level += 1
- self.writeline('for event in context.blocks[%r][-1](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()
- if frame.buffer is None:
- self.writeline('yield event')
- else:
- self.writeline('%s.append(event)' % frame.buffer)
+ self.simple_write('event', frame)
self.outdent(level)
def visit_Extends(self, node, frame):
"""Calls the extender."""
if not frame.toplevel:
- raise TemplateAssertionError('cannot use extend from a non '
- 'top-level scope', node.lineno,
- self.name)
+ self.fail('cannot use extend from a non top-level scope',
+ node.lineno)
# if the number of extends statements in general is zero so
# far, we don't have to add a check if something extended
self.indent()
self.writeline('raise TemplateRuntimeError(%r)' %
'extended multiple times')
+ self.outdent()
# if we have a known extends already we don't need that code here
# as we know that the template execution will end here.
if self.has_known_extends:
raise CompilerExit()
- self.outdent()
- self.writeline('parent_template = environment.get_template(', node, 1)
+ self.writeline('parent_template = environment.get_template(', node)
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, []).'
- 'insert(0, parent_block)')
+ 'append(parent_block)')
self.outdent()
# if this extends statement was in the root level we can take
# advantage of that information and simplify the generated code
# in the top level from this point onwards
- self.has_known_extends = True
+ if frame.rootlevel:
+ self.has_known_extends = True
# and now we have one more
self.extends_so_far += 1
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)):')
+ '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._TemplateModule__body_stream:' %
- self.name)
+ self.writeline('for event in template.module._body_stream:')
+
self.indent()
- if frame.buffer is None:
- self.writeline('yield event')
- else:
- self.writeline('%s.append(event)' % frame.buffer)
+ self.simple_write('event', frame)
self.outdent()
+ if node.ignore_missing:
+ self.outdent()
+
def visit_Import(self, node, frame):
"""Visit regular imports."""
- self.writeline(mask_identifier(node.target) + ' = ', node)
+ 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)
self.write('environment.get_template(')
self.visit(node.template, frame)
self.write(', %r).' % self.name)
if node.with_context:
- self.write('make_module(context.parent, True)')
+ self.write('make_module(context.parent, True, locals())')
else:
self.write('module')
- if frame.toplevel and not node.target.startswith('__'):
+ 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."""
self.write('make_module(context.parent, True)')
else:
self.write('module')
+
+ var_names = []
+ discarded_names = []
for name in node.names:
if isinstance(name, tuple):
name, alias = name
else:
alias = name
- self.writeline('%s = getattr(included_template, '
- '%r, missing)' % (mask_identifier(alias), name))
- self.writeline('if %s is missing:' % mask_identifier(alias))
+ self.writeline('l_%s = getattr(included_template, '
+ '%r, missing)' % (alias, name))
+ self.writeline('if l_%s is missing:' % alias)
self.indent()
- self.writeline('%s = environment.undefined(%r %% '
- 'included_template.name, '
- 'name=included_template.name)' %
- (mask_identifier(alias), 'the template %r does '
- 'not export the requested name ' + repr(name)))
+ self.writeline('l_%s = environment.undefined(%r %% '
+ 'included_template.__name__, '
+ 'name=%r)' %
+ (alias, 'the template %%r (imported on %s) does '
+ 'not export the requested name %s' % (
+ self.position(node),
+ repr(name)
+ ), name))
self.outdent()
if frame.toplevel:
- self.writeline('context.vars[%r] = %s' %
- (alias, mask_identifier(alias)))
- if not alias.startswith('__'):
- self.writeline('context.exported_vars.discard(%r)' % alias)
+ 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:
+ name = var_names[0]
+ self.writeline('context.vars[%r] = l_%s' % (name, name))
+ else:
+ self.writeline('context.vars.update({%s})' % ', '.join(
+ '%r: l_%s' % (name, name) for name in var_names
+ ))
+ if discarded_names:
+ if len(discarded_names) == 1:
+ self.writeline('context.exported_vars.discard(%r)' %
+ discarded_names[0])
+ else:
+ self.writeline('context.exported_vars.difference_'
+ 'update((%s))' % ', '.join(map(repr, discarded_names)))
def visit_For(self, node, frame):
- loop_frame = frame.inner()
- loop_frame.inspect(node.iter_child_nodes(exclude=('iter',)))
- extended_loop = bool(node.else_) or \
- 'loop' in loop_frame.identifiers.undeclared
+ # when calculating the nodes for the inner frame we have to exclude
+ # the iterator contents from it
+ children = node.iter_child_nodes(exclude=('iter',))
+ if node.recursive:
+ loop_frame = self.function_scoping(node, frame, children,
+ find_special=False)
+ else:
+ loop_frame = frame.inner()
+ loop_frame.inspect(children)
+
+ # try to figure out if we have an extended loop. An extended loop
+ # is necessary if the loop is in recursive mode if the special loop
+ # variable is accessed in the body.
+ extended_loop = node.recursive or 'loop' in \
+ find_undeclared(node.iter_child_nodes(
+ only=('body',)), ('loop',))
+
+ # if we don't have an recursive loop we have to find the shadowed
+ # variables at that point. Because loops can be nested but the loop
+ # variable is a special one we have to enforce aliasing for it.
+ if not node.recursive:
+ aliases = self.push_scope(loop_frame, ('loop',))
+
+ # otherwise we set up a buffer and add a function def
+ else:
+ self.writeline('def loop(reciter, loop_render_func):', node)
+ self.indent()
+ self.buffer(loop_frame)
+ aliases = {}
+
+ # make sure the loop variable is a special one and raise a template
+ # assertion error if a loop tries to write to loop
if extended_loop:
loop_frame.identifiers.add_special('loop')
+ for name in node.find_all(nodes.Name):
+ if name.ctx == 'store' and name.name == 'loop':
+ self.fail('Can\'t assign to special loop variable '
+ 'in for-loop target', name.lineno)
- aliases = self.collect_shadowed(loop_frame)
self.pull_locals(loop_frame)
if node.else_:
- self.writeline('l_loop = None')
-
- self.newline(node)
- self.writeline('for ')
+ iteration_indicator = self.temporary_identifier()
+ self.writeline('%s = 1' % iteration_indicator)
+
+ # Create a fake parent loop if the else or test section of a
+ # loop is accessing the special loop variable and no parent loop
+ # exists.
+ if 'loop' not in aliases and 'loop' in find_undeclared(
+ node.iter_child_nodes(only=('else_', 'test')), ('loop',)):
+ self.writeline("l_loop = environment.undefined(%r, name='loop')" %
+ ("'loop' is undefined. the filter section of a loop as well "
+ "as the else block doesn't have access to the special 'loop'"
+ " variable of the current loop. Because there is no parent "
+ "loop it's undefined. Happened in loop on %s" %
+ self.position(node)))
+
+ self.writeline('for ', node)
self.visit(node.target, loop_frame)
self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
- # the expression pointing to the parent loop. We make the
- # undefined a bit more debug friendly at the same time.
- parent_loop = 'loop' in aliases and aliases['loop'] \
- or "environment.undefined(%r, name='loop')" % "'loop' " \
- 'is undefined. "the filter section of a loop as well ' \
- 'as the else block doesn\'t have access to the ' \
- "special 'loop' variable of the current loop. " \
- "Because there is no parent loop it's undefined."
-
# if we have an extened loop and a node test, we filter in the
# "outer frame".
if extended_loop and node.test is not None:
self.write(' for ')
self.visit(node.target, loop_frame)
self.write(' in ')
- self.visit(node.iter, loop_frame)
+ if node.recursive:
+ self.write('reciter')
+ else:
+ self.visit(node.iter, loop_frame)
self.write(' if (')
test_frame = loop_frame.copy()
- self.writeline('l_loop = ' + parent_loop)
self.visit(node.test, test_frame)
self.write('))')
+ elif node.recursive:
+ self.write('reciter')
else:
self.visit(node.iter, loop_frame)
- self.write(extended_loop and '):' or ':')
+ if node.recursive:
+ self.write(', recurse=loop_render_func):')
+ else:
+ self.write(extended_loop and '):' or ':')
# tests in not extended loops become a continue
if not extended_loop and node.test is not None:
self.outdent(2)
self.indent()
- self.blockvisit(node.body, loop_frame, force_generator=True)
+ self.blockvisit(node.body, loop_frame)
+ if node.else_:
+ self.writeline('%s = 0' % iteration_indicator)
self.outdent()
if node.else_:
- self.writeline('if l_loop is None:')
+ self.writeline('if %s:' % iteration_indicator)
self.indent()
- self.writeline('l_loop = ' + parent_loop)
- self.blockvisit(node.else_, loop_frame, force_generator=False)
+ self.blockvisit(node.else_, loop_frame)
self.outdent()
# reset the aliases if there are any.
- for name, alias in aliases.iteritems():
- self.writeline('%s = %s' % (mask_identifier(name), alias))
+ 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
+ if node.recursive:
+ self.return_buffer_contents(loop_frame)
+ self.outdent()
+ self.start_write(frame, node)
+ self.write('loop(')
+ self.visit(node.iter, frame)
+ self.write(', loop)')
+ self.end_write(frame)
def visit_If(self, node, frame):
if_frame = frame.soft()
self.outdent()
def visit_Macro(self, node, frame):
- macro_frame = self.function_scoping(node, frame)
- args = macro_frame.arguments
- self.writeline('def macro(%s):' % ', '.join(args), node)
- macro_frame.buffer = buf = self.temporary_identifier()
- self.indent()
- self.pull_locals(macro_frame)
- self.writeline('%s = []' % buf)
- self.blockvisit(node.body, macro_frame)
- if self.environment.autoescape:
- self.writeline('return Markup(concat(%s))' % buf)
- else:
- self.writeline("return concat(%s)" % buf)
- self.outdent()
+ macro_frame = self.macro_body(node, frame)
self.newline()
if frame.toplevel:
- if not node.name.startswith('__'):
+ if not node.name.startswith('_'):
self.write('context.exported_vars.add(%r)' % node.name)
self.writeline('context.vars[%r] = ' % node.name)
- arg_tuple = ', '.join(repr(x.name) for x in node.args)
- if len(node.args) == 1:
- arg_tuple += ','
- self.write('%s = Macro(environment, macro, %r, (%s), (' %
- (mask_identifier(node.name), node.name, arg_tuple))
- for arg in node.defaults:
- self.visit(arg, macro_frame)
- self.write(', ')
- self.write('), %s, %s, %s)' % (
- macro_frame.accesses_kwargs and '1' or '0',
- macro_frame.accesses_varargs and '1' or '0',
- macro_frame.accesses_caller and '1' or '0'
- ))
+ self.write('l_%s = ' % node.name)
+ self.macro_def(node, macro_frame)
+ frame.assigned_names.add(node.name)
def visit_CallBlock(self, node, frame):
- call_frame = self.function_scoping(node, frame, node.iter_child_nodes
- (exclude=('call',)))
- args = call_frame.arguments
- self.writeline('def call(%s):' % ', '.join(args), node)
- call_frame.buffer = buf = self.temporary_identifier()
- self.indent()
- self.pull_locals(call_frame)
- self.writeline('%s = []' % buf)
- self.blockvisit(node.body, call_frame)
- if self.environment.autoescape:
- self.writeline("return Markup(concat(%s))" % buf)
- else:
- self.writeline('return concat(%s)' % buf)
- self.outdent()
- arg_tuple = ', '.join(repr(x.name) for x in node.args)
- if len(node.args) == 1:
- arg_tuple += ','
- self.writeline('caller = Macro(environment, call, None, (%s), (' %
- arg_tuple)
- for arg in node.defaults:
- self.visit(arg, call_frame)
- self.write(', ')
- self.write('), %s, %s, 0)' % (
- call_frame.accesses_kwargs and '1' or '0',
- call_frame.accesses_varargs and '1' or '0'
- ))
- if frame.buffer is None:
- self.writeline('yield ', node)
- else:
- self.writeline('%s.append(' % frame.buffer, node)
- self.visit_Call(node.call, call_frame,
- extra_kwargs={'caller': 'caller'})
- if frame.buffer is not None:
- self.write(')')
+ children = node.iter_child_nodes(exclude=('call',))
+ call_frame = self.macro_body(node, frame, children)
+ self.writeline('caller = ')
+ self.macro_def(node, call_frame)
+ self.start_write(frame, node)
+ self.visit_Call(node.call, call_frame, forward_caller=True)
+ self.end_write(frame)
def visit_FilterBlock(self, node, frame):
filter_frame = frame.inner()
filter_frame.inspect(node.iter_child_nodes())
-
- aliases = self.collect_shadowed(filter_frame)
+ aliases = self.push_scope(filter_frame)
self.pull_locals(filter_frame)
- filter_frame.buffer = buf = self.temporary_identifier()
-
- self.writeline('%s = []' % buf, node)
- for child in node.body:
- self.visit(child, filter_frame)
-
- if frame.buffer is None:
- self.writeline('yield ', node)
- else:
- self.writeline('%s.append(' % frame.buffer, node)
- self.visit_Filter(node.filter, filter_frame, 'concat(%s)' % buf)
- if frame.buffer is not None:
- self.write(')')
+ self.buffer(filter_frame)
+ self.blockvisit(node.body, filter_frame)
+ self.start_write(frame, node)
+ self.visit_Filter(node.filter, filter_frame)
+ self.end_write(frame)
+ self.pop_scope(aliases, filter_frame)
def visit_ExprStmt(self, node, frame):
self.newline(node)
def visit_Output(self, node, frame):
# if we have a known extends statement, we don't output anything
- if self.has_known_extends and frame.toplevel:
+ # if we are in a require_output_check section
+ if self.has_known_extends and frame.require_output_check:
return
- self.newline(node)
+ if self.environment.finalize:
+ finalize = lambda x: unicode(self.environment.finalize(x))
+ else:
+ finalize = unicode
- # if we are in the toplevel scope and there was already an extends
- # statement we have to add a check that disables our yield(s) here
- # so that they don't appear in the output.
+ # if we are inside a frame that requires output checking, we do so
outdent_later = False
- if frame.toplevel and self.extends_so_far != 0:
+ if frame.require_output_check:
self.writeline('if parent_template is None:')
self.indent()
outdent_later = True
body = []
for child in node.nodes:
try:
- const = unicode(child.as_const())
+ const = child.as_const(frame.eval_ctx)
+ except nodes.Impossible:
+ body.append(child)
+ continue
+ # the frame can't be volatile here, becaus otherwise the
+ # as_const() function would raise an Impossible exception
+ # at that point.
+ try:
+ if frame.eval_ctx.autoescape:
+ if hasattr(const, '__html__'):
+ const = const.__html__()
+ else:
+ const = escape(const)
+ const = finalize(const)
except:
+ # if something goes wrong here we evaluate the node
+ # at runtime for easier debugging
body.append(child)
continue
if body and isinstance(body[-1], list):
else:
body.append([const])
- # if we have less than 3 nodes or less than 6 and a buffer we
- # yield or extend
- if len(body) < 3 or (frame.buffer is not None and len(body) < 6):
+ # if we have less than 3 nodes or a buffer we yield or extend/append
+ if len(body) < 3 or frame.buffer is not None:
if frame.buffer is not None:
- self.writeline('%s.extend((' % frame.buffer)
+ # for one item we append, for more we extend
+ if len(body) == 1:
+ self.writeline('%s.append(' % frame.buffer)
+ else:
+ self.writeline('%s.extend((' % frame.buffer)
+ self.indent()
for item in body:
if isinstance(item, list):
val = repr(concat(item))
if frame.buffer is None:
self.writeline('yield ' + val)
else:
- self.write(val + ', ')
+ self.writeline(val + ', ')
else:
if frame.buffer is None:
- self.writeline('yield ')
+ self.writeline('yield ', item)
+ else:
+ self.newline(item)
close = 1
- if self.environment.autoescape:
+ if frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' escape or to_string)(')
+ elif frame.eval_ctx.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
if frame.buffer is not None:
self.write(', ')
if frame.buffer is not None:
- self.write('))')
+ # close the open parentheses
+ self.outdent()
+ self.writeline(len(body) == 1 and ')' or '))')
# otherwise we create a format string as this is faster in that case
else:
else:
format.append('%s')
arguments.append(item)
- if frame.buffer is None:
- self.writeline('yield ')
- else:
- self.writeline('%s.append(' % frame.buffer)
+ self.writeline('yield ')
self.write(repr(concat(format)) + ' % (')
idx = -1
self.indent()
for argument in arguments:
self.newline(argument)
close = 0
- if self.environment.autoescape:
+ if frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' escape or to_string)(')
+ close += 1
+ elif frame.eval_ctx.autoescape:
self.write('escape(')
close += 1
if self.environment.finalize is not None:
self.write('environment.finalize(')
close += 1
self.visit(argument, frame)
- self.write(')' * close + ',')
+ self.write(')' * close + ', ')
self.outdent()
self.writeline(')')
- if frame.buffer is not None:
- self.write(')')
if outdent_later:
self.outdent()
# 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)
# make sure toplevel assignments are added to the context.
if frame.toplevel:
- for name in assignment_frame.assigned_names:
- self.writeline('context.vars[%r] = %s' %
- (name, mask_identifier(name)))
- if not name.startswith('__'):
- self.writeline('context.exported_vars.add(%r)' % name)
+ public_names = [x for x in assignment_frame.toplevel_assignments
+ if not x.startswith('_')]
+ 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.toplevel_assignments):
+ if idx:
+ self.write(', ')
+ self.write('%r: l_%s' % (name, name))
+ self.write('})')
+ if public_names:
+ if len(public_names) == 1:
+ self.writeline('context.exported_vars.add(%r)' %
+ public_names[0])
+ else:
+ self.writeline('context.exported_vars.update((%s))' %
+ ', '.join(map(repr, public_names)))
+
+ # -- Expression Visitors
def visit_Name(self, node, frame):
if node.ctx == 'store' and frame.toplevel:
- frame.assigned_names.add(node.name)
- self.write(mask_identifier(node.name))
-
- def visit_MarkSafe(self, node, frame):
- self.write('Markup(')
- self.visit(node.expr, frame)
- self.write(')')
-
- def visit_EnvironmentAttribute(self, node, frame):
- self.write('environment.' + node.name)
-
- def visit_ExtensionAttribute(self, node, frame):
- self.write('environment.extensions[%r].%s' % (node.identifier, node.attr))
-
- def visit_ImportedName(self, node, frame):
- self.write(self.import_aliases[node.importname])
-
- def visit_InternalName(self, node, frame):
- self.write(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
if isinstance(val, float):
- # XXX: add checks for infinity and nan
self.write(str(val))
else:
self.write(repr(val))
+ def visit_TemplateData(self, node, frame):
+ try:
+ self.write(repr(node.as_const(frame.eval_ctx)))
+ except nodes.Impossible:
+ self.write('(context.eval_ctx.autoescape and Markup or identity)(%r)'
+ % node.data)
+
def visit_Tuple(self, node, frame):
self.write('(')
idx = -1
del binop, uaop
def visit_Concat(self, node, frame):
- self.write('%s((' % self.environment.autoescape and
- 'markup_join' or 'unicode_join')
+ if frame.eval_ctx.volatile:
+ func_name = '(context.eval_ctx.volatile and' \
+ ' markup_join or unicode_join)'
+ elif frame.eval_ctx.autoescape:
+ func_name = 'markup_join'
+ else:
+ func_name = 'unicode_join'
+ self.write('%s((' % func_name)
for arg in node.nodes:
self.visit(arg, frame)
self.write(', ')
self.write(' %s ' % operators[node.op])
self.visit(node.expr, frame)
- def visit_Subscript(self, node, frame):
+ def visit_Getattr(self, node, frame):
+ self.write('environment.getattr(')
+ self.visit(node.node, frame)
+ self.write(', %r)' % node.attr)
+
+ def visit_Getitem(self, node, frame):
+ # slices bypass the environment getitem method.
if isinstance(node.arg, nodes.Slice):
self.visit(node.node, frame)
self.write('[')
self.visit(node.arg, frame)
self.write(']')
- return
- try:
- const = node.arg.as_const()
- have_const = True
- except nodes.Impossible:
- have_const = False
- self.write('environment.subscribe(')
- self.visit(node.node, frame)
- self.write(', ')
- if have_const:
- self.write(repr(const))
else:
+ self.write('environment.getitem(')
+ self.visit(node.node, frame)
+ self.write(', ')
self.visit(node.arg, frame)
- self.write(')')
+ self.write(')')
def visit_Slice(self, node, frame):
if node.start is not None:
self.write(':')
self.visit(node.step, frame)
- def visit_Filter(self, node, frame, initial=None):
+ def visit_Filter(self, node, frame):
self.write(self.filters[node.name] + '(')
func = self.environment.filters.get(node.name)
if func is None:
- raise TemplateAssertionError('no filter named %r' % node.name,
- node.lineno, self.filename)
+ self.fail('no filter named %r' % node.name, node.lineno)
if getattr(func, 'contextfilter', False):
self.write('context, ')
+ elif getattr(func, 'evalcontextfilter', False):
+ self.write('context.eval_ctx, ')
elif getattr(func, 'environmentfilter', False):
self.write('environment, ')
- if isinstance(node.node, nodes.Filter):
- self.visit_Filter(node.node, frame, initial)
- elif node.node is None:
- self.write(initial)
- else:
+
+ # if the filter node is None we are inside a filter block
+ # and want to write to the current buffer
+ if node.node is not None:
self.visit(node.node, frame)
+ elif frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' Markup(concat(%s)) or concat(%s))' %
+ (frame.buffer, frame.buffer))
+ elif frame.eval_ctx.autoescape:
+ self.write('Markup(concat(%s))' % frame.buffer)
+ else:
+ self.write('concat(%s)' % frame.buffer)
self.signature(node, frame)
self.write(')')
def visit_Test(self, node, frame):
self.write(self.tests[node.name] + '(')
if node.name not in self.environment.tests:
- raise TemplateAssertionError('no test named %r' % node.name,
- node.lineno, self.filename)
+ self.fail('no test named %r' % node.name, node.lineno)
self.visit(node.node, frame)
self.signature(node, frame)
self.write(')')
def visit_CondExpr(self, node, frame):
+ def write_expr2():
+ if node.expr2 is not None:
+ return self.visit(node.expr2, frame)
+ self.write('environment.undefined(%r)' % ('the inline if-'
+ 'expression on %s evaluated to false and '
+ 'no else section was defined.' % self.position(node)))
+
if not have_condexpr:
self.write('((')
self.visit(node.test, frame)
self.write(') and (')
self.visit(node.expr1, frame)
self.write(',) or (')
- self.visit(node.expr2, frame)
+ write_expr2()
self.write(',))[0]')
else:
self.write('(')
self.write(' if ')
self.visit(node.test, frame)
self.write(' else ')
- self.visit(node.expr2, frame)
+ write_expr2()
self.write(')')
- def visit_Call(self, node, frame, extra_kwargs=None):
+ def visit_Call(self, node, frame, forward_caller=False):
if self.environment.sandboxed:
- self.write('environment.call(')
+ self.write('environment.call(context, ')
+ else:
+ self.write('context.call(')
self.visit(node.node, frame)
- self.write(self.environment.sandboxed and ', ' or '(')
- self.signature(node, frame, False, extra_kwargs)
+ extra_kwargs = forward_caller and {'caller': 'caller'} or None
+ self.signature(node, frame, extra_kwargs)
self.write(')')
def visit_Keyword(self, node, frame):
self.write(node.key + '=')
self.visit(node.value, frame)
+
+ # -- Unused nodes for extensions
+
+ def visit_MarkSafe(self, node, frame):
+ self.write('Markup(')
+ self.visit(node.expr, frame)
+ self.write(')')
+
+ def visit_MarkSafeIfAutoescape(self, node, frame):
+ self.write('(context.eval_ctx.autoescape and Markup or identity)(')
+ self.visit(node.expr, frame)
+ self.write(')')
+
+ def visit_EnvironmentAttribute(self, node, frame):
+ self.write('environment.' + node.name)
+
+ def visit_ExtensionAttribute(self, node, frame):
+ self.write('environment.extensions[%r].%s' % (node.identifier, node.name))
+
+ def visit_ImportedName(self, node, frame):
+ self.write(self.import_aliases[node.importname])
+
+ def visit_InternalName(self, node, frame):
+ self.write(node.name)
+
+ def visit_ContextReference(self, node, frame):
+ self.write('context')
+
+ def visit_Continue(self, node, frame):
+ self.writeline('continue', node)
+
+ 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)
+
+ def visit_EvalContextModifier(self, node, frame):
+ for keyword in node.options:
+ self.writeline('context.eval_ctx.%s = ' % keyword.key)
+ self.visit(keyword.value, frame)
+ try:
+ val = keyword.value.as_const(frame.eval_ctx)
+ except nodes.Impossible:
+ frame.eval_ctx.volatile = True
+ else:
+ setattr(frame.eval_ctx, keyword.key, val)
+
+ def visit_ScopedEvalContextModifier(self, node, frame):
+ old_ctx_name = self.temporary_identifier()
+ safed_ctx = frame.eval_ctx.save()
+ self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
+ self.visit_EvalContextModifier(node, frame)
+ for child in node.body:
+ self.visit(child, frame)
+ frame.eval_ctx.revert(safed_ctx)
+ self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)