On Saturday 05 December 2009 20:09:21 Luke Plant wrote:

> I'm not likely to able to look at this before Tuesday.  If anyone
> wants to look at it, I think the right approach is something like
>  the following:
> http://effbot.org/zone/simple-top-down-parsing.htm
> (without the globals, obviously, they can be converted to instance
> variables/methods).

Cancel that - I unexpectedly had free time this evening, and I 
implemented this.  It's a nice replacement I think (credit to Vaughan 
Pratt and Fredrik Lundh for the basic approach and Python 
implementation respectively).  The new implementation is pretty much 
the same length as the old one, and hopefully easier to read, with a 
much smaller core parser.  Precedence is specified directly, rather 
than implicitly, so it's much easier to check that it's the same as 
Python's.

Latest patch attached to this e-mail.

Regards,

Luke

-- 
"Idiocy: Never underestimate the power of stupid people in large 
groups." (despair.com)

Luke Plant || http://lukeplant.me.uk/

--

You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-develop...@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.


diff -r 70e75e8cd224 django/template/defaulttags.py
--- a/django/template/defaulttags.py	Thu Dec 03 15:11:14 2009 +0000
+++ b/django/template/defaulttags.py	Sun Dec 06 01:04:04 2009 +0000
@@ -11,6 +11,7 @@
 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.smartif import IfParser, Literal
 from django.conf import settings
 from django.utils.encoding import smart_str, smart_unicode
 from django.utils.itercompat import groupby
@@ -227,10 +228,9 @@
         return self.nodelist_false.render(context)
 
 class IfNode(Node):
-    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
-        self.bool_exprs = bool_exprs
+    def __init__(self, var, nodelist_true, nodelist_false=None):
         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
-        self.link_type = link_type
+        self.var = var
 
     def __repr__(self):
         return "<If node>"
@@ -250,28 +250,11 @@
         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)
+        if self.var.resolve(context):
+            return self.nodelist_true.render(context)
+        if self.nodelist_false:
             return self.nodelist_false.render(context)
-        else:
-            for ifnot, bool_expr in self.bool_exprs:
-                try:
-                    value = bool_expr.resolve(context, True)
-                except VariableDoesNotExist:
-                    value = None
-                if not ((value and not ifnot) or (ifnot and not value)):
-                    return self.nodelist_false.render(context)
-            return self.nodelist_true.render(context)
-
-    class LinkTypes:
-        and_ = 0,
-        or_ = 1
+        return ''
 
 class RegroupNode(Node):
     def __init__(self, target, expression, var_name):
@@ -761,6 +744,27 @@
     return do_ifequal(parser, token, True)
 ifnotequal = register.tag(ifnotequal)
 
+class TemplateLiteral(Literal):
+    def __init__(self, value, text):
+        self.value = value
+        self.text = text # for better error messages
+
+    def display(self):
+        return self.text
+
+    def resolve(self, context):
+        return self.value.resolve(context, ignore_failures=True)
+
+class TemplateIfParser(IfParser):
+    error_class = TemplateSyntaxError
+
+    def __init__(self, parser, *args, **kwargs):
+        self.template_parser = parser
+        return super(TemplateIfParser, self).__init__(*args, **kwargs)
+
+    def create_var(self, value):
+        return TemplateLiteral(self.template_parser.compile_filter(value), value)
+
 #...@register.tag(name="if")
 def do_if(parser, token):
     """
@@ -805,47 +809,21 @@
             There are some athletes and absolutely no coaches.
         {% endif %}
 
-    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
-    because the order of logic would be ambigous. For example, this is
-    invalid::
+    Comparison operators are also available, and the use of filters is also
+    allowed, for example:
 
-        {% if athlete_list and coach_list or cheerleader_list %}
+        {% if articles|length >= 5 %}...{% endif %}
 
-    If you need to combine ``and`` and ``or`` to do advanced logic, just use
-    nested if tags. For example::
+    Arguments and operators _must_ have a space between them, so
+    ``{% if 1>2 %}`` is not a valid if tag.
 
-        {% if athlete_list %}
-            {% if coach_list or cheerleader_list %}
-                We have athletes, and either coaches or cheerleaders!
-            {% endif %}
-        {% endif %}
+    All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
+    ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
+
+    Operator precedence follows Python.
     """
