With number of course. Jinja2.pdf not Jinja.pdf
[jinja2.git] / jinja2 / compiler.py
index 9f99045bbac37756198b80918b75114cbc8573e7..d0aadad521dd8396f4446e1373937ae3cdc73436 100644 (file)
@@ -5,19 +5,17 @@
 
     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.
 """
-from time import time
-from copy import copy
-from random import randrange
-from keyword import iskeyword
 from cStringIO import StringIO
 from itertools import chain
+from copy import deepcopy
 from jinja2 import nodes
-from jinja2.visitor import NodeVisitor, NodeTransformer
+from jinja2.nodes import EvalContext
+from jinja2.visitor import NodeVisitor
 from jinja2.exceptions import TemplateAssertionError
-from jinja2.utils import Markup, concat
+from jinja2.utils import Markup, concat, escape, is_python_keyword, next
 
 
 operators = {
@@ -39,12 +37,29 @@ else:
     have_condexpr = True
 
 
-def generate(node, environment, name, filename, stream=None):
+# 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,
+             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')
-    node.freeze()
-    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()
@@ -107,9 +122,6 @@ class Identifiers(object):
         # names that are declared by parameters
         self.declared_parameter = set()
 
-        # static subscribes
-        self.static_subscribes = {}
-
     def add_special(self, name):
         """Register a special name like `loop`."""
         self.undeclared.discard(name)
@@ -123,16 +135,15 @@ class Identifiers(object):
             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.
@@ -143,6 +154,10 @@ class Frame(object):
         # 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
@@ -152,8 +167,8 @@ class Frame(object):
         # the name of the block we're in, otherwise None.
         self.block = parent and parent.block or None
 
-        # node overlays.  see `CodeGenerator.overlay` for more details
-        self.overlays = {}
+        # a set of actually assigned names
+        self.assigned_names = set()
 
         # the parent of this frame
         self.parent = parent
@@ -161,9 +176,8 @@ class Frame(object):
         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 -
@@ -173,8 +187,10 @@ class Frame(object):
 
     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):
@@ -186,19 +202,30 @@ 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)
+        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."""
@@ -262,26 +289,36 @@ class FrameIdentifierVisitor(NodeVisitor):
              self.identifiers.is_declared(node.name, self.hard_scope):
             self.identifiers.undeclared.add(node.name)
 
-    def visit_Subscript(self, node):
-        """Under some circumstances subscripts are aliased with local names:
-
-        - the subscript node is either already aliased or a name that was not
-          reassigned.
-        - and the subscription argument is a constant string.
-        """
-        self.generic_visit(node)
-        if isinstance(node.arg, nodes.Const) and \
-           isinstance(node.arg.value, basestring) and \
-           ((isinstance(node.node, nodes.Name) and
-            # this code ignores parameter declared names as the may only
-            # occour at the very beginning of a scope and we pull the
-            # attributes afterwards.
-            node.node.name not in (self.identifiers.declared_locally)) or
-            node.node in self.identifiers.static_subscribes):
-            if node in self.identifiers.static_subscribes:
-                self.identifiers.static_subscribes[node] += 1
-            else:
-                self.identifiers.static_subscribes[node] = 1
+    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.identifiers.declared_locally.add(node.name)
@@ -310,12 +347,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."""
 
@@ -329,13 +368,16 @@ class CompilerExit(Exception):
 
 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 = {}
@@ -379,10 +421,37 @@ class CodeGenerator(NodeVisitor):
         # 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."""
@@ -392,13 +461,33 @@ class CodeGenerator(NodeVisitor):
         """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)
@@ -432,52 +521,41 @@ class CodeGenerator(NodeVisitor):
             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)
@@ -493,51 +571,14 @@ class CodeGenerator(NodeVisitor):
                 self.write('}')
 
         elif node.dyn_kwargs is not None:
-            touch_comma()
-            self.write('**')
+            self.write(', **')
             self.visit(node.dyn_kwargs, frame)
 
-    def overlay(self, node, frame):
-        """Visit an overlay and return `True` or `False` if the overlay
-        does not exist or is exhausted.  An overlay is used to replace a
-        node with another one (or an identifier) N times.  This is for
-        example used to replace static subscribes before reassignments
-        of a name that occour more than one time.  If a node has overlays
-        it's important that this method is called, otherwise the count
-        will be out of sync and the generated code is broken.
-        """
-        if node not in frame.overlays:
-            return False
-        overlay, count = frame.overlays[node]
-        if count is not None and count <= 0:
-            return False
-        if isinstance(overlay, basestring):
-            self.write(overlay)
-        else:
-            self.visit(overlay, frame)
-        frame.overlays[node] = (overlay, count - 1)
-        return True
-
     def pull_locals(self, frame):
         """Pull all the references identifiers into the local scope."""
         for name in frame.identifiers.undeclared:
             self.writeline('l_%s = context.resolve(%r)' % (name, name))
 
-        # find all the static subscribes with a count > 2 and
-        # order them properly so that subscribes depending on other
-        # subscribes are handled later.
-        static_subscribes = [(node, count) for node, count in
-                             frame.identifiers.static_subscribes.items() if
-                             count >= 2]
-        if static_subscribes:
-            static_subscribes.sort(key=lambda x: type(x[0].node)
-                                                 is nodes.Subscript)
-            for node, count in static_subscribes:
-                ident = self.temporary_identifier()
-                self.writeline(ident + ' = ', node)
-                self.visit(node, frame)
-                frame.overlays[node] = (ident, count)
-
     def pull_dependencies(self, nodes):
         """Pull all the dependencies."""
         visitor = DependencyFinderVisitor()
@@ -551,17 +592,54 @@ class CodeGenerator(NodeVisitor):
                 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`.
         """
         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 = 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 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
