web/lib/django/template/smartif.py
changeset 29 cc9b7e14412b
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
       
     1 """
       
     2 Parser and utilities for the smart 'if' tag
       
     3 """
       
     4 import operator
       
     5 
       
     6 # Using a simple top down parser, as described here:
       
     7 #    http://effbot.org/zone/simple-top-down-parsing.htm.
       
     8 # 'led' = left denotation
       
     9 # 'nud' = null denotation
       
    10 # 'bp' = binding power (left = lbp, right = rbp)
       
    11 
       
    12 class TokenBase(object):
       
    13     """
       
    14     Base class for operators and literals, mainly for debugging and for throwing
       
    15     syntax errors.
       
    16     """
       
    17     id = None # node/token type name
       
    18     value = None # used by literals
       
    19     first = second = None # used by tree nodes
       
    20 
       
    21     def nud(self, parser):
       
    22         # Null denotation - called in prefix context
       
    23         raise parser.error_class(
       
    24             "Not expecting '%s' in this position in if tag." % self.id
       
    25         )
       
    26 
       
    27     def led(self, left, parser):
       
    28         # Left denotation - called in infix context
       
    29         raise parser.error_class(
       
    30             "Not expecting '%s' as infix operator in if tag." % self.id
       
    31         )
       
    32 
       
    33     def display(self):
       
    34         """
       
    35         Returns what to display in error messages for this node
       
    36         """
       
    37         return self.id
       
    38 
       
    39     def __repr__(self):
       
    40         out = [str(x) for x in [self.id, self.first, self.second] if x is not None]
       
    41         return "(" + " ".join(out) + ")"
       
    42 
       
    43 
       
    44 def infix(bp, func):
       
    45     """
       
    46     Creates an infix operator, given a binding power and a function that
       
    47     evaluates the node
       
    48     """
       
    49     class Operator(TokenBase):
       
    50         lbp = bp
       
    51 
       
    52         def led(self, left, parser):
       
    53             self.first = left
       
    54             self.second = parser.expression(bp)
       
    55             return self
       
    56 
       
    57         def eval(self, context):
       
    58             try:
       
    59                 return func(context, self.first, self.second)
       
    60             except Exception:
       
    61                 # Templates shouldn't throw exceptions when rendering.  We are
       
    62                 # most likely to get exceptions for things like {% if foo in bar
       
    63                 # %} where 'bar' does not support 'in', so default to False
       
    64                 return False
       
    65 
       
    66     return Operator
       
    67 
       
    68 
       
    69 def prefix(bp, func):
       
    70     """
       
    71     Creates a prefix operator, given a binding power and a function that
       
    72     evaluates the node.
       
    73     """
       
    74     class Operator(TokenBase):
       
    75         lbp = bp
       
    76 
       
    77         def nud(self, parser):
       
    78             self.first = parser.expression(bp)
       
    79             self.second = None
       
    80             return self
       
    81 
       
    82         def eval(self, context):
       
    83             try:
       
    84                 return func(context, self.first)
       
    85             except Exception:
       
    86                 return False
       
    87 
       
    88     return Operator
       
    89 
       
    90 
       
    91 # Operator precedence follows Python.
       
    92 # NB - we can get slightly more accurate syntax error messages by not using the
       
    93 # same object for '==' and '='.
       
    94 # We defer variable evaluation to the lambda to ensure that terms are
       
    95 # lazily evaluated using Python's boolean parsing logic.
       
    96 OPERATORS = {
       
    97     'or': infix(6, lambda context, x, y: x.eval(context) or y.eval(context)),
       
    98     'and': infix(7, lambda context, x, y: x.eval(context) and y.eval(context)),
       
    99     'not': prefix(8, lambda context, x: not x.eval(context)),
       
   100     'in': infix(9, lambda context, x, y: x.eval(context) in y.eval(context)),
       
   101     'not in': infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)),
       
   102     '=': infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
       
   103     '==': infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
       
   104     '!=': infix(10, lambda context, x, y: x.eval(context) != y.eval(context)),
       
   105     '>': infix(10, lambda context, x, y: x.eval(context) > y.eval(context)),
       
   106     '>=': infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)),
       
   107     '<': infix(10, lambda context, x, y: x.eval(context) < y.eval(context)),
       
   108     '<=': infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)),
       
   109 }
       
   110 
       
   111 # Assign 'id' to each:
       
   112 for key, op in OPERATORS.items():
       
   113     op.id = key
       
   114 
       
   115 
       
   116 class Literal(TokenBase):
       
   117     """
       
   118     A basic self-resolvable object similar to a Django template variable.
       
   119     """
       
   120     # IfParser uses Literal in create_var, but TemplateIfParser overrides
       
   121     # create_var so that a proper implementation that actually resolves
       
   122     # variables, filters etc is used.
       
   123     id = "literal"
       
   124     lbp = 0
       
   125 
       
   126     def __init__(self, value):
       
   127         self.value = value
       
   128 
       
   129     def display(self):
       
   130         return repr(self.value)
       
   131 
       
   132     def nud(self, parser):
       
   133         return self
       
   134 
       
   135     def eval(self, context):
       
   136         return self.value
       
   137 
       
   138     def __repr__(self):
       
   139         return "(%s %r)" % (self.id, self.value)
       
   140 
       
   141 
       
   142 class EndToken(TokenBase):
       
   143     lbp = 0
       
   144 
       
   145     def nud(self, parser):
       
   146         raise parser.error_class("Unexpected end of expression in if tag.")
       
   147 
       
   148 EndToken = EndToken()
       
   149 
       
   150 
       
   151 class IfParser(object):
       
   152     error_class = ValueError
       
   153 
       
   154     def __init__(self, tokens):
       
   155         # pre-pass necessary to turn  'not','in' into single token
       
   156         l = len(tokens)
       
   157         mapped_tokens = []
       
   158         i = 0
       
   159         while i < l:
       
   160             token = tokens[i]
       
   161             if token == "not" and i + 1 < l and tokens[i+1] == "in":
       
   162                 token = "not in"
       
   163                 i += 1 # skip 'in'
       
   164             mapped_tokens.append(self.translate_token(token))
       
   165             i += 1
       
   166 
       
   167         self.tokens = mapped_tokens
       
   168         self.pos = 0
       
   169         self.current_token = self.next()
       
   170 
       
   171     def translate_token(self, token):
       
   172         try:
       
   173             op = OPERATORS[token]
       
   174         except (KeyError, TypeError):
       
   175             return self.create_var(token)
       
   176         else:
       
   177             return op()
       
   178 
       
   179     def next(self):
       
   180         if self.pos >= len(self.tokens):
       
   181             return EndToken
       
   182         else:
       
   183             retval = self.tokens[self.pos]
       
   184             self.pos += 1
       
   185             return retval
       
   186 
       
   187     def parse(self):
       
   188         retval = self.expression()
       
   189         # Check that we have exhausted all the tokens
       
   190         if self.current_token is not EndToken:
       
   191             raise self.error_class("Unused '%s' at end of if expression." %
       
   192                                    self.current_token.display())
       
   193         return retval
       
   194 
       
   195     def expression(self, rbp=0):
       
   196         t = self.current_token
       
   197         self.current_token = self.next()
       
   198         left = t.nud(self)
       
   199         while rbp < self.current_token.lbp:
       
   200             t = self.current_token
       
   201             self.current_token = self.next()
       
   202             left = t.led(left, self)
       
   203         return left
       
   204 
       
   205     def create_var(self, value):
       
   206         return Literal(value)