Code

Ticket #7806: tplrf-r8970.diff

File tplrf-r8970.diff, 129.2 KB (added by emulbreh, 6 years ago)

with tests

Line 
1Index: django/templatetags/i18n.py
2===================================================================
3--- django/templatetags/i18n.py (revision 8969)
4+++ django/templatetags/i18n.py (working copy)
5@@ -1,8 +1,7 @@
6 import re
7 
8-from django.template import Node, Variable, VariableNode
9-from django.template import TemplateSyntaxError, TokenParser, Library
10-from django.template import TOKEN_TEXT, TOKEN_VAR
11+from django.template import Node, VariableNode, TokenParser, Variable, TemplateSyntaxError, Library
12+from django.template.compiler import uses_token_stream, TOKEN_TEXT, TOKEN_VAR
13 from django.utils import translation
14 from django.utils.encoding import force_unicode
15 
16@@ -35,7 +34,7 @@
17 
18 class TranslateNode(Node):
19     def __init__(self, value, noop):
20-        self.value = Variable(value)
21+        self.value = value
22         self.noop = noop
23 
24     def render(self, context):
25@@ -141,7 +140,9 @@
26         raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args
27     return GetCurrentLanguageBidiNode(args[2])
28 
29-def do_translate(parser, token):
30+
31+#@uses_token_stream
32+def do_translate(parser, bits):
33     """
34     This will mark a string for translation and will
35     translate the string for the current language.
36@@ -171,18 +172,8 @@
37     the variable ``variable``. Make sure that the string
38     in there is something that is in the .po file.
39     """
40-    class TranslateParser(TokenParser):
41-        def top(self):
42-            value = self.value()
43-            if self.more():
44-                if self.tag() == 'noop':
45-                    noop = True
46-                else:
47-                    raise TemplateSyntaxError, "only option for 'trans' is 'noop'"
48-            else:
49-                noop = False
50-            return (value, noop)
51-    value, noop = TranslateParser(token.contents).top()
52+    value = bits.parse_expression(required=True)
53+    noop = bits.pop_lexem('noop')
54     return TranslateNode(value, noop)
55 
56 def do_block_translate(parser, token):
57@@ -255,5 +246,5 @@
58 register.tag('get_available_languages', do_get_available_languages)
59 register.tag('get_current_language', do_get_current_language)
60 register.tag('get_current_language_bidi', do_get_current_language_bidi)
61-register.tag('trans', do_translate)
62+register.tag('trans', uses_token_stream(do_translate))
63 register.tag('blocktrans', do_block_translate)
64Index: django/templatetags/cache.py
65===================================================================
66--- django/templatetags/cache.py        (revision 8969)
67+++ django/templatetags/cache.py        (working copy)
68@@ -1,5 +1,5 @@
69-from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
70-from django.template import resolve_variable
71+from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist, TokenSyntaxError
72+from django.template.compiler import uses_token_stream
73 from django.core.cache import cache
74 from django.utils.encoding import force_unicode
75 from django.utils.http import urlquote
76@@ -9,7 +9,7 @@
77 class CacheNode(Node):
78     def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
79         self.nodelist = nodelist
80-        self.expire_time_var = Variable(expire_time_var)
81+        self.expire_time_var = expire_time_var
82         self.fragment_name = fragment_name
83         self.vary_on = vary_on
84 
85@@ -23,14 +23,14 @@
86         except (ValueError, TypeError):
87             raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
88         # Build a unicode key for this fragment and all vary-on's.
89-        cache_key = u':'.join([self.fragment_name] + [urlquote(resolve_variable(var, context)) for var in self.vary_on])
90+        cache_key = u':'.join([self.fragment_name] + [urlquote(var.resolve_safe(context)) for var in self.vary_on])
91         value = cache.get(cache_key)
92         if value is None:
93             value = self.nodelist.render(context)
94             cache.set(cache_key, value, expire_time)
95         return value
96 
97-def do_cache(parser, token):
98+def do_cache(parser, bits):
99     """
100     This will cache the contents of a template fragment for a given amount
101     of time.
102@@ -51,11 +51,12 @@
103 
104     Each unique set of arguments will result in a unique cache entry.
105     """
106-    nodelist = parser.parse(('endcache',))
107-    parser.delete_first_token()
108-    tokens = token.contents.split()
109-    if len(tokens) < 3:
110-        raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0])
111-    return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
112+    nodelist = parser.parse_nodelist(('endcache',))
113+    expire_time = bits.parse_expression(required=True)
114+    name = bits.pop_name()
115+    if not name:
116+        raise TemplateSyntaxError, "'cache' requires a fragment name"   
117+    vary_on = bits.parse_expression_list()
118+    return CacheNode(nodelist, expire_time, name, vary_on)
119 
120-register.tag('cache', do_cache)
121+register.tag('cache', uses_token_stream(do_cache))
122Index: django/template/nodes.py
123===================================================================
124--- django/template/nodes.py    (revision 0)
125+++ django/template/nodes.py    (revision 0)
126@@ -0,0 +1,91 @@
127+from django.utils.encoding import force_unicode
128+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
129+from django.utils.html import escape
130+from django.template.expressions import LookupError
131+from django.conf import settings
132+
133+class Node(object):
134+    # Set this to True for nodes that must be first in the template (although
135+    # they can be preceded by text nodes.
136+    must_be_first = False
137+
138+    def render(self, context):
139+        "Return the node rendered as a string"
140+        pass
141+       
142+    def __repr__(self):
143+        return "<%s>" % self.__class__.__name__
144+
145+    def __iter__(self):
146+        yield self
147+
148+    def get_nodes_by_type(self, nodetype):
149+        "Return a list of all nodes (within this node and its nodelist) of the given type"
150+        nodes = []
151+        if isinstance(self, nodetype):
152+            nodes.append(self)
153+        if hasattr(self, 'nodelist'):
154+            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
155+        return nodes
156+
157+class NodeList(list):
158+    # Set to True the first time a non-TextNode is inserted by
159+    # extend_nodelist().
160+    contains_nontext = False
161+
162+    def render(self, context):
163+        bits = []
164+        for node in self:
165+            if isinstance(node, Node):
166+                bits.append(self.render_node(node, context))
167+            else:
168+                bits.append(node)
169+        return mark_safe(''.join([force_unicode(b) for b in bits]))
170+
171+    def get_nodes_by_type(self, nodetype):
172+        "Return a list of all nodes of the given type"
173+        nodes = []
174+        for node in self:
175+            nodes.extend(node.get_nodes_by_type(nodetype))
176+        return nodes
177+
178+    def render_node(self, node, context):
179+        return node.render(context)
180+
181+class TextNode(Node):
182+    def __init__(self, s):
183+        self.s = s
184+
185+    def __repr__(self):
186+        return "<Text Node: '%s'>" % self.s[:25]
187+
188+    def render(self, context):
189+        return self.s
190+
191+class ExpressionNode(Node):
192+    def __init__(self, expression):
193+        self.expression = expression
194+
195+    def __repr__(self):
196+        return "<Variable Node: %s>" % self.expression
197+
198+    def render(self, context):
199+        try:
200+            output = force_unicode(self.expression.resolve(context))
201+        except LookupError:
202+            if settings.TEMPLATE_STRING_IF_INVALID:
203+                from django.template import invalid_var_format_string
204+                if invalid_var_format_string is None:
205+                    invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
206+                if invalid_var_format_string:
207+                    return settings.TEMPLATE_STRING_IF_INVALID % self.expression
208+                return settings.TEMPLATE_STRING_IF_INVALID
209+            else:
210+                return u''
211+        except UnicodeDecodeError:
212+            # Unicode conversion can fail sometimes for reasons out of our
213+            # control (e.g. exception rendering). In that case, we fail quietly.
214+            return u''       
215+        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
216+            output = escape(output)
217+        return output
218Index: django/template/compiler.py
219===================================================================
220--- django/template/compiler.py (revision 0)
221+++ django/template/compiler.py (revision 0)
222@@ -0,0 +1,537 @@
223+import re
224+from inspect import getargspec
225+from django.conf import settings
226+from django.template.context import Context, RequestContext, ContextPopException
227+from django.template.expressions import Expression, Literal, Lookup, FilterExpression
228+from django.template.nodes import Node, NodeList, ExpressionNode, TextNode
229+from django.utils.encoding import smart_unicode, smart_str
230+from django.utils.functional import wraps
231+from django.utils.safestring import mark_safe
232+from django.utils.text import smart_split
233+from django.utils.translation import ugettext
234+
235+__all__ = ('Template', 'TemplateSyntaxError', 'TokenSyntaxError', 'TokenStream')
236+
237+TOKEN_TEXT = 0
238+TOKEN_VAR = 1
239+TOKEN_BLOCK = 2
240+TOKEN_COMMENT = 3
241+
242+# template syntax constants
243+BLOCK_TAG_START = '{%'
244+BLOCK_TAG_END = '%}'
245+VARIABLE_TAG_START = '{{'
246+VARIABLE_TAG_END = '}}'
247+COMMENT_TAG_START = '{#'
248+COMMENT_TAG_END = '#}'
249+SINGLE_BRACE_START = '{'
250+SINGLE_BRACE_END = '}'
251+FILTER_SEPARATOR = '|'
252+FILTER_ARGUMENT_SEPARATOR = ':'
253+VARIABLE_ATTRIBUTE_SEPARATOR = '.'
254+
255+# what to report as the origin for templates that come from non-loader sources
256+# (e.g. strings)
257+UNKNOWN_SOURCE="&lt;unknown source&gt;"
258+
259+# match a variable or block tag and capture the entire tag, including start/end delimiters
260+tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
261+                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
262+                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
263+
264+class TemplateSyntaxError(Exception):
265+    def __str__(self):
266+        try:
267+            import cStringIO as StringIO
268+        except ImportError:
269+            import StringIO
270+        output = StringIO.StringIO()
271+        output.write(Exception.__str__(self))
272+        # Check if we wrapped an exception and print that too.
273+        if hasattr(self, 'exc_info'):
274+            import traceback
275+            output.write('\n\nOriginal ')
276+            e = self.exc_info
277+            traceback.print_exception(e[0], e[1], e[2], 500, output)
278+        return output.getvalue()
279+
280+class TemplateEncodingError(Exception):
281+    pass
282+
283+class Origin(object):
284+    def __init__(self, name):
285+        self.name = name
286+
287+    def reload(self):
288+        raise NotImplementedError
289+
290+    def __str__(self):
291+        return self.name
292+
293+class StringOrigin(Origin):
294+    def __init__(self, source):
295+        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
296+        self.source = source
297+
298+    def reload(self):
299+        return self.source
300+
301+class Template(object):
302+    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
303+        try:
304+            template_string = smart_unicode(template_string)
305+        except UnicodeDecodeError:
306+            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
307+        if settings.TEMPLATE_DEBUG and origin is None:
308+            origin = StringOrigin(template_string)
309+        self.nodelist = compile_string(template_string, origin)
310+        self.name = name
311+
312+    def __iter__(self):
313+        for node in self.nodelist:
314+            for subnode in node:
315+                yield subnode
316+
317+    def render(self, context):
318+        "Display stage -- can be called many times"
319+        return self.nodelist.render(context)
320+
321+def compile_string(template_string, origin):
322+    "Compiles template_string into NodeList ready for rendering"
323+    if settings.TEMPLATE_DEBUG:
324+        from debug import DebugLexer, DebugParser
325+        lexer_class, parser_class = DebugLexer, DebugParser
326+    else:
327+        lexer_class, parser_class = Lexer, Parser
328+    lexer = lexer_class(template_string, origin)
329+    parser = parser_class(lexer.tokenize())
330+    return parser.parse()
331+
332+class Token(object):
333+    def __init__(self, token_type, contents):
334+        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
335+        self.token_type, self.contents = token_type, contents
336+
337+    def __str__(self):
338+        return '<%s token: "%s...">' % \
339+            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
340+            self.contents[:20].replace('\n', ''))
341+
342+    def split_contents(self):
343+        split = []
344+        bits = iter(smart_split(self.contents))
345+        for bit in bits:
346+            # Handle translation-marked template pieces
347+            if bit.startswith('_("') or bit.startswith("_('"):
348+                sentinal = bit[2] + ')'
349+                trans_bit = [bit]
350+                while not bit.endswith(sentinal):
351+                    bit = bits.next()
352+                    trans_bit.append(bit)
353+                bit = ' '.join(trans_bit)
354+            split.append(bit)
355+        return split
356+
357+class Lexer(object):
358+    def __init__(self, template_string, origin):
359+        self.template_string = template_string
360+        self.origin = origin
361+
362+    def tokenize(self):
363+        "Return a list of tokens from a given template_string."
364+        in_tag = False
365+        result = []
366+        for bit in tag_re.split(self.template_string):
367+            if bit:
368+                result.append(self.create_token(bit, in_tag))
369+            in_tag = not in_tag
370+        return result
371+
372+    def create_token(self, token_string, in_tag):
373+        """
374+        Convert the given token string into a new Token object and return it.
375+        If in_tag is True, we are processing something that matched a tag,
376+        otherwise it should be treated as a literal string.
377+        """
378+        if in_tag:
379+            if token_string.startswith(VARIABLE_TAG_START):
380+                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
381+            elif token_string.startswith(BLOCK_TAG_START):
382+                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
383+            elif token_string.startswith(COMMENT_TAG_START):
384+                token = Token(TOKEN_COMMENT, '')
385+        else:
386+            token = Token(TOKEN_TEXT, token_string)
387+        return token
388+
389+class Parser(object):
390+    def __init__(self, tokens):
391+        self.tokens = tokens
392+        self.tags = {}
393+        self.filters = {}
394+        from django.template.library import builtins
395+        for lib in builtins:
396+            self.add_library(lib)
397+
398+    def parse(self, parse_until=None):
399+        if parse_until is None: parse_until = []
400+        nodelist = self.create_nodelist()
401+        while self.tokens:
402+            token = self.next_token()
403+            if token.token_type == TOKEN_TEXT:
404+                self.extend_nodelist(nodelist, TextNode(token.contents), token)
405+            elif token.token_type == TOKEN_VAR:
406+                if not token.contents:
407+                    self.empty_variable(token)
408+                filter_expression = self.compile_filter(token.contents)
409+                var_node = self.create_variable_node(filter_expression)
410+                self.extend_nodelist(nodelist, var_node,token)
411+            elif token.token_type == TOKEN_BLOCK:
412+                if token.contents in parse_until:
413+                    # put token back on token list so calling code knows why it terminated
414+                    self.prepend_token(token)
415+                    return nodelist
416+                try:
417+                    command = token.contents.split()[0]
418+                except IndexError:
419+                    self.empty_block_tag(token)
420+                # execute callback function for this tag and append resulting node
421+                self.enter_command(command, token)
422+                try:
423+                    compile_func = self.tags[command]
424+                except KeyError:
425+                    self.invalid_block_tag(token, command)
426+                try:
427+                    compiled_result = compile_func(self, token)
428+                except TemplateSyntaxError, e:
429+                    if not self.compile_function_error(token, e):
430+                        raise
431+                self.extend_nodelist(nodelist, compiled_result, token)
432+                self.exit_command()
433+        if parse_until:
434+            self.unclosed_block_tag(parse_until)
435+        return nodelist
436+
437+    def parse_nodelist(self, parse_until=None):
438+        nodelist = self.parse(parse_until=parse_until)
439+        self.delete_first_token()
440+        return nodelist
441+
442+    def skip_past(self, endtag):
443+        while self.tokens:
444+            token = self.next_token()
445+            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
446+                return
447+        self.unclosed_block_tag([endtag])
448+
449+    def create_variable_node(self, expression):   
450+        return ExpressionNode(expression)
451+
452+    def create_nodelist(self):
453+        return NodeList()
454+
455+    def extend_nodelist(self, nodelist, node, token):
456+        if node.must_be_first and nodelist:
457+            try:
458+                if nodelist.contains_nontext:
459+                    raise AttributeError
460+            except AttributeError:
461+                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
462+        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
463+            nodelist.contains_nontext = True
464+        nodelist.append(node)
465+
466+    def enter_command(self, command, token):
467+        pass
468+
469+    def exit_command(self):
470+        pass
471+
472+    def error(self, token, msg):
473+        return TemplateSyntaxError(msg)
474+
475+    def empty_variable(self, token):
476+        raise self.error(token, "Empty variable tag")
477+
478+    def empty_block_tag(self, token):
479+        raise self.error(token, "Empty block tag")
480+
481+    def invalid_block_tag(self, token, command):
482+        raise self.error(token, "Invalid block tag: '%s'" % command)
483+
484+    def unclosed_block_tag(self, parse_until):
485+        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
486+
487+    def compile_function_error(self, token, e):
488+        pass
489+
490+    def next_token(self):
491+        return self.tokens.pop(0)
492+
493+    def prepend_token(self, token):
494+        self.tokens.insert(0, token)
495+
496+    def delete_first_token(self):
497+        del self.tokens[0]
498+
499+    def add_library(self, lib):
500+        self.tags.update(lib.tags)
501+        self.filters.update(lib.filters)
502+
503+    def token_stream(self, token):
504+        return TokenStream(self, token)
505+
506+    def compile_filter(self, token):
507+        stream = self.token_stream(token)
508+        expr = stream.parse_expression(required=True)
509+        if not stream.consumed():
510+            raise TemplateSyntaxError("Invalid filter expression")
511+        return expr
512+
513+    def find_filter(self, filter_name):
514+        if filter_name in self.filters:
515+            return self.filters[filter_name]
516+        else:
517+            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
518+
519+def filter_args_check(name, func, provided):
520+    provided = list(provided)
521+    plen = len(provided)
522+    # Check to see if a decorator is providing the real function.
523+    func = getattr(func, '_decorated_function', func)
524+    args, varargs, varkw, defaults = getargspec(func)
525+   
526+    if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)):
527+        return True
528+   
529+    # First argument is filter input.
530+    args.pop(0)
531+    if defaults:
532+        nondefs = args[:-len(defaults)]
533+    else:
534+        nondefs = args
535+    # Args without defaults must be provided.
536+    try:
537+        for arg in nondefs:
538+            provided.pop(0)
539+    except IndexError:
540+        # Not enough
541+        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
542+
543+    # Defaults can be overridden.
544+    defaults = defaults and list(defaults) or []
545+    try:
546+        for parg in provided:
547+            defaults.pop(0)
548+    except IndexError:
549+        # Too many.
550+        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
551+
552+    return True
553+
554+EOT = '<EOT>'
555+punctuation_chars = r':|=,;<>!?%&@"\'/()\[\]{}`*+-'
556+bit_re = re.compile(r"""
557+    (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')
558+    |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*)
559+    |(?P<char>[%s]) # punctuation
560+    |(?P<name>[^\s%s]+) # keyword or variable
561+""" % ((punctuation_chars,) * 2), re.VERBOSE)
562+STRING_LITERAL = "string_literal" #1
563+NUMERIC_LITERAL = "numeric_literal" #2
564+CHAR = "char" #3
565+NAME = "name" #4
566+
567+def unescape_string_literal(literal):
568+    q = literal[0]
569+    return literal.replace(r'\%s' % q, q).replace(r'\\', '')[1:-1]
570+
571+class TokenSyntaxError(Exception):
572+    pass
573+
574+def token_stream_parser(func):
575+    def wrapper(self, required=False, *args, **kwargs):
576+        mark = self.offset
577+        try:
578+            return func(self, *args, **kwargs)
579+        except TokenSyntaxError:
580+            if required:
581+                #FIXME: hack
582+                self.expected("<%s>" % ' '.join(func.__name__.split('_')[1:]))
583+            self.offset = mark
584+            raise
585+    return wraps(func)(wrapper)
586+
587+class TokenStream(object):
588+    def __init__(self, parser, source):
589+        self.parser = parser
590+        self.source = source
591+        self.offset = 0
592+        self.name = None
593+        self.token = None
594+        if isinstance(source, Token):
595+            bits = source.contents.split(None, 1)
596+            self.source = len(bits) == 2 and bits[1] or ''
597+            self.token = source
598+            self.name = bits[0]
599+        self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)]
600+
601+    def consumed(self):
602+        return self.offset == len(self.tokens)
603+
604+    def pop(self):
605+        if self.offset == len(self.tokens):
606+            raise TokenSyntaxError
607+        next = self.tokens[self.offset]
608+        self.offset += 1
609+        return next
610+
611+    def pop_lexem(self, match):
612+        if self.offset == len(self.tokens):
613+            return False
614+        tokentype, lexem = self.tokens[self.offset]
615+        if lexem == match:
616+            self.offset += 1
617+            return True
618+        return False
619+
620+    def pop_name(self):
621+        if self.offset == len(self.tokens):
622+            return None
623+        tokentype, lexem = self.tokens[self.offset]
624+        if tokentype == NAME:
625+            self.offset += 1
626+            return lexem
627+        return None
628+
629+    def pushback(self):
630+        self.offset -= 1
631+
632+    def syntax_error(self, msg):
633+        if self.name:
634+            msg = u"{%% %s %%} %s" % (self.name, msg)
635+        raise TemplateSyntaxError(msg)       
636+
637+    def expected(self, what):
638+        if self.consumed():
639+            found = EOT
640+        else:
641+            found = "<%s> %s" % self.tokens[self.offset]
642+        self.syntax_error("expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace')))
643+
644+    #@token_stream_parser
645+    def parse_string(self, bare=False):
646+        tokentype, lexem = self.pop()
647+        if tokentype == STRING_LITERAL:
648+            return unescape_string_literal(lexem)
649+        if bare and tokentype == NAME:
650+            return lexem
651+        raise TokenSyntaxError
652+    parse_string = token_stream_parser(parse_string)
653+
654+    #@token_stream_parser
655+    def parse_int(self):
656+        token_type, lexem = self.pop()
657+        if token_type == NUMERIC_LITERAL:
658+            try:
659+                return int(lexem)
660+            except ValueError:
661+                pass
662+        raise TokenSyntaxError
663+    parse_int = token_stream_parser(parse_int)
664+
665+    #@token_stream_parser
666+    def parse_value(self):
667+        translate = False
668+        if self.pop_lexem('_'):
669+            if not self.pop_lexem('('):
670+                raise TokenSyntaxError
671+            translate = True
672+
673+        tokentype, lexem = self.pop()   
674+        if tokentype == NAME:
675+            if lexem.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexem[0] == '_':
676+                raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexem)
677+            value = Lookup(tuple(lexem.split(VARIABLE_ATTRIBUTE_SEPARATOR)), var=lexem)
678+        elif tokentype == STRING_LITERAL:
679+            value = Literal(mark_safe(unescape_string_literal(lexem)))
680+        elif tokentype == NUMERIC_LITERAL:
681+            try:
682+                value = float(lexem)
683+            except ValueError:
684+                raise TokenSyntaxError
685+            if '.' not in lexem and 'e' not in lexem.lower():
686+                value = int(value)
687+            #FIXME: this causes a test failure: `ifequal-numeric07`
688+            if lexem.endswith('.'):
689+                raise TemplateSyntaxError, "Numeric literals may not end with '.': %s" % lexem
690+            value = Literal(value)
691+        elif tokentype == CHAR:
692+            raise TokenSyntaxError
693+
694+        if translate:
695+            if not self.pop_lexem(')'):
696+                raise TokenSyntaxError
697+            # Don't pass the empty string to gettext, because the empty
698+            # string translates to meta information
699+            value = FilterExpression(value, [(lambda x: x and ugettext(x) or u'', ())])
700+        return value
701+    parse_value = token_stream_parser(parse_value)
702+
703+    #@token_stream_parser
704+    def parse_filter(self):
705+        if not self.pop_lexem('|'):
706+            raise TokenSyntaxError
707+        name = self.pop_name()
708+        if not name:
709+            raise TokenSyntaxError
710+        args = []
711+        if self.pop_lexem(':'):
712+            args.append(self.parse_value())
713+        func = self.parser.find_filter(name)
714+        filter_args_check(name, func, args)
715+        return func, args
716+    parse_filter = token_stream_parser(parse_filter)
717+
718+    #@token_stream_parser
719+    def parse_expression(self):
720+        var = self.parse_value()
721+        filters = []
722+        try:
723+            while True:
724+                filters.append(self.parse_filter())
725+        except TokenSyntaxError:
726+            pass
727+        if filters:
728+            return FilterExpression(var, filters)
729+        return var
730+    parse_expression = token_stream_parser(parse_expression)
731+
732+    #@token_stream_parser
733+    def parse_expression_list(self, minimum=0, maximum=None, count=None):
734+        expressions = []
735+        if count:
736+            minimum = count
737+            maximum = count
738+        try:
739+            while True:
740+                if len(expressions) == maximum:
741+                    break           
742+                expressions.append(self.parse_expression())
743+        except TokenSyntaxError:
744+            pass
745+        if len(expressions) < minimum:
746+            self.expected("expression")
747+        return expressions
748+    parse_expression_list = token_stream_parser(parse_expression_list)
749+
750+
751+def uses_token_stream(func):
752+    def decorator(parser, token):
753+        bits = parser.token_stream(token)
754+        result = func(parser, bits)
755+        if bits.offset != len(bits.tokens):
756+            bits.expected(EOT)
757+        return result   
758+    return wraps(func)(decorator)
759+
760Index: django/template/__init__.py
761===================================================================
762--- django/template/__init__.py (revision 8969)
763+++ django/template/__init__.py (working copy)
764@@ -12,9 +12,9 @@
765 Node objects.
766 
767 Each Node is responsible for creating some sort of output -- e.g. simple text
768-(TextNode), variable values in a given context (VariableNode), results of basic
769+(TextNode), variable values in a given context (ExpressionNode), results of basic
770 logic (IfNode), results of looping (ForNode), or anything else. The core Node
771-types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
772+types are TextNode, ExpressionNode, IfNode and ForNode, but plugin modules can
773 define their own custom node types.
774 
775 Each Node has a render() method, which takes a Context and returns a string of
776@@ -48,904 +48,33 @@
777 >>> t.render(c)
778 u'<html></html>'
779 """
780-import re
781-from inspect import getargspec
782-from django.conf import settings
783+
784 from django.template.context import Context, RequestContext, ContextPopException
785-from django.utils.itercompat import is_iterable
786-from django.utils.functional import curry, Promise
787-from django.utils.text import smart_split
788-from django.utils.encoding import smart_unicode, force_unicode
789-from django.utils.translation import ugettext as _
790-from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
791-from django.utils.html import escape
792+from django.template.nodes import Node, NodeList, TextNode, ExpressionNode
793+from django.template.compiler import Origin, StringOrigin, Template, \
794+    TemplateSyntaxError, compile_string, TokenSyntaxError
795+from django.template.library import Library, InvalidTemplateLibrary, \
796+    get_library, add_to_builtins, libraries
797+from django.template.expressions import Expression, LookupError
798 
799-__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
800+#backcompat
801+from django.template.compat import Variable, VariableDoesNotExist, VariableNode, \
802+    resolve_variable, TokenParser
803+from django.template.loader import TemplateDoesNotExist
804 
805-TOKEN_TEXT = 0
806-TOKEN_VAR = 1
807-TOKEN_BLOCK = 2
808-TOKEN_COMMENT = 3
809+__all__ = (
810+    'Template', 'TemplateSyntaxError', 'compile_string', 'Origin', 'TokenSyntaxError',
811+    'Context', 'RequestContext',
812+    'Expression', 'LookupError',
813+    #FIXME: should these be public? 'Literal', 'Lookup', 'FilterExpression'
814+    'Library', 'InvalidTemplateLibrary', 'get_library', 'add_to_builtins', 'libraries',
815+    #backcompat
816+    'Variable', 'VariableDoesNotExist', 'resolve_variable', 'TemplateDoesNotExist', 'TokenParser', 'VariableNode',
817+)
818 
819-# template syntax constants
820-FILTER_SEPARATOR = '|'
821-FILTER_ARGUMENT_SEPARATOR = ':'
822-VARIABLE_ATTRIBUTE_SEPARATOR = '.'
823-BLOCK_TAG_START = '{%'
824-BLOCK_TAG_END = '%}'
825-VARIABLE_TAG_START = '{{'
826-VARIABLE_TAG_END = '}}'
827-COMMENT_TAG_START = '{#'
828-COMMENT_TAG_END = '#}'
829-SINGLE_BRACE_START = '{'
830-SINGLE_BRACE_END = '}'
831-
832-ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
833-
834-# what to report as the origin for templates that come from non-loader sources
835-# (e.g. strings)
836-UNKNOWN_SOURCE="&lt;unknown source&gt;"
837-
838-# match a variable or block tag and capture the entire tag, including start/end delimiters
839-tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
840-                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
841-                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
842-
843-# global dictionary of libraries that have been loaded using get_library
844-libraries = {}
845-# global list of libraries to load by default for a new parser
846-builtins = []
847-
848 # True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
849 # uninitialised.
850 invalid_var_format_string = None
851 
852-class TemplateSyntaxError(Exception):
853-    def __str__(self):
854-        try:
855-            import cStringIO as StringIO
856-        except ImportError:
857-            import StringIO
858-        output = StringIO.StringIO()
859-        output.write(Exception.__str__(self))
860-        # Check if we wrapped an exception and print that too.
861-        if hasattr(self, 'exc_info'):
862-            import traceback
863-            output.write('\n\nOriginal ')
864-            e = self.exc_info
865-            traceback.print_exception(e[0], e[1], e[2], 500, output)
866-        return output.getvalue()
867-
868-class TemplateDoesNotExist(Exception):
869-    pass
870-
871-class TemplateEncodingError(Exception):
872-    pass
873-
874-class VariableDoesNotExist(Exception):
875-
876-    def __init__(self, msg, params=()):
877-        self.msg = msg
878-        self.params = params
879-
880-    def __str__(self):
881-        return unicode(self).encode('utf-8')
882-
883-    def __unicode__(self):
884-        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
885-
886-class InvalidTemplateLibrary(Exception):
887-    pass
888-
889-class Origin(object):
890-    def __init__(self, name):
891-        self.name = name
892-
893-    def reload(self):
894-        raise NotImplementedError
895-
896-    def __str__(self):
897-        return self.name
898-
899-class StringOrigin(Origin):
900-    def __init__(self, source):
901-        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
902-        self.source = source
903-
904-    def reload(self):
905-        return self.source
906-
907-class Template(object):
908-    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
909-        try:
910-            template_string = smart_unicode(template_string)
911-        except UnicodeDecodeError:
912-            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
913-        if settings.TEMPLATE_DEBUG and origin is None:
914-            origin = StringOrigin(template_string)
915-        self.nodelist = compile_string(template_string, origin)
916-        self.name = name
917-
918-    def __iter__(self):
919-        for node in self.nodelist:
920-            for subnode in node:
921-                yield subnode
922-
923-    def render(self, context):
924-        "Display stage -- can be called many times"
925-        return self.nodelist.render(context)
926-
927-def compile_string(template_string, origin):
928-    "Compiles template_string into NodeList ready for rendering"
929-    if settings.TEMPLATE_DEBUG:
930-        from debug import DebugLexer, DebugParser
931-        lexer_class, parser_class = DebugLexer, DebugParser
932-    else:
933-        lexer_class, parser_class = Lexer, Parser
934-    lexer = lexer_class(template_string, origin)
935-    parser = parser_class(lexer.tokenize())
936-    return parser.parse()
937-
938-class Token(object):
939-    def __init__(self, token_type, contents):
940-        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
941-        self.token_type, self.contents = token_type, contents
942-
943-    def __str__(self):
944-        return '<%s token: "%s...">' % \
945-            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
946-            self.contents[:20].replace('\n', ''))
947-
948-    def split_contents(self):
949-        split = []
950-        bits = iter(smart_split(self.contents))
951-        for bit in bits:
952-            # Handle translation-marked template pieces
953-            if bit.startswith('_("') or bit.startswith("_('"):
954-                sentinal = bit[2] + ')'
955-                trans_bit = [bit]
956-                while not bit.endswith(sentinal):
957-                    bit = bits.next()
958-                    trans_bit.append(bit)
959-                bit = ' '.join(trans_bit)
960-            split.append(bit)
961-        return split
962-
963-class Lexer(object):
964-    def __init__(self, template_string, origin):
965-        self.template_string = template_string
966-        self.origin = origin
967-
968-    def tokenize(self):
969-        "Return a list of tokens from a given template_string."
970-        in_tag = False
971-        result = []
972-        for bit in tag_re.split(self.template_string):
973-            if bit:
974-                result.append(self.create_token(bit, in_tag))
975-            in_tag = not in_tag
976-        return result
977-
978-    def create_token(self, token_string, in_tag):
979-        """
980-        Convert the given token string into a new Token object and return it.
981-        If in_tag is True, we are processing something that matched a tag,
982-        otherwise it should be treated as a literal string.
983-        """
984-        if in_tag:
985-            if token_string.startswith(VARIABLE_TAG_START):
986-                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
987-            elif token_string.startswith(BLOCK_TAG_START):
988-                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
989-            elif token_string.startswith(COMMENT_TAG_START):
990-                token = Token(TOKEN_COMMENT, '')
991-        else:
992-            token = Token(TOKEN_TEXT, token_string)
993-        return token
994-
995-class Parser(object):
996-    def __init__(self, tokens):
997-        self.tokens = tokens
998-        self.tags = {}
999-        self.filters = {}
1000-        for lib in builtins:
1001-            self.add_library(lib)
1002-
1003-    def parse(self, parse_until=None):
1004-        if parse_until is None: parse_until = []
1005-        nodelist = self.create_nodelist()
1006-        while self.tokens:
1007-            token = self.next_token()
1008-            if token.token_type == TOKEN_TEXT:
1009-                self.extend_nodelist(nodelist, TextNode(token.contents), token)
1010-            elif token.token_type == TOKEN_VAR:
1011-                if not token.contents:
1012-                    self.empty_variable(token)
1013-                filter_expression = self.compile_filter(token.contents)
1014-                var_node = self.create_variable_node(filter_expression)
1015-                self.extend_nodelist(nodelist, var_node,token)
1016-            elif token.token_type == TOKEN_BLOCK:
1017-                if token.contents in parse_until:
1018-                    # put token back on token list so calling code knows why it terminated
1019-                    self.prepend_token(token)
1020-                    return nodelist
1021-                try:
1022-                    command = token.contents.split()[0]
1023-                except IndexError:
1024-                    self.empty_block_tag(token)
1025-                # execute callback function for this tag and append resulting node
1026-                self.enter_command(command, token)
1027-                try:
1028-                    compile_func = self.tags[command]
1029-                except KeyError:
1030-                    self.invalid_block_tag(token, command)
1031-                try:
1032-                    compiled_result = compile_func(self, token)
1033-                except TemplateSyntaxError, e:
1034-                    if not self.compile_function_error(token, e):
1035-                        raise
1036-                self.extend_nodelist(nodelist, compiled_result, token)
1037-                self.exit_command()
1038-        if parse_until:
1039-            self.unclosed_block_tag(parse_until)
1040-        return nodelist
1041-
1042-    def skip_past(self, endtag):
1043-        while self.tokens:
1044-            token = self.next_token()
1045-            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
1046-                return
1047-        self.unclosed_block_tag([endtag])
1048-
1049-    def create_variable_node(self, filter_expression):
1050-        return VariableNode(filter_expression)
1051-
1052-    def create_nodelist(self):
1053-        return NodeList()
1054-
1055-    def extend_nodelist(self, nodelist, node, token):
1056-        if node.must_be_first and nodelist:
1057-            try:
1058-                if nodelist.contains_nontext:
1059-                    raise AttributeError
1060-            except AttributeError:
1061-                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
1062-        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
1063-            nodelist.contains_nontext = True
1064-        nodelist.append(node)
1065-
1066-    def enter_command(self, command, token):
1067-        pass
1068-
1069-    def exit_command(self):
1070-        pass
1071-
1072-    def error(self, token, msg):
1073-        return TemplateSyntaxError(msg)
1074-
1075-    def empty_variable(self, token):
1076-        raise self.error(token, "Empty variable tag")
1077-
1078-    def empty_block_tag(self, token):
1079-        raise self.error(token, "Empty block tag")
1080-
1081-    def invalid_block_tag(self, token, command):
1082-        raise self.error(token, "Invalid block tag: '%s'" % command)
1083-
1084-    def unclosed_block_tag(self, parse_until):
1085-        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
1086-
1087-    def compile_function_error(self, token, e):
1088-        pass
1089-
1090-    def next_token(self):
1091-        return self.tokens.pop(0)
1092-
1093-    def prepend_token(self, token):
1094-        self.tokens.insert(0, token)
1095-
1096-    def delete_first_token(self):
1097-        del self.tokens[0]
1098-
1099-    def add_library(self, lib):
1100-        self.tags.update(lib.tags)
1101-        self.filters.update(lib.filters)
1102-
1103-    def compile_filter(self, token):
1104-        "Convenient wrapper for FilterExpression"
1105-        return FilterExpression(token, self)
1106-
1107-    def find_filter(self, filter_name):
1108-        if filter_name in self.filters:
1109-            return self.filters[filter_name]
1110-        else:
1111-            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
1112-
1113-class TokenParser(object):
1114-    """
1115-    Subclass this and implement the top() method to parse a template line. When
1116-    instantiating the parser, pass in the line from the Django template parser.
1117-
1118-    The parser's "tagname" instance-variable stores the name of the tag that
1119-    the filter was called with.
1120-    """
1121-    def __init__(self, subject):
1122-        self.subject = subject
1123-        self.pointer = 0
1124-        self.backout = []
1125-        self.tagname = self.tag()
1126-
1127-    def top(self):
1128-        "Overload this method to do the actual parsing and return the result."
1129-        raise NotImplementedError()
1130-
1131-    def more(self):
1132-        "Returns True if there is more stuff in the tag."
1133-        return self.pointer < len(self.subject)
1134-
1135-    def back(self):
1136-        "Undoes the last microparser. Use this for lookahead and backtracking."
1137-        if not len(self.backout):
1138-            raise TemplateSyntaxError("back called without some previous parsing")
1139-        self.pointer = self.backout.pop()
1140-
1141-    def tag(self):
1142-        "A microparser that just returns the next tag from the line."
1143-        subject = self.subject
1144-        i = self.pointer
1145-        if i >= len(subject):
1146-            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
1147-        p = i
1148-        while i < len(subject) and subject[i] not in (' ', '\t'):
1149-            i += 1
1150-        s = subject[p:i]
1151-        while i < len(subject) and subject[i] in (' ', '\t'):
1152-            i += 1
1153-        self.backout.append(self.pointer)
1154-        self.pointer = i
1155-        return s
1156-
1157-    def value(self):
1158-        "A microparser that parses for a value: some string constant or variable name."
1159-        subject = self.subject
1160-        i = self.pointer
1161-        if i >= len(subject):
1162-            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
1163-        if subject[i] in ('"', "'"):
1164-            p = i
1165-            i += 1
1166-            while i < len(subject) and subject[i] != subject[p]:
1167-                i += 1
1168-            if i >= len(subject):
1169-                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
1170-            i += 1
1171-            res = subject[p:i]
1172-            while i < len(subject) and subject[i] in (' ', '\t'):
1173-                i += 1
1174-            self.backout.append(self.pointer)
1175-            self.pointer = i
1176-            return res
1177-        else:
1178-            p = i
1179-            while i < len(subject) and subject[i] not in (' ', '\t'):
1180-                if subject[i] in ('"', "'"):
1181-                    c = subject[i]
1182-                    i += 1
1183-                    while i < len(subject) and subject[i] != c:
1184-                        i += 1
1185-                    if i >= len(subject):
1186-                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
1187-                i += 1
1188-            s = subject[p:i]
1189-            while i < len(subject) and subject[i] in (' ', '\t'):
1190-                i += 1
1191-            self.backout.append(self.pointer)
1192-            self.pointer = i
1193-            return s
1194-
1195-filter_raw_string = r"""
1196-^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
1197-^"(?P<constant>%(str)s)"|
1198-^(?P<var>[%(var_chars)s]+)|
1199- (?:%(filter_sep)s
1200-     (?P<filter_name>\w+)
1201-         (?:%(arg_sep)s
1202-             (?:
1203-              %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
1204-              "(?P<constant_arg>%(str)s)"|
1205-              (?P<var_arg>[%(var_chars)s]+)
1206-             )
1207-         )?
1208- )""" % {
1209-    'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
1210-    'var_chars': "\w\." ,
1211-    'filter_sep': re.escape(FILTER_SEPARATOR),
1212-    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
1213-    'i18n_open' : re.escape("_("),
1214-    'i18n_close' : re.escape(")"),
1215-  }
1216-
1217-filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
1218-filter_re = re.compile(filter_raw_string, re.UNICODE)
1219-
1220-class FilterExpression(object):
1221-    """
1222-    Parses a variable token and its optional filters (all as a single string),
1223-    and return a list of tuples of the filter name and arguments.
1224-    Sample:
1225-        >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
1226-        >>> p = Parser('')
1227-        >>> fe = FilterExpression(token, p)
1228-        >>> len(fe.filters)
1229-        2
1230-        >>> fe.var
1231-        <Variable: 'variable'>
1232-
1233-    This class should never be instantiated outside of the
1234-    get_filters_from_token helper function.
1235-    """
1236-    def __init__(self, token, parser):
1237-        self.token = token
1238-        matches = filter_re.finditer(token)
1239-        var = None
1240-        filters = []
1241-        upto = 0
1242-        for match in matches:
1243-            start = match.start()
1244-            if upto != start:
1245-                raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
1246-                                           (token[:upto], token[upto:start], token[start:]))
1247-            if var == None:
1248-                var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
1249-                if i18n_constant is not None:
1250-                    # Don't pass the empty string to gettext, because the empty
1251-                    # string translates to meta information.
1252-                    if i18n_constant == "":
1253-                        var = '""'
1254-                    else:
1255-                        var = '"%s"' %  _(i18n_constant.replace(r'\"', '"'))
1256-                elif constant is not None:
1257-                    var = '"%s"' % constant.replace(r'\"', '"')
1258-                upto = match.end()
1259-                if var == None:
1260-                    raise TemplateSyntaxError("Could not find variable at start of %s" % token)
1261-                elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
1262-                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
1263-            else:
1264-                filter_name = match.group("filter_name")
1265-                args = []
1266-                constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
1267-                if i18n_arg:
1268-                    args.append((False, _(i18n_arg.replace(r'\"', '"'))))
1269-                elif constant_arg is not None:
1270-                    args.append((False, constant_arg.replace(r'\"', '"')))
1271-                elif var_arg:
1272-                    args.append((True, Variable(var_arg)))
1273-                filter_func = parser.find_filter(filter_name)
1274-                self.args_check(filter_name,filter_func, args)
1275-                filters.append( (filter_func,args))
1276-                upto = match.end()
1277-        if upto != len(token):
1278-            raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
1279-        self.filters = filters
1280-        self.var = Variable(var)
1281-
1282-    def resolve(self, context, ignore_failures=False):
1283-        try:
1284-            obj = self.var.resolve(context)
1285-        except VariableDoesNotExist:
1286-            if ignore_failures:
1287-                obj = None
1288-            else:
1289-                if settings.TEMPLATE_STRING_IF_INVALID:
1290-                    global invalid_var_format_string
1291-                    if invalid_var_format_string is None:
1292-                        invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
1293-                    if invalid_var_format_string:
1294-                        return settings.TEMPLATE_STRING_IF_INVALID % self.var
1295-                    return settings.TEMPLATE_STRING_IF_INVALID
1296-                else:
1297-                    obj = settings.TEMPLATE_STRING_IF_INVALID
1298-        for func, args in self.filters:
1299-            arg_vals = []
1300-            for lookup, arg in args:
1301-                if not lookup:
1302-                    arg_vals.append(mark_safe(arg))
1303-                else:
1304-                    arg_vals.append(arg.resolve(context))
1305-            if getattr(func, 'needs_autoescape', False):
1306-                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
1307-            else:
1308-                new_obj = func(obj, *arg_vals)
1309-            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
1310-                obj = mark_safe(new_obj)
1311-            elif isinstance(obj, EscapeData):
1312-                obj = mark_for_escaping(new_obj)
1313-            else:
1314-                obj = new_obj
1315-        return obj
1316-
1317-    def args_check(name, func, provided):
1318-        provided = list(provided)
1319-        plen = len(provided)
1320-        # Check to see if a decorator is providing the real function.
1321-        func = getattr(func, '_decorated_function', func)
1322-        args, varargs, varkw, defaults = getargspec(func)
1323-        # First argument is filter input.
1324-        args.pop(0)
1325-        if defaults:
1326-            nondefs = args[:-len(defaults)]
1327-        else:
1328-            nondefs = args
1329-        # Args without defaults must be provided.
1330-        try:
1331-            for arg in nondefs:
1332-                provided.pop(0)
1333-        except IndexError:
1334-            # Not enough
1335-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
1336-
1337-        # Defaults can be overridden.
1338-        defaults = defaults and list(defaults) or []
1339-        try:
1340-            for parg in provided:
1341-                defaults.pop(0)
1342-        except IndexError:
1343-            # Too many.
1344-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
1345-
1346-        return True
1347-    args_check = staticmethod(args_check)
1348-
1349-    def __str__(self):
1350-        return self.token
1351-
1352-def resolve_variable(path, context):
1353-    """
1354-    Returns the resolved variable, which may contain attribute syntax, within
1355-    the given context.
1356-
1357-    Deprecated; use the Variable class instead.
1358-    """
1359-    return Variable(path).resolve(context)
1360-
1361-class Variable(object):
1362-    """
1363-    A template variable, resolvable against a given context. The variable may be
1364-    a hard-coded string (if it begins and ends with single or double quote
1365-    marks)::
1366-
1367-        >>> c = {'article': {'section':u'News'}}
1368-        >>> Variable('article.section').resolve(c)
1369-        u'News'
1370-        >>> Variable('article').resolve(c)
1371-        {'section': u'News'}
1372-        >>> class AClass: pass
1373-        >>> c = AClass()
1374-        >>> c.article = AClass()
1375-        >>> c.article.section = u'News'
1376-        >>> Variable('article.section').resolve(c)
1377-        u'News'
1378-
1379-    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
1380-    """
1381-
1382-    def __init__(self, var):
1383-        self.var = var
1384-        self.literal = None
1385-        self.lookups = None
1386-        self.translate = False
1387-
1388-        try:
1389-            # First try to treat this variable as a number.
1390-            #
1391-            # Note that this could cause an OverflowError here that we're not
1392-            # catching. Since this should only happen at compile time, that's
1393-            # probably OK.
1394-            self.literal = float(var)
1395-
1396-            # So it's a float... is it an int? If the original value contained a
1397-            # dot or an "e" then it was a float, not an int.
1398-            if '.' not in var and 'e' not in var.lower():
1399-                self.literal = int(self.literal)
1400-
1401-            # "2." is invalid
1402-            if var.endswith('.'):
1403-                raise ValueError
1404-
1405-        except ValueError:
1406-            # A ValueError means that the variable isn't a number.
1407-            if var.startswith('_(') and var.endswith(')'):
1408-                # The result of the lookup should be translated at rendering
1409-                # time.
1410-                self.translate = True
1411-                var = var[2:-1]
1412-            # If it's wrapped with quotes (single or double), then
1413-            # we're also dealing with a literal.
1414-            if var[0] in "\"'" and var[0] == var[-1]:
1415-                self.literal = mark_safe(var[1:-1])
1416-            else:
1417-                # Otherwise we'll set self.lookups so that resolve() knows we're
1418-                # dealing with a bonafide variable
1419-                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
1420-
1421-    def resolve(self, context):
1422-        """Resolve this variable against a given context."""
1423-        if self.lookups is not None:
1424-            # We're dealing with a variable that needs to be resolved
1425-            value = self._resolve_lookup(context)
1426-        else:
1427-            # We're dealing with a literal, so it's already been "resolved"
1428-            value = self.literal
1429-        if self.translate:
1430-            return _(value)
1431-        return value
1432-
1433-    def __repr__(self):
1434-        return "<%s: %r>" % (self.__class__.__name__, self.var)
1435-
1436-    def __str__(self):
1437-        return self.var
1438-
1439-    def _resolve_lookup(self, context):
1440-        """
1441-        Performs resolution of a real variable (i.e. not a literal) against the
1442-        given context.
1443-
1444-        As indicated by the method's name, this method is an implementation
1445-        detail and shouldn't be called by external code. Use Variable.resolve()
1446-        instead.
1447-        """
1448-        current = context
1449-        for bit in self.lookups:
1450-            try: # dictionary lookup
1451-                current = current[bit]
1452-            except (TypeError, AttributeError, KeyError):
1453-                try: # attribute lookup
1454-                    current = getattr(current, bit)
1455-                    if callable(current):
1456-                        if getattr(current, 'alters_data', False):
1457-                            current = settings.TEMPLATE_STRING_IF_INVALID
1458-                        else:
1459-                            try: # method call (assuming no args required)
1460-                                current = current()
1461-                            except TypeError: # arguments *were* required
1462-                                # GOTCHA: This will also catch any TypeError
1463-                                # raised in the function itself.
1464-                                current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
1465-                            except Exception, e:
1466-                                if getattr(e, 'silent_variable_failure', False):
1467-                                    current = settings.TEMPLATE_STRING_IF_INVALID
1468-                                else:
1469-                                    raise
1470-                except (TypeError, AttributeError):
1471-                    try: # list-index lookup
1472-                        current = current[int(bit)]
1473-                    except (IndexError, # list index out of range
1474-                            ValueError, # invalid literal for int()
1475-                            KeyError,   # current is a dict without `int(bit)` key
1476-                            TypeError,  # unsubscriptable object
1477-                            ):
1478-                        raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
1479-                except Exception, e:
1480-                    if getattr(e, 'silent_variable_failure', False):
1481-                        current = settings.TEMPLATE_STRING_IF_INVALID
1482-                    else:
1483-                        raise
1484-
1485-        return current
1486-
1487-class Node(object):
1488-    # Set this to True for nodes that must be first in the template (although
1489-    # they can be preceded by text nodes.
1490-    must_be_first = False
1491-
1492-    def render(self, context):
1493-        "Return the node rendered as a string"
1494-        pass
1495-
1496-    def __iter__(self):
1497-        yield self
1498-
1499-    def get_nodes_by_type(self, nodetype):
1500-        "Return a list of all nodes (within this node and its nodelist) of the given type"
1501-        nodes = []
1502-        if isinstance(self, nodetype):
1503-            nodes.append(self)
1504-        if hasattr(self, 'nodelist'):
1505-            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
1506-        return nodes
1507-
1508-class NodeList(list):
1509-    # Set to True the first time a non-TextNode is inserted by
1510-    # extend_nodelist().
1511-    contains_nontext = False
1512-
1513-    def render(self, context):
1514-        bits = []
1515-        for node in self:
1516-            if isinstance(node, Node):
1517-                bits.append(self.render_node(node, context))
1518-            else:
1519-                bits.append(node)
1520-        return mark_safe(''.join([force_unicode(b) for b in bits]))
1521-
1522-    def get_nodes_by_type(self, nodetype):
1523-        "Return a list of all nodes of the given type"
1524-        nodes = []
1525-        for node in self:
1526-            nodes.extend(node.get_nodes_by_type(nodetype))
1527-        return nodes
1528-
1529-    def render_node(self, node, context):
1530-        return node.render(context)
1531-
1532-class TextNode(Node):
1533-    def __init__(self, s):
1534-        self.s = s
1535-
1536-    def __repr__(self):
1537-        return "<Text Node: '%s'>" % self.s[:25]
1538-
1539-    def render(self, context):
1540-        return self.s
1541-
1542-class VariableNode(Node):
1543-    def __init__(self, filter_expression):
1544-        self.filter_expression = filter_expression
1545-
1546-    def __repr__(self):
1547-        return "<Variable Node: %s>" % self.filter_expression
1548-
1549-    def render(self, context):
1550-        try:
1551-            output = force_unicode(self.filter_expression.resolve(context))
1552-        except UnicodeDecodeError:
1553-            # Unicode conversion can fail sometimes for reasons out of our
1554-            # control (e.g. exception rendering). In that case, we fail quietly.
1555-            return ''
1556-        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
1557-            return force_unicode(escape(output))
1558-        else:
1559-            return force_unicode(output)
1560-
1561-def generic_tag_compiler(params, defaults, name, node_class, parser, token):
1562-    "Returns a template.Node subclass."
1563-    bits = token.split_contents()[1:]
1564-    bmax = len(params)
1565-    def_len = defaults and len(defaults) or 0
1566-    bmin = bmax - def_len
1567-    if(len(bits) < bmin or len(bits) > bmax):
1568-        if bmin == bmax:
1569-            message = "%s takes %s arguments" % (name, bmin)
1570-        else:
1571-            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
1572-        raise TemplateSyntaxError(message)
1573-    return node_class(bits)
1574-
1575-class Library(object):
1576-    def __init__(self):
1577-        self.filters = {}
1578-        self.tags = {}
1579-
1580-    def tag(self, name=None, compile_function=None):
1581-        if name == None and compile_function == None:
1582-            # @register.tag()
1583-            return self.tag_function
1584-        elif name != None and compile_function == None:
1585-            if(callable(name)):
1586-                # @register.tag
1587-                return self.tag_function(name)
1588-            else:
1589-                # @register.tag('somename') or @register.tag(name='somename')
1590-                def dec(func):
1591-                    return self.tag(name, func)
1592-                return dec
1593-        elif name != None and compile_function != None:
1594-            # register.tag('somename', somefunc)
1595-            self.tags[name] = compile_function
1596-            return compile_function
1597-        else:
1598-            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
1599-
1600-    def tag_function(self,func):
1601-        self.tags[getattr(func, "_decorated_function", func).__name__] = func
1602-        return func
1603-
1604-    def filter(self, name=None, filter_func=None):
1605-        if name == None and filter_func == None:
1606-            # @register.filter()
1607-            return self.filter_function
1608-        elif filter_func == None:
1609-            if(callable(name)):
1610-                # @register.filter
1611-                return self.filter_function(name)
1612-            else:
1613-                # @register.filter('somename') or @register.filter(name='somename')
1614-                def dec(func):
1615-                    return self.filter(name, func)
1616-                return dec
1617-        elif name != None and filter_func != None:
1618-            # register.filter('somename', somefunc)
1619-            self.filters[name] = filter_func
1620-            return filter_func
1621-        else:
1622-            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
1623-
1624-    def filter_function(self, func):
1625-        self.filters[getattr(func, "_decorated_function", func).__name__] = func
1626-        return func
1627-
1628-    def simple_tag(self,func):
1629-        params, xx, xxx, defaults = getargspec(func)
1630-
1631-        class SimpleNode(Node):
1632-            def __init__(self, vars_to_resolve):
1633-                self.vars_to_resolve = map(Variable, vars_to_resolve)
1634-
1635-            def render(self, context):
1636-                resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
1637-                return func(*resolved_vars)
1638-
1639-        compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
1640-        compile_func.__doc__ = func.__doc__
1641-        self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
1642-        return func
1643-
1644-    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
1645-        def dec(func):
1646-            params, xx, xxx, defaults = getargspec(func)
1647-            if takes_context:
1648-                if params[0] == 'context':
1649-                    params = params[1:]
1650-                else:
1651-                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
1652-
1653-            class InclusionNode(Node):
1654-                def __init__(self, vars_to_resolve):
1655-                    self.vars_to_resolve = map(Variable, vars_to_resolve)
1656-
1657-                def render(self, context):
1658-                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
1659-                    if takes_context:
1660-                        args = [context] + resolved_vars
1661-                    else:
1662-                        args = resolved_vars
1663-
1664-                    dict = func(*args)
1665-
1666-                    if not getattr(self, 'nodelist', False):
1667-                        from django.template.loader import get_template, select_template
1668-                        if not isinstance(file_name, basestring) and is_iterable(file_name):
1669-                            t = select_template(file_name)
1670-                        else:
1671-                            t = get_template(file_name)
1672-                        self.nodelist = t.nodelist
1673-                    return self.nodelist.render(context_class(dict,
1674-                            autoescape=context.autoescape))
1675-
1676-            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
1677-            compile_func.__doc__ = func.__doc__
1678-            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
1679-            return func
1680-        return dec
1681-
1682-def get_library(module_name):
1683-    lib = libraries.get(module_name, None)
1684-    if not lib:
1685-        try:
1686-            mod = __import__(module_name, {}, {}, [''])
1687-        except ImportError, e:
1688-            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
1689-        try:
1690-            lib = mod.register
1691-            libraries[module_name] = lib
1692-        except AttributeError:
1693-            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
1694-    return lib
1695-
1696-def add_to_builtins(module_name):
1697-    builtins.append(get_library(module_name))
1698-
1699 add_to_builtins('django.template.defaulttags')
1700 add_to_builtins('django.template.defaultfilters')
1701Index: django/template/utils.py
1702===================================================================
1703--- django/template/utils.py    (revision 0)
1704+++ django/template/utils.py    (revision 0)
1705@@ -0,0 +1,80 @@
1706+import re
1707+from django.template import Node, NodeList, TokenSyntaxError, TemplateSyntaxError
1708+
1709+class EmptyNode(Node):
1710+    def render(self, context):
1711+        return u''
1712+       
1713+class ConditionalNode(Node):
1714+    def __init__(self, nodelist_true, nodelist_false):
1715+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1716+
1717+    def __iter__(self):
1718+        for node in self.nodelist_true:
1719+            yield node
1720+        for node in self.nodelist_false:
1721+            yield node
1722+
1723+    def get_nodes_by_type(self, nodetype):
1724+        nodes = []
1725+        if isinstance(self, nodetype):
1726+            nodes.append(self)
1727+        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
1728+        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
1729+        return nodes
1730+       
1731+    def check_condition(self, context):
1732+        return False
1733+       
1734+    def render(self, context):
1735+        if self.check_condition(context):
1736+            return self.nodelist_true.render(context)
1737+        elif self.nodelist_false:
1738+            return self.nodelist_false.render(context)
1739+        return u''
1740+
1741+def parse_conditional_nodelists(parser, name):
1742+    end_tag = 'end' + name
1743+    nodelist_true = parser.parse(('else', end_tag))
1744+    token = parser.next_token()
1745+    if token.contents == 'else':
1746+        nodelist_false = parser.parse((end_tag,))
1747+        parser.delete_first_token()
1748+    else:
1749+        nodelist_false = NodeList()
1750+    return nodelist_true, nodelist_false
1751+
1752+def parse_args_and_kwargs(bits, until=()):
1753+    args = []
1754+    kwargs = {}
1755+    while True:
1756+        name = bits.pop_name()
1757+        if name in until:
1758+            bits.pushback()
1759+            break
1760+        if name and bits.pop_lexem('='):
1761+            kwargs[name] = bits.parse_expression(required=True)
1762+        else:
1763+            if name:
1764+                bits.pushback()
1765+            try:
1766+                args.append(bits.parse_expression())
1767+            except TokenSyntaxError:
1768+                break
1769+        if not bits.pop_lexem(','):
1770+            break
1771+    return args, kwargs
1772+
1773+def parse_as(bits):
1774+    if bits.pop_lexem('as'):
1775+        name = bits.pop_name()
1776+        if name:
1777+            return name
1778+    raise TokenSyntaxError
1779+
1780+def resolve_args_and_kwargs(args, kwargs, context):
1781+       resolved_args = [arg.resolve(context, True) for arg in args]
1782+       resolved_kwargs = {}
1783+       for name in kwargs:
1784+               resolved_kwargs[name] = kwargs[name].resolve(context, True)
1785+       return resolved_args, resolved_kwargs
1786Index: django/template/expressions.py
1787===================================================================
1788--- django/template/expressions.py      (revision 0)
1789+++ django/template/expressions.py      (revision 0)
1790@@ -0,0 +1,114 @@
1791+from django.conf import settings
1792+from django.utils.encoding import force_unicode
1793+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
1794+
1795+__all__ = ('LookupError', 'Expression', 'Variable', 'Literal', 'Lookup', 'FilterExpression')
1796+
1797+class LookupError(Exception):
1798+    def __init__(self, var, msg, params=()):
1799+        self.var = var
1800+        self.msg = msg
1801+        self.params = params
1802+
1803+    def __str__(self):
1804+        return unicode(self).encode('utf-8')
1805+
1806+    def __unicode__(self):
1807+        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
1808+
1809+
1810+class Expression(object):
1811+    def resolve_safe(self, context, default=None):
1812+        try:
1813+            return self.resolve(context)
1814+        except LookupError:
1815+            return default
1816+
1817+    def resolve(self, context):
1818+        pass
1819+
1820+class Literal(Expression):
1821+    def __init__(self, value):
1822+        self.value = value
1823+
1824+    def resolve(self, context):
1825+        return self.value
1826+       
1827+class Lookup(Expression):
1828+    def __init__(self, lookups, var=None):
1829+        self.var = var
1830+        self.lookups = lookups
1831+
1832+    def __str__(self):
1833+        return "%s" % self.var
1834+       
1835+    def resolve(self, context):
1836+        current = context
1837+        for bit in self.lookups:
1838+            try: # dictionary lookup
1839+                current = current[bit]
1840+            except (TypeError, AttributeError, KeyError):
1841+                try: # attribute lookup
1842+                    current = getattr(current, bit)
1843+                    if callable(current):
1844+                        if getattr(current, 'alters_data', False):
1845+                            current = settings.TEMPLATE_STRING_IF_INVALID
1846+                        else:
1847+                            try: # method call (assuming no args required)
1848+                                current = current()
1849+                            except TypeError: # arguments *were* required
1850+                                # GOTCHA: This will also catch any TypeError
1851+                                # raised in the function itself.
1852+                                current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
1853+                            except Exception, e:
1854+                                if getattr(e, 'silent_variable_failure', False):
1855+                                    current = settings.TEMPLATE_STRING_IF_INVALID
1856+                                else:
1857+                                    raise
1858+                except (TypeError, AttributeError):
1859+                    try: # list-index lookup
1860+                        current = current[int(bit)]
1861+                    except (IndexError, # list index out of range
1862+                            ValueError, # invalid literal for int()
1863+                            KeyError,   # current is a dict without `int(bit)` key
1864+                            TypeError,  # unsubscriptable object
1865+                            ):
1866+                        raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current))
1867+                except Exception, e:
1868+                    if getattr(e, 'silent_variable_failure', False):
1869+                        current = settings.TEMPLATE_STRING_IF_INVALID
1870+                    else:
1871+                        raise
1872+
1873+        return current
1874+
1875+class FilterExpression(Expression):
1876+    def __init__(self, root, filters):
1877+        self.root = root
1878+        self.filters = filters
1879+
1880+    def resolve(self, context):
1881+        try:
1882+            obj = self.root.resolve(context)
1883+        except LookupError:
1884+            if not self.filters:
1885+                raise
1886+            obj = settings.TEMPLATE_STRING_IF_INVALID
1887+        for func, args in self.filters:
1888+            arg_vals = []         
1889+            for arg in args:
1890+                arg_vals.append(arg.resolve(context))
1891+            if getattr(func, 'needs_autoescape', False):
1892+                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
1893+            else:
1894+                new_obj = func(obj, *arg_vals)
1895+            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
1896+                obj = mark_safe(new_obj)
1897+            elif isinstance(obj, EscapeData):
1898+                obj = mark_for_escaping(new_obj)
1899+            else:
1900+                obj = new_obj
1901+        return obj
1902+
1903+    def __str__(self):
1904+        return str(self.root)+'|<filtered>'
1905Index: django/template/defaulttags.py
1906===================================================================
1907--- django/template/defaulttags.py      (revision 8969)
1908+++ django/template/defaulttags.py      (working copy)
1909@@ -8,11 +8,13 @@
1910 except NameError:
1911     from django.utils.itercompat import reversed     # Python 2.3 fallback
1912 
1913-from django.template import Node, NodeList, Template, Context, Variable
1914-from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
1915+from django.template import Node, NodeList, Template, Context
1916+from django.template import TemplateSyntaxError, LookupError, TokenSyntaxError
1917 from django.template import get_library, Library, InvalidTemplateLibrary
1918+from django.template.compiler import uses_token_stream, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
1919+from django.template.utils import EmptyNode, ConditionalNode, parse_conditional_nodelists, parse_as, parse_args_and_kwargs
1920 from django.conf import settings
1921-from django.utils.encoding import smart_str, smart_unicode
1922+from django.utils.encoding import smart_str, force_unicode
1923 from django.utils.itercompat import groupby
1924 from django.utils.safestring import mark_safe
1925 
1926@@ -33,17 +35,15 @@
1927         else:
1928             return output
1929 
1930-class CommentNode(Node):
1931-    def render(self, context):
1932-        return ''
1933+class CommentNode(EmptyNode): pass
1934 
1935 class CycleNode(Node):
1936-    def __init__(self, cyclevars, variable_name=None):
1937-        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
1938+    def __init__(self, cyclevals, variable_name=None):
1939+        self.cycle_iter = itertools_cycle(cyclevals)
1940         self.variable_name = variable_name
1941 
1942     def render(self, context):
1943-        value = self.cycle_iter.next().resolve(context)
1944+        value = self.cycle_iter.next().resolve_safe(context)
1945         if self.variable_name:
1946             context[self.variable_name] = value
1947         return value
1948@@ -62,60 +62,46 @@
1949 
1950     def render(self, context):
1951         output = self.nodelist.render(context)
1952-        # Apply filters.
1953         context.update({'var': output})
1954-        filtered = self.filter_expr.resolve(context)
1955+        filtered = self.filter_expr.resolve_safe(context, default='')
1956         context.pop()
1957         return filtered
1958 
1959 class FirstOfNode(Node):
1960-    def __init__(self, vars):
1961-        self.vars = map(Variable, vars)
1962+    def __init__(self, vals):
1963+        self.vals = vals
1964 
1965     def render(self, context):
1966-        for var in self.vars:
1967-            try:
1968-                value = var.resolve(context)
1969-            except VariableDoesNotExist:
1970-                continue
1971+        for val in self.vals:
1972+            value = val.resolve_safe(context)
1973             if value:
1974-                return smart_unicode(value)
1975+                return value
1976         return u''
1977 
1978 class ForNode(Node):
1979-    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
1980+    def __init__(self, loopvars, sequence, is_reversed, nodelist):
1981         self.loopvars, self.sequence = loopvars, sequence
1982         self.is_reversed = is_reversed
1983-        self.nodelist_loop = nodelist_loop
1984+        self.nodelist = nodelist
1985 
1986     def __repr__(self):
1987         reversed_text = self.is_reversed and ' reversed' or ''
1988         return "<For Node: for %s in %s, tail_len: %d%s>" % \
1989-            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
1990+            (', '.join(self.loopvars), self.sequence, len(self.nodelist),
1991              reversed_text)
1992 
1993     def __iter__(self):
1994-        for node in self.nodelist_loop:
1995+        for node in self.nodelist:
1996             yield node
1997 
1998-    def get_nodes_by_type(self, nodetype):
1999-        nodes = []
2000-        if isinstance(self, nodetype):
2001-            nodes.append(self)
2002-        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
2003-        return nodes
2004-
2005     def render(self, context):
2006-        nodelist = NodeList()
2007+        result = []
2008         if 'forloop' in context:
2009             parentloop = context['forloop']
2010         else:
2011             parentloop = {}
2012         context.push()
2013-        try:
2014-            values = self.sequence.resolve(context, True)
2015-        except VariableDoesNotExist:
2016-            values = []
2017+        values = self.sequence.resolve_safe(context, default=[])
2018         if values is None:
2019             values = []
2020         if not hasattr(values, '__len__'):
2021@@ -144,8 +130,8 @@
2022                 context.update(dict(zip(self.loopvars, item)))
2023             else:
2024                 context[self.loopvars[0]] = item
2025-            for node in self.nodelist_loop:
2026-                nodelist.append(node.render(context))
2027+            for node in self.nodelist:
2028+                result.append(node.render(context))
2029             if unpack:
2030                 # The loop variables were pushed on to the context so pop them
2031                 # off again. This is necessary because the tag lets the length
2032@@ -154,32 +140,32 @@
2033                 # context.
2034                 context.pop()
2035         context.pop()
2036-        return nodelist.render(context)
2037+        return mark_safe(''.join([force_unicode(b) for b in result]))
2038 
2039-class IfChangedNode(Node):
2040-    def __init__(self, nodelist_true, nodelist_false, *varlist):
2041-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
2042-        self._last_seen = None
2043-        self._varlist = map(Variable, varlist)
2044-        self._id = str(id(self))
2045+class IfChangedNode(ConditionalNode):
2046+    def __init__(self, nodelist_true, nodelist_false, vallist):
2047+        super(IfChangedNode, self).__init__(nodelist_true, nodelist_false)
2048+        self.last_seen = None
2049+        self.vallist = vallist
2050+        self.id = str(id(self))
2051 
2052     def render(self, context):
2053-        if 'forloop' in context and self._id not in context['forloop']:
2054-            self._last_seen = None
2055-            context['forloop'][self._id] = 1
2056+        if 'forloop' in context and self.id not in context['forloop']:
2057+            self.last_seen = None
2058+            context['forloop'][self.id] = 1
2059         try:
2060-            if self._varlist:
2061+            if self.vallist:
2062                 # Consider multiple parameters.  This automatically behaves
2063                 # like an OR evaluation of the multiple variables.
2064-                compare_to = [var.resolve(context) for var in self._varlist]
2065+                compare_to = [expr.resolve(context) for expr in self.vallist]
2066             else:
2067                 compare_to = self.nodelist_true.render(context)
2068-        except VariableDoesNotExist:
2069+        except LookupError:
2070             compare_to = None
2071 
2072-        if compare_to != self._last_seen:
2073-            firstloop = (self._last_seen == None)
2074-            self._last_seen = compare_to
2075+        if  compare_to != self.last_seen:
2076+            firstloop = (self.last_seen == None)
2077+            self.last_seen = compare_to
2078             context.push()
2079             context['ifchanged'] = {'firstloop': firstloop}
2080             content = self.nodelist_true.render(context)
2081@@ -187,96 +173,59 @@
2082             return content
2083         elif self.nodelist_false:
2084             return self.nodelist_false.render(context)
2085-        return ''
2086+        return u''
2087 
2088-class IfEqualNode(Node):
2089-    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
2090-        self.var1, self.var2 = Variable(var1), Variable(var2)
2091-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
2092+
2093+class IfEqualNode(ConditionalNode):
2094+    def __init__(self, val1, val2, nodelist_true, nodelist_false, negate):
2095+        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
2096+        self.val1, self.val2 = val1, val2
2097         self.negate = negate
2098 
2099-    def __repr__(self):
2100-        return "<IfEqualNode>"
2101+    def check_condition(self, context):
2102+        val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context)
2103+        return self.negate == (val1 != val2)
2104 
2105-    def render(self, context):
2106-        try:
2107-            val1 = self.var1.resolve(context)
2108-        except VariableDoesNotExist:
2109-            val1 = None
2110-        try:
2111-            val2 = self.var2.resolve(context)
2112-        except VariableDoesNotExist:
2113-            val2 = None
2114-        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
2115-            return self.nodelist_true.render(context)
2116-        return self.nodelist_false.render(context)
2117 
2118-class IfNode(Node):
2119+class IfNode(ConditionalNode):
2120     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
2121-        self.bool_exprs = bool_exprs
2122-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
2123-        self.link_type = link_type
2124+        super(IfNode, self).__init__(nodelist_true, nodelist_false)
2125+        self.bool_exprs, self.link_type = bool_exprs, link_type
2126 
2127-    def __repr__(self):
2128-        return "<If node>"
2129-
2130-    def __iter__(self):
2131-        for node in self.nodelist_true:
2132-            yield node
2133-        for node in self.nodelist_false:
2134-            yield node
2135-
2136-    def get_nodes_by_type(self, nodetype):
2137-        nodes = []
2138-        if isinstance(self, nodetype):
2139-            nodes.append(self)
2140-        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
2141-        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
2142-        return nodes
2143-
2144-    def render(self, context):
2145-        if self.link_type == IfNode.LinkTypes.or_:
2146-            for ifnot, bool_expr in self.bool_exprs:
2147-                try:
2148-                    value = bool_expr.resolve(context, True)
2149-                except VariableDoesNotExist:
2150-                    value = None
2151-                if (value and not ifnot) or (ifnot and not value):
2152-                    return self.nodelist_true.render(context)
2153-            return self.nodelist_false.render(context)
2154+    def check_condition(self, context):
2155+        if self.link_type == 'or':
2156+            for negated, bool_expr in self.bool_exprs:
2157+                value = bool_expr.resolve_safe(context, default=False)
2158+                if bool(value) != negated:
2159+                    return True
2160+            return False
2161         else:
2162-            for ifnot, bool_expr in self.bool_exprs:
2163-                try:
2164-                    value = bool_expr.resolve(context, True)
2165-                except VariableDoesNotExist:
2166-                    value = None
2167-                if not ((value and not ifnot) or (ifnot and not value)):
2168-                    return self.nodelist_false.render(context)
2169-            return self.nodelist_true.render(context)
2170+            for negated, bool_expr in self.bool_exprs:
2171+                value = bool_expr.resolve_safe(context, default=False)
2172+                if bool(value) == negated:
2173+                    return False
2174+            return True
2175+       
2176 
2177-    class LinkTypes:
2178-        and_ = 0,
2179-        or_ = 1
2180-
2181 class RegroupNode(Node):
2182     def __init__(self, target, expression, var_name):
2183         self.target, self.expression = target, expression
2184         self.var_name = var_name
2185 
2186     def render(self, context):
2187-        obj_list = self.target.resolve(context, True)
2188+        obj_list = self.target.resolve_safe(context)
2189         if obj_list == None:
2190             # target variable wasn't found in context; fail silently.
2191             context[self.var_name] = []
2192-            return ''
2193+            return u''
2194         # List of dictionaries in the format:
2195         # {'grouper': 'key', 'list': [list of contents]}.
2196         context[self.var_name] = [
2197             {'grouper': key, 'list': list(val)}
2198             for key, val in
2199-            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
2200+            groupby(obj_list, lambda v, f=self.expression.resolve_safe: f(v))
2201         ]
2202-        return ''
2203+        return u''
2204 
2205 def include_is_allowed(filepath):
2206     for root in settings.ALLOWED_INCLUDE_ROOTS:
2207@@ -293,7 +242,7 @@
2208             if settings.DEBUG:
2209                 return "[Didn't have permission to include file]"
2210             else:
2211-                return '' # Fail silently for invalid includes.
2212+                return u'' # Fail silently for invalid includes.
2213         try:
2214             fp = open(self.filepath, 'r')
2215             output = fp.read()
2216@@ -308,12 +257,10 @@
2217                 if settings.DEBUG:
2218                     return "[Included template had syntax error: %s]" % e
2219                 else:
2220-                    return '' # Fail silently for invalid included templates.
2221+                    return u'' # Fail silently for invalid included templates.
2222         return output
2223 
2224-class LoadNode(Node):
2225-    def render(self, context):
2226-        return ''
2227+class LoadNode(EmptyNode): pass
2228 
2229 class NowNode(Node):
2230     def __init__(self, format_string):
2231@@ -323,7 +270,7 @@
2232         from datetime import datetime
2233         from django.utils.dateformat import DateFormat
2234         df = DateFormat(datetime.now())
2235-        return df.format(self.format_string)
2236+        return df.format(self.format_string.resolve_safe(context))
2237 
2238 class SpacelessNode(Node):
2239     def __init__(self, nodelist):
2240@@ -359,8 +306,8 @@
2241 
2242     def render(self, context):
2243         from django.core.urlresolvers import reverse, NoReverseMatch
2244-        args = [arg.resolve(context) for arg in self.args]
2245-        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
2246+        args = [arg.resolve_safe(context) for arg in self.args]
2247+        kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context))
2248                        for k, v in self.kwargs.items()])
2249         
2250         
2251@@ -382,7 +329,7 @@
2252                     
2253         if self.asvar:
2254             context[self.asvar] = url
2255-            return ''
2256+            return u''
2257         else:
2258             return url
2259 
2260@@ -396,29 +343,23 @@
2261         try:
2262             value = self.val_expr.resolve(context)
2263             maxvalue = self.max_expr.resolve(context)
2264-        except VariableDoesNotExist:
2265-            return ''
2266+        except LookupError:
2267+            return u''
2268         try:
2269-            value = float(value)
2270-            maxvalue = float(maxvalue)
2271-            ratio = (value / maxvalue) * int(self.max_width)
2272+            ratio = (float(value) / float(maxvalue)) * int(self.max_width)
2273         except (ValueError, ZeroDivisionError):
2274-            return ''
2275-        return str(int(round(ratio)))
2276+            return u''
2277+        return unicode(int(round(ratio)))
2278 
2279 class WithNode(Node):
2280-    def __init__(self, var, name, nodelist):
2281-        self.var = var
2282-        self.name = name
2283+    def __init__(self, expr, name, nodelist):
2284+        self.expr = expr
2285+        self.name = name       
2286         self.nodelist = nodelist
2287 
2288-    def __repr__(self):
2289-        return "<WithNode>"
2290-
2291     def render(self, context):
2292-        val = self.var.resolve(context)
2293         context.push()
2294-        context[self.name] = val
2295+        context[self.name] = self.expr.resolve_safe(context)
2296         output = self.nodelist.render(context)
2297         context.pop()
2298         return output
2299@@ -434,8 +375,7 @@
2300     arg = args[1]
2301     if arg not in (u'on', u'off'):
2302         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
2303-    nodelist = parser.parse(('endautoescape',))
2304-    parser.delete_first_token()
2305+    nodelist = parser.parse_nodelist(('endautoescape',))
2306     return AutoEscapeControlNode((arg == 'on'), nodelist)
2307 autoescape = register.tag(autoescape)
2308 
2309@@ -493,7 +433,7 @@
2310         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
2311         # case.
2312         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
2313-
2314+   
2315     if len(args) == 2:
2316         # {% cycle foo %} case.
2317         name = args[1]
2318@@ -505,12 +445,14 @@
2319 
2320     if len(args) > 4 and args[-2] == 'as':
2321         name = args[-1]
2322-        node = CycleNode(args[1:-2], name)
2323+        values = [parser.compile_filter(arg) for arg in args[1:-2]]
2324+        node = CycleNode(values, name)
2325         if not hasattr(parser, '_namedCycleNodes'):
2326             parser._namedCycleNodes = {}
2327         parser._namedCycleNodes[name] = node
2328     else:
2329-        node = CycleNode(args[1:])
2330+        values = [parser.compile_filter(arg) for arg in args[1:]]
2331+        node = CycleNode(values)
2332     return node
2333 cycle = register.tag(cycle)
2334 
2335@@ -542,18 +484,22 @@
2336             This text will be HTML-escaped, and will appear in lowercase.
2337         {% endfilter %}
2338     """
2339-    _, rest = token.contents.split(None, 1)
2340-    filter_expr = parser.compile_filter("var|%s" % (rest))
2341-    for func, unused in filter_expr.filters:
2342+    name, filter_ = token.contents.split(None, 1)       
2343+    bits = parser.token_stream("var|%s" % filter_)
2344+    try:
2345+        filter_expr = bits.parse_expression()
2346+    except TokenSyntaxError:
2347+        raise TemplateSyntaxError("'%s' requires a valid filter chain, got: '%s'" % name, filter_)
2348+    for func, args in filter_expr.filters:
2349         if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
2350             raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
2351-    nodelist = parser.parse(('endfilter',))
2352-    parser.delete_first_token()
2353+    nodelist = parser.parse_nodelist(('endfilter',))
2354     return FilterNode(filter_expr, nodelist)
2355 do_filter = register.tag("filter", do_filter)
2356 
2357 #@register.tag
2358-def firstof(parser, token):
2359+#@uses_token_stream
2360+def firstof(parser, bits):
2361     """
2362     Outputs the first variable passed that is not False.
2363 
2364@@ -581,15 +527,13 @@
2365         {% firstof var1 var2 var3 "fallback value" %}
2366 
2367     """
2368-    bits = token.split_contents()[1:]
2369-    if len(bits) < 1:
2370-        raise TemplateSyntaxError("'firstof' statement requires at least one"
2371-                                  " argument")
2372-    return FirstOfNode(bits)
2373-firstof = register.tag(firstof)
2374+    expressions = bits.parse_expression_list(minimum=1)
2375+    return FirstOfNode(expressions)
2376+firstof = register.tag(uses_token_stream(firstof))
2377 
2378 #@register.tag(name="for")
2379-def do_for(parser, token):
2380+#@uses_token_stream
2381+def do_for(parser, bits):
2382     """
2383     Loops over each item in an array.
2384 
2385@@ -628,45 +572,33 @@
2386         ==========================  ================================================
2387 
2388     """
2389-    bits = token.contents.split()
2390-    if len(bits) < 4:
2391-        raise TemplateSyntaxError("'for' statements should have at least four"
2392-                                  " words: %s" % token.contents)
2393+    loopvars = []
2394+    while True:
2395+        var = bits.pop_name()
2396+        if not var:
2397+            break
2398+        loopvars.append(var)
2399+        if not bits.pop_lexem(','):
2400+            break
2401+    if not loopvars:
2402+        raise TemplateSyntaxError("'for' tag requires at least one loopvar")   
2403+       
2404+    if not bits.pop_lexem('in'):
2405+        raise TemplateSyntaxError("'for' tag requires 'in' keyword")
2406+    sequence = bits.parse_expression(required=True)
2407+    reversed = bits.pop_lexem('reversed')
2408+    nodelist = parser.parse_nodelist(('endfor',))
2409+    return ForNode(loopvars, sequence, reversed, nodelist)
2410+do_for = register.tag("for", uses_token_stream(do_for))
2411 
2412-    is_reversed = bits[-1] == 'reversed'
2413-    in_index = is_reversed and -3 or -2
2414-    if bits[in_index] != 'in':
2415-        raise TemplateSyntaxError("'for' statements should use the format"
2416-                                  " 'for x in y': %s" % token.contents)
2417+def do_ifequal(parser, bits, negate):
2418+    val1, val2 = bits.parse_expression_list(count=2)
2419+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
2420+    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
2421 
2422-    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
2423-    for var in loopvars:
2424-        if not var or ' ' in var:
2425-            raise TemplateSyntaxError("'for' tag received an invalid argument:"
2426-                                      " %s" % token.contents)
2427-
2428-    sequence = parser.compile_filter(bits[in_index+1])
2429-    nodelist_loop = parser.parse(('endfor',))
2430-    parser.delete_first_token()
2431-    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
2432-do_for = register.tag("for", do_for)
2433-
2434-def do_ifequal(parser, token, negate):
2435-    bits = list(token.split_contents())
2436-    if len(bits) != 3:
2437-        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
2438-    end_tag = 'end' + bits[0]
2439-    nodelist_true = parser.parse(('else', end_tag))
2440-    token = parser.next_token()
2441-    if token.contents == 'else':
2442-        nodelist_false = parser.parse((end_tag,))
2443-        parser.delete_first_token()
2444-    else:
2445-        nodelist_false = NodeList()
2446-    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
2447-
2448 #@register.tag
2449-def ifequal(parser, token):
2450+#@uses_token_stream
2451+def ifequal(parser, bits):
2452     """
2453     Outputs the contents of the block if the two arguments equal each other.
2454 
2455@@ -682,20 +614,22 @@
2456             ...
2457         {% endifnotequal %}
2458     """
2459-    return do_ifequal(parser, token, False)
2460-ifequal = register.tag(ifequal)
2461+    return do_ifequal(parser, bits, False)
2462+ifequal = register.tag(uses_token_stream(ifequal))
2463 
2464 #@register.tag
2465-def ifnotequal(parser, token):
2466+#@uses_token_stream
2467+def ifnotequal(parser, bits):
2468     """
2469     Outputs the contents of the block if the two arguments are not equal.
2470     See ifequal.
2471     """
2472-    return do_ifequal(parser, token, True)
2473-ifnotequal = register.tag(ifnotequal)
2474+    return do_ifequal(parser, bits, True)
2475+ifnotequal = register.tag(uses_token_stream(ifnotequal))
2476 
2477 #@register.tag(name="if")
2478-def do_if(parser, token):
2479+#@uses_token_stream
2480+def do_if(parser, bits):
2481     """
2482     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
2483     (i.e., exists, is not empty, and is not a false boolean value), the
2484@@ -753,44 +687,34 @@
2485             {% endif %}
2486         {% endif %}
2487     """
2488-    bits = token.contents.split()
2489-    del bits[0]
2490-    if not bits:
2491-        raise TemplateSyntaxError("'if' statement requires at least one argument")
2492-    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
2493-    bitstr = ' '.join(bits)
2494-    boolpairs = bitstr.split(' and ')
2495+    link_type = None
2496+    link = None
2497     boolvars = []
2498-    if len(boolpairs) == 1:
2499-        link_type = IfNode.LinkTypes.or_
2500-        boolpairs = bitstr.split(' or ')
2501-    else:
2502-        link_type = IfNode.LinkTypes.and_
2503-        if ' or ' in bitstr:
2504-            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
2505-    for boolpair in boolpairs:
2506-        if ' ' in boolpair:
2507-            try:
2508-                not_, boolvar = boolpair.split()
2509-            except ValueError:
2510-                raise TemplateSyntaxError, "'if' statement improperly formatted"
2511-            if not_ != 'not':
2512-                raise TemplateSyntaxError, "Expected 'not' in if statement"
2513-            boolvars.append((True, parser.compile_filter(boolvar)))
2514+    while True:
2515+        negated = False
2516+        if bits.pop_lexem('not'):
2517+            negated = True
2518+        expr = bits.parse_expression(required=True)
2519+        boolvars.append((negated, expr))
2520+        link = bits.pop_name()
2521+        if not link:
2522+            break
2523+        if link_type:
2524+            if link_type != link:
2525+                bits.syntax_error("can't mix 'and' and 'or'")
2526         else:
2527-            boolvars.append((False, parser.compile_filter(boolpair)))
2528-    nodelist_true = parser.parse(('else', 'endif'))
2529-    token = parser.next_token()
2530-    if token.contents == 'else':
2531-        nodelist_false = parser.parse(('endif',))
2532-        parser.delete_first_token()
2533-    else:
2534-        nodelist_false = NodeList()
2535+            if not link in ('and', 'or'):
2536+                bits.pushback()
2537+                bits.expected("'and' or 'or'")
2538+            link_type = link
2539+   
2540+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')   
2541     return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
2542-do_if = register.tag("if", do_if)
2543+do_if = register.tag("if", uses_token_stream(do_if))
2544 
2545 #@register.tag
2546-def ifchanged(parser, token):
2547+#@uses_token_stream
2548+def ifchanged(parser, bits):
2549     """
2550     Checks if a value has changed from the last iteration of a loop.
2551 
2552@@ -817,17 +741,11 @@
2553                     {{ date.hour }}
2554                 {% endifchanged %}
2555             {% endfor %}
2556-    """
2557-    bits = token.contents.split()
2558-    nodelist_true = parser.parse(('else', 'endifchanged'))
2559-    token = parser.next_token()
2560-    if token.contents == 'else':
2561-        nodelist_false = parser.parse(('endifchanged',))
2562-        parser.delete_first_token()
2563-    else:
2564-        nodelist_false = NodeList()
2565-    return IfChangedNode(nodelist_true, nodelist_false, *bits[1:])
2566-ifchanged = register.tag(ifchanged)
2567+    """   
2568+    values = bits.parse_expression_list()
2569+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
2570+    return IfChangedNode(nodelist_true, nodelist_false, values)
2571+ifchanged = register.tag(uses_token_stream(ifchanged))
2572 
2573 #@register.tag
2574 def ssi(parser, token):
2575@@ -882,7 +800,8 @@
2576 load = register.tag(load)
2577 
2578 #@register.tag
2579-def now(parser, token):
2580+#@uses_token_stream
2581+def now(parser, bits):
2582     """
2583     Displays the date, formatted according to the given string.
2584 
2585@@ -893,15 +812,13 @@
2586 
2587         It is {% now "jS F Y H:i" %}
2588     """
2589-    bits = token.contents.split('"')
2590-    if len(bits) != 3:
2591-        raise TemplateSyntaxError, "'now' statement takes one argument"
2592-    format_string = bits[1]
2593+    format_string = bits.parse_expression(required=True)
2594     return NowNode(format_string)
2595-now = register.tag(now)
2596+now = register.tag(uses_token_stream(now))
2597 
2598 #@register.tag
2599-def regroup(parser, token):
2600+#@uses_token_stream
2601+def regroup(parser, bits):
2602     """
2603     Regroups a list of alike objects by a common attribute.
2604 
2605@@ -947,22 +864,16 @@
2606         {% regroup people|dictsort:"gender" by gender as grouped %}
2607 
2608     """
2609-    firstbits = token.contents.split(None, 3)
2610-    if len(firstbits) != 4:
2611-        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
2612-    target = parser.compile_filter(firstbits[1])
2613-    if firstbits[2] != 'by':
2614-        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
2615-    lastbits_reversed = firstbits[3][::-1].split(None, 2)
2616-    if lastbits_reversed[1][::-1] != 'as':
2617-        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
2618-                                  " be 'as'")
2619-
2620-    expression = parser.compile_filter(lastbits_reversed[2][::-1])
2621-
2622-    var_name = lastbits_reversed[0][::-1]
2623+    target = bits.parse_expression(required=True)   
2624+    if not bits.pop_lexem('by'):
2625+        raise bits.expected("'by'")
2626+    expression = bits.parse_expression(required=True)
2627+    try:
2628+        var_name = parse_as(bits)
2629+    except TokenSyntaxError:
2630+        raise bits.expected("as <name>")
2631     return RegroupNode(target, expression, var_name)
2632-regroup = register.tag(regroup)
2633+regroup = register.tag(uses_token_stream(regroup))
2634 
2635 def spaceless(parser, token):
2636     """
2637@@ -989,9 +900,7 @@
2638             </strong>
2639         {% endspaceless %}
2640     """
2641-    nodelist = parser.parse(('endspaceless',))
2642-    parser.delete_first_token()
2643-    return SpacelessNode(nodelist)
2644+    return SpacelessNode(parser.parse_nodelist(('endspaceless',)))
2645 spaceless = register.tag(spaceless)
2646 
2647 #@register.tag
2648@@ -1028,7 +937,9 @@
2649     return TemplateTagNode(tag)
2650 templatetag = register.tag(templatetag)
2651 
2652-def url(parser, token):
2653+#@register.tag
2654+#@uses_token_stream
2655+def url(parser, bits):
2656     """
2657     Returns an absolute URL matching given view with its parameters.
2658 
2659@@ -1059,34 +970,18 @@
2660 
2661     The URL will look like ``/clients/client/123/``.
2662     """
2663-    bits = token.contents.split(' ')
2664-    if len(bits) < 2:
2665-        raise TemplateSyntaxError("'%s' takes at least one argument"
2666-                                  " (path to a view)" % bits[0])
2667-    viewname = bits[1]
2668-    args = []
2669-    kwargs = {}
2670-    asvar = None
2671-       
2672-    if len(bits) > 2:
2673-        bits = iter(bits[2:])
2674-        for bit in bits:
2675-            if bit == 'as':
2676-                asvar = bits.next()
2677-                break
2678-            else:
2679-                for arg in bit.split(","):
2680-                    if '=' in arg:
2681-                        k, v = arg.split('=', 1)
2682-                        k = k.strip()
2683-                        kwargs[k] = parser.compile_filter(v)
2684-                    elif arg:
2685-                        args.append(parser.compile_filter(arg))
2686-    return URLNode(viewname, args, kwargs, asvar)
2687-url = register.tag(url)
2688+    view = bits.parse_string(bare=True, required=True)
2689+    args, kwargs = parse_args_and_kwargs(bits, until=('as',))
2690+    try:
2691+        asvar = parse_as(bits)
2692+    except TokenSyntaxError:
2693+        asvar = None
2694+    return URLNode(view, args, kwargs, asvar)
2695+url = register.tag(uses_token_stream(url))
2696 
2697 #@register.tag
2698-def widthratio(parser, token):
2699+#@uses_token_stream
2700+def widthratio(parser, bits):
2701     """
2702     For creating bar charts and such, this tag calculates the ratio of a given
2703     value to a maximum value, and then applies that ratio to a constant.
2704@@ -1099,20 +994,14 @@
2705     the above example will be 88 pixels wide (because 175/200 = .875;
2706     .875 * 100 = 87.5 which is rounded up to 88).
2707     """
2708-    bits = token.contents.split()
2709-    if len(bits) != 4:
2710-        raise TemplateSyntaxError("widthratio takes three arguments")
2711-    tag, this_value_expr, max_value_expr, max_width = bits
2712-    try:
2713-        max_width = int(max_width)
2714-    except ValueError:
2715-        raise TemplateSyntaxError("widthratio final argument must be an integer")
2716-    return WidthRatioNode(parser.compile_filter(this_value_expr),
2717-                          parser.compile_filter(max_value_expr), max_width)
2718-widthratio = register.tag(widthratio)
2719+    this_value_expr, max_value_expr = bits.parse_expression_list(count=2)
2720+    max_width = bits.parse_int(required=True)
2721+    return WidthRatioNode(this_value_expr, max_value_expr, max_width)
2722+widthratio = register.tag(uses_token_stream(widthratio))
2723 
2724 #@register.tag
2725-def do_with(parser, token):
2726+#@uses_token_stream
2727+def do_with(parser, bits):
2728     """
2729     Adds a value to the context (inside of this block) for caching and easy
2730     access.
2731@@ -1122,14 +1011,13 @@
2732         {% with person.some_sql_method as total %}
2733             {{ total }} object{{ total|pluralize }}
2734         {% endwith %}
2735+       
2736     """
2737-    bits = list(token.split_contents())
2738-    if len(bits) != 4 or bits[2] != "as":
2739-        raise TemplateSyntaxError("%r expected format is 'value as name'" %
2740-                                  bits[0])
2741-    var = parser.compile_filter(bits[1])
2742-    name = bits[3]
2743-    nodelist = parser.parse(('endwith',))
2744-    parser.delete_first_token()
2745-    return WithNode(var, name, nodelist)
2746-do_with = register.tag('with', do_with)
2747+    try:
2748+        expr = bits.parse_expression()
2749+        name = parse_as(bits)
2750+    except TokenSyntaxError:
2751+        bits.expected("value as name")
2752+    nodelist = parser.parse_nodelist(('endwith',))
2753+    return WithNode(expr, name, nodelist)
2754+do_with = register.tag('with', uses_token_stream(do_with))
2755Index: django/template/compat.py
2756===================================================================
2757--- django/template/compat.py   (revision 0)
2758+++ django/template/compat.py   (revision 0)
2759@@ -0,0 +1,123 @@
2760+import warnings
2761+from django.template.expressions import Expression, LookupError
2762+from django.template.compiler import TemplateSyntaxError
2763+from django.template.nodes import ExpressionNode
2764+
2765+VariableDoesNotExist = LookupError
2766+VariableNode = ExpressionNode
2767+
2768+class Variable(Expression):
2769+    def __init__(self, var):
2770+        warnings.warn('Use Lookup instead of Variable.', DeprecationWarning, stacklevel=2)   
2771+        self.var = var
2772+        from django.template.compiler import TokenStream
2773+        stream = TokenStream(None, var)
2774+        self.expression = stream.parse_value()
2775+        stream.assert_consumed("Invalid variable: %s" % var)
2776+
2777+    def resolve(self, context):
2778+        return self.expression.resolve(context)
2779+
2780+    def __repr__(self):
2781+        return "<%s: %r>" % (self.__class__.__name__, self.var)
2782+
2783+    def __str__(self):
2784+        return self.var
2785+       
2786+
2787+def resolve_variable(path, context):
2788+    """
2789+    Returns the resolved variable, which may contain attribute syntax, within
2790+    the given context.
2791+
2792+    Deprecated.
2793+    """
2794+    warnings.warn('Use Lookup instead of resolve_variable.', DeprecationWarning, stacklevel=2)
2795+    from django.template.compiler import TokenStream
2796+    stream = TokenStream(None, path)
2797+    val = stream.parse_value()
2798+    stream.assert_consumed("Invalid variable: %s" % path)   
2799+    return val.resolve(context)
2800+
2801+
2802+class TokenParser(object):
2803+    """
2804+    Subclass this and implement the top() method to parse a template line. When
2805+    instantiating the parser, pass in the line from the Django template parser.
2806+
2807+    The parser's "tagname" instance-variable stores the name of the tag that
2808+    the filter was called with.
2809+    """
2810+    def __init__(self, subject):
2811+        self.subject = subject
2812+        self.pointer = 0
2813+        self.backout = []
2814+        self.tagname = self.tag()
2815+
2816+    def top(self):
2817+        "Overload this method to do the actual parsing and return the result."
2818+        raise NotImplementedError()
2819+
2820+    def more(self):
2821+        "Returns True if there is more stuff in the tag."
2822+        return self.pointer < len(self.subject)
2823+
2824+    def back(self):
2825+        "Undoes the last microparser. Use this for lookahead and backtracking."
2826+        if not len(self.backout):
2827+            raise TemplateSyntaxError("back called without some previous parsing")
2828+        self.pointer = self.backout.pop()
2829+
2830+    def tag(self):
2831+        "A microparser that just returns the next tag from the line."
2832+        subject = self.subject
2833+        i = self.pointer
2834+        if i >= len(subject):
2835+            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
2836+        p = i
2837+        while i < len(subject) and subject[i] not in (' ', '\t'):
2838+            i += 1
2839+        s = subject[p:i]
2840+        while i < len(subject) and subject[i] in (' ', '\t'):
2841+            i += 1
2842+        self.backout.append(self.pointer)
2843+        self.pointer = i
2844+        return s
2845+
2846+    def value(self):
2847+        "A microparser that parses for a value: some string constant or variable name."
2848+        subject = self.subject
2849+        i = self.pointer
2850+        if i >= len(subject):
2851+            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
2852+        if subject[i] in ('"', "'"):
2853+            p = i
2854+            i += 1
2855+            while i < len(subject) and subject[i] != subject[p]:
2856+                i += 1
2857+            if i >= len(subject):
2858+                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
2859+            i += 1
2860+            res = subject[p:i]
2861+            while i < len(subject) and subject[i] in (' ', '\t'):
2862+                i += 1
2863+            self.backout.append(self.pointer)
2864+            self.pointer = i
2865+            return res
2866+        else:
2867+            p = i
2868+            while i < len(subject) and subject[i] not in (' ', '\t'):
2869+                if subject[i] in ('"', "'"):
2870+                    c = subject[i]
2871+                    i += 1
2872+                    while i < len(subject) and subject[i] != c:
2873+                        i += 1
2874+                    if i >= len(subject):
2875+                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
2876+                i += 1
2877+            s = subject[p:i]
2878+            while i < len(subject) and subject[i] in (' ', '\t'):
2879+                i += 1
2880+            self.backout.append(self.pointer)
2881+            self.pointer = i
2882+            return s
2883Index: django/template/loader_tags.py
2884===================================================================
2885--- django/template/loader_tags.py      (revision 8969)
2886+++ django/template/loader_tags.py      (working copy)
2887@@ -1,6 +1,7 @@
2888-from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
2889-from django.template import Library, Node, TextNode
2890-from django.template.loader import get_template, get_template_from_string, find_template_source
2891+from django.template import TemplateSyntaxError, Library, Node, TextNode
2892+from django.template.compiler import uses_token_stream
2893+from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source
2894+from django.template.expressions import Literal
2895 from django.conf import settings
2896 from django.utils.safestring import mark_safe
2897 
2898@@ -28,7 +29,7 @@
2899     def super(self):
2900         if self.parent:
2901             return mark_safe(self.parent.render(self.context))
2902-        return ''
2903+        return u''
2904 
2905     def add_parent(self, nodelist):
2906         if self.parent:
2907@@ -39,24 +40,18 @@
2908 class ExtendsNode(Node):
2909     must_be_first = True
2910 
2911-    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
2912+    def __init__(self, nodelist, parent_name, template_dirs=None):
2913         self.nodelist = nodelist
2914-        self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
2915+        self.parent_name = parent_name
2916         self.template_dirs = template_dirs
2917 
2918     def __repr__(self):
2919-        if self.parent_name_expr:
2920-            return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
2921         return '<ExtendsNode: extends "%s">' % self.parent_name
2922 
2923     def get_parent(self, context):
2924-        if self.parent_name_expr:
2925-            self.parent_name = self.parent_name_expr.resolve(context)
2926-        parent = self.parent_name
2927+        parent = self.parent_name.resolve_safe(context)
2928         if not parent:
2929             error_msg = "Invalid template name in 'extends' tag: %r." % parent
2930-            if self.parent_name_expr:
2931-                error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
2932             raise TemplateSyntaxError, error_msg
2933         if hasattr(parent, 'render'):
2934             return parent # parent is a Template object
2935@@ -114,20 +109,22 @@
2936 
2937 class IncludeNode(Node):
2938     def __init__(self, template_name):
2939-        self.template_name = Variable(template_name)
2940+        self.template_name = template_name
2941 
2942     def render(self, context):
2943         try:
2944-            template_name = self.template_name.resolve(context)
2945+            template_name = self.template_name.resolve_safe(context)
2946             t = get_template(template_name)
2947             return t.render(context)
2948         except TemplateSyntaxError, e:
2949             if settings.TEMPLATE_DEBUG:
2950                 raise
2951-            return ''
2952+            return u''
2953         except:
2954-            return '' # Fail silently for invalid included templates.
2955+            return u'' # Fail silently for invalid included templates.
2956 
2957+
2958+#@register.tag('block')
2959 def do_block(parser, token):
2960     """
2961     Define a block that can be overridden by child templates.
2962@@ -144,34 +141,34 @@
2963         parser.__loaded_blocks.append(block_name)
2964     except AttributeError: # parser.__loaded_blocks isn't a list yet
2965         parser.__loaded_blocks = [block_name]
2966-    nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
2967-    parser.delete_first_token()
2968+    nodelist = parser.parse_nodelist(('endblock', 'endblock %s' % block_name))
2969     return BlockNode(block_name, nodelist)
2970+register.tag('block', do_block)
2971 
2972-def do_extends(parser, token):
2973+
2974+#@register.tag('extends')
2975+#@uses_token_stream
2976+def do_extends(parser, bits):
2977     """
2978     Signal that this template extends a parent template.
2979 
2980     This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
2981     uses the literal value "base" as the name of the parent template to extend,
2982-    or ``{% extends variable %}`` uses the value of ``variable`` as either the
2983+    or ``{% extends expression %}`` uses the value of ``expression`` as either the
2984     name of the parent template to extend (if it evaluates to a string) or as
2985     the parent tempate itelf (if it evaluates to a Template object).
2986     """
2987-    bits = token.contents.split()
2988-    if len(bits) != 2:
2989-        raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
2990-    parent_name, parent_name_expr = None, None
2991-    if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
2992-        parent_name = bits[1][1:-1]
2993-    else:
2994-        parent_name_expr = parser.compile_filter(bits[1])
2995+    parent_name = bits.parse_expression(required=True)
2996     nodelist = parser.parse()
2997     if nodelist.get_nodes_by_type(ExtendsNode):
2998-        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
2999-    return ExtendsNode(nodelist, parent_name, parent_name_expr)
3000+        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits.name
3001+    return ExtendsNode(nodelist, parent_name)
3002+register.tag('extends', uses_token_stream(do_extends))
3003 
3004-def do_include(parser, token):
3005+
3006+#@register.tag('include')
3007+#@uses_token_stream
3008+def do_include(parser, bits):
3009     """
3010     Loads a template and renders it with the current context.
3011 
3012@@ -179,14 +176,9 @@
3013 
3014         {% include "foo/some_include" %}
3015     """
3016-    bits = token.contents.split()
3017-    if len(bits) != 2:
3018-        raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
3019-    path = bits[1]
3020-    if path[0] in ('"', "'") and path[-1] == path[0]:
3021-        return ConstantIncludeNode(path[1:-1])
3022-    return IncludeNode(bits[1])
3023-
3024-register.tag('block', do_block)
3025-register.tag('extends', do_extends)
3026-register.tag('include', do_include)
3027+    template_name = bits.parse_expression(required=True)
3028+    if isinstance(template_name, Literal):
3029+        # remove ConstantIncludeNode and this hack will be gone
3030+        return ConstantIncludeNode(template_name.resolve(None))
3031+    return IncludeNode(template_name)
3032+register.tag('include', uses_token_stream(do_include))
3033Index: django/template/library.py
3034===================================================================
3035--- django/template/library.py  (revision 0)
3036+++ django/template/library.py  (revision 0)
3037@@ -0,0 +1,161 @@
3038+import re
3039+from inspect import getargspec
3040+from django.conf import settings
3041+from django.template.context import Context
3042+from django.template.nodes import Node
3043+from django.template.compiler import TemplateSyntaxError
3044+from django.utils.itercompat import is_iterable
3045+from django.utils.functional import curry
3046+
3047+__all__ = ('InvalidTemplateLibrary', 'Library', 'get_library', 'add_to_builtins')
3048+
3049+# global dictionary of libraries that have been loaded using get_library
3050+libraries = {}
3051+# global list of libraries to load by default for a new parser
3052+builtins = []
3053+
3054+class InvalidTemplateLibrary(Exception):
3055+    pass
3056+
3057+def generic_tag_compiler(params, defaults, name, node_class, parser, token, takes_context=False, takes_nodelist=False):
3058+    "Returns a template.Node subclass."
3059+    bits = parser.token_stream(token)
3060+    bmax = len(params)
3061+    def_len = defaults and len(defaults) or 0
3062+    bmin = bmax - def_len
3063+    args = stream.parse_expression_list(minimum=bmin, maximum=bmax)
3064+    stream.assert_comsumed()
3065+    if takes_context:
3066+        node_class = curry(node_class, takes_context=takes_context)
3067+    if takes_nodelist:
3068+        nodelist = parser.parse_nodelist(('end%s' % name,))
3069+        node_class = curry(node_class, nodelist=nodelist)     
3070+    return node_class(args)
3071+
3072+class Library(object):
3073+    def __init__(self):
3074+        self.filters = {}
3075+        self.tags = {}
3076+
3077+    def tag(self, name=None, compile_function=None):
3078+        if name == None and compile_function == None:
3079+            # @register.tag()
3080+            return self.tag_function
3081+        elif name != None and compile_function == None:
3082+            if(callable(name)):
3083+                # @register.tag
3084+                return self.tag_function(name)
3085+            else:
3086+                # @register.tag('somename') or @register.tag(name='somename')
3087+                def dec(func):
3088+                    return self.tag(name, func)
3089+                return dec
3090+        elif name != None and compile_function != None:
3091+            # register.tag('somename', somefunc)
3092+            self.tags[name] = compile_function
3093+            return compile_function
3094+        else:
3095+            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
3096+
3097+    def tag_function(self,func):
3098+        self.tags[getattr(func, "_decorated_function", func).__name__] = func
3099+        return func
3100+
3101+    def filter(self, name=None, filter_func=None):
3102+        if name == None and filter_func == None:
3103+            # @register.filter()
3104+            return self.filter_function
3105+        elif filter_func == None:
3106+            if(callable(name)):
3107+                # @register.filter
3108+                return self.filter_function(name)
3109+            else:
3110+                # @register.filter('somename') or @register.filter(name='somename')
3111+                def dec(func):
3112+                    return self.filter(name, func)
3113+                return dec
3114+        elif name != None and filter_func != None:
3115+            # register.filter('somename', somefunc)
3116+            self.filters[name] = filter_func
3117+            return filter_func
3118+        else:
3119+            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
3120+
3121+    def filter_function(self, func):
3122+        self.filters[getattr(func, "_decorated_function", func).__name__] = func
3123+        return func
3124+
3125+    def simple_tag(self, compile_function=None):
3126+        def dec(func):
3127+            params, xx, xxx, defaults = getargspec(func)
3128+
3129+            class SimpleNode(Node):
3130+                def __init__(self, args):
3131+                    self.args = args
3132+
3133+                def render(self, context):
3134+                    return func(*[arg.resolve_safe(context) for arg in self.args])
3135+
3136+            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
3137+            compile_func.__doc__ = func.__doc__
3138+            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
3139+            return func
3140+       
3141+        if callable(compile_function):
3142+            # @register.simple_tag
3143+            return dec(compile_function)
3144+        return dec
3145+
3146+    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
3147+        def dec(func):
3148+            params, xx, xxx, defaults = getargspec(func)
3149+            if takes_context:
3150+                if params[0] == 'context':
3151+                    params = params[1:]
3152+                else:
3153+                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
3154+
3155+            class InclusionNode(Node):
3156+                def __init__(self, args):
3157+                    self.args = args
3158+
3159+                def render(self, context):
3160+                    resolved_vars = [arg.resolve_safe(context) for arg in self.args]
3161+                    if takes_context:
3162+                        args = [context] + resolved_vars
3163+                    else:
3164+                        args = resolved_vars
3165+
3166+                    dict = func(*args)
3167+
3168+                    if not getattr(self, 'nodelist', False):
3169+                        from django.template.loader import get_template, select_template
3170+                        if not isinstance(file_name, basestring) and is_iterable(file_name):
3171+                            t = select_template(file_name)
3172+                        else:
3173+                            t = get_template(file_name)
3174+                        self.nodelist = t.nodelist
3175+                    return self.nodelist.render(context_class(dict, autoescape=context.autoescape))
3176+
3177+            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
3178+            compile_func.__doc__ = func.__doc__
3179+            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
3180+            return func
3181+        return dec
3182+
3183+def get_library(module_name):
3184+    lib = libraries.get(module_name, None)
3185+    if not lib:
3186+        try:
3187+            mod = __import__(module_name, {}, {}, [''])
3188+        except ImportError, e:
3189+            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
3190+        try:
3191+            lib = mod.register
3192+            libraries[module_name] = lib
3193+        except AttributeError:
3194+            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
3195+    return lib
3196+
3197+def add_to_builtins(module_name):
3198+    builtins.append(get_library(module_name))
3199Index: django/template/debug.py
3200===================================================================
3201--- django/template/debug.py    (revision 8969)
3202+++ django/template/debug.py    (working copy)
3203@@ -1,7 +1,6 @@
3204-from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
3205-from django.utils.encoding import force_unicode
3206-from django.utils.html import escape
3207-from django.utils.safestring import SafeData, EscapeData
3208+from django.template.compiler import Lexer, Parser, Token, TokenStream, tag_re, bit_re
3209+from django.template import NodeList, ExpressionNode, TemplateSyntaxError
3210+from django.utils.encoding import force_unicode, smart_str
3211 
3212 class DebugLexer(Lexer):
3213     def __init__(self, template_string, origin):
3214@@ -50,7 +49,7 @@
3215         return DebugNodeList()
3216 
3217     def create_variable_node(self, contents):
3218-        return DebugVariableNode(contents)
3219+        return DebugExpressionNode(contents)
3220 
3221     def extend_nodelist(self, nodelist, node, token):
3222         node.source = token.source
3223@@ -65,6 +64,10 @@
3224         if not hasattr(e, 'source'):
3225             e.source = token.source
3226 
3227+    def token_stream(self, token):
3228+        return DebugTokenStream(self, token)
3229+
3230+
3231 class DebugNodeList(NodeList):
3232     def render_node(self, node, context):
3233         try:
3234@@ -81,17 +84,20 @@
3235             raise wrapped
3236         return result
3237 
3238-class DebugVariableNode(VariableNode):
3239+class DebugExpressionNode(ExpressionNode):
3240     def render(self, context):
3241         try:
3242-            output = force_unicode(self.filter_expression.resolve(context))
3243+            return super(DebugExpressionNode, self).render(context)
3244         except TemplateSyntaxError, e:
3245             if not hasattr(e, 'source'):
3246                 e.source = self.source
3247             raise
3248-        except UnicodeDecodeError:
3249-            return ''
3250-        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
3251-            return escape(output)
3252-        else:
3253-            return output
3254+           
3255+class DebugTokenStream(TokenStream):
3256+    def syntax_error(self, msg):
3257+        if self.name:
3258+            msg = u"{%% %s %%} %s" % (self.name, msg)           
3259+        if self.token:
3260+            raise self.parser.source_error(self.token.source, msg)
3261+        raise TemplateSyntaxError(msg)
3262+           
3263Index: django/template/loader.py
3264===================================================================
3265--- django/template/loader.py   (revision 8969)
3266+++ django/template/loader.py   (working copy)
3267@@ -21,11 +21,14 @@
3268 # installed, because pkg_resources is necessary to read eggs.
3269 
3270 from django.core.exceptions import ImproperlyConfigured
3271-from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
3272+from django.template import Origin, Template, Context, add_to_builtins
3273 from django.conf import settings
3274 
3275 template_source_loaders = None
3276 
3277+class TemplateDoesNotExist(Exception):
3278+    pass
3279+
3280 class LoaderOrigin(Origin):
3281     def __init__(self, display_name, loader, name, dirs):
3282         super(LoaderOrigin, self).__init__(display_name)
3283Index: tests/regressiontests/templates/tests.py
3284===================================================================
3285--- tests/regressiontests/templates/tests.py    (revision 8969)
3286+++ tests/regressiontests/templates/tests.py    (working copy)
3287@@ -132,7 +132,7 @@
3288 
3289     def test_token_smart_split(self):
3290         # Regression test for #7027
3291-        token = template.Token(template.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
3292+        token = template.compiler.Token(template.compiler.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
3293         split = token.split_contents()
3294         self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")'])
3295 
3296@@ -194,7 +194,7 @@
3297                     output = self.render(test_template, vals)
3298                 except Exception, e:
3299                     if e.__class__ != result:
3300-                        raise
3301+                        #raise
3302                         failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
3303                     continue
3304                 if output != result:
3305@@ -288,6 +288,9 @@
3306             'basic-syntax25': ('{{ "fred" }}', {}, "fred"),
3307             'basic-syntax26': (r'{{ "\"fred\"" }}', {}, "\"fred\""),
3308             'basic-syntax27': (r'{{ _("\"fred\"") }}', {}, "\"fred\""),
3309+            'basic-syntax28': ("{{ 'fred' }}", {}, "fred"),
3310+            'basic-syntax29': (r"{{ '\'fred\'' }}", {}, "'fred'"),
3311+            'basic-syntax30': (r"{{ _('\'fred\'') }}", {}, "'fred'"),
3312 
3313             # List-index syntax allows a template to access a certain item of a subscriptable object.
3314             'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"),
3315@@ -347,7 +350,7 @@
3316             'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
3317 
3318             # Default argument testing
3319-            'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
3320+            'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),           
3321 
3322             # Fail silently for methods that raise an exception with a
3323             # "silent_variable_failure" attribute
3324@@ -375,6 +378,9 @@
3325             
3326             #filters should accept empty string constants
3327             'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""),
3328+           
3329+            # Single-quoted argument
3330+            'filter-syntax20': (r"{{ var|yesno:'yup,nup,mup' }} {{ var|yesno }}", {"var": True}, 'yup yes'),             
3331 
3332             ### COMMENT SYNTAX ########################################################
3333             'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
3334@@ -419,6 +425,11 @@
3335             'cycle14': ("{% cycle one two as foo %}{% cycle foo %}", {'one': '1','two': '2'}, '12'),
3336             'cycle13': ("{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}", {'test': range(5), 'aye': 'a', 'bee': 'b'}, 'a0,b1,a2,b3,a4,'),
3337 
3338+            ### EMPTY STRINGS #########################################################
3339+            'emptystring01': ("{{ '' }}", {}, ""),
3340+            'emptystring02': ("{% ifequal foo '' %}x{% endifequal %}", {'foo': ''}, 'x'),
3341+            'emptystring03': ("{% ifequal foo|default:'' foo %}x{% endifequal %}", {'foo': ''}, 'x'),
3342+
3343             ### EXCEPTIONS ############################################################
3344 
3345             # Raise exception for invalid template name
3346@@ -621,6 +632,13 @@
3347             'ifequal-numeric10': ('{% ifequal x -5 %}yes{% endifequal %}', {'x': -5}, 'yes'),
3348             'ifequal-numeric11': ('{% ifequal x -5.2 %}yes{% endifequal %}', {'x': -5.2}, 'yes'),
3349             'ifequal-numeric12': ('{% ifequal x +5 %}yes{% endifequal %}', {'x': 5}, 'yes'),
3350+           
3351+            # FILTER EXPRESSIONS AS ARGUMENTS
3352+            'ifequal-filter01': ('{% ifequal a|upper "A" %}x{% endifequal %}', {'a': 'a'}, 'x'),
3353+            'ifequal-filter02': ('{% ifequal "A" a|upper %}x{% endifequal %}', {'a': 'a'}, 'x'),
3354+            'ifequal-filter03': ('{% ifequal a|upper b|upper %}x{% endifequal %}', {'a': 'x', 'b': 'X'}, 'x'),
3355+            'ifequal-filter04': ('{% ifequal x|slice:"1" "a" %}x{% endifequal %}', {'x': 'aaa'}, 'x'),
3356+            'ifequal-filter05': ('{% ifequal x|slice:"1"|upper "A" %}x{% endifequal %}', {'x': 'aaa'}, 'x'),             
3357 
3358             ### IFNOTEQUAL TAG ########################################################
3359             'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
3360@@ -816,6 +834,13 @@
3361                             you
3362                             gentlemen.
3363                             """),
3364+                           
3365+            ### NEGATIVE NUMERIC LITERALS #############################################                             
3366+            'negative-numeric-literal01': ('{{ -1 }}', {}, '-1'),
3367+            'negative-numeric-literal02': ('{{ -2.01 }}', {}, '-2.01'),
3368+            'negative-numeric-literal03': ('{{ -0.1 }}', {}, '-0.1'),
3369+            'negative-numeric-literal04': ('{% ifequal -1 -1 %}x{% endifequal %}', {}, 'x'),
3370+            'negative-numeric-literal05': ('{{ foo|default:-1 }}', {'foo': None}, '-1'),                           
3371 
3372             ### REGROUP TAG ###########################################################
3373             'regroup01': ('{% regroup data by bar as grouped %}' + \
3374@@ -841,6 +866,20 @@
3375                           '{% endfor %},' + \
3376                           '{% endfor %}',
3377                           {}, ''),
3378+                         
3379+            'regroup03': ('{% regroup data by created|date:"F Y" as grouped %}' + \
3380+                          '{% for group in grouped %}' + \
3381+                          '{{ group.grouper }}' + \
3382+                          '({% for item in group.list %}' + \
3383+                          '{{ item.created|date:"d" }}' + \
3384+                          '{% endfor %})' + \
3385+                          '{% endfor %}',
3386+                          {'data': [
3387+                                {'created': datetime(2008, 1, 1)},
3388+                                {'created': datetime(2008, 2, 2)},
3389+                                {'created': datetime(2008, 3, 3)},
3390+                                {'created': datetime(2008, 4, 4)},
3391+                          ]}, 'January 2008(01)February 2008(02)March 2008(03)April 2008(04)'),
3392 
3393             ### TEMPLATETAG TAG #######################################################
3394             'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
3395@@ -902,6 +941,7 @@
3396             'url07': (u'{% url regressiontests.templates.views.client2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
3397             'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
3398             'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
3399+            'url10': (u'{% url "метка_оператора" v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
3400 
3401             # Failures
3402             'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),