added support for new call statement
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 12 Apr 2008 12:19:36 +0000 (14:19 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 12 Apr 2008 12:19:36 +0000 (14:19 +0200)
--HG--
branch : trunk

jinja2/compiler.py
jinja2/lexer.py
jinja2/nodes.py
jinja2/parser.py
jinja2/runtime.py

index 63a140f6c200920f7fb5a40240b92023e3e3e5e3..83e07e69b507d7ea5c964bfba5ed06c9539bb73b 100644 (file)
@@ -258,7 +258,7 @@ class CodeGenerator(NodeVisitor):
             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 @@ class CodeGenerator(NodeVisitor):
         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 @@ class CodeGenerator(NodeVisitor):
         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 @@ class CodeGenerator(NodeVisitor):
             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 @@ class CodeGenerator(NodeVisitor):
         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 @@ class CodeGenerator(NodeVisitor):
         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):
index 5d8ab7b4cb6220a6dde4aeec1c65d4bf269969ca..1f033d7ca012a15d2bf842821a54d300ef02323c 100644 (file)
@@ -33,7 +33,6 @@ string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
 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 @@ class Lexer(object):
             ] + 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 @@ class Lexer(object):
                     value = float(value)
                 elif token == 'operator':
                     token = operators[value]
-                    value = ''
                 yield Token(lineno, token, value)
         return TokenStream(generate(), filename)
 
@@ -398,12 +395,12 @@ class Lexer(object):
 
         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 @@ class Lexer(object):
                 # 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 @@ class Lexer(object):
                                 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 @@ class Lexer(object):
                                                           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
index 00f6d62cfe2bf0b6e0e51351e4ad65d78d945b05..b569a31a98eb9cdef8976515492896152964d42a 100644 (file)
@@ -222,7 +222,7 @@ class Macro(Stmt):
 
 class CallBlock(Stmt):
     """A node that represents am extended macro call."""
-    fields = ('call', 'body')
+    fields = ('call', 'args', 'defaults', 'body')
 
 
 class Set(Stmt):
index 74660f6c2189996073fdbf7d49ddc5a402abc5bd..81c21ee86004adf4959b81a9ac9f1407098df0f3 100644 (file)
@@ -175,8 +175,30 @@ class Parser(object):
         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 @@ class Parser(object):
             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
 
index 1bc196e4e9c8252f6de35140bd923e364efac270..238d4cfcf3c64ae676517b6daca4ac5712deabf0 100644 (file)
@@ -193,12 +193,13 @@ class StaticLoopContext(LoopContextBase):
 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 @@ class Macro(object):
                     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 @@ class Undefined(object):
 
     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: