Index: django/templatetags/i18n.py
===================================================================
--- django/templatetags/i18n.py	(revision 11593)
+++ django/templatetags/i18n.py	(working copy)
@@ -1,8 +1,10 @@
 import re
 
-from django.template import Node, Variable, VariableNode, _render_value_in_context
+from django.template import Node, _render_value_in_context
 from django.template import TemplateSyntaxError, TokenParser, Library
 from django.template import TOKEN_TEXT, TOKEN_VAR
+from django.template.utils import parse_context_bindings, parse_as, resolve_dict
+from django.template.parser import uses_token_stream
 from django.utils import translation
 from django.utils.encoding import force_unicode
 
@@ -35,7 +37,7 @@
 
 class TranslateNode(Node):
     def __init__(self, value, noop):
-        self.value = Variable(value)
+        self.value = value
         self.noop = noop
 
     def render(self, context):
@@ -66,9 +68,7 @@
         return ''.join(result), vars
 
     def render(self, context):
-        tmp_context = {}
-        for var, val in self.extra_context.items():
-            tmp_context[var] = val.render(context)
+        tmp_context = resolve_dict(self.extra_context, context)
         # Update() works like a push(), so corresponding context.pop() is at
         # the end of function
         context.update(tmp_context)
@@ -141,7 +141,8 @@
         raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args
     return GetCurrentLanguageBidiNode(args[2])
 
-def do_translate(parser, token):
+#@uses_token_stream
+def do_translate(parser, bits):
     """
     This will mark a string for translation and will
     translate the string for the current language.
@@ -171,21 +172,12 @@
     the variable ``variable``. Make sure that the string
     in there is something that is in the .po file.
     """
-    class TranslateParser(TokenParser):
-        def top(self):
-            value = self.value()
-            if self.more():
-                if self.tag() == 'noop':
-                    noop = True
-                else:
-                    raise TemplateSyntaxError, "only option for 'trans' is 'noop'"
-            else:
-                noop = False
-            return (value, noop)
-    value, noop = TranslateParser(token.contents).top()
+    value = bits.parse_expression(required=True)
+    noop = bits.pop_lexeme('noop')
     return TranslateNode(value, noop)
 
-def do_block_translate(parser, token):
+#@uses_token_stream
+def do_block_translate(parser, bits):
     """
     This will translate a block of text with parameters.
 
@@ -205,29 +197,15 @@
 
     This is much like ngettext, only in template syntax.
     """
-    class BlockTranslateParser(TokenParser):
-        def top(self):
-            countervar = None
-            counter = None
-            extra_context = {}
-            while self.more():
-                tag = self.tag()
-                if tag == 'with' or tag == 'and':
-                    value = self.value()
-                    if self.tag() != 'as':
-                        raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'"
-                    extra_context[self.tag()] = VariableNode(
-                            parser.compile_filter(value))
-                elif tag == 'count':
-                    counter = parser.compile_filter(self.value())
-                    if self.tag() != 'as':
-                        raise TemplateSyntaxError, "counter specification in 'blocktrans' must be 'count value as variable'"
-                    countervar = self.tag()
-                else:
-                    raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag
-            return (countervar, counter, extra_context)
+    countervar = None
+    counter = None
+    extra_context = {}
 
-    countervar, counter, extra_context = BlockTranslateParser(token.contents).top()
+    if bits.pop_lexeme('with'):
+        extra_context = parse_context_bindings(bits)
+    elif bits.pop_lexeme('count'):
+        counter = bits.parse_expression(required=True)
+        countervar = parse_as(bits, required=True)
 
     singular = []
     plural = []
@@ -255,5 +233,5 @@
 register.tag('get_available_languages', do_get_available_languages)
 register.tag('get_current_language', do_get_current_language)
 register.tag('get_current_language_bidi', do_get_current_language_bidi)
-register.tag('trans', do_translate)
-register.tag('blocktrans', do_block_translate)
+register.tag('trans', uses_token_stream(do_translate))
+register.tag('blocktrans', uses_token_stream(do_block_translate))
Index: django/templatetags/cache.py
===================================================================
--- django/templatetags/cache.py	(revision 11593)
+++ django/templatetags/cache.py	(working copy)
@@ -1,30 +1,30 @@
-from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
-from django.template import resolve_variable
+from django.template import Library, Node, TemplateSyntaxError
+from django.template.parser import uses_token_stream
+from django.template.expressions import LookupError
 from django.core.cache import cache
-from django.utils.encoding import force_unicode
 from django.utils.http import urlquote
 from django.utils.hashcompat import md5_constructor
 
 register = Library()
 
 class CacheNode(Node):
-    def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
+    def __init__(self, nodelist, expire_time, fragment_name, vary_on):
         self.nodelist = nodelist
-        self.expire_time_var = Variable(expire_time_var)
+        self.expire_time = expire_time
         self.fragment_name = fragment_name
         self.vary_on = vary_on
 
     def render(self, context):
         try:
-            expire_time = self.expire_time_var.resolve(context)
-        except VariableDoesNotExist:
-            raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time_var.var)
+            expire_time = self.expire_time.resolve(context)
+        except LookupError:
+            raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time)
         try:
             expire_time = int(expire_time)
         except (ValueError, TypeError):
             raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
         # Build a unicode key for this fragment and all vary-on's.
-        args = md5_constructor(u':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on]))
+        args = md5_constructor(u':'.join([urlquote(exp.resolve(context)) for exp in self.vary_on]))
         cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest())
         value = cache.get(cache_key)
         if value is None:
@@ -32,7 +32,8 @@
             cache.set(cache_key, value, expire_time)
         return value
 
-def do_cache(parser, token):
+#@uses_token_stream
+def do_cache(parser, bits):
     """
     This will cache the contents of a template fragment for a given amount
     of time.
@@ -53,11 +54,10 @@
 
     Each unique set of arguments will result in a unique cache entry.
     """
-    nodelist = parser.parse(('endcache',))
-    parser.delete_first_token()
-    tokens = token.contents.split()
-    if len(tokens) < 3:
-        raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0])
-    return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
+    nodelist = parser.parse_nodelist('endcache')
+    expire_time = bits.parse_expression(required=True)
+    fragment_name = bits.pop_name(required=True)
+    vary_on = bits.parse_expression_list()
+    return CacheNode(nodelist, expire_time, fragment_name, vary_on)
 
-register.tag('cache', do_cache)
+register.tag('cache', uses_token_stream(do_cache))
Index: django/contrib/comments/templatetags/comments.py
===================================================================
--- django/contrib/comments/templatetags/comments.py	(revision 11593)
+++ django/contrib/comments/templatetags/comments.py	(working copy)
@@ -1,5 +1,7 @@
 from django import template
+from django.template.parser import uses_token_stream, TokenSyntaxError
 from django.template.loader import render_to_string
+from django.template.utils import parse_as
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 from django.contrib import comments
@@ -13,36 +15,30 @@
     Looks a bit strange, but the subclasses below should make this a bit more
     obvious.
     """
-
     #@classmethod
-    def handle_token(cls, parser, token):
+    def handle_token(cls, parser, bits):
         """Class method to parse get_comment_list/count/form and return a Node."""
-        tokens = token.contents.split()
-        if tokens[1] != 'for':
-            raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
-
-        # {% get_whatever for obj as varname %}
-        if len(tokens) == 5:
-            if tokens[3] != 'as':
-                raise template.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens[0])
+        bits.pop_lexeme('for', required=True)
+        try:
+            # {% get_whatever for app.model pk as varname %}
+            app_and_model = bits.parse_string(bare=True)
+            if bits.pop_lexeme('as'):
+                bits.pushback() # pushback "as"
+                bits.pushback() # pushback app_and_model
+                raise TokenSyntaxError
+            pk = bits.parse_expression(required=True)
+            varname = parse_as(bits, required=True)
             return cls(
-                object_expr = parser.compile_filter(tokens[2]),
-                as_varname = tokens[4],
-            )
+                ctype=BaseCommentNode.lookup_content_type(app_and_model, bits.name),
+                object_pk_expr = pk,
+                as_varname = varname
+            )            
+        except TokenSyntaxError:        
+            # {% get_whatever for obj as varname %}
+            obj = bits.parse_expression(required=True)
+            varname = parse_as(bits, required=True)
+            return cls(object_expr=obj, as_varname = varname)
 
-        # {% get_whatever for app.model pk as varname %}
-        elif len(tokens) == 6:
-            if tokens[4] != 'as':
-                raise template.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens[0])
-            return cls(
-                ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
-                object_pk_expr = parser.compile_filter(tokens[3]),
-                as_varname = tokens[5]
-            )
-
-        else:
-            raise template.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens[0])
-
     handle_token = classmethod(handle_token)
 
     #@staticmethod
@@ -136,22 +132,24 @@
     """Render the comment form directly"""
 
     #@classmethod
-    def handle_token(cls, parser, token):
+    def handle_token(cls, parser, bits):
         """Class method to parse render_comment_form and return a Node."""
-        tokens = token.contents.split()
-        if tokens[1] != 'for':
-            raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
-
-        # {% render_comment_form for obj %}
-        if len(tokens) == 3:
-            return cls(object_expr=parser.compile_filter(tokens[2]))
-
-        # {% render_comment_form for app.models pk %}
-        elif len(tokens) == 4:
-            return cls(
-                ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
-                object_pk_expr = parser.compile_filter(tokens[3])
-            )
+        bits.pop_lexeme('for', required=True)
+        try:
+            # {% render_comment_form for app.models pk %}
+            app_and_model = bits.parse_string(bare=True)
+            try:
+                pk = bits.parse_expression()
+                return cls(
+                    ctype = BaseCommentNode.lookup_content_type(app_and_model, bits.name),
+                    object_pk_expr = pk
+                )                
+            except TokenSyntaxError:
+                bits.pushback()
+                raise
+        except TokenSyntaxError:
+            # {% render_comment_form for obj %}            
+            return cls(object_expr=bits.parse_expression(required=True))
     handle_token = classmethod(handle_token)
 
     def render(self, context):
@@ -174,6 +172,7 @@
 # wrapper function that just exists to hold the docstring.
 
 #@register.tag
+#@uses_token_stream
 def get_comment_count(parser, token):
     """
     Gets the comment count for the given params and populates the template
@@ -195,6 +194,7 @@
     return CommentCountNode.handle_token(parser, token)
 
 #@register.tag
+#@uses_token_stream
 def get_comment_list(parser, token):
     """
     Gets the list of comments for the given params and populates the template
@@ -217,6 +217,7 @@
     return CommentListNode.handle_token(parser, token)
 
 #@register.tag
+#@uses_token_stream
 def get_comment_form(parser, token):
     """
     Get a (new) form object to post a new comment.
@@ -252,8 +253,8 @@
     """
     return comments.get_form_target()
 
-register.tag(get_comment_count)
-register.tag(get_comment_list)
-register.tag(get_comment_form)
-register.tag(render_comment_form)
+register.tag(uses_token_stream(get_comment_count))
+register.tag(uses_token_stream(get_comment_list))
+register.tag(uses_token_stream(get_comment_form))
+register.tag(uses_token_stream(render_comment_form))
 register.simple_tag(comment_form_target)
Index: django/template/nodes.py
===================================================================
--- django/template/nodes.py	(revision 0)
+++ django/template/nodes.py	(revision 0)
@@ -0,0 +1,91 @@
+from django.utils.encoding import force_unicode
+from django.utils.safestring import SafeData, EscapeData, mark_safe
+from django.utils.html import escape
+from django.template.expressions import LookupError
+from django.conf import settings
+
+class Node(object):
+    # Set this to True for nodes that must be first in the template (although
+    # they can be preceded by text nodes.
+    must_be_first = False
+
+    def render(self, context):
+        "Return the node rendered as a string"
+        pass
+        
+    def __repr__(self):
+        return "<%s>" % self.__class__.__name__
+
+    def __iter__(self):
+        yield self
+
+    def get_nodes_by_type(self, nodetype):
+        "Return a list of all nodes (within this node and its nodelist) of the given type"
+        nodes = []
+        if isinstance(self, nodetype):
+            nodes.append(self)
+        if hasattr(self, 'nodelist'):
+            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
+        return nodes
+
+class NodeList(list):
+    # Set to True the first time a non-TextNode is inserted by
+    # extend_nodelist().
+    contains_nontext = False
+
+    def render(self, context):
+        bits = []
+        for node in self:
+            if isinstance(node, Node):
+                bits.append(self.render_node(node, context))
+            else:
+                bits.append(node)
+        return mark_safe(''.join([force_unicode(b) for b in bits]))
+
+    def get_nodes_by_type(self, nodetype):
+        "Return a list of all nodes of the given type"
+        nodes = []
+        for node in self:
+            nodes.extend(node.get_nodes_by_type(nodetype))
+        return nodes
+
+    def render_node(self, node, context):
+        return node.render(context)
+
+class TextNode(Node):
+    def __init__(self, s):
+        self.s = s
+
+    def __repr__(self):
+        return "<Text Node: '%s'>" % self.s[:25]
+
+    def render(self, context):
+        return self.s
+
+class ExpressionNode(Node):
+    def __init__(self, expression):
+        self.expression = expression
+
+    def __repr__(self):
+        return "<Variable Node: %s>" % self.expression
+
+    def render(self, context):
+        try:
+            output = force_unicode(self.expression.resolve(context))
+        except LookupError:
+            if settings.TEMPLATE_STRING_IF_INVALID:
+                from django.template import invalid_var_format_string
+                if invalid_var_format_string is None:
+                    invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
+                if invalid_var_format_string:
+                    return settings.TEMPLATE_STRING_IF_INVALID % self.expression
+                return settings.TEMPLATE_STRING_IF_INVALID
+            else:
+                return u''
+        except UnicodeDecodeError:
+            # Unicode conversion can fail sometimes for reasons out of our
+            # control (e.g. exception rendering). In that case, we fail quietly.
+            return u''        
+        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
+            output = escape(output)
+        return output
Index: django/template/parser.py
===================================================================
--- django/template/parser.py	(revision 0)
+++ django/template/parser.py	(revision 0)
@@ -0,0 +1,561 @@
+import re
+from inspect import getargspec
+from django.conf import settings
+from django.utils.functional import wraps
+from django.utils.translation import ugettext
+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
+from django.utils.html import escape
+from django.utils.encoding import smart_unicode, force_unicode, smart_str
+from django.utils.text import smart_split
+from django.template.nodes import Node, ExpressionNode, NodeList, TextNode
+from django.template.expressions import LookupError, Literal, FilterExpression, Lookup
+
+
+# what to report as the origin for templates that come from non-loader sources
+# (e.g. strings)
+UNKNOWN_SOURCE="&lt;unknown source&gt;"
+
+TOKEN_TEXT = 0
+TOKEN_VAR = 1
+TOKEN_BLOCK = 2
+TOKEN_COMMENT = 3
+
+# template syntax constants
+FILTER_SEPARATOR = '|'
+FILTER_ARGUMENT_SEPARATOR = ':'
+VARIABLE_ATTRIBUTE_SEPARATOR = '.'
+BLOCK_TAG_START = '{%'
+BLOCK_TAG_END = '%}'
+VARIABLE_TAG_START = '{{'
+VARIABLE_TAG_END = '}}'
+COMMENT_TAG_START = '{#'
+COMMENT_TAG_END = '#}'
+SINGLE_BRACE_START = '{'
+SINGLE_BRACE_END = '}'
+
+ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
+
+# match a variable or block tag and capture the entire tag, including start/end delimiters
+tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
+                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
+                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
+
+def _render_value_in_context(value, context):
+    """
+    Converts any value to a string to become part of a rendered template. This
+    means escaping, if required, and conversion to a unicode object. If value
+    is a string, it is expected to have already been translated.
+    """
+    value = force_unicode(value)
+    if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
+        return escape(value)
+    else:
+        return value
+        
+class Origin(object):
+    def __init__(self, name):
+        self.name = name
+
+    def reload(self):
+        raise NotImplementedError
+
+    def __str__(self):
+        return self.name
+
+class StringOrigin(Origin):
+    def __init__(self, source):
+        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
+        self.source = source
+
+    def reload(self):
+        return self.source
+
+class TemplateSyntaxError(Exception):
+    def __str__(self):
+        try:
+            import cStringIO as StringIO
+        except ImportError:
+            import StringIO
+        output = StringIO.StringIO()
+        output.write(Exception.__str__(self))
+        # Check if we wrapped an exception and print that too.
+        if hasattr(self, 'exc_info'):
+            import traceback
+            output.write('\n\nOriginal ')
+            e = self.exc_info
+            traceback.print_exception(e[0], e[1], e[2], 500, output)
+        return output.getvalue()
+        
+class TemplateEncodingError(Exception):
+    pass
+        
+class Template(object):
+    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
+        try:
+            template_string = smart_unicode(template_string)
+        except UnicodeDecodeError:
+            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
+        if settings.TEMPLATE_DEBUG and origin is None:
+            origin = StringOrigin(template_string)
+        self.nodelist = compile_string(template_string, origin)
+        self.name = name
+
+    def __iter__(self):
+        for node in self.nodelist:
+            for subnode in node:
+                yield subnode
+
+    def render(self, context):
+        "Display stage -- can be called many times"
+        return self.nodelist.render(context)
+
+def compile_string(template_string, origin):
+    "Compiles template_string into NodeList ready for rendering"
+    if settings.TEMPLATE_DEBUG:
+        from debug import DebugLexer, DebugParser
+        lexer_class, parser_class = DebugLexer, DebugParser
+    else:
+        lexer_class, parser_class = Lexer, Parser
+    lexer = lexer_class(template_string, origin)
+    parser = parser_class(lexer.tokenize())
+    return parser.parse()
+
+class Token(object):
+    def __init__(self, token_type, contents):
+        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
+        self.token_type, self.contents = token_type, contents
+
+    def __str__(self):
+        return '<%s token: "%s...">' % \
+            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
+            self.contents[:20].replace('\n', ''))
+
+    def split_contents(self):
+        split = []
+        bits = iter(smart_split(self.contents))
+        for bit in bits:
+            # Handle translation-marked template pieces
+            if bit.startswith('_("') or bit.startswith("_('"):
+                sentinal = bit[2] + ')'
+                trans_bit = [bit]
+                while not bit.endswith(sentinal):
+                    bit = bits.next()
+                    trans_bit.append(bit)
+                bit = ' '.join(trans_bit)
+            split.append(bit)
+        return split
+
+class Lexer(object):
+    def __init__(self, template_string, origin):
+        self.template_string = template_string
+        self.origin = origin
+
+    def tokenize(self):
+        "Return a list of tokens from a given template_string."
+        in_tag = False
+        result = []
+        for bit in tag_re.split(self.template_string):
+            if bit:
+                result.append(self.create_token(bit, in_tag))
+            in_tag = not in_tag
+        return result
+
+    def create_token(self, token_string, in_tag):
+        """
+        Convert the given token string into a new Token object and return it.
+        If in_tag is True, we are processing something that matched a tag,
+        otherwise it should be treated as a literal string.
+        """
+        if in_tag:
+            if token_string.startswith(VARIABLE_TAG_START):
+                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
+            elif token_string.startswith(BLOCK_TAG_START):
+                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
+            elif token_string.startswith(COMMENT_TAG_START):
+                token = Token(TOKEN_COMMENT, '')
+        else:
+            token = Token(TOKEN_TEXT, token_string)
+        return token
+
+class Parser(object):
+    def __init__(self, tokens):
+        self.tokens = tokens
+        self.tags = {}
+        self.filters = {}
+        from django.template.library import get_builtins
+        for lib in get_builtins():
+            self.add_library(lib)
+
+    def parse(self, parse_until=None):
+        if parse_until is None: parse_until = []
+        nodelist = self.create_nodelist()
+        while self.tokens:
+            token = self.next_token()
+            if token.token_type == TOKEN_TEXT:
+                self.extend_nodelist(nodelist, TextNode(token.contents), token)
+            elif token.token_type == TOKEN_VAR:
+                if not token.contents:
+                    self.empty_variable(token)
+                filter_expression = self.compile_filter(token.contents)
+                var_node = self.create_variable_node(filter_expression)
+                self.extend_nodelist(nodelist, var_node,token)
+            elif token.token_type == TOKEN_BLOCK:
+                if token.contents in parse_until:
+                    # put token back on token list so calling code knows why it terminated
+                    self.prepend_token(token)
+                    return nodelist
+                try:
+                    command = token.contents.split()[0]
+                except IndexError:
+                    self.empty_block_tag(token)
+                # execute callback function for this tag and append resulting node
+                self.enter_command(command, token)
+                try:
+                    compile_func = self.tags[command]
+                except KeyError:
+                    self.invalid_block_tag(token, command)
+                try:
+                    compiled_result = compile_func(self, token)
+                except TemplateSyntaxError, e:
+                    if not self.compile_function_error(token, e):
+                        raise
+                self.extend_nodelist(nodelist, compiled_result, token)
+                self.exit_command()
+        if parse_until:
+            self.unclosed_block_tag(parse_until)
+        return nodelist
+        
+    def parse_nodelist(self, until):
+        nodelist = self.parse((until,))
+        self.delete_first_token()
+        return nodelist
+
+    def skip_past(self, endtag):
+        while self.tokens:
+            token = self.next_token()
+            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
+                return
+        self.unclosed_block_tag([endtag])
+
+    def create_variable_node(self, expression):
+        return ExpressionNode(expression)
+
+    def create_nodelist(self):
+        return NodeList()
+
+    def extend_nodelist(self, nodelist, node, token):
+        if node.must_be_first and nodelist:
+            try:
+                if nodelist.contains_nontext:
+                    raise AttributeError
+            except AttributeError:
+                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
+        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
+            nodelist.contains_nontext = True
+        nodelist.append(node)
+
+    def enter_command(self, command, token):
+        pass
+
+    def exit_command(self):
+        pass
+
+    def error(self, token, msg):
+        return TemplateSyntaxError(msg)
+
+    def empty_variable(self, token):
+        raise self.error(token, "Empty variable tag")
+
+    def empty_block_tag(self, token):
+        raise self.error(token, "Empty block tag")
+
+    def invalid_block_tag(self, token, command):
+        raise self.error(token, "Invalid block tag: '%s'" % command)
+
+    def unclosed_block_tag(self, parse_until):
+        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
+
+    def compile_function_error(self, token, e):
+        pass
+
+    def next_token(self):
+        return self.tokens.pop(0)
+
+    def prepend_token(self, token):
+        self.tokens.insert(0, token)
+
+    def delete_first_token(self):
+        del self.tokens[0]
+
+    def add_library(self, lib):
+        self.tags.update(lib.tags)
+        self.filters.update(lib.filters)
+
+    def token_stream(self, token):
+        return TokenStream(self, token)
+
+    def compile_filter(self, token):
+        stream = self.token_stream(token)
+        expr = stream.parse_expression(required=True)
+        if not stream.consumed():
+            raise TemplateSyntaxError("Invalid filter expression")
+        return expr
+
+    def find_filter(self, filter_name):
+        if filter_name in self.filters:
+            return self.filters[filter_name]
+        else:
+            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
+
+def filter_args_check(name, func, provided):
+    provided = list(provided)
+    plen = len(provided)
+    # Check to see if a decorator is providing the real function.
+    func = getattr(func, '_decorated_function', func)
+    args, varargs, varkw, defaults = getargspec(func)
+    
+    if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)):
+        return True
+    
+    # First argument is filter input.
+    args.pop(0)
+    if defaults:
+        nondefs = args[:-len(defaults)]
+    else:
+        nondefs = args
+    # Args without defaults must be provided.
+    try:
+        for arg in nondefs:
+            provided.pop(0)
+    except IndexError:
+        # Not enough
+        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
+
+    # Defaults can be overridden.
+    defaults = defaults and list(defaults) or []
+    try:
+        for parg in provided:
+            defaults.pop(0)
+    except IndexError:
+        # Too many.
+        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
+
+    return True
+
+
+def parse_lookup(lookup):
+    return Lookup(tuple(lookup.split(VARIABLE_ATTRIBUTE_SEPARATOR)), var=lookup)
+
+EOT = '<EOT>'
+punctuation_chars = r':|=,;<>!?%&@"\'/()\[\]{}`*+'
+bit_re = re.compile(r"""
+    (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')
+    |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*)
+    |(?P<char>[%s]) # punctuation
+    |(?P<name>[^\s%s]+) # keyword or variable
+""" % ((punctuation_chars,) * 2), re.VERBOSE)
+STRING_LITERAL = "string_literal" #1
+NUMERIC_LITERAL = "numeric_literal" #2
+CHAR = "char" #3
+NAME = "name" #4
+
+def _unescape_string_literal(literal):
+    q = literal[0]
+    return literal.replace(r'\%s' % q, q).replace(r'\\', '')[1:-1]
+
+class TokenSyntaxError(Exception):
+    pass
+
+def token_stream_parser(func):
+    def wrapper(self, required=False, *args, **kwargs):
+        mark = self.offset
+        try:
+            return func(self, *args, **kwargs)
+        except TokenSyntaxError:
+            if required:
+                #FIXME: hack
+                self.expected("<%s>" % ' '.join(func.__name__.split('_')[1:]))
+            self.offset = mark
+            raise
+    return wraps(func)(wrapper)
+
+class TokenStream(object):
+    def __init__(self, parser, source):
+        self.parser = parser
+        self.source = source
+        self.offset = 0
+        self.name = None
+        self.token = None
+        if isinstance(source, Token):
+            bits = source.contents.split(None, 1)
+            self.source = len(bits) == 2 and bits[1] or ''
+            self.token = source
+            self.name = bits[0]
+        self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)]
+
+    def consumed(self):
+        return self.offset == len(self.tokens)
+
+    def pop(self):
+        if self.offset == len(self.tokens):
+            raise TokenSyntaxError
+        next = self.tokens[self.offset]
+        self.offset += 1
+        return next
+
+    def pop_lexeme(self, match, required=False):
+        if self.offset == len(self.tokens):
+            if required:
+                self.expected(match)
+            return False
+        tokentype, lexeme = self.tokens[self.offset]
+        if lexeme == match:
+            self.offset += 1
+            return True
+        if required:
+            self.expected(match)
+        return False
+
+    def pop_name(self, required=False):
+        if self.offset == len(self.tokens):
+            if required:
+                self.expected('<name>')        
+            return None
+        tokentype, lexeme = self.tokens[self.offset]
+        if tokentype == NAME:
+            self.offset += 1
+            return lexeme
+        if required:
+            self.expected('<name>')
+        return None
+
+    def pushback(self):
+        self.offset -= 1
+
+    def syntax_error(self, msg):
+        if self.name:
+            msg = u"{%% %s %%} %s" % (self.name, msg)
+        raise TemplateSyntaxError(msg)
+
+    def expected(self, what):
+        if self.consumed():
+            found = EOT
+        else:
+            found = "<%s> %s" % self.tokens[self.offset]
+        self.syntax_error("expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace')))
+
+    #@token_stream_parser
+    def parse_string(self, bare=False):
+        tokentype, lexeme = self.pop()
+        if tokentype == STRING_LITERAL:
+            return _unescape_string_literal(lexeme)
+        if bare and tokentype == NAME:
+            return lexeme
+        raise TokenSyntaxError
+    parse_string = token_stream_parser(parse_string)
+
+    #@token_stream_parser
+    def parse_int(self):
+        token_type, lexeme = self.pop()
+        if token_type == NUMERIC_LITERAL:
+            try:
+                return int(lexeme)
+            except ValueError:
+                pass
+        raise TokenSyntaxError
+    parse_int = token_stream_parser(parse_int)
+
+    #@token_stream_parser
+    def parse_value(self):
+        translate = False
+        if self.pop_lexeme('_'):
+            if not self.pop_lexeme('('):
+                raise TokenSyntaxError
+            translate = True
+
+        tokentype, lexeme = self.pop()
+        if tokentype == NAME:
+            if lexeme.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexeme[0] == '_':
+                raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexeme)
+            value = parse_lookup(lexeme)
+        elif tokentype == STRING_LITERAL:
+            value = Literal(mark_safe(_unescape_string_literal(lexeme)))
+        elif tokentype == NUMERIC_LITERAL:
+            try:
+                value = float(lexeme)
+            except ValueError:
+                raise TokenSyntaxError
+            if '.' not in lexeme and 'e' not in lexeme.lower():
+                value = int(value)
+            # This is a backwards compatibility hack: Literals with a trailing dot 
+            # have been treated as an invalid variable before, thus resolved as None.
+            if lexeme.endswith('.'):
+                value = None
+            value = Literal(value)
+        elif tokentype == CHAR:
+            raise TokenSyntaxError
+
+        if translate:
+            if not self.pop_lexeme(')'):
+                raise TokenSyntaxError
+            # Don't pass the empty string to gettext, because the empty 
+            # string translates to meta information
+            value = FilterExpression(value, [(lambda x: x and ugettext(x) or u'', ())])
+        return value
+    parse_value = token_stream_parser(parse_value)
+
+    #@token_stream_parser
+    def parse_filter(self):
+        if not self.pop_lexeme('|'):
+            raise TokenSyntaxError
+        name = self.pop_name()
+        if not name:
+            raise TokenSyntaxError
+        args = []
+        if self.pop_lexeme(':'):
+            args.append(self.parse_value())
+        func = self.parser.find_filter(name)
+        filter_args_check(name, func, args)
+        return func, args
+    parse_filter = token_stream_parser(parse_filter)
+
+    #@token_stream_parser
+    def parse_expression(self):
+        var = self.parse_value()
+        filters = []
+        try:
+            while True:
+                filters.append(self.parse_filter())
+        except TokenSyntaxError:
+            pass
+        if filters:
+            return FilterExpression(var, filters)
+        return var
+    parse_expression = token_stream_parser(parse_expression)
+
+    #@token_stream_parser
+    def parse_expression_list(self, minimum=0, maximum=None, count=None):
+        expressions = []
+        if count:
+            minimum = count
+            maximum = count
+        try:
+            while True:
+                if len(expressions) == maximum:
+                    break            
+                expressions.append(self.parse_expression())
+        except TokenSyntaxError:
+            pass
+        if len(expressions) < minimum:
+            self.expected("expression")
+        return expressions
+    parse_expression_list = token_stream_parser(parse_expression_list)
+
+def uses_token_stream(func):
+    def decorator(parser, token):
+        bits = parser.token_stream(token)
+        result = func(parser, bits)
+        if bits.offset != len(bits.tokens):
+            bits.expected(EOT)
+        return result    
+    return wraps(func)(decorator)
Index: django/template/defaultfilters.py
===================================================================
--- django/template/defaultfilters.py	(revision 11593)
+++ django/template/defaultfilters.py	(working copy)
@@ -13,7 +13,8 @@
 except ImportError:
     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
 
-from django.template import Variable, Library
+from django.template import Library
+from django.template.parser import parse_lookup
 from django.conf import settings
 from django.utils.translation import ugettext, ungettext
 from django.utils.encoding import force_unicode, iri_to_uri
@@ -467,8 +468,8 @@
     Takes a list of dicts, returns that list sorted by the property given in
     the argument.
     """
-    var_resolve = Variable(arg).resolve
-    decorated = [(var_resolve(item), item) for item in value]
+    lookup = parse_lookup(arg)
+    decorated = [(lookup.resolve(item), item) for item in value]
     decorated.sort()
     return [item[1] for item in decorated]
 dictsort.is_safe = False
@@ -478,8 +479,8 @@
     Takes a list of dicts, returns that list sorted in reverse order by the
     property given in the argument.
     """
-    var_resolve = Variable(arg).resolve
-    decorated = [(var_resolve(item), item) for item in value]
+    lookup = parse_lookup(arg)
+    decorated = [(lookup.resolve(item), item) for item in value]
     decorated.sort()
     decorated.reverse()
     return [item[1] for item in decorated]
Index: django/template/__init__.py
===================================================================
--- django/template/__init__.py	(revision 11593)
+++ django/template/__init__.py	(working copy)
@@ -48,925 +48,18 @@
 >>> t.render(c)
 u'<html></html>'
 """
-import re
-from inspect import getargspec
-
-from django.conf import settings
 from django.template.context import Context, RequestContext, ContextPopException
-from django.utils.importlib import import_module
-from django.utils.itercompat import is_iterable
-from django.utils.functional import curry, Promise
-from django.utils.text import smart_split, unescape_string_literal
-from django.utils.encoding import smart_unicode, force_unicode, smart_str
-from django.utils.translation import ugettext as _
-from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
-from django.utils.html import escape
+from django.template.nodes import Node, NodeList, TextNode, ExpressionNode
+from django.template.parser import Parser, Lexer, Token, Template, Origin, StringOrigin, TemplateSyntaxError, TemplateEncodingError, TokenStream, _render_value_in_context, compile_string
+from django.template.parser import (BLOCK_TAG_START, BLOCK_TAG_END, 
+    VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, 
+    COMMENT_TAG_START, COMMENT_TAG_END, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, TOKEN_COMMENT)
+from django.template.loader import TemplateDoesNotExist
+from django.template.library import Library, InvalidTemplateLibrary, get_library, add_to_builtins, libraries, builtins
+from django.template.compat import Variable, resolve_variable, TokenParser, VariableDoesNotExist, VariableNode
 
-__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
+__all__ = ('Template', 'Context', 'RequestContext', 'compile_string', 'Library', 'Parser', 'TemplateDoesNotExist')
 
-TOKEN_TEXT = 0
-TOKEN_VAR = 1
-TOKEN_BLOCK = 2
-TOKEN_COMMENT = 3
-
-# template syntax constants
-FILTER_SEPARATOR = '|'
-FILTER_ARGUMENT_SEPARATOR = ':'
-VARIABLE_ATTRIBUTE_SEPARATOR = '.'
-BLOCK_TAG_START = '{%'
-BLOCK_TAG_END = '%}'
-VARIABLE_TAG_START = '{{'
-VARIABLE_TAG_END = '}}'
-COMMENT_TAG_START = '{#'
-COMMENT_TAG_END = '#}'
-SINGLE_BRACE_START = '{'
-SINGLE_BRACE_END = '}'
-
-ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
-
-# what to report as the origin for templates that come from non-loader sources
-# (e.g. strings)
-UNKNOWN_SOURCE="&lt;unknown source&gt;"
-
-# match a variable or block tag and capture the entire tag, including start/end delimiters
-tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
-                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
-                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
-
-# global dictionary of libraries that have been loaded using get_library
-libraries = {}
-# global list of libraries to load by default for a new parser
-builtins = []
-
 # True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
 # uninitialised.
 invalid_var_format_string = None
-
-class TemplateSyntaxError(Exception):
-    def __str__(self):
-        try:
-            import cStringIO as StringIO
-        except ImportError:
-            import StringIO
-        output = StringIO.StringIO()
-        output.write(Exception.__str__(self))
-        # Check if we wrapped an exception and print that too.
-        if hasattr(self, 'exc_info'):
-            import traceback
-            output.write('\n\nOriginal ')
-            e = self.exc_info
-            traceback.print_exception(e[0], e[1], e[2], 500, output)
-        return output.getvalue()
-
-class TemplateDoesNotExist(Exception):
-    pass
-
-class TemplateEncodingError(Exception):
-    pass
-
-class VariableDoesNotExist(Exception):
-
-    def __init__(self, msg, params=()):
-        self.msg = msg
-        self.params = params
-
-    def __str__(self):
-        return unicode(self).encode('utf-8')
-
-    def __unicode__(self):
-        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
-
-class InvalidTemplateLibrary(Exception):
-    pass
-
-class Origin(object):
-    def __init__(self, name):
-        self.name = name
-
-    def reload(self):
-        raise NotImplementedError
-
-    def __str__(self):
-        return self.name
-
-class StringOrigin(Origin):
-    def __init__(self, source):
-        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
-        self.source = source
-
-    def reload(self):
-        return self.source
-
-class Template(object):
-    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
-        try:
-            template_string = smart_unicode(template_string)
-        except UnicodeDecodeError:
-            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
-        if settings.TEMPLATE_DEBUG and origin is None:
-            origin = StringOrigin(template_string)
-        self.nodelist = compile_string(template_string, origin)
-        self.name = name
-
-    def __iter__(self):
-        for node in self.nodelist:
-            for subnode in node:
-                yield subnode
-
-    def render(self, context):
-        "Display stage -- can be called many times"
-        return self.nodelist.render(context)
-
-def compile_string(template_string, origin):
-    "Compiles template_string into NodeList ready for rendering"
-    if settings.TEMPLATE_DEBUG:
-        from debug import DebugLexer, DebugParser
-        lexer_class, parser_class = DebugLexer, DebugParser
-    else:
-        lexer_class, parser_class = Lexer, Parser
-    lexer = lexer_class(template_string, origin)
-    parser = parser_class(lexer.tokenize())
-    return parser.parse()
-
-class Token(object):
-    def __init__(self, token_type, contents):
-        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
-        self.token_type, self.contents = token_type, contents
-
-    def __str__(self):
-        return '<%s token: "%s...">' % \
-            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
-            self.contents[:20].replace('\n', ''))
-
-    def split_contents(self):
-        split = []
-        bits = iter(smart_split(self.contents))
-        for bit in bits:
-            # Handle translation-marked template pieces
-            if bit.startswith('_("') or bit.startswith("_('"):
-                sentinal = bit[2] + ')'
-                trans_bit = [bit]
-                while not bit.endswith(sentinal):
-                    bit = bits.next()
-                    trans_bit.append(bit)
-                bit = ' '.join(trans_bit)
-            split.append(bit)
-        return split
-
-class Lexer(object):
-    def __init__(self, template_string, origin):
-        self.template_string = template_string
-        self.origin = origin
-
-    def tokenize(self):
-        "Return a list of tokens from a given template_string."
-        in_tag = False
-        result = []
-        for bit in tag_re.split(self.template_string):
-            if bit:
-                result.append(self.create_token(bit, in_tag))
-            in_tag = not in_tag
-        return result
-
-    def create_token(self, token_string, in_tag):
-        """
-        Convert the given token string into a new Token object and return it.
-        If in_tag is True, we are processing something that matched a tag,
-        otherwise it should be treated as a literal string.
-        """
-        if in_tag:
-            if token_string.startswith(VARIABLE_TAG_START):
-                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
-            elif token_string.startswith(BLOCK_TAG_START):
-                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
-            elif token_string.startswith(COMMENT_TAG_START):
-                token = Token(TOKEN_COMMENT, '')
-        else:
-            token = Token(TOKEN_TEXT, token_string)
-        return token
-
-class Parser(object):
-    def __init__(self, tokens):
-        self.tokens = tokens
-        self.tags = {}
-        self.filters = {}
-        for lib in builtins:
-            self.add_library(lib)
-
-    def parse(self, parse_until=None):
-        if parse_until is None: parse_until = []
-        nodelist = self.create_nodelist()
-        while self.tokens:
-            token = self.next_token()
-            if token.token_type == TOKEN_TEXT:
-                self.extend_nodelist(nodelist, TextNode(token.contents), token)
-            elif token.token_type == TOKEN_VAR:
-                if not token.contents:
-                    self.empty_variable(token)
-                filter_expression = self.compile_filter(token.contents)
-                var_node = self.create_variable_node(filter_expression)
-                self.extend_nodelist(nodelist, var_node,token)
-            elif token.token_type == TOKEN_BLOCK:
-                if token.contents in parse_until:
-                    # put token back on token list so calling code knows why it terminated
-                    self.prepend_token(token)
-                    return nodelist
-                try:
-                    command = token.contents.split()[0]
-                except IndexError:
-                    self.empty_block_tag(token)
-                # execute callback function for this tag and append resulting node
-                self.enter_command(command, token)
-                try:
-                    compile_func = self.tags[command]
-                except KeyError:
-                    self.invalid_block_tag(token, command)
-                try:
-                    compiled_result = compile_func(self, token)
-                except TemplateSyntaxError, e:
-                    if not self.compile_function_error(token, e):
-                        raise
-                self.extend_nodelist(nodelist, compiled_result, token)
-                self.exit_command()
-        if parse_until:
-            self.unclosed_block_tag(parse_until)
-        return nodelist
-
-    def skip_past(self, endtag):
-        while self.tokens:
-            token = self.next_token()
-            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
-                return
-        self.unclosed_block_tag([endtag])
-
-    def create_variable_node(self, filter_expression):
-        return VariableNode(filter_expression)
-
-    def create_nodelist(self):
-        return NodeList()
-
-    def extend_nodelist(self, nodelist, node, token):
-        if node.must_be_first and nodelist:
-            try:
-                if nodelist.contains_nontext:
-                    raise AttributeError
-            except AttributeError:
-                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
-        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
-            nodelist.contains_nontext = True
-        nodelist.append(node)
-
-    def enter_command(self, command, token):
-        pass
-
-    def exit_command(self):
-        pass
-
-    def error(self, token, msg):
-        return TemplateSyntaxError(msg)
-
-    def empty_variable(self, token):
-        raise self.error(token, "Empty variable tag")
-
-    def empty_block_tag(self, token):
-        raise self.error(token, "Empty block tag")
-
-    def invalid_block_tag(self, token, command):
-        raise self.error(token, "Invalid block tag: '%s'" % command)
-
-    def unclosed_block_tag(self, parse_until):
-        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
-
-    def compile_function_error(self, token, e):
-        pass
-
-    def next_token(self):
-        return self.tokens.pop(0)
-
-    def prepend_token(self, token):
-        self.tokens.insert(0, token)
-
-    def delete_first_token(self):
-        del self.tokens[0]
-
-    def add_library(self, lib):
-        self.tags.update(lib.tags)
-        self.filters.update(lib.filters)
-
-    def compile_filter(self, token):
-        "Convenient wrapper for FilterExpression"
-        return FilterExpression(token, self)
-
-    def find_filter(self, filter_name):
-        if filter_name in self.filters:
-            return self.filters[filter_name]
-        else:
-            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
-
-class TokenParser(object):
-    """
-    Subclass this and implement the top() method to parse a template line. When
-    instantiating the parser, pass in the line from the Django template parser.
-
-    The parser's "tagname" instance-variable stores the name of the tag that
-    the filter was called with.
-    """
-    def __init__(self, subject):
-        self.subject = subject
-        self.pointer = 0
-        self.backout = []
-        self.tagname = self.tag()
-
-    def top(self):
-        "Overload this method to do the actual parsing and return the result."
-        raise NotImplementedError()
-
-    def more(self):
-        "Returns True if there is more stuff in the tag."
-        return self.pointer < len(self.subject)
-
-    def back(self):
-        "Undoes the last microparser. Use this for lookahead and backtracking."
-        if not len(self.backout):
-            raise TemplateSyntaxError("back called without some previous parsing")
-        self.pointer = self.backout.pop()
-
-    def tag(self):
-        "A microparser that just returns the next tag from the line."
-        subject = self.subject
-        i = self.pointer
-        if i >= len(subject):
-            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
-        p = i
-        while i < len(subject) and subject[i] not in (' ', '\t'):
-            i += 1
-        s = subject[p:i]
-        while i < len(subject) and subject[i] in (' ', '\t'):
-            i += 1
-        self.backout.append(self.pointer)
-        self.pointer = i
-        return s
-
-    def value(self):
-        "A microparser that parses for a value: some string constant or variable name."
-        subject = self.subject
-        i = self.pointer
-        if i >= len(subject):
-            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
-        if subject[i] in ('"', "'"):
-            p = i
-            i += 1
-            while i < len(subject) and subject[i] != subject[p]:
-                i += 1
-            if i >= len(subject):
-                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
-            i += 1
-            res = subject[p:i]
-            while i < len(subject) and subject[i] in (' ', '\t'):
-                i += 1
-            self.backout.append(self.pointer)
-            self.pointer = i
-            return res
-        else:
-            p = i
-            while i < len(subject) and subject[i] not in (' ', '\t'):
-                if subject[i] in ('"', "'"):
-                    c = subject[i]
-                    i += 1
-                    while i < len(subject) and subject[i] != c:
-                        i += 1
-                    if i >= len(subject):
-                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
-                i += 1
-            s = subject[p:i]
-            while i < len(subject) and subject[i] in (' ', '\t'):
-                i += 1
-            self.backout.append(self.pointer)
-            self.pointer = i
-            return s
-
-# This only matches constant *strings* (things in quotes or marked for
-# translation). Numbers are treated as variables for implementation reasons
-# (so that they retain their type when passed to filters).
-constant_string = r"""
-(?:%(i18n_open)s%(strdq)s%(i18n_close)s|
-%(i18n_open)s%(strsq)s%(i18n_close)s|
-%(strdq)s|
-%(strsq)s)
-""" % {
-    'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
-    'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
-    'i18n_open' : re.escape("_("),
-    'i18n_close' : re.escape(")"),
-    }
-constant_string = constant_string.replace("\n", "")
-
-filter_raw_string = r"""
-^(?P<constant>%(constant)s)|
-^(?P<var>[%(var_chars)s]+|%(num)s)|
- (?:%(filter_sep)s
-     (?P<filter_name>\w+)
-         (?:%(arg_sep)s
-             (?:
-              (?P<constant_arg>%(constant)s)|
-              (?P<var_arg>[%(var_chars)s]+|%(num)s)
-             )
-         )?
- )""" % {
-    'constant': constant_string,
-    'num': r'[-+\.]?\d[\d\.e]*',
-    'var_chars': "\w\." ,
-    'filter_sep': re.escape(FILTER_SEPARATOR),
-    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
-  }
-
-filter_re = re.compile(filter_raw_string, re.UNICODE|re.VERBOSE)
-
-class FilterExpression(object):
-    r"""
-    Parses a variable token and its optional filters (all as a single string),
-    and return a list of tuples of the filter name and arguments.
-    Sample:
-        >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
-        >>> p = Parser('')
-        >>> fe = FilterExpression(token, p)
-        >>> len(fe.filters)
-        2
-        >>> fe.var
-        <Variable: 'variable'>
-
-    This class should never be instantiated outside of the
-    get_filters_from_token helper function.
-    """
-    def __init__(self, token, parser):
-        self.token = token
-        matches = filter_re.finditer(token)
-        var_obj = None
-        filters = []
-        upto = 0
-        for match in matches:
-            start = match.start()
-            if upto != start:
-                raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
-                        (token[:upto], token[upto:start], token[start:]))
-            if var_obj is None:
-                var, constant = match.group("var", "constant")
-                if constant:
-                    try:
-                        var_obj = Variable(constant).resolve({})
-                    except VariableDoesNotExist:
-                        var_obj = None
-                elif var is None:
-                    raise TemplateSyntaxError("Could not find variable at start of %s." % token)
-                elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
-                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
-                else:
-                    var_obj = Variable(var)
-            else:
-                filter_name = match.group("filter_name")
-                args = []
-                constant_arg, var_arg = match.group("constant_arg", "var_arg")
-                if constant_arg:
-                    args.append((False, Variable(constant_arg).resolve({})))
-                elif var_arg:
-                    args.append((True, Variable(var_arg)))
-                filter_func = parser.find_filter(filter_name)
-                self.args_check(filter_name,filter_func, args)
-                filters.append( (filter_func,args))
-            upto = match.end()
-        if upto != len(token):
-            raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
-
-        self.filters = filters
-        self.var = var_obj
-
-    def resolve(self, context, ignore_failures=False):
-        if isinstance(self.var, Variable):
-            try:
-                obj = self.var.resolve(context)
-            except VariableDoesNotExist:
-                if ignore_failures:
-                    obj = None
-                else:
-                    if settings.TEMPLATE_STRING_IF_INVALID:
-                        global invalid_var_format_string
-                        if invalid_var_format_string is None:
-                            invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
-                        if invalid_var_format_string:
-                            return settings.TEMPLATE_STRING_IF_INVALID % self.var
-                        return settings.TEMPLATE_STRING_IF_INVALID
-                    else:
-                        obj = settings.TEMPLATE_STRING_IF_INVALID
-        else:
-            obj = self.var
-        for func, args in self.filters:
-            arg_vals = []
-            for lookup, arg in args:
-                if not lookup:
-                    arg_vals.append(mark_safe(arg))
-                else:
-                    arg_vals.append(arg.resolve(context))
-            if getattr(func, 'needs_autoescape', False):
-                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
-            else:
-                new_obj = func(obj, *arg_vals)
-            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
-                obj = mark_safe(new_obj)
-            elif isinstance(obj, EscapeData):
-                obj = mark_for_escaping(new_obj)
-            else:
-                obj = new_obj
-        return obj
-
-    def args_check(name, func, provided):
-        provided = list(provided)
-        plen = len(provided)
-        # Check to see if a decorator is providing the real function.
-        func = getattr(func, '_decorated_function', func)
-        args, varargs, varkw, defaults = getargspec(func)
-        # First argument is filter input.
-        args.pop(0)
-        if defaults:
-            nondefs = args[:-len(defaults)]
-        else:
-            nondefs = args
-        # Args without defaults must be provided.
-        try:
-            for arg in nondefs:
-                provided.pop(0)
-        except IndexError:
-            # Not enough
-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
-
-        # Defaults can be overridden.
-        defaults = defaults and list(defaults) or []
-        try:
-            for parg in provided:
-                defaults.pop(0)
-        except IndexError:
-            # Too many.
-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
-
-        return True
-    args_check = staticmethod(args_check)
-
-    def __str__(self):
-        return self.token
-
-def resolve_variable(path, context):
-    """
-    Returns the resolved variable, which may contain attribute syntax, within
-    the given context.
-
-    Deprecated; use the Variable class instead.
-    """
-    return Variable(path).resolve(context)
-
-class Variable(object):
-    r"""
-    A template variable, resolvable against a given context. The variable may be
-    a hard-coded string (if it begins and ends with single or double quote
-    marks)::
-
-        >>> c = {'article': {'section':u'News'}}
-        >>> Variable('article.section').resolve(c)
-        u'News'
-        >>> Variable('article').resolve(c)
-        {'section': u'News'}
-        >>> class AClass: pass
-        >>> c = AClass()
-        >>> c.article = AClass()
-        >>> c.article.section = u'News'
-
-    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
-    """
-
-    def __init__(self, var):
-        self.var = var
-        self.literal = None
-        self.lookups = None
-        self.translate = False
-
-        try:
-            # First try to treat this variable as a number.
-            #
-            # Note that this could cause an OverflowError here that we're not
-            # catching. Since this should only happen at compile time, that's
-            # probably OK.
-            self.literal = float(var)
-
-            # So it's a float... is it an int? If the original value contained a
-            # dot or an "e" then it was a float, not an int.
-            if '.' not in var and 'e' not in var.lower():
-                self.literal = int(self.literal)
-
-            # "2." is invalid
-            if var.endswith('.'):
-                raise ValueError
-
-        except ValueError:
-            # A ValueError means that the variable isn't a number.
-            if var.startswith('_(') and var.endswith(')'):
-                # The result of the lookup should be translated at rendering
-                # time.
-                self.translate = True
-                var = var[2:-1]
-            # If it's wrapped with quotes (single or double), then
-            # we're also dealing with a literal.
-            try:
-                self.literal = mark_safe(unescape_string_literal(var))
-            except ValueError:
-                # Otherwise we'll set self.lookups so that resolve() knows we're
-                # dealing with a bonafide variable
-                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
-
-    def resolve(self, context):
-        """Resolve this variable against a given context."""
-        if self.lookups is not None:
-            # We're dealing with a variable that needs to be resolved
-            value = self._resolve_lookup(context)
-        else:
-            # We're dealing with a literal, so it's already been "resolved"
-            value = self.literal
-        if self.translate:
-            return _(value)
-        return value
-
-    def __repr__(self):
-        return "<%s: %r>" % (self.__class__.__name__, self.var)
-
-    def __str__(self):
-        return self.var
-
-    def _resolve_lookup(self, context):
-        """
-        Performs resolution of a real variable (i.e. not a literal) against the
-        given context.
-
-        As indicated by the method's name, this method is an implementation
-        detail and shouldn't be called by external code. Use Variable.resolve()
-        instead.
-        """
-        current = context
-        for bit in self.lookups:
-            try: # dictionary lookup
-                current = current[bit]
-            except (TypeError, AttributeError, KeyError):
-                try: # attribute lookup
-                    current = getattr(current, bit)
-                    if callable(current):
-                        if getattr(current, 'alters_data', False):
-                            current = settings.TEMPLATE_STRING_IF_INVALID
-                        else:
-                            try: # method call (assuming no args required)
-                                current = current()
-                            except TypeError: # arguments *were* required
-                                # GOTCHA: This will also catch any TypeError
-                                # raised in the function itself.
-                                current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
-                            except Exception, e:
-                                if getattr(e, 'silent_variable_failure', False):
-                                    current = settings.TEMPLATE_STRING_IF_INVALID
-                                else:
-                                    raise
-                except (TypeError, AttributeError):
-                    try: # list-index lookup
-                        current = current[int(bit)]
-                    except (IndexError, # list index out of range
-                            ValueError, # invalid literal for int()
-                            KeyError,   # current is a dict without `int(bit)` key
-                            TypeError,  # unsubscriptable object
-                            ):
-                        raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
-                except Exception, e:
-                    if getattr(e, 'silent_variable_failure', False):
-                        current = settings.TEMPLATE_STRING_IF_INVALID
-                    else:
-                        raise
-
-        return current
-
-class Node(object):
-    # Set this to True for nodes that must be first in the template (although
-    # they can be preceded by text nodes.
-    must_be_first = False
-
-    def render(self, context):
-        "Return the node rendered as a string"
-        pass
-
-    def __iter__(self):
-        yield self
-
-    def get_nodes_by_type(self, nodetype):
-        "Return a list of all nodes (within this node and its nodelist) of the given type"
-        nodes = []
-        if isinstance(self, nodetype):
-            nodes.append(self)
-        if hasattr(self, 'nodelist'):
-            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
-        return nodes
-
-class NodeList(list):
-    # Set to True the first time a non-TextNode is inserted by
-    # extend_nodelist().
-    contains_nontext = False
-
-    def render(self, context):
-        bits = []
-        for node in self:
-            if isinstance(node, Node):
-                bits.append(self.render_node(node, context))
-            else:
-                bits.append(node)
-        return mark_safe(''.join([force_unicode(b) for b in bits]))
-
-    def get_nodes_by_type(self, nodetype):
-        "Return a list of all nodes of the given type"
-        nodes = []
-        for node in self:
-            nodes.extend(node.get_nodes_by_type(nodetype))
-        return nodes
-
-    def render_node(self, node, context):
-        return node.render(context)
-
-class TextNode(Node):
-    def __init__(self, s):
-        self.s = s
-
-    def __repr__(self):
-        return "<Text Node: '%s'>" % smart_str(self.s[:25], 'ascii',
-                errors='replace')
-
-    def render(self, context):
-        return self.s
-    
-def _render_value_in_context(value, context):
-    """
-    Converts any value to a string to become part of a rendered template. This
-    means escaping, if required, and conversion to a unicode object. If value
-    is a string, it is expected to have already been translated.
-    """
-    value = force_unicode(value)
-    if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
-        return escape(value)
-    else:
-        return value
-
-class VariableNode(Node):
-    def __init__(self, filter_expression):
-        self.filter_expression = filter_expression
-
-    def __repr__(self):
-        return "<Variable Node: %s>" % self.filter_expression
-
-    def render(self, context):
-        try:
-            output = self.filter_expression.resolve(context)
-        except UnicodeDecodeError:
-            # Unicode conversion can fail sometimes for reasons out of our
-            # control (e.g. exception rendering). In that case, we fail quietly.
-            return ''
-        return _render_value_in_context(output, context)
-
-def generic_tag_compiler(params, defaults, name, node_class, parser, token):
-    "Returns a template.Node subclass."
-    bits = token.split_contents()[1:]
-    bmax = len(params)
-    def_len = defaults and len(defaults) or 0
-    bmin = bmax - def_len
-    if(len(bits) < bmin or len(bits) > bmax):
-        if bmin == bmax:
-            message = "%s takes %s arguments" % (name, bmin)
-        else:
-            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
-        raise TemplateSyntaxError(message)
-    return node_class(bits)
-
-class Library(object):
-    def __init__(self):
-        self.filters = {}
-        self.tags = {}
-
-    def tag(self, name=None, compile_function=None):
-        if name == None and compile_function == None:
-            # @register.tag()
-            return self.tag_function
-        elif name != None and compile_function == None:
-            if(callable(name)):
-                # @register.tag
-                return self.tag_function(name)
-            else:
-                # @register.tag('somename') or @register.tag(name='somename')
-                def dec(func):
-                    return self.tag(name, func)
-                return dec
-        elif name != None and compile_function != None:
-            # register.tag('somename', somefunc)
-            self.tags[name] = compile_function
-            return compile_function
-        else:
-            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
-
-    def tag_function(self,func):
-        self.tags[getattr(func, "_decorated_function", func).__name__] = func
-        return func
-
-    def filter(self, name=None, filter_func=None):
-        if name == None and filter_func == None:
-            # @register.filter()
-            return self.filter_function
-        elif filter_func == None:
-            if(callable(name)):
-                # @register.filter
-                return self.filter_function(name)
-            else:
-                # @register.filter('somename') or @register.filter(name='somename')
-                def dec(func):
-                    return self.filter(name, func)
-                return dec
-        elif name != None and filter_func != None:
-            # register.filter('somename', somefunc)
-            self.filters[name] = filter_func
-            return filter_func
-        else:
-            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
-
-    def filter_function(self, func):
-        self.filters[getattr(func, "_decorated_function", func).__name__] = func
-        return func
-
-    def simple_tag(self,func):
-        params, xx, xxx, defaults = getargspec(func)
-
-        class SimpleNode(Node):
-            def __init__(self, vars_to_resolve):
-                self.vars_to_resolve = map(Variable, vars_to_resolve)
-
-            def render(self, context):
-                resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
-                return func(*resolved_vars)
-
-        compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
-        compile_func.__doc__ = func.__doc__
-        self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
-        return func
-
-    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
-        def dec(func):
-            params, xx, xxx, defaults = getargspec(func)
-            if takes_context:
-                if params[0] == 'context':
-                    params = params[1:]
-                else:
-                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
-
-            class InclusionNode(Node):
-                def __init__(self, vars_to_resolve):
-                    self.vars_to_resolve = map(Variable, vars_to_resolve)
-
-                def render(self, context):
-                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
-                    if takes_context:
-                        args = [context] + resolved_vars
-                    else:
-                        args = resolved_vars
-
-                    dict = func(*args)
-
-                    if not getattr(self, 'nodelist', False):
-                        from django.template.loader import get_template, select_template
-                        if not isinstance(file_name, basestring) and is_iterable(file_name):
-                            t = select_template(file_name)
-                        else:
-                            t = get_template(file_name)
-                        self.nodelist = t.nodelist
-                    return self.nodelist.render(context_class(dict,
-                            autoescape=context.autoescape))
-
-            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
-            compile_func.__doc__ = func.__doc__
-            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
-            return func
-        return dec
-
-def get_library(module_name):
-    lib = libraries.get(module_name, None)
-    if not lib:
-        try:
-            mod = import_module(module_name)
-        except ImportError, e:
-            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
-        try:
-            lib = mod.register
-            libraries[module_name] = lib
-        except AttributeError:
-            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
-    return lib
-
-def add_to_builtins(module_name):
-    builtins.append(get_library(module_name))
-
-add_to_builtins('django.template.defaulttags')
-add_to_builtins('django.template.defaultfilters')
Index: django/template/utils.py
===================================================================
--- django/template/utils.py	(revision 0)
+++ django/template/utils.py	(revision 0)
@@ -0,0 +1,115 @@
+import re
+from django.utils.encoding import smart_str
+from django.template.nodes import Node, NodeList
+from django.template.parser import TokenSyntaxError, TemplateSyntaxError
+
+class EmptyNode(Node):
+    def render(self, context):
+        return u''
+        
+class ConditionalNode(Node):
+    def __init__(self, nodelist_true, nodelist_false):
+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+
+    def __iter__(self):
+        for node in self.nodelist_true:
+            yield node
+        for node in self.nodelist_false:
+            yield node
+
+    def get_nodes_by_type(self, nodetype):
+        nodes = []
+        if isinstance(self, nodetype):
+            nodes.append(self)
+        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
+        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
+        return nodes
+        
+    def check_condition(self, context):
+        return False
+        
+    def render(self, context):
+        if self.check_condition(context):
+            return self.nodelist_true.render(context)
+        elif self.nodelist_false:
+            return self.nodelist_false.render(context)
+        return u""
+        
+def parse_conditional_nodelists(parser, name):
+    end_tag = 'end' + name
+    nodelist_true = parser.parse(('else', end_tag))
+    token = parser.next_token()
+    if token.contents == 'else':
+        nodelist_false = parser.parse((end_tag,))
+        parser.delete_first_token()
+    else:
+        nodelist_false = NodeList()
+    return nodelist_true, nodelist_false
+
+def parse_args_and_kwargs(bits, until=()):
+    args = []
+    kwargs = {}
+    while True:
+        name = bits.pop_name()
+        if name in until:
+            bits.pushback()
+            break
+        if name and bits.pop_lexeme('='):
+            kwargs[name] = bits.parse_expression(required=True)
+        else:
+            if name:
+                bits.pushback()
+            try:
+                args.append(bits.parse_expression())
+            except TokenSyntaxError:
+                break
+        if not bits.pop_lexeme(','):
+            break
+    return args, kwargs
+
+def parse_as(bits, required=False):
+    if bits.pop_lexeme('as', required=required):
+        return bits.pop_name(required=required)
+    return None
+
+def parse_context_bindings(bits, empty=False):
+    bindings = {}
+    try:
+        while True:
+            expression = bits.parse_expression()
+            name = parse_as(bits, required=True)
+            bindings[name] = expression
+            if not bits.pop_lexeme('and'):
+                break
+    except TokenSyntaxError:
+        if bindings or not bindings and not empty:
+            bits.expected('<expression> as <name>')
+    return bindings
+
+def resolve_list(expressions, context, safe=False, default=None, ignore_errors=False):
+    result = []
+    for exp in expressions:
+        try:
+            value = exp.resolve(context)
+        except LookupError:
+            if not safe and not ignore_errors:
+                raise
+            if safe:
+                value = default
+        else:
+            result.append(value)
+    return result
+
+def resolve_dict(expressions, context, kwargs=False, safe=False, default=None, ignore_errors=False):
+    result = {}
+    for key, exp in expressions.items():
+        try:
+            value = exp.resolve(context)
+        except LookupError:
+            if not safe and not ignore_errors:
+                raise
+            if safe:
+                value = default
+        else:
+            result[kwargs and smart_str(key, 'ascii') or key] = value
+    return result
Index: django/template/expressions.py
===================================================================
--- django/template/expressions.py	(revision 0)
+++ django/template/expressions.py	(revision 0)
@@ -0,0 +1,137 @@
+from django.conf import settings
+from django.utils.encoding import force_unicode
+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
+
+__all__ = ('LookupError', 'Expression', 'Variable', 'Literal', 'Lookup', 'FilterExpression')
+
+class LookupError(Exception):
+    def __init__(self, var, msg, params=()):
+        self.var = var
+        self.msg = msg
+        self.params = params
+
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+
+    def __unicode__(self):
+        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
+
+
+class Expression(object):
+    def __init__(self, token=None):
+        self.token = token
+
+    def resolve_safe(self, context, default=None):
+        try:
+            return self.resolve(context)
+        except LookupError:
+            return default
+
+    def resolve(self, context, ignore_failures=False):
+        return None
+
+
+class Literal(Expression):
+    def __init__(self, value, token=None):
+        super(Literal, self).__init__(token=token)
+        self.value = value
+
+    def resolve(self, context, ignore_failures=False):
+        return self.value
+        
+    def __repr__(self):
+        return '<Literal %s>' % repr(self.value)
+        
+        
+def handle_lookup_exception(e):
+    if getattr(e, 'silent_variable_failure', False):
+        return settings.TEMPLATE_STRING_IF_INVALID
+    raise e
+        
+class Lookup(Expression):
+    def __init__(self, lookups, var=None, token=None):
+        super(Lookup, self).__init__(token=token)    
+        self.var = var
+        self.lookups = lookups
+
+    def __str__(self):
+        return "%s" % self.var
+        
+    def __repr__(self):
+        return "<Lookup %s>" % self.var
+        
+    def resolve(self, context, ignore_failures=False):
+        current = context
+        try:
+            for bit in self.lookups:
+                try: # dictionary lookup
+                    current = current[bit]
+                except (TypeError, AttributeError, KeyError):
+                    try: # attribute lookup
+                        current = getattr(current, bit)
+                        if callable(current):
+                            if getattr(current, 'alters_data', False):
+                                current = settings.TEMPLATE_STRING_IF_INVALID
+                            else:
+                                try: # method call (assuming no args required)
+                                    current = current()
+                                except TypeError: # arguments *were* required
+                                    # GOTCHA: This will also catch any TypeError
+                                    # raised in the function itself.
+                                    current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
+                                except Exception, e:
+                                    current = handle_lookup_exception(e)
+                    except (TypeError, AttributeError):
+                        try: # list-index lookup
+                            current = current[int(bit)]
+                        except (IndexError, # list index out of range
+                                ValueError, # invalid literal for int()
+                                KeyError,   # current is a dict without `int(bit)` key
+                                TypeError,  # unsubscriptable object
+                                ):
+                            raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current))
+                    except Exception, e:
+                        current = handle_lookup_exception(e)
+        except LookupError:
+            if ignore_failures:
+                return None
+            raise
+        return current
+
+class FilterExpression(Expression):
+    def __init__(self, root, filters, token=None):
+        super(FilterExpression, self).__init__(token=token)    
+        self.root = root
+        self.filters = filters
+        
+    def __repr__(self):
+        return "<FilterExpression %s>" % "|".join(["%s:%s" % (func.__name__, arg) for func, arg in self.filters])
+
+    def resolve(self, context, ignore_failures=False):
+        try:
+            obj = self.root.resolve(context)
+        except LookupError:
+            if ignore_failures:
+                obj = None
+            elif not self.filters:
+                raise
+            else:
+                obj = settings.TEMPLATE_STRING_IF_INVALID
+        for func, args in self.filters:
+            arg_vals = []         
+            for arg in args:
+                arg_vals.append(arg.resolve(context))
+            if getattr(func, 'needs_autoescape', False):
+                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
+            else:
+                new_obj = func(obj, *arg_vals)
+            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
+                obj = mark_safe(new_obj)
+            elif isinstance(obj, EscapeData):
+                obj = mark_for_escaping(new_obj)
+            else:
+                obj = new_obj
+        return obj
+
+    def __str__(self):
+        return str(self.root)+'|<filtered>'
Index: django/template/defaulttags.py
===================================================================
--- django/template/defaulttags.py	(revision 11593)
+++ django/template/defaulttags.py	(working copy)
@@ -8,11 +8,12 @@
 except NameError:
     from django.utils.itercompat import reversed     # Python 2.3 fallback
 
-from django.template import Node, NodeList, Template, Context, Variable
-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
-from django.template import get_library, Library, InvalidTemplateLibrary
+from django.template import Node, NodeList, Template, Context
+from django.template.library import get_library, Library, InvalidTemplateLibrary
+from django.template.parser import uses_token_stream, TokenSyntaxError, TemplateSyntaxError, parse_lookup
+from django.template.parser import BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
+from django.template.utils import EmptyNode, ConditionalNode, parse_conditional_nodelists, parse_as, parse_args_and_kwargs, resolve_list, resolve_dict
 from django.conf import settings
-from django.utils.encoding import smart_str, smart_unicode
 from django.utils.itercompat import groupby
 from django.utils.safestring import mark_safe
 
@@ -33,9 +34,7 @@
         else:
             return output
 
-class CommentNode(Node):
-    def render(self, context):
-        return ''
+class CommentNode(EmptyNode): pass
 
 class CycleNode(Node):
     def __init__(self, cyclevars, variable_name=None):
@@ -74,9 +73,9 @@
 
     def render(self, context):
         for var in self.vars:
-            value = var.resolve(context, True)
+            value = var.resolve_safe(context)
             if value:
-                return smart_unicode(value)
+                return value
         return u''
 
 class ForNode(Node):
@@ -114,11 +113,8 @@
             parentloop = context['forloop']
         else:
             parentloop = {}
-        context.push()
-        try:
-            values = self.sequence.resolve(context, True)
-        except VariableDoesNotExist:
-            values = []
+        context.push()  
+        values = self.sequence.resolve_safe(context, [])
         if values is None:
             values = []
         if not hasattr(values, '__len__'):
@@ -163,95 +159,57 @@
         context.pop()
         return nodelist.render(context)
 
-class IfChangedNode(Node):
-    def __init__(self, nodelist_true, nodelist_false, *varlist):
-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+class IfChangedNode(ConditionalNode):
+    def __init__(self, nodelist_true, nodelist_false, expressions):
+        super(IfChangedNode, self).__init__(nodelist_true, nodelist_false)
+        self.expressions = expressions        
         self._last_seen = None
-        self._varlist = varlist
         self._id = str(id(self))
 
-    def render(self, context):
+    def check_condition(self, context):        
         if 'forloop' in context and self._id not in context['forloop']:
             self._last_seen = None
             context['forloop'][self._id] = 1
-        try:
-            if self._varlist:
-                # Consider multiple parameters.  This automatically behaves
-                # like an OR evaluation of the multiple variables.
-                compare_to = [var.resolve(context, True) for var in self._varlist]
-            else:
-                compare_to = self.nodelist_true.render(context)
-        except VariableDoesNotExist:
-            compare_to = None
+        if self.expressions:
+            # Consider multiple parameters.  This automatically behaves
+            # like an OR evaluation of the multiple variables.
+            try:
+                compare_to = [var.resolve(context, True) for var in self.expressions]
+            except LookupError:
+                compare_to = None
+        else:
+            compare_to = self.nodelist_true.render(context)
 
         if compare_to != self._last_seen:
-            firstloop = (self._last_seen == None)
             self._last_seen = compare_to
-            content = self.nodelist_true.render(context)
-            return content
-        elif self.nodelist_false:
-            return self.nodelist_false.render(context)
-        return ''
+            return True        
+        return False
 
-class IfEqualNode(Node):
+class IfEqualNode(ConditionalNode):
     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
+        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
         self.var1, self.var2 = var1, var2
-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
         self.negate = negate
 
-    def __repr__(self):
-        return "<IfEqualNode>"
+    def check_condition(self, context):
+        val1 = self.var1.resolve_safe(context)
+        val2 = self.var2.resolve_safe(context)
+        return (self.negate and val1 != val2) or (not self.negate and val1 == val2)
 
-    def render(self, context):
-        val1 = self.var1.resolve(context, True)
-        val2 = self.var2.resolve(context, True)
-        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
-            return self.nodelist_true.render(context)
-        return self.nodelist_false.render(context)
-
-class IfNode(Node):
+class IfNode(ConditionalNode):
     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
+        super(IfNode, self).__init__(nodelist_true, nodelist_false)
         self.bool_exprs = bool_exprs
-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
         self.link_type = link_type
 
-    def __repr__(self):
-        return "<If node>"
+    def check_condition(self, context):
+        or_link = self.link_type == IfNode.LinkTypes.or_
+        for ifnot, bool_expr in self.bool_exprs:
+            value = bool(bool_expr.resolve_safe(context, False))
+            if (ifnot != value) == or_link:
+                return or_link
+        return not or_link
 
-    def __iter__(self):
-        for node in self.nodelist_true:
-            yield node
-        for node in self.nodelist_false:
-            yield node
-
-    def get_nodes_by_type(self, nodetype):
-        nodes = []
-        if isinstance(self, nodetype):
-            nodes.append(self)
-        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
-        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
-        return nodes
-
-    def render(self, context):
-        if self.link_type == IfNode.LinkTypes.or_:
-            for ifnot, bool_expr in self.bool_exprs:
-                try:
-                    value = bool_expr.resolve(context, True)
-                except VariableDoesNotExist:
-                    value = None
-                if (value and not ifnot) or (ifnot and not value):
-                    return self.nodelist_true.render(context)
-            return self.nodelist_false.render(context)
-        else:
-            for ifnot, bool_expr in self.bool_exprs:
-                try:
-                    value = bool_expr.resolve(context, True)
-                except VariableDoesNotExist:
-                    value = None
-                if not ((value and not ifnot) or (ifnot and not value)):
-                    return self.nodelist_false.render(context)
-            return self.nodelist_true.render(context)
-
     class LinkTypes:
         and_ = 0,
         or_ = 1
@@ -309,9 +267,7 @@
                     return '' # Fail silently for invalid included templates.
         return output
 
-class LoadNode(Node):
-    def render(self, context):
-        return ''
+class LoadNode(EmptyNode): pass
 
 class NowNode(Node):
     def __init__(self, format_string):
@@ -321,7 +277,7 @@
         from datetime import datetime
         from django.utils.dateformat import DateFormat
         df = DateFormat(datetime.now())
-        return df.format(self.format_string)
+        return df.format(self.format_string.resolve_safe(context, ""))
 
 class SpacelessNode(Node):
     def __init__(self, nodelist):
@@ -357,9 +313,8 @@
 
     def render(self, context):
         from django.core.urlresolvers import reverse, NoReverseMatch
-        args = [arg.resolve(context) for arg in self.args]
-        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
-                       for k, v in self.kwargs.items()])
+        args = resolve_list(self.args, context)
+        kwargs = resolve_dict(self.kwargs, context, kwargs=True)
 
         # Try to look up the URL twice: once given the view name, and again
         # relative to what we guess is the "main" app. If they both fail,
@@ -401,8 +356,8 @@
             value = self.val_expr.resolve(context)
             maxvalue = self.max_expr.resolve(context)
             max_width = int(self.max_width.resolve(context))
-        except VariableDoesNotExist:
-            return ''
+        except LookupError:
+            return u""
         except ValueError:
             raise TemplateSyntaxError("widthratio final argument must be an number")
         try:
@@ -441,8 +396,7 @@
     arg = args[1]
     if arg not in (u'on', u'off'):
         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
-    nodelist = parser.parse(('endautoescape',))
-    parser.delete_first_token()
+    nodelist = parser.parse_nodelist('endautoescape')
     return AutoEscapeControlNode((arg == 'on'), nodelist)
 autoescape = register.tag(autoescape)
 
@@ -556,13 +510,13 @@
     for func, unused in filter_expr.filters:
         if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
             raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
-    nodelist = parser.parse(('endfilter',))
-    parser.delete_first_token()
+    nodelist = parser.parse_nodelist('endfilter')
     return FilterNode(filter_expr, nodelist)
 do_filter = register.tag("filter", do_filter)
 
 #@register.tag
-def firstof(parser, token):
+#@uses_token_stream
+def firstof(parser, bits):
     """
     Outputs the first variable passed that is not False, without escaping.
 
@@ -596,15 +550,12 @@
 	{% endfilter %}
 
     """
-    bits = token.split_contents()[1:]
-    if len(bits) < 1:
-        raise TemplateSyntaxError("'firstof' statement requires at least one"
-                                  " argument")
-    return FirstOfNode([parser.compile_filter(bit) for bit in bits])
-firstof = register.tag(firstof)
+    return FirstOfNode(bits.parse_expression_list(minimum=1))
+firstof = register.tag(uses_token_stream(firstof))
 
 #@register.tag(name="for")
-def do_for(parser, token):
+#@uses_token_stream
+def do_for(parser, bits):
     """
     Loops over each item in an array.
 
@@ -667,52 +618,40 @@
         ==========================  ================================================
 
     """
-    bits = token.contents.split()
-    if len(bits) < 4:
-        raise TemplateSyntaxError("'for' statements should have at least four"
-                                  " words: %s" % token.contents)
+    loopvars = []
+    while True:
+        name = bits.pop_name()
+        if not name:
+            if loopvars:
+                bits.pushback() #pushback the trailing comma
+            break
+        if name == 'in':
+            if loopvars:
+                bits.pushback() #pushback the trailing comma        
+            bits.pushback()
+            break
+        loopvars.append(name)
+        if not bits.pop_lexeme(','):
+            break
+    if not loopvars:
+        raise TemplateSyntaxError("'for' statements should use the format: for loopvar[, ...] in expression")
 
-    is_reversed = bits[-1] == 'reversed'
-    in_index = is_reversed and -3 or -2
-    if bits[in_index] != 'in':
-        raise TemplateSyntaxError("'for' statements should use the format"
-                                  " 'for x in y': %s" % token.contents)
-
-    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
-    for var in loopvars:
-        if not var or ' ' in var:
-            raise TemplateSyntaxError("'for' tag received an invalid argument:"
-                                      " %s" % token.contents)
-
-    sequence = parser.compile_filter(bits[in_index+1])
+    bits.pop_lexeme('in', required=True)
+    sequence = bits.parse_expression(required=True)    
+    is_reversed = bits.pop_lexeme('reversed')
+    
     nodelist_loop = parser.parse(('empty', 'endfor',))
     token = parser.next_token()
     if token.contents == 'empty':
-        nodelist_empty = parser.parse(('endfor',))
-        parser.delete_first_token()
+        nodelist_empty = parser.parse_nodelist('endfor')
     else:
         nodelist_empty = None
     return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)
-do_for = register.tag("for", do_for)
+do_for = register.tag("for", uses_token_stream(do_for))
 
-def do_ifequal(parser, token, negate):
-    bits = list(token.split_contents())
-    if len(bits) != 3:
-        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
-    end_tag = 'end' + bits[0]
-    nodelist_true = parser.parse(('else', end_tag))
-    token = parser.next_token()
-    if token.contents == 'else':
-        nodelist_false = parser.parse((end_tag,))
-        parser.delete_first_token()
-    else:
-        nodelist_false = NodeList()
-    val1 = parser.compile_filter(bits[1])
-    val2 = parser.compile_filter(bits[2])
-    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
-
 #@register.tag
-def ifequal(parser, token):
+#@uses_token_stream
+def ifequal(parser, bits):
     """
     Outputs the contents of the block if the two arguments equal each other.
 
@@ -728,20 +667,26 @@
             ...
         {% endifnotequal %}
     """
-    return do_ifequal(parser, token, False)
-ifequal = register.tag(ifequal)
+    exp1, exp2 = bits.parse_expression_list(count=2)
+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
+    return IfEqualNode(exp1, exp2, nodelist_true, nodelist_false, False)    
+ifequal = register.tag(uses_token_stream(ifequal))
 
 #@register.tag
-def ifnotequal(parser, token):
+#@uses_token_stream
+def ifnotequal(parser, bits):
     """
     Outputs the contents of the block if the two arguments are not equal.
     See ifequal.
     """
-    return do_ifequal(parser, token, True)
-ifnotequal = register.tag(ifnotequal)
+    exp1, exp2 = bits.parse_expression_list(count=2)
+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
+    return IfEqualNode(exp1, exp2, nodelist_true, nodelist_false, True)    
+ifnotequal = register.tag(uses_token_stream(ifnotequal))
 
 #@register.tag(name="if")
-def do_if(parser, token):
+#@uses_token_stream
+def do_if(parser, bits):
     """
     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
     (i.e., exists, is not empty, and is not a false boolean value), the
@@ -799,44 +744,35 @@
             {% endif %}
         {% endif %}
     """
-    bits = token.contents.split()
-    del bits[0]
-    if not bits:
-        raise TemplateSyntaxError("'if' statement requires at least one argument")
-    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
-    bitstr = ' '.join(bits)
-    boolpairs = bitstr.split(' and ')
     boolvars = []
-    if len(boolpairs) == 1:
-        link_type = IfNode.LinkTypes.or_
-        boolpairs = bitstr.split(' or ')
-    else:
-        link_type = IfNode.LinkTypes.and_
-        if ' or ' in bitstr:
+    link_type = None
+    while True:
+        negate = bits.pop_lexeme('not')
+        try:
+            boolvars.append((negate, bits.parse_expression()))
+        except TokenSyntaxError:
+            # If the variable is named "not" we have been too greedy
+            if negate:
+                boolvars.append((False, parse_lookup('not')))
+            else:
+                bits.expected('<expression>')
+        link = bits.pop_name()
+        if not link:
+            break
+        if link not in ('and', 'or'):
+            bits.pushback()
+            break
+        if link_type and link != link_type:
             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
-    for boolpair in boolpairs:
-        if ' ' in boolpair:
-            try:
-                not_, boolvar = boolpair.split()
-            except ValueError:
-                raise TemplateSyntaxError, "'if' statement improperly formatted"
-            if not_ != 'not':
-                raise TemplateSyntaxError, "Expected 'not' in if statement"
-            boolvars.append((True, parser.compile_filter(boolvar)))
         else:
-            boolvars.append((False, parser.compile_filter(boolpair)))
-    nodelist_true = parser.parse(('else', 'endif'))
-    token = parser.next_token()
-    if token.contents == 'else':
-        nodelist_false = parser.parse(('endif',))
-        parser.delete_first_token()
-    else:
-        nodelist_false = NodeList()
-    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
-do_if = register.tag("if", do_if)
+            link_type = link
+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')
+    return IfNode(boolvars, nodelist_true, nodelist_false, link_type == 'and' and IfNode.LinkTypes.and_ or IfNode.LinkTypes.or_)
+do_if = register.tag("if", uses_token_stream(do_if))
 
 #@register.tag
-def ifchanged(parser, token):
+#@uses_token_stream
+def ifchanged(parser, bits):
     """
     Checks if a value has changed from the last iteration of a loop.
 
@@ -864,19 +800,13 @@
                 {% endifchanged %}
             {% endfor %}
     """
-    bits = token.contents.split()
-    nodelist_true = parser.parse(('else', 'endifchanged'))
-    token = parser.next_token()
-    if token.contents == 'else':
-        nodelist_false = parser.parse(('endifchanged',))
-        parser.delete_first_token()
-    else:
-        nodelist_false = NodeList()
-    values = [parser.compile_filter(bit) for bit in bits[1:]]
-    return IfChangedNode(nodelist_true, nodelist_false, *values)
-ifchanged = register.tag(ifchanged)
+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'ifchanged')
+    expressions = bits.parse_expression_list()
+    return IfChangedNode(nodelist_true, nodelist_false, expressions)
+ifchanged = register.tag(uses_token_stream(ifchanged))
 
 #@register.tag
+#@uses_token_stream
 def ssi(parser, token):
     """
     Outputs the contents of a given file into the page.
@@ -892,19 +822,11 @@
 
         {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
     """
-    bits = token.contents.split()
-    parsed = False
-    if len(bits) not in (2, 3):
-        raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
-                                  " the file to be included")
-    if len(bits) == 3:
-        if bits[2] == 'parsed':
-            parsed = True
-        else:
-            raise TemplateSyntaxError("Second (optional) argument to %s tag"
-                                      " must be 'parsed'" % bits[0])
-    return SsiNode(bits[1], parsed)
-ssi = register.tag(ssi)
+    path = bits.parse_string(bare=True, required=True)
+    parsed = bits.pop_lexeme('parsed')
+    
+    return SsiNode(path, parsed)
+ssi = register.tag(uses_token_stream(ssi))
 
 #@register.tag
 def load(parser, token):
@@ -929,7 +851,8 @@
 load = register.tag(load)
 
 #@register.tag
-def now(parser, token):
+#@uses_token_stream
+def now(parser, bits):
     """
     Displays the date, formatted according to the given string.
 
@@ -940,15 +863,12 @@
 
         It is {% now "jS F Y H:i" %}
     """
-    bits = token.contents.split('"')
-    if len(bits) != 3:
-        raise TemplateSyntaxError, "'now' statement takes one argument"
-    format_string = bits[1]
-    return NowNode(format_string)
-now = register.tag(now)
+    return NowNode(bits.parse_expression(required=True))
+now = register.tag(uses_token_stream(now))
 
 #@register.tag
-def regroup(parser, token):
+#@uses_token_stream
+def regroup(parser, bits):
     """
     Regroups a list of alike objects by a common attribute.
 
@@ -994,22 +914,13 @@
         {% regroup people|dictsort:"gender" by gender as grouped %}
 
     """
-    firstbits = token.contents.split(None, 3)
-    if len(firstbits) != 4:
-        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
-    target = parser.compile_filter(firstbits[1])
-    if firstbits[2] != 'by':
-        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
-    lastbits_reversed = firstbits[3][::-1].split(None, 2)
-    if lastbits_reversed[1][::-1] != 'as':
-        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
-                                  " be 'as'")
-
-    expression = parser.compile_filter(lastbits_reversed[2][::-1])
-
-    var_name = lastbits_reversed[0][::-1]
+    target = bits.parse_expression(required=True)
+    bits.pop_lexeme('by', required=True)
+    expression = bits.parse_expression(required=True)    
+    bits.pop_lexeme('as', required=True)
+    var_name = bits.pop_name(required=True)
     return RegroupNode(target, expression, var_name)
-regroup = register.tag(regroup)
+regroup = register.tag(uses_token_stream(regroup))
 
 def spaceless(parser, token):
     """
@@ -1036,8 +947,7 @@
             </strong>
         {% endspaceless %}
     """
-    nodelist = parser.parse(('endspaceless',))
-    parser.delete_first_token()
+    nodelist = parser.parse_nodelist('endspaceless')
     return SpacelessNode(nodelist)
 spaceless = register.tag(spaceless)
 
@@ -1075,7 +985,9 @@
     return TemplateTagNode(tag)
 templatetag = register.tag(templatetag)
 
-def url(parser, token):
+#@register.tag
+#@uses_token_stream
+def url(parser, bits):
     """
     Returns an absolute URL matching given view with its parameters.
 
@@ -1106,34 +1018,17 @@
 
     The URL will look like ``/clients/client/123/``.
     """
-    bits = token.split_contents()
-    if len(bits) < 2:
-        raise TemplateSyntaxError("'%s' takes at least one argument"
-                                  " (path to a view)" % bits[0])
-    viewname = bits[1]
-    args = []
-    kwargs = {}
-    asvar = None
-
-    if len(bits) > 2:
-        bits = iter(bits[2:])
-        for bit in bits:
-            if bit == 'as':
-                asvar = bits.next()
-                break
-            else:
-                for arg in bit.split(","):
-                    if '=' in arg:
-                        k, v = arg.split('=', 1)
-                        k = k.strip()
-                        kwargs[k] = parser.compile_filter(v)
-                    elif arg:
-                        args.append(parser.compile_filter(arg))
+    viewname = bits.parse_string(bare=True, required=True)
+    if bits.pop_lexeme(':'):
+        viewname = "%s:%s" % (viewname, bits.parse_string(bare=True, required=True))
+    args, kwargs = parse_args_and_kwargs(bits, until=('as',))
+    asvar = parse_as(bits)
     return URLNode(viewname, args, kwargs, asvar)
-url = register.tag(url)
+url = register.tag(uses_token_stream(url))
 
 #@register.tag
-def widthratio(parser, token):
+#@uses_token_stream
+def widthratio(parser, bits):
     """
     For creating bar charts and such, this tag calculates the ratio of a given
     value to a maximum value, and then applies that ratio to a constant.
@@ -1146,18 +1041,14 @@
     the above example will be 88 pixels wide (because 175/200 = .875;
     .875 * 100 = 87.5 which is rounded up to 88).
     """
-    bits = token.contents.split()
-    if len(bits) != 4:
-        raise TemplateSyntaxError("widthratio takes three arguments")
-    tag, this_value_expr, max_value_expr, max_width = bits
+    this_value_expr, max_value_expr, max_width = bits.parse_expression_list(count=3)
 
-    return WidthRatioNode(parser.compile_filter(this_value_expr),
-                          parser.compile_filter(max_value_expr),
-                          parser.compile_filter(max_width))
-widthratio = register.tag(widthratio)
+    return WidthRatioNode(this_value_expr, max_value_expr, max_width)
+widthratio = register.tag(uses_token_stream(widthratio))
 
 #@register.tag
-def do_with(parser, token):
+#@uses_token_stream
+def do_with(parser, bits):
     """
     Adds a value to the context (inside of this block) for caching and easy
     access.
@@ -1168,13 +1059,8 @@
             {{ total }} object{{ total|pluralize }}
         {% endwith %}
     """
-    bits = list(token.split_contents())
-    if len(bits) != 4 or bits[2] != "as":
-        raise TemplateSyntaxError("%r expected format is 'value as name'" %
-                                  bits[0])
-    var = parser.compile_filter(bits[1])
-    name = bits[3]
-    nodelist = parser.parse(('endwith',))
-    parser.delete_first_token()
-    return WithNode(var, name, nodelist)
-do_with = register.tag('with', do_with)
+    expression = bits.parse_expression(required=True)
+    name = parse_as(bits, required=True)
+    nodelist = parser.parse_nodelist('endwith')
+    return WithNode(expression, name, nodelist)
+do_with = register.tag('with', uses_token_stream(do_with))
Index: django/template/compat.py
===================================================================
--- django/template/compat.py	(revision 0)
+++ django/template/compat.py	(revision 0)
@@ -0,0 +1,132 @@
+import warnings
+from django.template.expressions import Expression, LookupError
+from django.template.nodes import ExpressionNode
+
+VariableDoesNotExist = LookupError
+VariableNode = ExpressionNode
+
+class Variable(Expression):
+    def __init__(self, var):
+        warnings.warn('Use Lookup instead of Variable.', DeprecationWarning, stacklevel=2)    
+        self.var = var
+        from django.template.parser import TokenStream, TemplateSyntaxError
+        stream = TokenStream(None, var)
+        self.expression = stream.parse_value()
+        if not stream.consumed():
+            raise TemplateSyntaxError("Invalid variable: %s" % var)
+
+    def resolve(self, context):
+        return self.expression.resolve(context)
+
+    def __repr__(self):
+        return "<%s: %r>" % (self.__class__.__name__, self.var)
+
+    def __str__(self):
+        return self.var    
+
+def resolve_variable(path, context):
+    """
+    Returns the resolved variable, which may contain attribute syntax, within
+    the given context.
+
+    Deprecated.
+    """
+    warnings.warn('Use Lookup instead of resolve_variable.', DeprecationWarning, stacklevel=2)
+    from django.template.parser import TokenStream, TemplateSyntaxError
+    stream = TokenStream(None, path)
+    val = stream.parse_value()
+    if not stream.consumed():
+        raise TemplateSyntaxError("Invalid variable: %s" % path)    
+    return val.resolve(context)
+
+class TokenParser(object):
+    """
+    Subclass this and implement the top() method to parse a template line. When
+    instantiating the parser, pass in the line from the Django template parser.
+
+    The parser's "tagname" instance-variable stores the name of the tag that
+    the filter was called with.
+    """
+    def __init__(self, subject):
+        self.subject = subject
+        self.pointer = 0
+        self.backout = []
+        self.tagname = self.tag()
+
+    def top(self):
+        "Overload this method to do the actual parsing and return the result."
+        raise NotImplementedError()
+
+    def more(self):
+        "Returns True if there is more stuff in the tag."
+        return self.pointer < len(self.subject)
+
+    def back(self):
+        "Undoes the last microparser. Use this for lookahead and backtracking."
+        if not len(self.backout):
+            # avoid cyclic imports
+            from django.template import TemplateSyntaxError
+            raise TemplateSyntaxError("back called without some previous parsing")
+        self.pointer = self.backout.pop()
+
+    def tag(self):
+        "A microparser that just returns the next tag from the line."
+        subject = self.subject
+        i = self.pointer
+        if i >= len(subject):
+            # avoid cyclic imports
+            from django.template import TemplateSyntaxError        
+            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
+        p = i
+        while i < len(subject) and subject[i] not in (' ', '\t'):
+            i += 1
+        s = subject[p:i]
+        while i < len(subject) and subject[i] in (' ', '\t'):
+            i += 1
+        self.backout.append(self.pointer)
+        self.pointer = i
+        return s
+
+    def value(self):
+        "A microparser that parses for a value: some string constant or variable name."
+        subject = self.subject
+        i = self.pointer
+        if i >= len(subject):
+            # avoid cyclic imports
+            from django.template import TemplateSyntaxError        
+            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
+        if subject[i] in ('"', "'"):
+            p = i
+            i += 1
+            while i < len(subject) and subject[i] != subject[p]:
+                i += 1
+            if i >= len(subject):
+                # avoid cyclic imports
+                from django.template import TemplateSyntaxError            
+                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
+            i += 1
+            res = subject[p:i]
+            while i < len(subject) and subject[i] in (' ', '\t'):
+                i += 1
+            self.backout.append(self.pointer)
+            self.pointer = i
+            return res
+        else:
+            p = i
+            while i < len(subject) and subject[i] not in (' ', '\t'):
+                if subject[i] in ('"', "'"):
+                    c = subject[i]
+                    i += 1
+                    while i < len(subject) and subject[i] != c:
+                        i += 1
+                    if i >= len(subject):
+                        # avoid cyclic imports
+                        from django.template import TemplateSyntaxError                    
+                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
+                i += 1
+            s = subject[p:i]
+            while i < len(subject) and subject[i] in (' ', '\t'):
+                i += 1
+            self.backout.append(self.pointer)
+            self.pointer = i
+            return s
Index: django/template/library.py
===================================================================
--- django/template/library.py	(revision 0)
+++ django/template/library.py	(revision 0)
@@ -0,0 +1,154 @@
+from inspect import getargspec
+from django.template.context import Context
+from django.template.nodes import Node
+from django.template.utils import resolve_list
+from django.utils.importlib import import_module
+from django.utils.itercompat import is_iterable
+from django.utils.functional import curry
+
+# global dictionary of libraries that have been loaded using get_library
+libraries = {}
+# global list of libraries to load by default for a new parser
+builtins = []
+
+class InvalidTemplateLibrary(Exception):
+    pass
+
+def generic_tag_compiler(params, defaults, name, node_class, parser, bits):
+    "Returns a template.Node subclass."
+    bmax = len(params)
+    def_len = defaults and len(defaults) or 0
+    bmin = bmax - def_len
+    expressions = bits.parse_expression_list(minimum=bmin, maximum=bmax)
+    return node_class(expressions)
+
+class Library(object):
+    def __init__(self):
+        self.filters = {}
+        self.tags = {}
+
+    def tag(self, name=None, compile_function=None):
+        if name == None and compile_function == None:
+            # @register.tag()
+            return self.tag_function
+        elif name != None and compile_function == None:
+            if(callable(name)):
+                # @register.tag
+                return self.tag_function(name)
+            else:
+                # @register.tag('somename') or @register.tag(name='somename')
+                def dec(func):
+                    return self.tag(name, func)
+                return dec
+        elif name != None and compile_function != None:
+            # register.tag('somename', somefunc)
+            self.tags[name] = compile_function
+            return compile_function
+        else:
+            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
+
+    def tag_function(self,func):
+        self.tags[getattr(func, "_decorated_function", func).__name__] = func
+        return func
+
+    def filter(self, name=None, filter_func=None):
+        if name == None and filter_func == None:
+            # @register.filter()
+            return self.filter_function
+        elif filter_func == None:
+            if(callable(name)):
+                # @register.filter
+                return self.filter_function(name)
+            else:
+                # @register.filter('somename') or @register.filter(name='somename')
+                def dec(func):
+                    return self.filter(name, func)
+                return dec
+        elif name != None and filter_func != None:
+            # register.filter('somename', somefunc)
+            self.filters[name] = filter_func
+            return filter_func
+        else:
+            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
+
+    def filter_function(self, func):
+        self.filters[getattr(func, "_decorated_function", func).__name__] = func
+        return func
+
+    def simple_tag(self,func):
+        params, xx, xxx, defaults = getargspec(func)
+
+        class SimpleNode(Node):
+            def __init__(self, args):
+                self.args = args
+
+            def render(self, context):                
+                return func(*resolve_list(self.args, context))
+
+        from django.template.parser import uses_token_stream
+        compile_func = uses_token_stream(curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode))
+        compile_func.__doc__ = func.__doc__
+        self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
+        return func
+
+    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
+        def dec(func):
+            params, xx, xxx, defaults = getargspec(func)
+            if takes_context:
+                if params[0] == 'context':
+                    params = params[1:]
+                else:
+                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
+
+            class InclusionNode(Node):
+                def __init__(self, args):
+                    self.args = args
+
+                def render(self, context):
+                    args = resolve_list(self.args, context)
+                    if takes_context:
+                        args = [context] + args
+                    context_dict = func(*args)
+
+                    if not getattr(self, 'nodelist', False):
+                        from django.template.loader import get_template, select_template
+                        if not isinstance(file_name, basestring) and is_iterable(file_name):
+                            t = select_template(file_name)
+                        else:
+                            t = get_template(file_name)
+                        self.nodelist = t.nodelist
+                    return self.nodelist.render(context_class(context_dict, autoescape=context.autoescape))
+
+            from django.template.parser import uses_token_stream
+            compile_func = uses_token_stream(curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode))
+            compile_func.__doc__ = func.__doc__
+            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
+            return func
+        return dec
+
+def get_library(module_name):
+    lib = libraries.get(module_name, None)
+    if not lib:
+        try:
+            mod = import_module(module_name)
+        except ImportError, e:
+            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
+        try:
+            lib = mod.register
+            libraries[module_name] = lib
+        except AttributeError:
+            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
+    return lib
+
+def add_to_builtins(module_name, lazy=False):
+    builtins.append(lazy and module_name or get_library(module_name))
+    
+def get_builtins():
+    for index, lib in enumerate(builtins):
+        if isinstance(lib, str):
+            builtins[index] = get_library(lib)
+        yield builtins[index]
+
+add_to_builtins('django.template.defaulttags', True)
+add_to_builtins('django.template.defaultfilters', True)
+
Index: django/template/loader_tags.py
===================================================================
--- django/template/loader_tags.py	(revision 11593)
+++ django/template/loader_tags.py	(working copy)
@@ -1,6 +1,7 @@
-from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
 from django.template import Library, Node, TextNode
-from django.template.loader import get_template, get_template_from_string, find_template_source
+from django.template.loader import get_template, get_template_from_string, find_template_source, TemplateDoesNotExist
+from django.template.parser import TemplateSyntaxError, uses_token_stream
+from django.template.expressions import Literal
 from django.conf import settings
 from django.utils.safestring import mark_safe
 
@@ -39,24 +40,18 @@
 class ExtendsNode(Node):
     must_be_first = True
 
-    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
+    def __init__(self, nodelist, parent_name_expr, template_dirs=None):
         self.nodelist = nodelist
-        self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
+        self.parent_name_expr = parent_name_expr
         self.template_dirs = template_dirs
 
     def __repr__(self):
-        if self.parent_name_expr:
-            return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
-        return '<ExtendsNode: extends "%s">' % self.parent_name
+        return "<ExtendsNode: extends %s>" % self.parent_name_expr
 
     def get_parent(self, context):
-        if self.parent_name_expr:
-            self.parent_name = self.parent_name_expr.resolve(context)
-        parent = self.parent_name
+        parent = self.parent_name_expr.resolve_safe(context)
         if not parent:
-            error_msg = "Invalid template name in 'extends' tag: %r." % parent
-            if self.parent_name_expr:
-                error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
+            error_msg = "Invalid template name in 'extends' tag: %r.  Got this from '%s'." % (parent, self.parent_name_expr)
             raise TemplateSyntaxError, error_msg
         if hasattr(parent, 'render'):
             return parent # parent is a Template object
@@ -114,7 +109,7 @@
 
 class IncludeNode(Node):
     def __init__(self, template_name):
-        self.template_name = Variable(template_name)
+        self.template_name = template_name
 
     def render(self, context):
         try:
@@ -148,7 +143,8 @@
     parser.delete_first_token()
     return BlockNode(block_name, nodelist)
 
-def do_extends(parser, token):
+#@uses_token_stream
+def do_extends(parser, bits):
     """
     Signal that this template extends a parent template.
 
@@ -158,20 +154,14 @@
     name of the parent template to extend (if it evaluates to a string) or as
     the parent tempate itelf (if it evaluates to a Template object).
     """
-    bits = token.split_contents()
-    if len(bits) != 2:
-        raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
-    parent_name, parent_name_expr = None, None
-    if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
-        parent_name = bits[1][1:-1]
-    else:
-        parent_name_expr = parser.compile_filter(bits[1])
+    parent_name_expr = bits.parse_expression()
     nodelist = parser.parse()
     if nodelist.get_nodes_by_type(ExtendsNode):
         raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
-    return ExtendsNode(nodelist, parent_name, parent_name_expr)
+    return ExtendsNode(nodelist, parent_name_expr)
 
-def do_include(parser, token):
+#@uses_token_stream
+def do_include(parser, bits):
     """
     Loads a template and renders it with the current context.
 
@@ -179,14 +169,11 @@
 
         {% include "foo/some_include" %}
     """
-    bits = token.split_contents()
-    if len(bits) != 2:
-        raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
-    path = bits[1]
-    if path[0] in ('"', "'") and path[-1] == path[0]:
-        return ConstantIncludeNode(path[1:-1])
-    return IncludeNode(bits[1])
+    expression = bits.parse_expression(required=True)
+    if isinstance(expression, Literal):
+        return ConstantIncludeNode(expression.value)
+    return IncludeNode(expression)
 
 register.tag('block', do_block)
-register.tag('extends', do_extends)
-register.tag('include', do_include)
+register.tag('extends', uses_token_stream(do_extends))
+register.tag('include', uses_token_stream(do_include))
Index: django/template/loader.py
===================================================================
--- django/template/loader.py	(revision 11593)
+++ django/template/loader.py	(working copy)
@@ -21,12 +21,17 @@
 # installed, because pkg_resources is necessary to read eggs.
 
 from django.core.exceptions import ImproperlyConfigured
-from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
+from django.template.parser import Origin, Template
+from django.template.library import add_to_builtins
+from django.template.context import Context
 from django.utils.importlib import import_module
 from django.conf import settings
 
 template_source_loaders = None
 
+class TemplateDoesNotExist(Exception):
+    pass
+
 class LoaderOrigin(Origin):
     def __init__(self, display_name, loader, name, dirs):
         super(LoaderOrigin, self).__init__(display_name)
@@ -117,4 +122,5 @@
     # If we get here, none of the templates could be loaded
     raise TemplateDoesNotExist, ', '.join(template_name_list)
 
-add_to_builtins('django.template.loader_tags')
+add_to_builtins('django.template.loader_tags', True)
+
Index: django/template/debug.py
===================================================================
--- django/template/debug.py	(revision 11593)
+++ django/template/debug.py	(working copy)
@@ -1,4 +1,5 @@
-from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
+from django.template import Lexer, Parser, NodeList, ExpressionNode, TemplateSyntaxError
+from django.template.parser import tag_re
 from django.utils.encoding import force_unicode
 from django.utils.html import escape
 from django.utils.safestring import SafeData, EscapeData
@@ -50,7 +51,7 @@
         return DebugNodeList()
 
     def create_variable_node(self, contents):
-        return DebugVariableNode(contents)
+        return DebugExpressionNode(contents)
 
     def extend_nodelist(self, nodelist, node, token):
         node.source = token.source
@@ -81,17 +82,13 @@
             raise wrapped
         return result
 
-class DebugVariableNode(VariableNode):
+class DebugExpressionNode(ExpressionNode):
     def render(self, context):
         try:
-            output = force_unicode(self.filter_expression.resolve(context))
+            return super(DebugExpressionNode, self).render(context)
         except TemplateSyntaxError, e:
             if not hasattr(e, 'source'):
                 e.source = self.source
             raise
         except UnicodeDecodeError:
             return ''
-        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
-            return escape(output)
-        else:
-            return output
Index: tests/regressiontests/templates/parser.py
===================================================================
--- tests/regressiontests/templates/parser.py	(revision 11593)
+++ tests/regressiontests/templates/parser.py	(working copy)
@@ -3,11 +3,11 @@
 """
 
 filter_parsing = r"""
->>> from django.template import FilterExpression, Parser
+>>> from django.template import Parser
 
 >>> c = {'article': {'section': u'News'}}
 >>> p = Parser("")
->>> def fe_test(s): return FilterExpression(s, p).resolve(c)
+>>> def fe_test(s): return p.compile_filter(s).resolve(c)
 
 >>> fe_test('article.section')
 u'News'
@@ -22,11 +22,6 @@
 >>> fe_test(ur"'Some \'Bad\' News'")
 u"Some 'Bad' News"
 
->>> fe = FilterExpression(ur'"Some \"Good\" News"', p)
->>> fe.filters
-[]
->>> fe.var
-u'Some "Good" News'
 """
 
 variable_parsing = r"""
Index: tests/regressiontests/templates/tests.py
===================================================================
--- tests/regressiontests/templates/tests.py	(revision 11593)
+++ tests/regressiontests/templates/tests.py	(working copy)
@@ -370,11 +370,11 @@
             # Chained filters
             'filter-syntax02': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
 
-            # Raise TemplateSyntaxError for space between a variable and filter pipe
-            'filter-syntax03': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
+            # Basic filter usage with space between a variable and filter pipe
+            'filter-syntax03': ("{{ var |upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
 
-            # Raise TemplateSyntaxError for space after a filter pipe
-            'filter-syntax04': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
+            # Basic filter usage with space after a filter pipe
+            'filter-syntax04': ("{{ var| upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
 
             # Raise TemplateSyntaxError for a nonexistent filter
             'filter-syntax05': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
