Add Django-1.2.1
[frozenviper.git] / Django-1.2.1 / django / template / smartif.py
blobe835e0ff70f583d08a79cce7e216d4ea845f6e74
1 """
2 Parser and utilities for the smart 'if' tag
3 """
4 import operator
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)
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
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
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
33 def display(self):
34 """
35 Returns what to display in error messages for this node
36 """
37 return self.id
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) + ")"
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
52 def led(self, left, parser):
53 self.first = left
54 self.second = parser.expression(bp)
55 return self
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
66 return Operator
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
77 def nud(self, parser):
78 self.first = parser.expression(bp)
79 self.second = None
80 return self
82 def eval(self, context):
83 try:
84 return func(context, self.first)
85 except Exception:
86 return False
88 return Operator
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)),
111 # Assign 'id' to each:
112 for key, op in OPERATORS.items():
113 op.id = key
116 class Literal(TokenBase):
118 A basic self-resolvable object similar to a Django template variable.
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
126 def __init__(self, value):
127 self.value = value
129 def display(self):
130 return repr(self.value)
132 def nud(self, parser):
133 return self
135 def eval(self, context):
136 return self.value
138 def __repr__(self):
139 return "(%s %r)" % (self.id, self.value)
142 class EndToken(TokenBase):
143 lbp = 0
145 def nud(self, parser):
146 raise parser.error_class("Unexpected end of expression in if tag.")
148 EndToken = EndToken()
151 class IfParser(object):
152 error_class = ValueError
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
167 self.tokens = mapped_tokens
168 self.pos = 0
169 self.current_token = self.next()
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()
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
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
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
205 def create_var(self, value):
206 return Literal(value)