Index: django/template/nodes.py
===================================================================
--- django/template/nodes.py	(revision 0)
+++ django/template/nodes.py	(revision 0)
@@ -0,0 +1,88 @@
+from django.utils.encoding import force_unicode
+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
+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 __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 ''
+        except UnicodeDecodeError:
+            # Unicode conversion can fail sometimes for reasons out of our
+            # control (e.g. exception rendering). In that case, we fail quietly.
+            return ''        
+        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
+            output = escape(output)
+        return output
Index: django/template/compiler.py
===================================================================
--- django/template/compiler.py	(revision 0)
+++ django/template/compiler.py	(revision 0)
@@ -0,0 +1,492 @@
+import re
+from inspect import getargspec
+from django.conf import settings
+from django.template.context import Context, RequestContext, ContextPopException
+from django.template.expressions import Expression, Literal, Lookup, FilterExpression, VARIABLE_ATTRIBUTE_SEPARATOR
+from django.template.nodes import Node, NodeList, ExpressionNode, TextNode
+from django.utils.text import smart_split
+from django.utils.encoding import smart_unicode, smart_str
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext
+
+__all__ = ('Template', 'TemplateSyntaxError', 'TokenSyntaxError', 'TokenStream')
+
+TOKEN_TEXT = 0
+TOKEN_VAR = 1
+TOKEN_BLOCK = 2
+TOKEN_COMMENT = 3
+
+# template syntax constants
+FILTER_SEPARATOR = '|'
+FILTER_ARGUMENT_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)))
+
+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 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):
+        return list(smart_split(self.contents))
+
+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 builtins
+        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 parse_nodelist(self, parse_until=None):
+        nodelist = self.parse(parse_until=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 = TokenStream(self, token)
+        try:
+            expr = stream.parse_expression()
+        except TokenSyntaxError:
+            stream.expected("expression")
+        stream.assert_consumed("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
+
+
+bit_re = re.compile(r"""
+    (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')
+    |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*)
+    |(?P<name>[\w.]+) # keyword or variable
+    |(?P<char>\S) # punctuation
+""", re.VERBOSE)
+
+class TokenSyntaxError(Exception):
+    pass
+
+def token_stream_parser(func):
+    def wrapper(self, *args, **kwargs):
+        mark = self.offset
+        try:
+            return func(self, *args, **kwargs)
+        except TokenSyntaxError:
+            self.offset = mark
+            raise
+    return wrapper
+    
+class TokenStream(object):
+    exception = TokenSyntaxError()
+    def __init__(self, parser, source):
+        self.parser = parser
+        self.source = source
+        self.offset = 0
+        self.name = None
+        if isinstance(source, Token):
+            bits = source.contents.split(None, 1)                
+            self.source = len(bits) == 2 and bits[1] or ''
+            self.name = bits[0]
+        self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)]            
+
+    def peek(self):
+        return self.tokens[self.offset]
+        
+    def consumed(self):
+        return self.offset == len(self.tokens)
+            
+    def pop(self):
+        if self.offset == len(self.tokens):
+            raise self.exception
+        next = self.tokens[self.offset]
+        self.offset += 1        
+        return next
+    
+    def pop_lexem(self, lexem):
+        if self.offset == len(self.tokens):
+            return False
+        _, next = self.tokens[self.offset]
+        if next == lexem:
+            self.offset += 1
+            return True
+        return False
+        
+    def pop_name(self):
+        if self.offset == len(self.tokens):
+            return None
+        tokentype, lexem = self.tokens[self.offset]
+        if tokentype == 'name':
+            self.offset += 1
+            return lexem
+        return None
+        
+    def pushback(self):
+        self.offset -= 1
+        
+    def assert_consumed(self, msg=None):
+        if self.offset != len(self.tokens):
+            raise TemplateSyntaxError, (msg or "unmatched input") + repr(self.tokens[self.offset:])
+
+    def expected(self, what):
+        if self.consumed():
+            found = "<EOT>"
+        else:
+            found = "<%s> %s" % self.tokens[self.offset]
+        raise TemplateSyntaxError, "expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace'))
+
+    @token_stream_parser
+    def parse_string(self, bare=False):
+        tokentype, lexem = self.pop()
+        if tokentype == 'string_literal':
+            return lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1]
+        if bare and tokentype == 'name':
+            return lexem
+        raise self.exception
+
+    @token_stream_parser
+    def parse_value(self):
+        translate = False
+        if self.pop_lexem('_'):
+            if not self.pop_lexem('('):
+                raise self.exception
+            translate = True
+        tokentype, lexem = self.pop()
+    
+        if tokentype == 'char':
+            raise self.exception
+    
+        if tokentype == 'name':
+            if lexem.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexem[0] == '_':
+                raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexem)
+            value = Lookup(lexem)        
+        elif tokentype == 'string_literal':
+            value = lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1]
+            value = Literal(mark_safe(value))
+        elif tokentype == 'numeric_literal':
+            try:
+                value = float(lexem)
+            except ValueError:
+                raise self.exception
+            if '.' not in lexem and 'e' not in lexem.lower():
+                value = int(value)
+            #FIXME: this causes a test failure: `ifequal-numeric07`
+            if lexem.endswith('.'):
+                raise TemplateSyntaxError, "Numeric literals may not end with '.': %s" % lexem            
+            value = Literal(value)
+        if translate:
+            if not self.pop_lexem(')'):
+                raise self.exception
+            value = FilterExpression(value, [(ugettext, ())])        
+        return value
+        
+
+    @token_stream_parser
+    def parse_filter(self):
+        if not self.pop_lexem('|'):
+            raise self.exception
+        name = self.pop_name()
+        if not name:
+            raise self.exception
+        args = []
+        if self.pop_lexem(':'):
+            args.append(self.parse_value())
+        func = self.parser.find_filter(name)
+        filter_args_check(name, func, args)
+        return func, args
+        
+
+    @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
+        
+        
+    @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
+
Index: django/template/__init__.py
===================================================================
--- django/template/__init__.py	(revision 7884)
+++ django/template/__init__.py	(working copy)
@@ -12,9 +12,9 @@
 Node objects.
 
 Each Node is responsible for creating some sort of output -- e.g. simple text