@@ -585,7 +663,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 &
@@ -593,11 +673,9 @@ class CodeGenerator(NodeVisitor):
              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.filename)
+            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.
@@ -631,13 +709,69 @@ class CodeGenerator(NodeVisitor):
             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.
@@ -646,9 +780,7 @@ class CodeGenerator(NodeVisitor):
         # 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.filename)
+                self.fail('block %r defined twice' % block.name, block.lineno)
             self.blocks[block.name] = block
 
         # find all imports and import them
@@ -667,20 +799,21 @@ class CodeGenerator(NodeVisitor):
         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()
 
@@ -698,11 +831,11 @@ class CodeGenerator(NodeVisitor):
 
         # 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:
@@ -737,21 +870,18 @@ 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()
-        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.filename)
+            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
@@ -767,18 +897,18 @@ class CodeGenerator(NodeVisitor):
                 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)
         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)')
@@ -796,25 +926,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)):')
+                           '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."""
+        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)
@@ -822,11 +977,12 @@ class CodeGenerator(NodeVisitor):
         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."""
@@ -853,13 +1009,17 @@ class CodeGenerator(NodeVisitor):
             self.writeline('l_%s = environment.undefined(%r %% '
                            'included_template.__name__, '
                            'name=%r)' %
-                           (alias, 'the template %r does not export '
-                            'the requested name ' + repr(name), name))
+                           (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:
                 var_names.append(alias)
-                if not alias.startswith('__'):
+                if not alias.startswith('_'):
                     discarded_names.append(alias)
+            frame.assigned_names.add(alias)
 
         if var_names:
             if len(var_names) == 1:
@@ -881,7 +1041,6 @@ class CodeGenerator(NodeVisitor):
         # 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)
@@ -889,42 +1048,56 @@ class CodeGenerator(NodeVisitor):
             loop_frame = frame.inner()
             loop_frame.inspect(children)
 
-        extended_loop = node.recursive or node.else_ or \
-                        'loop' in loop_frame.identifiers.undeclared
-        if extended_loop:
-            loop_frame.identifiers.add_special('loop')
+        # 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
+        # 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.collect_shadowed(loop_frame)
+            aliases = self.push_scope(loop_frame, ('loop',))
 
         # otherwise we set up a buffer and add a function def
         else:
-            loop_frame.buffer = buf = self.temporary_identifier()
             self.writeline('def loop(reciter, loop_render_func):', node)
             self.indent()
-            self.writeline('%s = []' % buf, node)
+            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)
+
         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 don'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:
@@ -939,7 +1112,6 @@ class CodeGenerator(NodeVisitor):
                 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('))')
 
@@ -964,36 +1136,31 @@ class CodeGenerator(NodeVisitor):
             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('l_%s = %s' % (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:
-            if self.environment.autoescape:
-                self.writeline('return Markup(concat(%s))' % buf)
-            else:
-                self.writeline('return concat(%s)' % buf)
+            self.return_buffer_contents(loop_frame)
             self.outdent()
-            if frame.buffer is None:
-                self.writeline('yield loop(', node)
-            else:
-                self.writeline('%s.append(loop(' % frame.buffer, node)
+            self.start_write(frame, node)
+            self.write('loop(')
             self.visit(node.iter, frame)
             self.write(', loop)')
-            if frame.buffer is not None:
-                self.write(')')
+            self.end_write(frame)
 
     def visit_If(self, node, frame):
         if_frame = frame.soft()
@@ -1010,93 +1177,36 @@ class CodeGenerator(NodeVisitor):
             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('l_%s = Macro(environment, macro, %r, (%s), (' %
-                   (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)
@@ -1104,16 +1214,18 @@ class CodeGenerator(NodeVisitor):
 
     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
@@ -1123,8 +1235,23 @@ class CodeGenerator(NodeVisitor):
         body = []
         for child in node.nodes:
             try:
-                const = unicode(child.as_const())
-            except:
+                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 Exception:
+                # 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):
@@ -1132,9 +1259,8 @@ class CodeGenerator(NodeVisitor):
             else:
                 body.append([const])
 
-        # if we have less than 3 nodes or less than 6 and a buffer we
-        # yield or extend/append
-        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:
                 # for one item we append, for more we extend
                 if len(body) == 1:
@@ -1151,14 +1277,17 @@ class CodeGenerator(NodeVisitor):
                         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
@@ -1181,17 +1310,18 @@ class CodeGenerator(NodeVisitor):
                 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()
+                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:
@@ -1201,8 +1331,6 @@ class CodeGenerator(NodeVisitor):
                 self.write(')' * close + ', ')
             self.outdent()
             self.writeline(')')
-            if frame.buffer is not None:
-                self.write(')')
 
         if outdent_later:
             self.outdent()
@@ -1215,7 +1343,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)
@@ -1224,14 +1352,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
-                            if not x.startswith('__')]
-            if len(assignment_frame.assigned_names) == 1:
-                name = iter(assignment_frame.assigned_names).next()
+            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.assigned_names):
+                for idx, name in enumerate(assignment_frame.toplevel_assignments):
                     if idx:
                         self.write(', ')
                     self.write('%r: l_%s' % (name, name))
@@ -1244,36 +1372,28 @@ class CodeGenerator(NodeVisitor):
                     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)
+            frame.toplevel_assignments.add(node.name)
         self.write('l_' + 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.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
@@ -1301,19 +1421,31 @@ class CodeGenerator(NodeVisitor):
             self.visit(item.value, frame)
         self.write('}')
 
-    def binop(operator):
+    def binop(operator, interceptable=True):
         def visitor(self, node, frame):
-            self.write('(')
-            self.visit(node.left, frame)
-            self.write(' %s ' % operator)
-            self.visit(node.right, frame)
+            if self.environment.sandboxed and \
+               operator in self.environment.intercepted_binops:
+                self.write('environment.call_binop(context, %r, ' % operator)
+                self.visit(node.left, frame)
+                self.write(', ')
+                self.visit(node.right, frame)
+            else:
+                self.write('(')
+                self.visit(node.left, frame)
+                self.write(' %s ' % operator)
+                self.visit(node.right, frame)
             self.write(')')
         return visitor
 
-    def uaop(operator):
+    def uaop(operator, interceptable=True):
         def visitor(self, node, frame):
-            self.write('(' + operator)
-            self.visit(node.node, frame)
+            if self.environment.sandboxed and \
+               operator in self.environment.intercepted_unops:
+                self.write('environment.call_unop(context, %r, ' % operator)
+                self.visit(node.node, frame)
+            else:
+                self.write('(' + operator)
+                self.visit(node.node, frame)
             self.write(')')
         return visitor
 
@@ -1324,16 +1456,22 @@ class CodeGenerator(NodeVisitor):
     visit_FloorDiv = binop('//')
     visit_Pow = binop('**')
     visit_Mod = binop('%')
-    visit_And = binop('and')
-    visit_Or = binop('or')
+    visit_And = binop('and', interceptable=False)
+    visit_Or = binop('or', interceptable=False)
     visit_Pos = uaop('+')
     visit_Neg = uaop('-')
-    visit_Not = uaop('not ')
+    visit_Not = uaop('not ', interceptable=False)
     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(', ')
@@ -1348,34 +1486,24 @@ class CodeGenerator(NodeVisitor):
         self.write(' %s ' % operators[node.op])
         self.visit(node.expr, frame)
 
-    def visit_Subscript(self, node, frame):
-        # subscripts can have overlays
-        if self.overlay(node, frame):
-            return
+    def visit_Getattr(self, node, frame):
+        self.write('environment.getattr(')
+        self.visit(node.node, frame)
+        self.write(', %r)' % node.attr)
 
-        # slices or integer subscriptions bypass the subscribe
-        # method if we can determine that at compile time.
-        if isinstance(node.arg, nodes.Slice) or \
-           (isinstance(node.arg, nodes.Const) and
-            isinstance(node.arg.value, (int, long))):
+    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:
@@ -1387,42 +1515,56 @@ class CodeGenerator(NodeVisitor):
             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('(')
@@ -1430,17 +1572,81 @@ class CodeGenerator(NodeVisitor):
             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)