there is now a workaround in the compiler that makes sure it's possible to call thing...
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 26 Apr 2008 14:26:52 +0000 (16:26 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 26 Apr 2008 14:26:52 +0000 (16:26 +0200)
--HG--
branch : trunk

examples/bench.py
jinja2/compiler.py
jinja2/environment.py
jinja2/ext.py
jinja2/nodes.py
jinja2/parser.py
jinja2/runtime.py

index 9073758fddacec088b205de3e266927c1d1ceb9d..287f8318ab257f74cea6a2eb01d1ae6cfb0c6a39 100644 (file)
@@ -1,12 +1,11 @@
+"""
+    This benchmark compares some python templating engines with Jinja 2 so
+    that we get a picture of how fast Jinja 2 is for a semi real world
+    template.  If a template engine is not installed the test is skipped.
+"""
 import sys
-from django.conf import settings
-settings.configure()
-from django.template import Template as DjangoTemplate, Context as DjangoContext
-from jinja2 import Environment as JinjaEnvironment
-from mako.template import Template as MakoTemplate
-from genshi.template import MarkupTemplate as GenshiTemplate
-from Cheetah.Template import Template as CheetahTemplate
 from timeit import Timer
+from jinja2 import Environment as JinjaEnvironment
 
 context = {
     'page_title': 'mitsuhiko\'s benchmark',
@@ -51,7 +50,17 @@ jinja_template = JinjaEnvironment(
 </html>\
 """)
 
-django_template = DjangoTemplate("""\
+def test_jinja():
+    jinja_template.render(context)
+
+try:
+    from django.conf import settings
+    settings.configure()
+    from django.template import Template as DjangoTemplate, Context as DjangoContext
+except ImportError:
+    test_django = None
+else:
+    django_template = DjangoTemplate("""\
 <!doctype html>
 <html>
   <head>
@@ -81,7 +90,18 @@ django_template = DjangoTemplate("""\
 </html>\
 """)
 
-mako_template = MakoTemplate("""\
+    def test_django():
+        c = DjangoContext(context)
+        c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'),
+                           ('products.html', 'Products')]
+        django_template.render(c)
+
+try:
+    from mako.template import Template as MakoTemplate
+except ImportError:
+    test_mako = None
+else:
+    mako_template = MakoTemplate("""\
 <!doctype html>
 <html>
   <head>
@@ -111,7 +131,15 @@ mako_template = MakoTemplate("""\
 </html>\
 """)
 
-genshi_template = GenshiTemplate("""\
+    def test_mako():
+        mako_template.render(**context)
+
+try:
+    from genshi.template import MarkupTemplate as GenshiTemplate
+except ImportError:
+    test_genshi = None
+else:
+    genshi_template = GenshiTemplate("""\
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/">
   <head>
     <title>${page_title}</title>
@@ -137,7 +165,15 @@ genshi_template = GenshiTemplate("""\
 </html>\
 """)
 
-cheetah_template = CheetahTemplate("""\
+    def test_genshi():
+        genshi_template.generate(**context).render('html', strip_whitespace=False)
+
+try:
+    from Cheetah.Template import Template as CheetahTemplate
+except ImportError:
+    test_cheetah = None
+else:
+    cheetah_template = CheetahTemplate("""\
 #import cgi
 <!doctype html>
 <html>
@@ -168,32 +204,63 @@ cheetah_template = CheetahTemplate("""\
 </html>\
 """, searchList=[dict(context)])
 
-def test_jinja():
-    jinja_template.render(context)
-
-def test_django():
-    c = DjangoContext(context)
-    c['navigation'] = [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]
-    django_template.render(c)
-
-def test_mako():
-    mako_template.render(**context)
+    def test_cheetah():
+        unicode(cheetah_template)
 
-def test_genshi():
-    genshi_template.generate(**context).render('html', strip_whitespace=False)
+try:
+    import tenjin
+except ImportError:
+    test_tenjin = None
+else:
+    tenjin_template = tenjin.Template()
+    tenjin_template.convert("""\
+<!doctype html>
+<html>
+  <head>
+    <title>${page_title}</title>
+  </head>
+  <body>
+    <div class="header">
+      <h1>${page_title}</h1>
+    </div>
+    <ul class="navigation">
+<?py for href, caption in [('index.html', 'Index'), ('downloads.html', 'Downloads'), ('products.html', 'Products')]: ?>
+      <li><a href="${href}">${caption}</a></li>
+<?py #end ?>
+    </ul>
+    <div class="table">
+      <table>
+<?py for row in table: ?>
+        <tr>
+<?py     for cell in row: ?>
+          <td>#{cell}</td>
+<?py #end ?>
+        </tr>
+<?py #end ?>
+      </table>
+    </div>
+  </body>
+</html>\
+""")
 
-def test_cheetah():
-    unicode(cheetah_template)
+    def test_tenjin():
+        from tenjin.helpers import escape, to_str
+        tenjin_template.render(context, locals())
 
-sys.stdout.write('\r%s\n%s\n%s\n' % (
+sys.stdout.write('\r' + '\n'.join((
     '=' * 80,
     'Template Engine BigTable Benchmark'.center(80),
+    '-' * 80,
+    __doc__,
     '-' * 80
-))
-for test in 'jinja', 'mako', 'django', 'genshi', 'cheetah':
+)) + '\n')
+for test in 'jinja', 'tenjin', 'mako', 'django', 'genshi', 'cheetah':
+    if locals()['test_' + test] is None:
+        sys.stdout.write('  %-20s*not installed*\n' % test)
+        continue
     t = Timer(setup='from __main__ import test_%s as bench' % test,
               stmt='bench()')
     sys.stdout.write('> %-20s<running>' % test)
     sys.stdout.flush()
-    sys.stdout.write('\r  %-20s%.4f ms\n' % (test, t.timeit(number=100) / 100))
+    sys.stdout.write('\r  %-20s%.4f ms\n' % (test, t.timeit(number=20) / 20))
 sys.stdout.write('=' * 80 + '\n')
index bc5163b885393dbf60ae57fab77ac3510c89964d..8541cba7640a65a6763ed2879d46854c3221cd19 100644 (file)
@@ -10,7 +10,9 @@
 """
 from copy import copy
 from random import randrange
+from keyword import iskeyword
 from cStringIO import StringIO
+from itertools import chain
 from jinja2 import nodes
 from jinja2.visitor import NodeVisitor, NodeTransformer
 from jinja2.exceptions import TemplateAssertionError
@@ -163,14 +165,19 @@ class Frame(object):
         rv.name_overrides = self.name_overrides.copy()
         return rv
 
-    def inspect(self, nodes, hard_scope=False):
-        """Walk the node and check for identifiers.  If the scope
-        is hard (eg: enforce on a python level) overrides from outer
-        scopes are tracked differently.
+    def inspect(self, nodes, with_depenencies=False, hard_scope=False):
+        """Walk the node and check for identifiers.  If the scope is hard (eg:
+        enforce on a python level) overrides from outer scopes are tracked
+        differently.
+
+        Per default filters and tests (dependencies) are not tracked.  That's
+        the case because filters and tests are absolutely immutable and so we
+        can savely use them in closures too.  The `Template` and `Block`
+        visitor visits the frame with dependencies to collect them.
         """
         visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
         for node in nodes:
-            visitor.visit(node)
+            visitor.visit(node, True, with_depenencies)
 
     def inner(self):
         """Return an inner frame."""
@@ -193,41 +200,63 @@ class FrameIdentifierVisitor(NodeVisitor):
         self.identifiers = identifiers
         self.hard_scope = hard_scope
 
-    def visit_Name(self, node):
+    def visit_Name(self, node, visit_ident, visit_deps):
         """All assignments to names go through this function."""
-        if node.ctx in ('store', 'param'):
-            self.identifiers.declared_locally.add(node.name)
-        elif node.ctx == 'load':
-            if not self.identifiers.is_declared(node.name, self.hard_scope):
+        if visit_ident:
+            if node.ctx in ('store', 'param'):
+                self.identifiers.declared_locally.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_Filter(self, node):
-        self.generic_visit(node)
-        self.identifiers.filters.add(node.name)
-
-    def visit_Test(self, node):
-        self.generic_visit(node)
-        self.identifiers.tests.add(node.name)
+    def visit_Filter(self, node, visit_ident, visit_deps):
+        if visit_deps:
+            self.generic_visit(node, visit_ident, True)
+            self.identifiers.filters.add(node.name)
 
-    def visit_Macro(self, node):
-        self.identifiers.declared_locally.add(node.name)
+    def visit_Test(self, node, visit_ident, visit_deps):
+        if visit_deps:
+            self.generic_visit(node, visit_ident, True)
+            self.identifiers.tests.add(node.name)
 
-    def visit_Import(self, node):
-        self.generic_visit(node)
-        self.identifiers.declared_locally.add(node.target)
+    def visit_Macro(self, node, visit_ident, visit_deps):
+        if visit_ident:
+            self.identifiers.declared_locally.add(node.name)
 
-    def visit_FromImport(self, node):
-        self.generic_visit(node)
-        self.identifiers.declared_locally.update(node.names)
+    def visit_Import(self, node, visit_ident, visit_deps):
+        if visit_ident:
+            self.generic_visit(node, True, visit_deps)
+            self.identifiers.declared_locally.add(node.target)
+
+    def visit_FromImport(self, node, visit_ident, visit_deps):
+        if visit_ident:
+            self.generic_visit(node, True, visit_deps)
+            for name in node.names:
+                if isinstance(name, tuple):
+                    self.identifiers.declared_locally.add(name[1])
+                else:
+                    self.identifiers.declared_locally.add(name)
 
-    def visit_Assign(self, node):
+    def visit_Assign(self, node, visit_ident, visit_deps):
         """Visit assignments in the correct order."""
-        self.visit(node.node)
-        self.visit(node.target)
+        self.visit(node.node, visit_ident, visit_deps)
+        self.visit(node.target, visit_ident, visit_deps)
+
+    def visit_For(self, node, visit_ident, visit_deps):
+        """Visiting stops at for blocks.  However the block sequence
+        is visited as part of the outer scope.
+        """
+        if visit_ident:
+            self.visit(node.iter, True, visit_deps)
+            if visit_deps:
+                for child in node.iter_child_nodes(exclude=('iter',)):
+                    self.visit(child, False, True)
 
-    # stop traversing at instructions that have their own scope.
-    visit_Block = visit_CallBlock = visit_FilterBlock = \
-        visit_For = lambda s, n: None
+    def ident_stop(self, node, visit_ident, visit_deps):
+        if visit_deps:
+            self.generic_visit(node, False, True)
+    visit_CallBlock = visit_FilterBlock = ident_stop
+    visit_Block = lambda s, n, a, b: None
 
 
 class CompilerExit(Exception):
@@ -344,10 +373,10 @@ class CodeGenerator(NodeVisitor):
     def signature(self, node, frame, have_comma=True, 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.  If extra_kwargs is
-        given it must be a string that represents a single keyword
-        argument call that is inserted at the end of the regular
-        keyword argument calls.
+        disabled by setting have_comma to False.  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():
@@ -356,20 +385,53 @@ class CodeGenerator(NodeVisitor):
             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):
+                kwarg_workaround = True
+                break
+
         for arg in node.args:
             touch_comma()
             self.visit(arg, frame)
-        for kwarg in node.kwargs:
-            touch_comma()
-            self.visit(kwarg, frame)
-        if extra_kwargs is not None:
-            touch_comma()
-            self.write(extra_kwargs)
+
+        if not kwarg_workaround:
+            for kwarg in node.kwargs:
+                touch_comma()
+                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))
         if node.dyn_args:
             touch_comma()
             self.write('*')
             self.visit(node.dyn_args, frame)
-        if node.dyn_kwargs:
+
+        if kwarg_workaround:
+            touch_comma()
+            if node.dyn_kwargs is not None:
+                self.write('**dict({')
+            else:
+                self.write('**{')
+            for kwarg in node.kwargs:
+                self.write('%r: ' % kwarg.key)
+                self.visit(kwarg.value, frame)
+                self.write(', ')
+            if extra_kwargs is not None:
+                for key, value in extra_kwargs.iteritems():
+                    touch_comma()
+                    self.write('%r: %s, ' % (key, value))
+            if node.dyn_kwargs is not None:
+                self.write('}, **')
+                self.visit(node.dyn_kwargs, frame)
+                self.write(')')
+            else:
+                self.write('}')
+
+        elif node.dyn_kwargs is not None:
             touch_comma()
             self.write('**')
             self.visit(node.dyn_kwargs, frame)
@@ -448,6 +510,10 @@ class CodeGenerator(NodeVisitor):
         func_frame.accesses_caller = False
         func_frame.arguments = args = ['l_' + x.name for x in node.args]
 
+        if 'caller' in func_frame.identifiers.undeclared:
+            func_frame.accesses_caller = True
+            func_frame.identifiers.add_special('caller')
+            args.append('l_caller')
         if 'kwargs' in func_frame.identifiers.undeclared:
             func_frame.accesses_kwargs = True
             func_frame.identifiers.add_special('kwargs')
@@ -456,17 +522,14 @@ class CodeGenerator(NodeVisitor):
             func_frame.accesses_varargs = True
             func_frame.identifiers.add_special('varargs')
             args.append('l_varargs')
-        if 'caller' in func_frame.identifiers.undeclared:
-            func_frame.accesses_caller = True
-            func_frame.identifiers.add_special('caller')
-            args.append('l_caller')
         return func_frame
 
     # -- Visitors
 
     def visit_Template(self, node, frame=None):
         assert frame is None, 'no root frame allowed'
-        self.writeline('from jinja2.runtime import *')
+        from jinja2.runtime import __all__ as exported
+        self.writeline('from jinja2.runtime import ' + ', '.join(exported))
         self.writeline('name = %r' % self.name)
 
         # do we have an extends tag at all?  If not, we can save some
@@ -491,7 +554,7 @@ class CodeGenerator(NodeVisitor):
 
         # process the root
         frame = Frame()
-        frame.inspect(node.body)
+        frame.inspect(node.body, with_depenencies=True)
         frame.toplevel = frame.rootlevel = True
         self.indent()
         self.pull_locals(frame, indent=False)
@@ -513,7 +576,7 @@ 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.inspect(block.body)
+            block_frame.inspect(block.body, with_depenencies=True)
             block_frame.block = name
             block_frame.identifiers.add_special('super')
             block_frame.name_overrides['super'] = 'context.super(%r, ' \
@@ -627,21 +690,25 @@ class CodeGenerator(NodeVisitor):
         self.visit(node.template, frame)
         self.write(', %r).include(context)' % self.name)
         for name in node.names:
+            if isinstance(name, tuple):
+                name, alias = name
+            else:
+                alias = name
             self.writeline('l_%s = getattr(included_template, '
-                           '%r, missing)' % (name, name))
-            self.writeline('if l_%s is missing:' % name)
+                           '%r, missing)' % (alias, name))
+            self.writeline('if l_%s is missing:' % alias)
             self.indent()
             self.writeline('l_%s = environment.undefined(%r %% '
                            'included_template.name)' %
-                           (name, 'the template %r does not export '
+                           (alias, 'the template %r does not export '
                             'the requested name ' + repr(name)))
             self.outdent()
             if frame.toplevel:
-                self.writeline('context[%r] = l_%s' % (name, name))
+                self.writeline('context[%r] = l_%s' % (alias, alias))
 
     def visit_For(self, node, frame):
         loop_frame = frame.inner()
-        loop_frame.inspect(node.iter_child_nodes())
+        loop_frame.inspect(node.iter_child_nodes(exclude=('iter',)))
         extended_loop = bool(node.else_) or \
                         'loop' in loop_frame.identifiers.undeclared
         if extended_loop:
@@ -774,7 +841,8 @@ class CodeGenerator(NodeVisitor):
             self.writeline('yield ', node)
         else:
             self.writeline('%s.append(' % frame.buffer, node)
-        self.visit_Call(node.call, call_frame, extra_kwargs='caller=caller')
+        self.visit_Call(node.call, call_frame,
+                        extra_kwargs={'caller': 'caller'})
         if frame.buffer is not None:
             self.write(')')
 
index 5325c2b40d0dde1f1dafb855ecf3a9a45d671d45..f61c740181aef96fcd2d188560601bcef3c30271 100644 (file)
@@ -395,21 +395,20 @@ class IncludedTemplate(object):
     """Represents an included template."""
 
     def __init__(self, template, context):
-        body = Markup(concat(template.root_render_func(context)))
+        self._body_stream = tuple(template.root_render_func(context))
         self.__dict__.update(context.get_exported())
-        self._name = template.name
-        self._rendered_body = body
+        self.__name__ = template.name
 
-    __html__ = lambda x: x._rendered_body
-    __unicode__ = lambda x: unicode(x._rendered_body)
+    __html__ = lambda x: Markup(concat(x._body_stream))
+    __unicode__ = lambda x: unicode(concat(x._body_stream))
 
     def __str__(self):
-        return unicode(self._rendered_body).encode('utf-8')
+        return unicode(self).encode('utf-8')
 
     def __repr__(self):
         return '<%s %r>' % (
             self.__class__.__name__,
-            self._name
+            self.__name__
         )
 
 
index 2c61bb0218defcd548d1f6222a7e835aedca1c3c..e480b00c2e83a1ecb165081fd426a111b9bbedfb 100644 (file)
@@ -13,7 +13,7 @@
 from collections import deque
 from jinja2 import nodes
 from jinja2.environment import get_spontaneous_environment
-from jinja2.runtime import Undefined
+from jinja2.runtime import Undefined, concat
 from jinja2.parser import statement_end_tokens
 from jinja2.exceptions import TemplateAssertionError
 from jinja2.utils import import_string
@@ -190,7 +190,7 @@ class TransExtension(Extension):
             else:
                 assert False, 'internal parser error'
 
-        return referenced, u''.join(buf)
+        return referenced, concat(buf)
 
     def _make_node(self, singular, plural, variables, plural_expr):
         """Generates a useful node from the data provided."""
index 3aed350ee061c2cb8f1f166b46033cd97fb860d6..69a156f6d562a3e3576a2f69bdde8d623eee5068 100644 (file)
@@ -92,17 +92,18 @@ class Node(object):
             raise TypeError('unknown keyword argument %r' %
                             iter(kw).next())
 
-    def iter_fields(self):
+    def iter_fields(self, exclude=()):
         """Iterate over all fields."""
         for name in self.fields:
-            try:
-                yield name, getattr(self, name)
-            except AttributeError:
-                pass
+            if name not in exclude:
+                try:
+                    yield name, getattr(self, name)
+                except AttributeError:
+                    pass
 
-    def iter_child_nodes(self):
+    def iter_child_nodes(self, exclude=()):
         """Iterate over all child nodes."""
-        for field, item in self.iter_fields():
+        for field, item in self.iter_fields(exclude):
             if isinstance(item, list):
                 for n in item:
                     if isinstance(n, Node):
@@ -243,7 +244,7 @@ class Macro(Stmt):
 
 class CallBlock(Stmt):
     """A node that represents am extended macro call."""
-    fields = ('call', 'args', 'defaults', 'body')
+    fields = ('call', 'body')
 
 
 class Set(Stmt):
@@ -279,6 +280,8 @@ class FromImport(Stmt):
     start with double underscores (which the parser asserts) this is not a
     problem for regular Jinja code, but if this node is used in an extension
     extra care must be taken.
+
+    The list of names may contain tuples if aliases are wanted.
     """
     fields = ('template', 'names')
 
index daa7a0d3ec2436a2bd586594156150ab789d56a4..34a3140874a668664829755a6493a21cdff3daa4 100644 (file)
@@ -13,10 +13,10 @@ from jinja2 import nodes
 from jinja2.exceptions import TemplateSyntaxError
 
 
+statement_end_tokens = set(['variable_end', 'block_end', 'in'])
 _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
                                  'macro', 'include', 'from', 'import'])
 _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in'])
-statement_end_tokens = set(['variable_end', 'block_end', 'in'])
 _tuple_edge_tokens = set(['rparen']) | statement_end_tokens
 
 
@@ -178,8 +178,17 @@ class Parser(object):
                                                  'underscores can not be '
                                                  'imported', target.lineno,
                                                  self.filename)
-                node.names.append(target.name)
                 self.stream.next()
+                if self.stream.current.test('name:as'):
+                    self.stream.next()
+                    alias = self.stream.expect('name')
+                    if not nodes.Name(alias.value, 'store').can_assign():
+                        raise TemplateSyntaxError('can\'t name imported '
+                                                  'object %r.' % alias.value,
+                                                  alias.lineno, self.filename)
+                    node.names.append((target.name, alias.value))
+                else:
+                    node.names.append(target.name)
                 if self.stream.current.type is not 'comma':
                     break
             else:
index 8f0e1cc70ff0da5e7a7adbb685918c493990487b..11924cf20f60e468ed74a39dd3c0f1079cfc8260 100644 (file)
@@ -9,12 +9,14 @@
     :license: GNU GPL.
 """
 from types import FunctionType
+from itertools import izip
 from jinja2.utils import Markup, partial
 from jinja2.exceptions import UndefinedError
 
 
+# these variables are exported to the template runtime
 __all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
-           'Macro', 'Markup', 'missing', 'concat']
+           'Macro', 'Markup', 'missing', 'concat', 'izip']
 
 
 # special singleton representing missing values for the runtime
@@ -34,18 +36,18 @@ class TemplateContext(object):
 
     def __init__(self, environment, parent, name, blocks):
         self.parent = parent
-        self.vars = {}
+        self.vars = vars = {}
         self.environment = environment
         self.exported_vars = set()
         self.name = name
 
         # bind functions to the context of environment if required
-        for name, obj in self.parent.iteritems():
+        for name, obj in parent.iteritems():
             if type(obj) is FunctionType:
                 if getattr(obj, 'contextfunction', 0):
-                    self.vars[name] = partial(obj, self)
+                    vars[name] = partial(obj, self)
                 elif getattr(obj, 'environmentfunction', 0):
-                    self.vars[name] = partial(obj, environment)
+                    vars[name] = partial(obj, environment)
 
         # create the initial mapping of blocks.  Whenever template inheritance
         # takes place the runtime will update this mapping with the new blocks
@@ -223,17 +225,18 @@ class Macro(object):
         self._func = func
         self.name = name
         self.arguments = arguments
+        self.argument_count = len(arguments)
         self.defaults = defaults
         self.catch_kwargs = catch_kwargs
         self.catch_varargs = catch_varargs
         self.caller = caller
 
     def __call__(self, *args, **kwargs):
-        arg_count = len(self.arguments)
-        if not self.catch_varargs and len(args) > arg_count:
+        self.argument_count = len(self.arguments)
+        if not self.catch_varargs and len(args) > self.argument_count:
             raise TypeError('macro %r takes not more than %d argument(s)' %
                             (self.name, len(self.arguments)))
-        arguments = {}
+        arguments = []
         for idx, name in enumerate(self.arguments):
             try:
                 value = args[idx]
@@ -242,24 +245,28 @@ class Macro(object):
                     value = kwargs.pop(name)
                 except KeyError:
                     try:
-                        value = self.defaults[idx - arg_count]
+                        value = self.defaults[idx - self.argument_count]
                     except IndexError:
                         value = self._environment.undefined(
                             'parameter %r was not provided' % name)
-            arguments['l_' + name] = value
+            arguments.append(value)
+
+        # it's important that the order of these arguments does not change
+        # if not also changed in the compiler's `function_scoping` method.
+        # the order is caller, keyword arguments, positional arguments!
         if self.caller:
             caller = kwargs.pop('caller', None)
             if caller is None:
                 caller = self._environment.undefined('No caller defined')
-            arguments['l_caller'] = caller
+            arguments.append(caller)
         if self.catch_kwargs:
-            arguments['l_kwargs'] = kwargs
+            arguments.append(kwargs)
         elif kwargs:
             raise TypeError('macro %r takes no keyword argument %r' %
                             (self.name, iter(kwargs).next()))
         if self.catch_varargs:
-            arguments['l_varargs'] = args[arg_count:]
-        return self._func(**arguments)
+            arguments.append(args[self.argument_count:])
+        return self._func(*arguments)
 
     def __repr__(self):
         return '<%s %s>' % (