-(TextNode), variable values in a given context (VariableNode), results of basic
+(TextNode), variable values in a given context (ExpressionNode), results of basic
 logic (IfNode), results of looping (ForNode), or anything else. The core Node
-types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
+types are TextNode, ExpressionNode, IfNode and ForNode, but plugin modules can
 define their own custom node types.
 
 Each Node has a render() method, which takes a Context and returns a string of
@@ -48,887 +48,33 @@
 >>> 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.itercompat import is_iterable
-from django.utils.functional import curry, Promise
-from django.utils.text import smart_split
-from django.utils.encoding import smart_unicode, force_unicode
-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.compiler import Origin, StringOrigin, Template, \
+    TemplateSyntaxError, compile_string, TokenSyntaxError
+from django.template.library import Library, InvalidTemplateLibrary, \
+    get_library, add_to_builtins, libraries
+from django.template.expressions import Expression, LookupError
 
-__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
+#backcompat
+from django.template.compat import Variable, VariableDoesNotExist, VariableNode, \
+    resolve_variable, TokenParser
+from django.template.loader import TemplateDoesNotExist
 
-TOKEN_TEXT = 0
-TOKEN_VAR = 1
-TOKEN_BLOCK = 2
-TOKEN_COMMENT = 3
+__all__ = (
+    'Template', 'TemplateSyntaxError', 'compile_string', 'Origin', 'TokenSyntaxError',
+    'Context', 'RequestContext',
+    'Expression', 'LookupError',
+    #FIXME: should these be public? 'Literal', 'Lookup', 'FilterExpression'
+    'Library', 'InvalidTemplateLibrary', 'get_library', 'add_to_builtins', 'libraries',
+    #backcompat
+    'Variable', 'VariableDoesNotExist', 'resolve_variable', 'TemplateDoesNotExist', 'TokenParser', 'VariableNode',
+)
 
-# 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):
-        return list(smart_split(self.contents))
-
-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
-
-filter_raw_string = r"""
-^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
-^"(?P<constant>%(str)s)"|
-^(?P<var>[%(var_chars)s]+)|
- (?:%(filter_sep)s
-     (?P<filter_name>\w+)
-         (?:%(arg_sep)s
-             (?:
-              %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
-              "(?P<constant_arg>%(str)s)"|
-              (?P<var_arg>[%(var_chars)s]+)
-             )
-         )?
- )""" % {
-    'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
-    'var_chars': "\w\." ,
-    'filter_sep': re.escape(FILTER_SEPARATOR),
-    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
-    'i18n_open' : re.escape("_("),
-    'i18n_close' : re.escape(")"),
-  }
-
-filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
-filter_re = re.compile(filter_raw_string, re.UNICODE)
-
-class FilterExpression(object):
-    """
-    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 = 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 == None:
-                var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
-                if i18n_constant:
-                    var = '"%s"' %  _(i18n_constant.replace(r'\"', '"'))
-                elif constant:
-                    var = '"%s"' % constant.replace(r'\"', '"')
-                upto = match.end()
-                if var == 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:
-                filter_name = match.group("filter_name")
-                args = []
-                constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
-                if i18n_arg:
-                    args.append((False, _(i18n_arg.replace(r'\"', '"'))))
-                elif constant_arg is not None:
-                    args.append((False, constant_arg.replace(r'\"', '"')))
-                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 = Variable(var)
-
-    def resolve(self, context, ignore_failures=False):
-        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
-        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):
-    """
-    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'
-        >>> Variable('article.section').resolve(c)
-        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.
-            if var[0] in "\"'" and var[0] == var[-1]:
-                self.literal = mark_safe(var[1:-1])
-            else:
-                # 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'>" % self.s[:25]
-
-    def render(self, context):
-        return self.s
-
-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 = force_unicode(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 ''
-        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
-            return force_unicode(escape(output))
-        else:
-            return force_unicode(output)
-
-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_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,81 @@
+import re
+
+from django.template import Node, NodeList, TokenSyntaxError
+
+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)
+        else:
+            return self.nodelist_false.render(context)        
+
+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(self):
+    args = []
+    kwargs = {}
+    while True:
+        name = self.pop_name()
+        if name and self.pop_lexem('='):
+            try:
+                kwargs[name] = self.parse_expression()
+            except TokenSyntaxError:
+                raise TemplateSyntaxError, "expected expression in kwargs"
+        else:
+            if name:
+                self.pushback()
+            try:
+                args.append(self.parse_expression())
+            except TokenSyntaxError:
+                break
+        if not self.pop_lexem(','):
+            break
+    return args, kwargs
+
+def parse_as(bits):
+    if bits.pop_lexem('as'):
+        name = bits.pop_name()
+        if name:
+            return name
+    raise bits.exception
+
+def resolve_args_and_kwargs(args, kwargs, context):
+	resolved_args = [arg.resolve(context, True) for arg in args]
+	resolved_kwargs = {}
+	for name in kwargs:
+		resolved_kwargs[name] = kwargs[name].resolve(context, True)
+	return resolved_args, resolved_kwargs
+    
Index: django/template/expressions.py
===================================================================
--- django/template/expressions.py	(revision 0)
+++ django/template/expressions.py	(revision 0)
@@ -0,0 +1,116 @@
+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')
+
+VARIABLE_ATTRIBUTE_SEPARATOR = '.'
+
+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 resolve_safe(self, context, default=None):
+        try:
+            return self.resolve(context)
+        except LookupError:
+            return default
+
+    def resolve(self, context):
+        pass
+
+class Literal(Expression):
+    def __init__(self, value):
+        self.value = value
+
+    def resolve(self, context):
+        return self.value
+        
+class Lookup(Expression):
+    def __init__(self, var):
+        self.var = var
+        self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
+
+    def __str__(self):
+        return self.var
+        
+    def resolve(self, context):
+        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 LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current))
+                except Exception, e:
+                    if getattr(e, 'silent_variable_failure', False):
+                        current = settings.TEMPLATE_STRING_IF_INVALID
+                    else:
+                        raise
+
+        return current
+
+class FilterExpression(Expression):
+    def __init__(self, root, filters):
+        self.root = root
+        self.filters = filters
+
+    def resolve(self, context):
+        try:
+            obj = self.root.resolve(context)
+        except LookupError:
+            if not self.filters:
+                raise
+            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 7884)
+++ django/template/defaulttags.py	(working copy)
@@ -9,10 +9,12 @@
     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 TemplateSyntaxError, LookupError, TokenSyntaxError
+from django.template.library import get_library, Library, InvalidTemplateLibrary
+from django.template.compiler 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
 from django.conf import settings
-from django.utils.encoding import smart_str, smart_unicode
+from django.utils.encoding import smart_str, force_unicode
 from django.utils.itercompat import groupby
 from django.utils.safestring import mark_safe
 
@@ -33,17 +35,15 @@
         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):
-        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
+    def __init__(self, cyclevals, variable_name=None):
+        self.cycle_iter = itertools_cycle(cyclevals)
         self.variable_name = variable_name
 
     def render(self, context):
-        value = self.cycle_iter.next().resolve(context)
+        value = self.cycle_iter.next().resolve_safe(context)
         if self.variable_name:
             context[self.variable_name] = value
         return value
@@ -62,31 +62,27 @@
 
     def render(self, context):
         output = self.nodelist.render(context)
-        # Apply filters.
         context.update({'var': output})
-        filtered = self.filter_expr.resolve(context)
+        filtered = self.filter_expr.resolve_safe(context, default='')
         context.pop()
         return filtered
 
 class FirstOfNode(Node):
-    def __init__(self, vars):
-        self.vars = map(Variable, vars)
+    def __init__(self, vals):
+        self.vals = vals
 
     def render(self, context):
-        for var in self.vars:
-            try:
-                value = var.resolve(context)
-            except VariableDoesNotExist:
-                continue
+        for val in self.vals:
+            value = val.resolve_safe(context)
             if value:
-                return smart_unicode(value)
+                return value
         return u''
 
 class ForNode(Node):
-    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
+    def __init__(self, loopvars, sequence, is_reversed, nodelist):
         self.loopvars, self.sequence = loopvars, sequence
         self.is_reversed = is_reversed
-        self.nodelist_loop = nodelist_loop
+        self.nodelist = nodelist
 
     def __repr__(self):
         reversed_text = self.is_reversed and ' reversed' or ''
@@ -98,24 +94,14 @@
         for node in self.nodelist_loop:
             yield node
 
-    def get_nodes_by_type(self, nodetype):
-        nodes = []
-        if isinstance(self, nodetype):
-            nodes.append(self)
-        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
-        return nodes
-
     def render(self, context):
-        nodelist = NodeList()
+        result = []
         if 'forloop' in context:
             parentloop = context['forloop']
         else:
             parentloop = {}
         context.push()
-        try:
-            values = self.sequence.resolve(context, True)
-        except VariableDoesNotExist:
-            values = []
+        values = self.sequence.resolve_safe(context, default=[])
         if values is None:
             values = []
         if not hasattr(values, '__len__'):
@@ -144,8 +130,8 @@
                 context.update(dict(zip(self.loopvars, item)))
             else:
                 context[self.loopvars[0]] = item
-            for node in self.nodelist_loop:
-                nodelist.append(node.render(context))
+            for node in self.nodelist:
+                result.append(node.render(context))
             if unpack:
                 # The loop variables were pushed on to the context so pop them
                 # off again. This is necessary because the tag lets the length
@@ -154,13 +140,13 @@
                 # context.
                 context.pop()
         context.pop()
-        return nodelist.render(context)
+        return mark_safe(''.join([force_unicode(b) for b in result]))
 
 class IfChangedNode(Node):
-    def __init__(self, nodelist, *varlist):
+    def __init__(self, nodelist, *vallist):
         self.nodelist = nodelist
         self._last_seen = None
-        self._varlist = map(Variable, varlist)
+        self._vallist = vallist
         self._id = str(id(self))
 
     def render(self, context):
@@ -168,10 +154,10 @@
             self._last_seen = None
             context['forloop'][self._id] = 1
         try:
-            if self._varlist:
+            if self._vallist:
                 # Consider multiple parameters.  This automatically behaves
                 # like an OR evaluation of the multiple variables.
-                compare_to = [var.resolve(context) for var in self._varlist]
+                compare_to = [var.resolve(context) for var in self._vallist]
             else:
                 compare_to = self.nodelist.render(context)
         except VariableDoesNotExist:
@@ -188,82 +174,50 @@
         else:
             return ''
 
-class IfEqualNode(Node):
-    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
-        self.var1, self.var2 = Variable(var1), Variable(var2)
-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+class IfEqualNode(ConditionalNode):
+    def __init__(self, val1, val2, nodelist_true, nodelist_false, negate):
+        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
+        self.val1, self.val2 = val1, val2        
         self.negate = negate
 
     def __repr__(self):
         return "<IfEqualNode>"
 
-    def render(self, context):
-        try:
-            val1 = self.var1.resolve(context)
-        except VariableDoesNotExist:
-            val1 = None
-        try:
-            val2 = self.var2.resolve(context)
-        except VariableDoesNotExist:
-            val2 = None
-        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)
+    def check_condition(self, context):
+        val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context)
+        return self.negate == (val1 != val2)
 
-class IfNode(Node):
+class IfNode(ConditionalNode):
     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
-        self.bool_exprs = bool_exprs
-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+        super(IfNode, self).__init__(nodelist_true, nodelist_false)
+        self.bool_exprs = bool_exprs        
         self.link_type = link_type
 
     def __repr__(self):
         return "<If node>"
 
-    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)
+    def check_condition(self, context):
+        if self.link_type == 'or':
+            for negated, bool_expr in self.bool_exprs:
+                value = bool_expr.resolve_safe(context, default=False)
+                if bool(value) != negated:
+                    return True
+            return False           
         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)
+            for negated, bool_expr in self.bool_exprs:
+                value = bool_expr.resolve_safe(context, default=False)
+                if bool(value) == negated:
+                    return False
+            return True
+        
 
-    class LinkTypes:
-        and_ = 0,
-        or_ = 1
-
 class RegroupNode(Node):
     def __init__(self, target, expression, var_name):
         self.target, self.expression = target, expression
         self.var_name = var_name
 
     def render(self, context):
-        obj_list = self.target.resolve(context, True)
+        obj_list = self.target.resolve_safe(context)
         if obj_list == None:
             # target variable wasn't found in context; fail silently.
             context[self.var_name] = []
@@ -273,7 +227,7 @@
         context[self.var_name] = [
             {'grouper': key, 'list': list(val)}
             for key, val in
-            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
+            groupby(obj_list, lambda v, f=self.expression.resolve: f(v))
         ]
         return ''
 
@@ -310,9 +264,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):
@@ -322,7 +274,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,8 +309,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))
+        args = [arg.resolve_safe(context) for arg in self.args]
+        kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context))
                        for k, v in self.kwargs.items()])
         try:
             return reverse(self.view_name, args=args, kwargs=kwargs)
@@ -391,18 +343,17 @@
         return str(int(round(ratio)))
 
 class WithNode(Node):
-    def __init__(self, var, name, nodelist):
-        self.var = var
-        self.name = name
+    def __init__(self, expr, name, nodelist):
+        self.expr = expr
+        self.name = name        
         self.nodelist = nodelist
 
     def __repr__(self):
         return "<WithNode>"
 
     def render(self, context):
-        val = self.var.resolve(context)
         context.push()
-        context[self.name] = val
+        context[self.name] = self.expr.resolve_safe(context)
         output = self.nodelist.render(context)
         context.pop()
         return output
@@ -418,8 +369,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)
 
@@ -489,12 +439,14 @@
 
     if len(args) > 4 and args[-2] == 'as':
         name = args[-1]
-        node = CycleNode(args[1:-2], name)
+        values = [parser.compile_filter(arg) for arg in args[1:-2]]
+        node = CycleNode(values, name)
         if not hasattr(parser, '_namedCycleNodes'):
             parser._namedCycleNodes = {}
         parser._namedCycleNodes[name] = node
     else:
-        node = CycleNode(args[1:])
+        values = [parser.compile_filter(arg) for arg in args[1:]]
+        node = CycleNode(values)
     return node
 cycle = register.tag(cycle)
 
@@ -527,12 +479,11 @@
         {% endfilter %}
     """
     _, rest = token.contents.split(None, 1)
-    filter_expr = parser.compile_filter("var|%s" % (rest))
+    filter_expr = parser.compile_filter("var|%s" % rest)
     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)
 
@@ -565,11 +516,10 @@
         {% firstof var1 var2 var3 "fallback value" %}
 
     """
-    bits = token.split_contents()[1:]
-    if len(bits) < 1:
-        raise TemplateSyntaxError("'firstof' statement requires at least one"
-                                  " argument")
-    return FirstOfNode(bits)
+    bits = parser.token_stream(token)
+    expressions = bits.parse_expression_list(minimum=1)
+    bits.assert_consumed()
+    return FirstOfNode(expressions)
 firstof = register.tag(firstof)
 
 #@register.tag(name="for")
@@ -612,42 +562,37 @@
         ==========================  ================================================
 
     """
-    bits = token.contents.split()
-    if len(bits) < 4:
-        raise TemplateSyntaxError("'for' statements should have at least four"
-                                  " words: %s" % token.contents)
-
-    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])
-    nodelist_loop = parser.parse(('endfor',))
-    parser.delete_first_token()
-    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
+    bits = parser.token_stream(token)
+    loopvars = []
+    while True:
+        var = bits.pop_name()
+        if var:
+            loopvars.append(var)
+            if not bits.pop_lexem(','):
+                break
+        else:
+            break
+    if not loopvars:
+        raise TemplateSyntaxError("'for' tag requires at least one loopvar")    
+        
+    if not bits.pop_lexem('in'):
+        raise TemplateSyntaxError("'for' tag requires 'in' keyword")
+    try:    
+        sequence = bits.parse_expression()
+    except TokenSyntaxError:
+        raise bits.expected("expression")
+    reversed = bits.pop_lexem('reversed')
+    bits.assert_consumed()
+    nodelist = parser.parse_nodelist(('endfor',))
+    return ForNode(loopvars, sequence, reversed, nodelist)
 do_for = register.tag("for", 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()
-    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
+    bits = parser.token_stream(token)
+    val1, val2 = bits.parse_expression_list(count=2)
+    bits.assert_consumed()
+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
+    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
 
 #@register.tag
 def ifequal(parser, token):
@@ -737,39 +682,36 @@
             {% 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 ')
+    bits = parser.token_stream(token)
+    link_type = None
+    link = None
     boolvars = []
-    if len(boolpairs) == 1:
-        link_type = IfNode.LinkTypes.or_
-        boolpairs = bitstr.split(' or ')
-    else:
-        link_type = IfNode.LinkTypes.and_
-        if ' or ' in bitstr:
-            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)))
+    while True:
+        negated = False
+        if bits.pop_lexem('not'):
+            negated = True
+        try:
+            expr = bits.parse_expression()
+        except TokenSyntaxError:
+            if link:
+                raise TemplateSyntaxError("'if' statement improperly formatted")
+            else:
+                raise TemplateSyntaxError("'if' statement requires at least one argument")
+        boolvars.append((negated, expr))
+        link = bits.pop_name()
+        if not link:
+            break
+        if link_type:
+            if link_type != link:
+                raise TemplateSyntaxError("'if' tags can't mix 'and' and 'or'")
         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()
+            if not link in ('and', 'or'):
+                raise TemplateSyntaxError("'if' tag expects 'and' or 'or', got: %s" % link)        
+            link_type = link
+    bits.assert_consumed()
+    
+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')
+    
     return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
 do_if = register.tag("if", do_if)
 
@@ -802,10 +744,11 @@
                 {% endifchanged %}
             {% endfor %}
     """
-    bits = token.contents.split()
-    nodelist = parser.parse(('endifchanged',))
-    parser.delete_first_token()
-    return IfChangedNode(nodelist, *bits[1:])
+    bits = parser.token_stream(token)
+    nodelist = parser.parse_nodelist(('endifchanged',))
+    values = bits.parse_expression_list()
+    bits.assert_consumed()
+    return IfChangedNode(nodelist, *values)
 ifchanged = register.tag(ifchanged)
 
 #@register.tag
@@ -872,10 +815,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]
+    bits = parser.token_stream(token)
+    try:
+        format_string = bits.parse_expression()
+    except TokenSyntaxError:
+        bits.expected("expression")
+    bits.assert_consumed()
     return NowNode(format_string)
 now = register.tag(now)
 
@@ -926,20 +871,23 @@
         {% 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]
+    bits = parser.token_stream(token)
+    try:
+        target = bits.parse_expression()
+    except TokenSyntaxError:
+        bits.expected("expression")
+    
+    if not bits.pop_lexem('by'):
+        raise bits.expected("'by'")
+    try:
+        expression = bits.parse_expression()
+    except TokenSyntaxError:
+        raise TemplateSyntaxError()
+    try:
+        var_name = parse_as(bits)
+    except TokenSyntaxError:
+        raise bits.expected("as <name>")
+    bits.assert_consumed()
     return RegroupNode(target, expression, var_name)
 regroup = register.tag(regroup)
 
@@ -968,9 +916,7 @@
             </strong>
         {% endspaceless %}
     """
-    nodelist = parser.parse(('endspaceless',))
-    parser.delete_first_token()
-    return SpacelessNode(nodelist)
+    return SpacelessNode(parser.parse_nodelist(('endspaceless',)))
 spaceless = register.tag(spaceless)
 
 #@register.tag
@@ -1038,21 +984,14 @@
 
     The URL will look like ``/clients/client/123/``.
     """
-    bits = token.contents.split(' ', 2)
-    if len(bits) < 2:
-        raise TemplateSyntaxError("'%s' takes at least one argument"
-                                  " (path to a view)" % bits[0])
-    args = []
-    kwargs = {}
-    if len(bits) > 2:
-        for arg in bits[2].split(','):
-            if '=' in arg:
-                k, v = arg.split('=', 1)
-                k = k.strip()
-                kwargs[k] = parser.compile_filter(v)
-            else:
-                args.append(parser.compile_filter(arg))
-    return URLNode(bits[1], args, kwargs)
+    bits = parser.token_stream(token)
+    try:
+        view = bits.parse_string(bare=True)
+    except TokenSyntaxError:
+        raise bits.expected("viewname")
+    args, kwargs = parse_args_and_kwargs(bits)
+    bits.assert_consumed()
+    return URLNode(view, args, kwargs)
 url = register.tag(url)
 
 #@register.tag
@@ -1084,7 +1023,7 @@
 #@register.tag
 def do_with(parser, token):
     """
-    Adds a value to the context (inside of this block) for caching and easy
+    Adds values to the context (inside of this block) for caching and easy
     access.
 
     For example::
@@ -1092,14 +1031,19 @@
         {% with person.some_sql_method as total %}
             {{ total }} object{{ total|pluralize }}
         {% endwith %}
+        
+        {% with person.some_sql_method as total, person.get_full_name as full_name %}
+            {{ full_name }}: {{ 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)
+    bits = parser.token_stream(token)
+    try:
+        expr = bits.parse_expression()
+        name = parse_as(bits)
+    except TokenSyntaxError:
+        raise TemplateSyntaxError("%r expected format is 'value as name'" % bits.name)
+    bits.assert_consumed()
+    nodelist = parser.parse_nodelist(('endwith',))
+    return WithNode(expr, name, nodelist)
 do_with = register.tag('with', do_with)
Index: django/template/compat.py
===================================================================
--- django/template/compat.py	(revision 0)
+++ django/template/compat.py	(revision 0)
@@ -0,0 +1,123 @@
+import warnings
+from django.template.expressions import Expression, LookupError
+from django.template.compiler import TemplateSyntaxError
+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.compiler import TokenStream
+        stream = TokenStream(None, var)
+        self.expression = stream.parse_value()
+        stream.assert_consumed("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.compiler import TokenStream
+    stream = TokenStream(None, path)
+    val = stream.parse_value()
+    stream.assert_consumed("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):
+            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
Index: django/template/loader_tags.py
===================================================================
--- django/template/loader_tags.py	(revision 7884)
+++ django/template/loader_tags.py	(working copy)
@@ -1,6 +1,6 @@
-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 import TemplateSyntaxError, Variable, Library, Node, TextNode
+from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source
+from django.template.expressions import Literal
 from django.conf import settings
 from django.utils.safestring import mark_safe
 
@@ -39,24 +39,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, template_dirs=None):
         self.nodelist = nodelist
-        self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
+        self.parent_name = parent_name
         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
 
     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.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
             raise TemplateSyntaxError, error_msg
         if hasattr(parent, 'render'):
             return parent # parent is a Template object
@@ -114,11 +108,11 @@
 
 class IncludeNode(Node):
     def __init__(self, template_name):
-        self.template_name = Variable(template_name)
+        self.template_name = template_name
 
     def render(self, context):
         try:
-            template_name = self.template_name.resolve(context)
+            template_name = self.template_name.resolve_safe(context)
             t = get_template(template_name)
             return t.render(context)
         except TemplateSyntaxError, e:
@@ -161,15 +155,11 @@
     bits = token.contents.split()
     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 = parser.compile_filter(bits[1])
     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)
 
 def do_include(parser, token):
     """
@@ -179,13 +169,15 @@
 
         {% include "foo/some_include" %}
     """
-    bits = token.contents.split()
-    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])
+    bits = parser.token_stream(token)
+    try:
+        template_name = bits.parse_expression()
+    except TokenSyntaxError:
+        raise bits.expected("expression")    
+    if isinstance(template_name, Literal):
+        # remove ConstantIncludeNode and this hack will be gone
+        return ConstantIncludeNode(template_name.resolve(None))
+    return IncludeNode(template_name)
 
 register.tag('block', do_block)
 register.tag('extends', do_extends)
Index: django/template/library.py
===================================================================
--- django/template/library.py	(revision 0)
+++ django/template/library.py	(revision 0)
@@ -0,0 +1,161 @@
+import re
+from inspect import getargspec
+from django.conf import settings
+from django.template.context import Context
+from django.template.nodes import Node
+from django.template.compiler import TemplateSyntaxError
+from django.utils.itercompat import is_iterable
+from django.utils.functional import curry
+
+__all__ = ('InvalidTemplateLibrary', 'Library', 'get_library', 'add_to_builtins')
+
+# 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, token, takes_context=False, takes_nodelist=False):
+    "Returns a template.Node subclass."
+    bits = parser.token_stream(token)
+    bmax = len(params)
+    def_len = defaults and len(defaults) or 0
+    bmin = bmax - def_len
+    args = stream.parse_expression_list(minimum=bmin, maximum=bmax)
+    stream.assert_comsumed()
+    if takes_context:
+        node_class = curry(node_class, takes_context=takes_context) 
+    if takes_nodelist:
+        nodelist = parser.parse_nodelist(('end%s' % name,)) 
+        node_class = curry(node_class, nodelist=nodelist)     
+    return node_class(args)
+
+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, compile_function=None):
+        def dec(func):
+            params, xx, xxx, defaults = getargspec(func)
+
+            class SimpleNode(Node):
+                def __init__(self, args): 
+                    self.args = args
+
+                def render(self, context):
+                    return func(*[arg.resolve_safe(context) for arg in self.args])
+
+            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
+        
+        if callable(compile_function):
+            # @register.simple_tag
+            return dec(compile_function)
+        return dec
+
+    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):
+                    resolved_vars = [arg.resolve_safe(context) for arg in self.args]
+                    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_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))
Index: django/template/debug.py
===================================================================
--- django/template/debug.py	(revision 7884)
+++ django/template/debug.py	(working copy)
@@ -1,4 +1,5 @@
-from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
+from django.template.compiler import Lexer, Parser, tag_re
+from django.template import NodeList, ExpressionNode, TemplateSyntaxError
 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,11 @@
             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: django/template/loader.py
===================================================================
--- django/template/loader.py	(revision 7884)
+++ django/template/loader.py	(working copy)
@@ -21,11 +21,14 @@
 # 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 import Origin, Template, Context, add_to_builtins
 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)
Index: django/templatetags/i18n.py
===================================================================
--- django/templatetags/i18n.py	(revision 7884)
+++ django/templatetags/i18n.py	(working copy)
@@ -1,8 +1,7 @@
 import re
 
-from django.template import Node, Variable, VariableNode
-from django.template import TemplateSyntaxError, TokenParser, Library
-from django.template import TOKEN_TEXT, TOKEN_VAR
+from django.template import Node, VariableNode, TokenParser, Variable, TemplateSyntaxError, Library
+from django.template.compiler import TOKEN_TEXT, TOKEN_VAR
 from django.utils import translation
 from django.utils.encoding import force_unicode
 
@@ -35,7 +34,7 @@
 
 class TranslateNode(Node):
     def __init__(self, value, noop):
-        self.value = Variable(value)
+        self.value = value
         self.noop = noop
 
     def render(self, context):
@@ -171,18 +170,13 @@
     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()
+    bits = parser.token_stream(token)
+    try:
+        value = bits.parse_expression()
+    except TokenSyntaxError:
+        bits.expected("expression")
+    noop = bits.pop_lexem('noop')
+    bits.assert_consumed()
     return TranslateNode(value, noop)
 
 def do_block_translate(parser, token):
Index: django/templatetags/cache.py
===================================================================
--- django/templatetags/cache.py	(revision 7884)
+++ django/templatetags/cache.py	(working copy)
@@ -1,5 +1,4 @@
-from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
-from django.template import resolve_variable
+from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist, TokenSyntaxError
 from django.core.cache import cache
 from django.utils.encoding import force_unicode
 
@@ -8,7 +7,7 @@
 class CacheNode(Node):
     def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
         self.nodelist = nodelist
-        self.expire_time_var = Variable(expire_time_var)
+        self.expire_time_var = expire_time_var
         self.fragment_name = fragment_name
         self.vary_on = vary_on
 
@@ -22,7 +21,7 @@
         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.
-        cache_key = u':'.join([self.fragment_name] + [force_unicode(resolve_variable(var, context)) for var in self.vary_on])
+        cache_key = u':'.join([self.fragment_name] + [force_unicode(var.resolve_safe(context)) for var in self.vary_on])
         value = cache.get(cache_key)
         if value is None:
             value = self.nodelist.render(context)
@@ -50,11 +49,17 @@
 
     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',))
+    bits = parser.token_stream(token)
+    try:
+        expire_time = bits.parse_expression()
+    except TokenSyntaxError:
+        bits.expected("expression")
+    name = bits.pop_name()
+    if not name:
+        raise TemplateSyntaxError, "'cache' requires a fragment name"    
+    vary_on = bits.parse_expression_list()
+    bits.assert_consumed()    
+    return CacheNode(nodelist, expire_time, name, vary_on)
 
 register.tag('cache', do_cache)
