added support for new call statement

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 63a140f..83e07e6 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -258,7 +258,7 @@
             self.new_lines = 1
             self._last_line = node.lineno
 
-    def signature(self, node, frame, have_comma=True):
+    def signature(self, node, frame, have_comma=True, extra_kwargs=None):
         have_comma = have_comma and [True] or []
         def touch_comma():
             if have_comma:
@@ -272,11 +272,16 @@
         for kwarg in node.kwargs:
             touch_comma()
             self.visit(kwarg, frame)
+        if extra_kwargs is not None:
+            touch_comma()
+            self.write(extra_kwargs)
         if node.dyn_args:
             touch_comma()
+            self.write('*')
             self.visit(node.dyn_args, frame)
         if node.dyn_kwargs:
             touch_comma()
+            self.write('**')
             self.visit(node.dyn_kwargs, frame)
 
     def pull_locals(self, frame, no_indent=False):
@@ -291,6 +296,48 @@
         if not no_indent:
             self.outdent()
 
+    def function_scoping(self, node, frame):
+        func_frame = frame.inner()
+        func_frame.inspect(node.iter_child_nodes(), hard_scope=True)
+
+        # 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 ^^
+        overriden_closure_vars = (
+            func_frame.identifiers.undeclared &
+            func_frame.identifiers.declared &
+            (func_frame.identifiers.declared_locally |
+             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)
+
+        # remove variables from a closure from the frame's undeclared
+        # identifiers.
+        func_frame.identifiers.undeclared -= (
+            func_frame.identifiers.undeclared &
+            func_frame.identifiers.declared
+        )
+
+        func_frame.accesses_arguments = False
+        func_frame.accesses_caller = False
+        func_frame.arguments = args = ['l_' + x.name for x in node.args]
+
+        if 'arguments' in func_frame.identifiers.undeclared:
+            func_frame.accesses_arguments = True
+            func_frame.identifiers.add_special('arguments')
+            args.append('l_arguments')
+        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):
@@ -489,45 +536,11 @@
             self.blockvisit(node.else_, if_frame)
 
     def visit_Macro(self, node, frame):
-        macro_frame = frame.inner()
-        macro_frame.inspect(node.iter_child_nodes(), hard_scope=True)
-
-        # 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 ^^
-        overriden_closure_vars = (
-            macro_frame.identifiers.undeclared &
-            macro_frame.identifiers.declared &
-            (macro_frame.identifiers.declared_locally |
-             macro_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)
-
-        # remove variables from a closure from the frame's undeclared
-        # identifiers.
-        macro_frame.identifiers.undeclared -= (
-            macro_frame.identifiers.undeclared &
-            macro_frame.identifiers.declared
-        )
-
-        args = ['l_' + x.name for x in node.args]
-        if 'arguments' in macro_frame.identifiers.undeclared:
-            accesses_arguments = True
-            args.append('l_arguments')
-        else:
-            accesses_arguments = False
+        macro_frame = self.function_scoping(node, frame)
+        args = macro_frame.arguments
         self.writeline('def macro(%s):' % ', '.join(args), node)
-        self.indent()
-        self.writeline('if 0: yield None')
-        self.outdent()
         self.pull_locals(macro_frame)
-        self.blockvisit(node.body, macro_frame)
+        self.blockvisit(node.body, macro_frame, True)
         self.newline()
         if frame.toplevel:
             self.write('context[%r] = ' % node.name)
@@ -539,7 +552,26 @@
         for arg in node.defaults:
             self.visit(arg)
             self.write(', ')
-        self.write('), %r)' % accesses_arguments)
+        self.write('), %r, %r)' % (
+            macro_frame.accesses_arguments,
+            macro_frame.accesses_caller
+        ))
+
+    def visit_CallBlock(self, node, frame):
+        call_frame = self.function_scoping(node, frame)
+        args = call_frame.arguments
+        self.writeline('def call(%s):' % ', '.join(args), node)
+        self.blockvisit(node.body, call_frame, node)
+        arg_tuple = ', '.join(repr(x.name) for x in node.args)
+        if len(node.args) == 1:
+            arg_tuple += ','
+        self.writeline('caller = Macro(call, None, (%s), (' % arg_tuple)
+        for arg in node.defaults:
+            self.visit(arg)
+            self.write(', ')
+        self.write('), %r, False)' % call_frame.accesses_arguments)
+        self.writeline('yield ', node)
+        self.visit_Call(node.call, call_frame, extra_kwargs='caller=caller')
 
     def visit_ExprStmt(self, node, frame):
         self.newline(node)
@@ -770,10 +802,10 @@
         self.signature(node, frame)
         self.write(')')
 
-    def visit_Call(self, node, frame):
+    def visit_Call(self, node, frame, extra_kwargs=None):
         self.visit(node.node, frame)
         self.write('(')
-        self.signature(node, frame, False)
+        self.signature(node, frame, False, extra_kwargs)
         self.write(')')
 
     def visit_Keyword(self, node, frame):
diff --git a/jinja2/lexer.py b/jinja2/lexer.py
index 5d8ab7b..1f033d7 100644
--- a/jinja2/lexer.py
+++ b/jinja2/lexer.py
@@ -33,7 +33,6 @@
 integer_re = re.compile(r'\d+')
 name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
 float_re = re.compile(r'\d+\.\d+')
-eol_re = re.compile(r'(\s*$\s*)+(?m)')
 
 
 # set of used keywords
@@ -340,9 +339,8 @@
             ] + tag_rules
 
     def tokenize(self, source, filename=None):
