2 Parser and utilities for the smart 'if' tag
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):
14 Base class for operators and literals, mainly for debugging and for throwing
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
35 Returns what to display in error messages for this node
40 out
= [str(x
) for x
in [self
.id, self
.first
, self
.second
] if x
is not None]
41 return "(" + " ".join(out
) + ")"
46 Creates an infix operator, given a binding power and a function that
49 class Operator(TokenBase
):
52 def led(self
, left
, parser
):
54 self
.second
= parser
.expression(bp
)
57 def eval(self
, context
):
59 return func(context
, self
.first
, self
.second
)
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
71 Creates a prefix operator, given a binding power and a function that
74 class Operator(TokenBase
):
77 def nud(self
, parser
):
78 self
.first
= parser
.expression(bp
)
82 def eval(self
, context
):
84 return func(context
, self
.first
)
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.
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():
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.
126 def __init__(self
, value
):
130 return repr(self
.value
)
132 def nud(self
, parser
):
135 def eval(self
, context
):
139 return "(%s %r)" % (self
.id, self
.value
)
142 class EndToken(TokenBase
):
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
161 if token
== "not" and i
+ 1 < l
and tokens
[i
+1] == "in":
164 mapped_tokens
.append(self
.translate_token(token
))
167 self
.tokens
= mapped_tokens
169 self
.current_token
= self
.next()
171 def translate_token(self
, token
):
173 op
= OPERATORS
[token
]
174 except (KeyError, TypeError):
175 return self
.create_var(token
)
180 if self
.pos
>= len(self
.tokens
):
183 retval
= self
.tokens
[self
.pos
]
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())
195 def expression(self
, rbp
=0):
196 t
= self
.current_token
197 self
.current_token
= self
.next()
199 while rbp
< self
.current_token
.lbp
:
200 t
= self
.current_token
201 self
.current_token
= self
.next()
202 left
= t
.led(left
, self
)
205 def create_var(self
, value
):
206 return Literal(value
)