-    bits = token.contents.split()
-    del bits[0]
-    if not bits:
-        raise TemplateSyntaxError("'if' statement requires at least one argument")
-    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
-    bitstr = ' '.join(bits)
-    boolpairs = bitstr.split(' and ')
-    boolvars = []
-    if len(boolpairs) == 1:
-        link_type = IfNode.LinkTypes.or_
-        boolpairs = bitstr.split(' or ')
-    else:
-        link_type = IfNode.LinkTypes.and_
-        if ' or ' in bitstr:
-            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
-    for boolpair in boolpairs:
-        if ' ' in boolpair:
-            try:
-                not_, boolvar = boolpair.split()
-            except ValueError:
-                raise TemplateSyntaxError, "'if' statement improperly formatted"
-            if not_ != 'not':
-                raise TemplateSyntaxError, "Expected 'not' in if statement"
-            boolvars.append((True, parser.compile_filter(boolvar)))
-        else:
-            boolvars.append((False, parser.compile_filter(boolpair)))
+    bits = token.split_contents()[1:]
+    var = TemplateIfParser(parser, bits).parse()
     nodelist_true = parser.parse(('else', 'endif'))
     token = parser.next_token()
     if token.contents == 'else':
@@ -853,7 +831,7 @@
         parser.delete_first_token()
     else:
         nodelist_false = NodeList()
-    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
+    return IfNode(var, nodelist_true, nodelist_false)
 do_if = register.tag("if", do_if)
 
 #...@register.tag
diff -r 70e75e8cd224 django/template/smartif.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/template/smartif.py	Sun Dec 06 01:04:04 2009 +0000
@@ -0,0 +1,181 @@
+"""
+Parser and utilities for the smart 'if' tag
+"""
+
+# Using a simple top down parser, as described here:
+#    http://effbot.org/zone/simple-top-down-parsing.htm.
+# 'led' = left denotation
+# 'nud' = null denotation
+# 'bp' = binding power (left = lbp, right = rbp)
+
+class TokenBase(object):
+    """
+    Base class for operators and literals, mainly for debugging and for throwing
+    syntax errors.
+    """
+    id = None # node/token type name
+    value = None # used by literals
+    first = second = None # used by tree nodes
+
+    def nud(self, parser):
+        # Null denotation - called in prefix context
+        raise parser.error_class(
+            "Not expecting '%s' in this position in if tag." % self.id
+        )
+
+    def led(self, left, parser):
+        # Left denotation - called in infix context
+        raise parser.error_class(
+            "Not expecting '%s' as infix operator in if tag." % self.id
+        )
+
+    def display(self):
+        """
+        What to display in error messages for this node
+        """
+        return self.id
+
+    def __repr__(self):
+        if self.id == "literal":
+            return "(%s %r)" % (self.id, self.value)
+        out = [self.id, self.first, self.second]
+        out = map(str, filter(None, out))
+        return "(" + " ".join(out) + ")"
+
+
+def infix(bp, func):
+    """
+    Create an infix operator, given a binding power and a function that
+    evaluates the node
+    """
+    class Operator(TokenBase):
+        lbp = bp
+
+        def led(self, left, parser):
+            self.first = left
+            self.second = parser.expression(bp)
+            return self
+
+        def resolve(self, context):
+            return func(self.first.resolve(context), self.second.resolve(context))
+
+    return Operator
+
+
+def prefix(bp, func):
+    """
+    Create a prefix operator, given a binding power and a function that
+    evaluates the node.
+    """
+    class Operator(TokenBase):
+        lbp = bp
+
+        def nud(self, parser):
+            self.first = parser.expression(bp)
+            self.second = None
+            return self
+
+        def resolve(self, context):
+            return func(self.first.resolve(context))
+
+    return Operator
+
+
+# Operator precedence follows Python.
+# NB - we can get slightly more accurate syntax error messages by not using the
+# same object for '==' and '='.
+
+OPERATORS = {
+    'or': infix(6, lambda x, y: x or y),
+    'and': infix(7, lambda x, y: x and y),
+    'not': prefix(8, lambda x: not x),
+    'in': infix(9, lambda x, y: y is not None and x in y),
+    '=': infix(10, lambda x, y: x == y),
+    '==': infix(10, lambda x, y: x == y),
+    '!=': infix(10, lambda x, y: x != y),
+    '>': infix(10, lambda x, y: x > y),
+    '>=': infix(10, lambda x, y: x >= y),
+    '<': infix(10, lambda x, y: x < y),
+    '<=': infix(10, lambda x, y: x <= y),
+}
+
+# Assign 'id' to each:
+for key, op in OPERATORS.items():
+    op.id = key
+
+
+class Literal(TokenBase):
+    """
+    A basic self-resolvable object similar to a Django template variable.
+    """
+    # IfParser uses Literal in create_var, but TemplateIfParser overrides
+    # create_var so that a proper implementation that actually resolves
+    # variables, filters etc are used.
+    id = "literal"
+    lbp = 0
+    def __init__(self, value):
+        self.value = value
+
+    def display(self):
+        return repr(self.value)
+
+    def nud(self, parser):
+        return self
+
+    def resolve(self, context):
+        return self.value
+
+
+class EndToken(TokenBase):
+    lbp = 0
+
+    def nud(self, parser):
+        raise parser.error_class("Unexpected end of expression in if tag.")
+
+EndToken = EndToken()
+
+
+class IfParser(object):
+    error_class = ValueError
+
+    def __init__(self, tokens):
+        self.tokens = map(self.translate_tokens, tokens)
+        self.pos = 0
+        self.current_token = self.next()
+
+    def translate_tokens(self, token):
+        try:
+            op = OPERATORS[token]
+        except (KeyError, TypeError):
+            return self.create_var(token)
+        else:
+            return op()
+
+    def next(self):
+        if self.pos >= len(self.tokens):
+            return EndToken
+        else:
+            retval = self.tokens[self.pos]
+            self.pos += 1
+            return retval
+
+    def parse(self):
+        retval = self.expression()
+        # Check that we have exhausted all the tokens
+        if self.current_token is not EndToken:
+            raise self.error_class("Unused '%s' at end of if expression." %
+                                   self.current_token.display())
+        return retval
+
+    def expression(self, rbp=0):
+        t = self.current_token
+        self.current_token = self.next()
+        left = t.nud(self)
+        while rbp < self.current_token.lbp:
+            t = self.current_token
+            self.current_token = self.next()
+            left = t.led(left, self)
+        return left
+
+    def create_var(self, value):
+        return Literal(value)
diff -r 70e75e8cd224 docs/ref/templates/builtins.txt
--- a/docs/ref/templates/builtins.txt	Thu Dec 03 15:11:14 2009 +0000
+++ b/docs/ref/templates/builtins.txt	Sun Dec 06 01:04:04 2009 +0000
@@ -313,6 +313,9 @@
 As you can see, the ``if`` tag can take an optional ``{% else %}`` clause that
 will be displayed if the test fails.
 
+Boolean operators
+^^^^^^^^^^^^^^^^^
+
 ``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
 to negate a given variable::
 
@@ -338,24 +341,153 @@
         There are some athletes and absolutely no coaches.
     {% endif %}
 
-``if`` tags don't allow ``and`` and ``or`` clauses within the same tag, because
-the order of logic would be ambiguous. For example, this is invalid::
+.. versionchanged:: 1.2
+
+If you use ``and`` and ``or`` clauses within the same tag, it is interpreted
+left to right, e.g.::
 
     {% if athlete_list and coach_list or cheerleader_list %}
 
-If you need to combine ``and`` and ``or`` to do advanced logic, just use nested
-``if`` tags. For example::
+will be interpreted like:
 
-    {% if athlete_list %}
-        {% if coach_list or cheerleader_list %}
-            We have athletes, and either coaches or cheerleaders!
-        {% endif %}
+.. code-block:: python
+
+    if (athlete_list and coach_list) or cheerleader_list
+
+Use of actual brackets in the ``if`` tag is invalid syntax.  If you need them to
+indicate precedence, you should use nested ``if`` tags.
+
+.. versionadded:: 1.2
+
+
+``if`` tags may also use the operators ``==``, ``!=``, ``<``, ``>``,
+``<=``, ``>=`` and ``in`` which work as follows:
+
+
+``==`` operator
+^^^^^^^^^^^^^^^
+
+Equality. Example::
+
+    {% if somevar == "x" %}
+      This appears if variable somevar equals the string "x"
     {% endif %}
 
-Multiple uses of the same logical operator are fine, as long as you use the
-same operator. For example, this is valid::
+``!=`` operator
+^^^^^^^^^^^^^^^
 
-    {% if athlete_list or coach_list or parent_list or teacher_list %}
+Inequality. Example::
+
+    {% if somevar != "x" %}
+      This appears if variable somevar does not equal the string "x",
+      or if somevar is not found in the context
+    {% endif %}
+
+``<`` operator
+^^^^^^^^^^^^^^
+
+Less than. Example::
+
+    {% if somevar < 100 %}
+      This appears if variable somevar is less than 100.
+    {% endif %}
+
+``>`` operator
+^^^^^^^^^^^^^^
+
+Greater than. Example::
+
+    {% if somevar > 0 %}
+      This appears if variable somevar is greater than 0.
+    {% endif %}
+
+``<=`` operator
+^^^^^^^^^^^^^^^
+
+Less than or equal to. Example::
+
+    {% if somevar <= 100 %}
+      This appears if variable somevar is less than 100 or equal to 100.
+    {% endif %}
+
+``>=`` operator
+^^^^^^^^^^^^^^^
+
+Greater than or equal to. Example::
+
+    {% if somevar >= 1 %}
+      This appears if variable somevar is greater than 1 or equal to 1.
+    {% endif %}
+
+``in`` operator
+^^^^^^^^^^^^^^^
+
+Contained within. This operator is supported by many Python containers to test
+whether the given value is in the container.  The following are some examples of
+how ``x in y`` will be interpreted::
+
+    {% if "bc" in "abcdef" %}
+      This appears since "bc" is a substring of "abcdef"
+    {% endif %}
+
+    {% if "hello" in greetings %}
+      If greetings is a list or set, one element of which is the string
+      "hello", this will appear.
+    {% endif %}
+
+    {% if user in users %}
+      If users is a QuerySet, this will appear if user is an
+      instance that belongs to the QuerySet.
+    {% endif %}
+
+
+The comparison operators cannot be 'chained' like in Python or in mathematical
+notation. For example, instead of using::
+
+    {% if a > b > c %}  (WRONG)
+
+you should use::
+
+    {% if a > b and b > c %}
+
+
+Filters
+^^^^^^^
+
+You can also use filters in the ``if`` expression. For example::
+
+    {% if messages|length >= 100 %}
+       You have lots of messages today!
+    {% endif %}
+
+Complex expressions
+^^^^^^^^^^^^^^^^^^^
+
+All of the above can be combined to form complex expressions. For such
+expressions, it can be important to know how the operators are grouped when the
+expression is evaluated - that is, the precedence rules.  The precedence of the
+operators, from lowest to highest, is as follows:
+
+ * ``or``
+ * ``and``
+ * ``not``
+ * ``in``
+ * ``==``, ``!=``, ``<``, ``>``,``<=``, ``>=``
+
+(This follows Python exactly). So, for example, the following complex if tag:
+
+    {% if a == b or c == d and e %}
+
+...will be interpreted as:
+
+.. code-block:: python
+
+    (a == b) or ((c == d) and e)
+
+If you need different precedence, you will need to use nested if tags. Sometimes
+that is better for clarity anyway, for the sake of those who do not know the
+precedence rules.
+
 
 .. templatetag:: ifchanged
 
@@ -427,6 +559,9 @@
 ``False``.  If you need to test if something is true or false, use the ``if``
 tag instead.
 
+.. versionadded:: 1.2
+   An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the ``==`` operator.
+
 .. templatetag:: ifnotequal
 
 ifnotequal
@@ -434,6 +569,9 @@
 
 Just like ``ifequal``, except it tests that the two arguments are not equal.
 
+.. versionadded:: 1.2
+   An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and the ``!=`` operator.
+
 .. templatetag:: include
 
 include
diff -r 70e75e8cd224 docs/releases/1.2.txt
--- a/docs/releases/1.2.txt	Thu Dec 03 15:11:14 2009 +0000
+++ b/docs/releases/1.2.txt	Sun Dec 06 01:04:04 2009 +0000
@@ -42,6 +42,15 @@
  * All of the CSRF has moved from contrib to core (with backwards compatible
    imports in the old locations, which are deprecated).
 
+:ttag:`if` tag changes
+----------------------
+
+Due to new features in the :ttag:`if` template tag, it no longer accepts 'and',
+'or' and 'not' as valid **variable** names.  Previously that worked in some
+cases even though these strings were normally treated as keywords.  Now, the
+keyword status is always enforced, and template code like ``{% if not %}`` or
+``{% if and %}`` will throw a TemplateSyntaxError.
+
 ``LazyObject``
 --------------
 
@@ -155,3 +164,36 @@
 :ref:`memory<topic-email-memory-backend>` - you can even configure all
 e-mail to be :ref:`thrown away<topic-email-dummy-backend>`.
 
+'Smart' if tag
+--------------
+
+The :ttag:`if` tag has been upgraded to be much more powerful.  First, support
+for comparison operators has been added. No longer will you have to type:
+
+.. code-block:: html+django
+
+    {% ifnotequal a b %}
+     ...
+    {% endifnotequal %}
+
+...as you can now do:
+
+.. code-block:: html+django
+
+    {% if a != b %}
+     ...
+    {% endif %}
+
+The operators supported are ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=`` and
+``in``, all of which work like the Python operators, in addition to ``and``,
+``or`` and ``not`` which were already supported.
+
+Also, filters may now be used in the ``if`` expression. For example:
+
+.. code-block:: html+django
+
+      <div
+        {% if user.email|lower == message.recipient|lower %}
+          class="highlight"
+        {% endif %}
+      >{{ message }}</div>
diff -r 70e75e8cd224 docs/topics/templates.txt
--- a/docs/topics/templates.txt	Thu Dec 03 15:11:14 2009 +0000
+++ b/docs/topics/templates.txt	Sun Dec 06 01:04:04 2009 +0000
@@ -187,8 +187,8 @@
                 <li>{{ athlete.name }}</li>
             {% endfor %}
             </ul>
-        
-    :ttag:`if` and :ttag:`else`
+
+    :ttag:`if` and ``else``
         Evaluates a variable, and if that variable is "true" the contents of the
         block are displayed::
 
@@ -200,20 +200,15 @@
 
         In the above, if ``athlete_list`` is not empty, the number of athletes
         will be displayed by the ``{{ athlete_list|length }}`` variable.
-        
-    :ttag:`ifequal` and :ttag:`ifnotequal`
-        Display some contents if two arguments are or are not equal. For example::
 
-            {% ifequal athlete.name coach.name %}
-                ...
-            {% endifequal %}
+        You can also use filters and various operators in the ``if`` tag::
 
-        Or::
+            {% if athlete_list|length > 1 %}
+               Team: {% for athlete in athlete_list %} ... {% endfor %}
+            {% else %}
+               Athlete: {{ athlete_list.0.name }}
+            {% endif %}
 
-            {% ifnotequal athlete.name "Joe" %}
-                ...
-            {% endifnotequal %}
-    
     :ttag:`block` and :ttag:`extends`
         Set up `template inheritance`_ (see below), a powerful way
         of cutting down on "boilerplate" in templates.
diff -r 70e75e8cd224 tests/regressiontests/templates/smartif.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/templates/smartif.py	Sun Dec 06 01:04:04 2009 +0000
@@ -0,0 +1,46 @@
+import unittest
+from django.template.smartif import IfParser, Literal
+
+class SmartIfTests(unittest.TestCase):
+
+    def assertCalcEqual(self, expected, tokens):
+        self.assertEqual(expected, IfParser(tokens).parse().resolve({}))
+
+    # We only test things here that are difficult to test elsewhere
+    # Many other tests are found in the main tests for builtin template tags
+    # Test parsing via the printed parse tree
+    def test_not(self):
+        var = IfParser(["not", False]).parse()
+        self.assertEqual("(not (literal False))", repr(var))
+        self.assert_(var.resolve({}))
+
+        self.assertFalse(IfParser(["not", True]).parse().resolve({}))
+
+    def test_or(self):
+        var = IfParser([True, "or", False]).parse()
+        self.assertEqual("(or (literal True) (literal False))", repr(var))
+        self.assert_(var.resolve({}))
+
+    def test_in(self):
+        list_ = [1,2,3]
+        self.assertCalcEqual(True, [1, 'in', list_])
+        self.assertCalcEqual(False, [1, 'in', None])
+        self.assertCalcEqual(False, [None, 'in', list_])
+
+    def test_precedence(self):
+        # (False and False) or True == True   <- we want this one, like Python
+        # False and (False or True) == False
+        self.assertCalcEqual(True, [False, 'and', False, 'or', True])
+
+        # True or (False and False) == True   <- we want this one, like Python
+        # (True or False) and False == False
+        self.assertCalcEqual(True, [True, 'or', False, 'and', False])
+
+        # (1 or 1) == 2  -> False
+        # 1 or (1 == 2)  -> True   <- we want this one
+        self.assertCalcEqual(True, [1, 'or', 1, '==', 2])
+
+        self.assertCalcEqual(True, [True, '==', True, 'or', True, '==', False])
+
+        self.assertEqual("(or (and (== (literal 1) (literal 2)) (literal 3)) (literal 4))",
+                         repr(IfParser([1, '==', 2, 'and', 3, 'or', 4]).parse()))
diff -r 70e75e8cd224 tests/regressiontests/templates/tests.py
--- a/tests/regressiontests/templates/tests.py	Thu Dec 03 15:11:14 2009 +0000
+++ b/tests/regressiontests/templates/tests.py	Sun Dec 06 01:04:04 2009 +0000
@@ -24,6 +24,7 @@
 from custom import custom_filters
 from parser import filter_parsing, variable_parsing
 from unicode import unicode_tests
+from smartif import *
 
 try:
     from loaders import *
@@ -534,6 +535,27 @@
             'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
             'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
 
+            # Filters
+            'if-tag-filter01': ("{% if foo|length == 5 %}yes{% else %}no{% endif %}", {'foo': 'abcde'}, "yes"),
+            'if-tag-filter02': ("{% if foo|upper == 'ABC' %}yes{% else %}no{% endif %}", {}, "no"),
+
+            # Equality
+            'if-tag-eq01': ("{% if foo == bar %}yes{% else %}no{% endif %}", {}, "yes"),
+            'if-tag-eq02': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1}, "no"),
+            'if-tag-eq03': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 1}, "yes"),
+            'if-tag-eq04': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 2}, "no"),
+            'if-tag-eq05': ("{% if foo == '' %}yes{% else %}no{% endif %}", {}, "no"),
+
+            # Comparison
+            'if-tag-gt-01': ("{% if 2 > 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+            'if-tag-gt-02': ("{% if 1 > 1 %}yes{% else %}no{% endif %}", {}, "no"),
+            'if-tag-gte-01': ("{% if 1 >= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+            'if-tag-gte-02': ("{% if 1 >= 2 %}yes{% else %}no{% endif %}", {}, "no"),
+            'if-tag-lt-01': ("{% if 1 < 2 %}yes{% else %}no{% endif %}", {}, "yes"),
+            'if-tag-lt-02': ("{% if 1 < 1 %}yes{% else %}no{% endif %}", {}, "no"),
+            'if-tag-lte-01': ("{% if 1 <= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+            'if-tag-lte-02': ("{% if 2 <= 1 %}yes{% else %}no{% endif %}", {}, "no"),
+
             # AND
             'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
             'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
@@ -554,14 +576,13 @@
             'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
             'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
 
-            # TODO: multiple ORs
+            # multiple ORs
+            'if-tag-or09': ("{% if foo or bar or baz %}yes{% else %}no{% endif %}", {'baz': True}, 'yes'),
 
             # NOT
             'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
-            'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
-            'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
-            'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
-            'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
+            'if-tag-not02': ("{% if not not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'no'),
+            # not03 to not05 removed, now TemplateSyntaxErrors
 
             'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
             'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
@@ -599,12 +620,21 @@
             'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
             'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
 
-            # AND and OR raises a TemplateSyntaxError
-            'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
+            # Various syntax errors
+            'if-tag-error01': ("{% if %}yes{% endif %}", {}, template.TemplateSyntaxError),
             'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
             'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
             'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
             'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+            'if-tag-error06': ("{% if abc def %}yes{% endif %}", {}, template.TemplateSyntaxError),
+            'if-tag-error07': ("{% if not %}yes{% endif %}", {}, template.TemplateSyntaxError),
+            'if-tag-error08': ("{% if and %}yes{% endif %}", {}, template.TemplateSyntaxError),
+            'if-tag-error09': ("{% if or %}yes{% endif %}", {}, template.TemplateSyntaxError),
+            'if-tag-error10': ("{% if == %}yes{% endif %}", {}, template.TemplateSyntaxError),
+            'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, template.TemplateSyntaxError),
+            'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, template.TemplateSyntaxError),
+
+            # Additional, more precise parsing tests are in SmartIfTests
 
             ### IFCHANGED TAG #########################################################
             'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'),

Reply via email to