parser and utilities for the smart if tag using a simple top down parser as described here http effbot orgzonesimpletopdownparsing htm led left denotation nud null denotation bp binding power left lbp right rbp base class for operators and literals mainly for debugging and for throwing syntax errors null denotation called in prefix context left denotation called in infix context return what to display in error messages for this node create an infix operator given a binding power and a function that evaluates the node templates shouldn t throw exceptions when rendering we are most likely to get exceptions for things like if foo in bar where bar does not support in so default to false create a prefix operator given a binding power and a function that evaluates the node operator precedence follows python we defer variable evaluation to the lambda to ensure that terms are lazily evaluated using python s boolean parsing logic assign id to each a basic selfresolvable object similar to a django template variable ifparser uses literal in createvar but templateifparser overrides createvar so that a proper implementation that actually resolves variables filters etc is used turn is not and not in into single tokens check that we have exhausted all the tokens 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 base class for operators and literals mainly for debugging and for throwing syntax errors node token type name used by literals used by tree nodes null denotation called in prefix context left denotation called in infix context return what to display in error messages for this node create an infix operator given a binding power and a function that evaluates the node templates shouldn t throw exceptions when rendering we are most likely to get exceptions for things like if foo in bar where bar does not support in so default to false create a prefix operator given a binding power and a function that evaluates the node operator precedence follows python we defer variable evaluation to the lambda to ensure that terms are lazily evaluated using python s boolean parsing logic assign id to each 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 is used turn is not and not in into single tokens skip not skip in check that we have exhausted all the tokens
class TokenBase:
    

    id = None  
    value = None  
    first = second = None  

    def nud(self, parser):
        
        raise parser.error_class(
            "Not expecting '%s' in this position in if tag." % self.id
        )

    def led(self, left, parser):
        
        raise parser.error_class(
            "Not expecting '%s' as infix operator in if tag." % self.id
        )

    def display(self):
        
        return self.id

    def __repr__(self):
        out = [str(x) for x in [self.id, self.first, self.second] if x is not None]
        return "(" + " ".join(out) + ")"


def infix(bp, func):
    

    class Operator(TokenBase):
        lbp = bp

        def led(self, left, parser):
            self.first = left
            self.second = parser.expression(bp)
            return self

        def eval(self, context):
            try:
                return func(context, self.first, self.second)
            except Exception:
                
                
                
                return False

    return Operator


def prefix(bp, func):
    

    class Operator(TokenBase):
        lbp = bp

        def nud(self, parser):
            self.first = parser.expression(bp)
            self.second = None
            return self

        def eval(self, context):
            try:
                return func(context, self.first)
            except Exception:
                return False

    return Operator





OPERATORS = {
    "or": infix(6, lambda context, x, y: x.eval(context) or y.eval(context)),
    "and": infix(7, lambda context, x, y: x.eval(context) and y.eval(context)),
    "not": prefix(8, lambda context, x: not x.eval(context)),
    "in": infix(9, lambda context, x, y: x.eval(context) in y.eval(context)),
    "not in": infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)),
    "is": infix(10, lambda context, x, y: x.eval(context) is y.eval(context)),
    "is not": infix(10, lambda context, x, y: x.eval(context) is not y.eval(context)),
    "==": infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
    "!=": infix(10, lambda context, x, y: x.eval(context) != y.eval(context)),
    ">": infix(10, lambda context, x, y: x.eval(context) > y.eval(context)),
    ">=": infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)),
    "<": infix(10, lambda context, x, y: x.eval(context) < y.eval(context)),
    "<=": infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)),
}


for key, op in OPERATORS.items():
    op.id = key


class Literal(TokenBase):
    

    
    
    
    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 eval(self, context):
        return self.value

    def __repr__(self):
        return "(%s %r)" % (self.id, 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:
    error_class = ValueError

    def __init__(self, tokens):
        
        num_tokens = len(tokens)
        mapped_tokens = []
        i = 0
        while i < num_tokens:
            token = tokens[i]
            if token == "is" and i + 1 < num_tokens and tokens[i + 1] == "not":
                token = "is not"
                i += 1  
            elif token == "not" and i + 1 < num_tokens and tokens[i + 1] == "in":
                token = "not in"
                i += 1  
            mapped_tokens.append(self.translate_token(token))
            i += 1

        self.tokens = mapped_tokens
        self.pos = 0
        self.current_token = self.next_token()

    def translate_token(self, token):
        try:
            op = OPERATORS[token]
        except (KeyError, TypeError):
            return self.create_var(token)
        else:
            return op()

    def next_token(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()
        
        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_token()
        left = t.nud(self)
        while rbp < self.current_token.lbp:
            t = self.current_token
            self.current_token = self.next_token()
            left = t.led(left, self)
        return left

    def create_var(self, value):
        return Literal(value)