-        """
-        Works like `tokeniter` but returns a tokenstream of tokens and not a
-        generator or token tuples. Additionally all token values are already
+        """Works like `tokeniter` but returns a tokenstream of tokens and not
+        a generator or token tuples. Additionally all token values are already
         converted into types and postprocessed. For example keywords are
         already keyword tokens, not named tokens, comments are removed,
         integers and floats converted, strings unescaped etc.
@@ -377,7 +375,6 @@
                     value = float(value)
                 elif token == 'operator':
                     token = operators[value]
-                    value = ''
                 yield Token(lineno, token, value)
         return TokenStream(generate(), filename)
 
@@ -398,12 +395,12 @@
 
         balancing_stack = []
 
-        while True:
+        while 1:
             # tokenizer loop
             for regex, tokens, new_state in statetokens:
                 m = regex.match(source, pos)
                 # if no match we try again with the next rule
-                if not m:
+                if m is None:
                     continue
 
                 # we only match blocks and variables if brances / parentheses
@@ -411,7 +408,8 @@
                 # is the operator rule. do this only if the end tags look
                 # like operators
                 if balancing_stack and \
-                   tokens in ('variable_end', 'block_end'):
+                   tokens in ('variable_end', 'block_end',
+                              'linestatement_end'):
                     continue
 
                 # tuples support more options
@@ -447,8 +445,7 @@
                                 yield lineno, token, data
                             lineno += data.count('\n')
 
-                # strings as token just are yielded as it, but just
-                # if the data is not empty
+                # strings as token just are yielded as it.
                 else:
                     data = m.group()
                     # update brace/parentheses balance
@@ -472,8 +469,7 @@
                                                           lineno, filename)
                     # yield items
                     if tokens is not None:
-                        if data:
-                            yield lineno, tokens, data
+                        yield lineno, tokens, data
                     lineno += data.count('\n')
 
                 # fetch new position into new variable so that we can check
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 00f6d62..b569a31 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -222,7 +222,7 @@
 
 class CallBlock(Stmt):
     """A node that represents am extended macro call."""
-    fields = ('call', 'body')
+    fields = ('call', 'args', 'defaults', 'body')
 
 
 class Set(Stmt):
diff --git a/jinja2/parser.py b/jinja2/parser.py
index 74660f6..81c21ee 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -175,8 +175,30 @@
         self.end_statement()
         return node
 
+    def parse_signature(self, node):
+        node.args = args = []
+        node.defaults = defaults = []
+        self.stream.expect('lparen')
+        while self.stream.current.type is not 'rparen':
+            if args:
+                self.stream.expect('comma')
+            token = self.stream.expect('name')
+            arg = nodes.Name(token.value, 'param', lineno=token.lineno)
+            if not arg.can_assign():
+                raise TemplateSyntaxError("can't assign to '%s'" %
+                                          arg.name, arg.lineno,
+                                          self.filename)
+            if self.stream.current.type is 'assign':
+                self.stream.next()
+                defaults.append(self.parse_expression())
+            args.append(arg)
+        self.stream.expect('rparen')
+
     def parse_call_block(self):
         node = nodes.CallBlock(lineno=self.stream.expect('call').lineno)
+        if self.stream.current.type is 'lparen':
+            self.parse_signature(node)
+
         node.call = self.parse_expression()
         if not isinstance(node.call, nodes.Call):
             raise TemplateSyntaxError('expected call', node.lineno,
@@ -192,23 +214,7 @@
             raise TemplateSyntaxError('can\'t assign macro to %r' %
                                       node.target, node.lineno,
                                       self.filename)
-        self.stream.expect('lparen')
-        node.args = args = []
-        node.defaults = defaults = []
-        while self.stream.current.type is not 'rparen':
-            if args:
-                self.stream.expect('comma')
-            token = self.stream.expect('name')
-            arg = nodes.Name(token.value, 'param', lineno=token.lineno)
-            if not arg.can_assign():
-                raise TemplateSyntaxError("can't assign to '%s'" %
-                                          arg.name, arg.lineno,
-                                          self.filename)
-            if self.stream.current.type is 'assign':
-                self.stream.next()
-                defaults.append(self.parse_expression())
-            args.append(arg)
-        self.stream.expect('rparen')
+        self.parse_signature(node)
         node.body = self.parse_statements(('endmacro',), drop_needle=True)
         return node
 
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 1bc196e..238d4cf 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -193,12 +193,13 @@
 class Macro(object):
     """Wraps a macro."""
 
-    def __init__(self, func, name, arguments, defaults, catch_all):
-        self.func = func
+    def __init__(self, func, name, arguments, defaults, catch_all, caller):
+        self._func = func
         self.name = name
         self.arguments = arguments
         self.defaults = defaults
         self.catch_all = catch_all
+        self.caller = caller
 
     def __call__(self, *args, **kwargs):
         arg_count = len(self.arguments)
@@ -218,9 +219,20 @@
                     except IndexError:
                         value = Undefined(name)
             arguments['l_' + name] = value
+        if self.caller:
+            caller = kwargs.pop('caller', None)
+            if caller is None:
+                caller = Undefined('caller')
+            arguments['l_caller'] = caller
         if self.catch_all:
             arguments['l_arguments'] = kwargs
-        return TemplateData(u''.join(self.func(**arguments)))
+        return TemplateData(u''.join(self._func(**arguments)))
+
+    def __repr__(self):
+        return '<%s %s>' % (
+            self.__class__.__name__,
+            self.name is None and 'anonymous' or repr(self.name)
+        )
 
 
 class Undefined(object):
@@ -228,7 +240,7 @@
 
     def __init__(self, name=None, attr=None):
         if attr is None:
-            self._undefined_hint = '%r is undefined' % attr
+            self._undefined_hint = '%r is undefined' % name
         elif name is None:
             self._undefined_hint = 'attribute %r is undefined' % name
         else: