blob: 9dab02b83099d4abe710ceb695359f9c09e7b673 [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
3 jinja2.datastructure
4 ~~~~~~~~~~~~~~~~~~~~
5
6 Module that helds several data types used in the template engine.
7
8 :copyright: 2007 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
10"""
11from operator import itemgetter
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020012from collections import deque
13from jinja2.exceptions import TemplateSyntaxError, TemplateRuntimeError
Armin Ronacher07bc6842008-03-31 14:18:49 +020014
15
Armin Ronacher07bc6842008-03-31 14:18:49 +020016class Token(tuple):
17 """
18 Token class.
19 """
20 __slots__ = ()
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020021 lineno, type, value = (property(itemgetter(x)) for x in range(3))
Armin Ronacher07bc6842008-03-31 14:18:49 +020022
23 def __new__(cls, lineno, type, value):
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020024 return tuple.__new__(cls, (lineno, intern(str(type)), value))
Armin Ronacher07bc6842008-03-31 14:18:49 +020025
26 def __str__(self):
27 from jinja.lexer import keywords, reverse_operators
28 if self.type in keywords:
29 return self.type
30 elif self.type in reverse_operators:
31 return reverse_operators[self.type]
Armin Ronacherf59bac22008-04-20 13:11:43 +020032 return '%s:%s' % (self.type, self.value)
33
34 def test(self, expr):
35 """Test a token against a token expression. This can either be a
36 token type or 'token_type:token_value'. This can only test against
37 string values!
38 """
39 # here we do a regular string equality check as test_many is usually
40 # passed an iterable of not interned strings.
41 if self.type == expr:
42 return True
43 elif ':' in expr:
44 return expr.split(':', 1) == [self.type, self.value]
45 return False
46
47 def test_many(self, iterable):
48 """Test against multiple token expressions."""
49 for expr in iterable:
50 if self.test(expr):
51 return True
52 return False
Armin Ronacher07bc6842008-03-31 14:18:49 +020053
54 def __repr__(self):
55 return 'Token(%r, %r, %r)' % (
56 self.lineno,
57 self.type,
58 self.value
59 )
60
61
62class TokenStreamIterator(object):
63 """
64 The iterator for tokenstreams. Iterate over the stream
65 until the eof token is reached.
66 """
67
68 def __init__(self, stream):
69 self._stream = stream
70
71 def __iter__(self):
72 return self
73
74 def next(self):
75 token = self._stream.current
76 if token.type == 'eof':
77 self._stream.close()
78 raise StopIteration()
Armin Ronachere791c2a2008-04-07 18:39:54 +020079 self._stream.next(False)
Armin Ronacher07bc6842008-03-31 14:18:49 +020080 return token
81
82
83class TokenStream(object):
84 """
85 A token stream wraps a generator and supports pushing tokens back.
86 It also provides some functions to expect tokens and similar stuff.
87
88 Important note: Do never push more than one token back to the
89 stream. Although the stream object won't stop you
90 from doing so, the behavior is undefined. Multiple
91 pushed tokens are only used internally!
92 """
93
94 def __init__(self, generator, filename):
95 self._next = generator.next
Armin Ronacher82b3f3d2008-03-31 20:01:08 +020096 self._pushed = deque()
Armin Ronacher07bc6842008-03-31 14:18:49 +020097 self.current = Token(1, 'initial', '')
98 self.filename = filename
99 self.next()
100
101 def __iter__(self):
102 return TokenStreamIterator(self)
103
Armin Ronacher07bc6842008-03-31 14:18:49 +0200104 def __nonzero__(self):
105 """Are we at the end of the tokenstream?"""
106 return bool(self._pushed) or self.current.type != 'eof'
107
108 eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
109
110 def push(self, token):
111 """Push a token back to the stream."""
112 self._pushed.append(token)
113
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200114 def look(self):
115 """Look at the next token."""
Armin Ronacher8efc5222008-04-08 14:47:40 +0200116 old_token = self.next()
117 result = self.current
118 self.push(result)
119 self.current = old_token
120 return result
Armin Ronacher82b3f3d2008-03-31 20:01:08 +0200121
Armin Ronacher07bc6842008-03-31 14:18:49 +0200122 def skip(self, n):
123 """Got n tokens ahead."""
124 for x in xrange(n):
125 self.next()
126
Armin Ronachere791c2a2008-04-07 18:39:54 +0200127 def next(self, skip_eol=True):
128 """Go one token ahead and return the old one"""
129 rv = self.current
130 while 1:
131 if self._pushed:
132 self.current = self._pushed.popleft()
133 elif self.current.type is not 'eof':
134 try:
135 self.current = self._next()
136 except StopIteration:
137 self.close()
138 if not skip_eol or self.current.type is not 'eol':
139 break
140 return rv
Armin Ronacher07bc6842008-03-31 14:18:49 +0200141
142 def close(self):
143 """Close the stream."""
144 self.current = Token(self.current.lineno, 'eof', '')
145 self._next = None
146
Armin Ronacherf59bac22008-04-20 13:11:43 +0200147 def expect(self, expr):
Armin Ronacher07bc6842008-03-31 14:18:49 +0200148 """Expect a given token type and return it"""
Armin Ronacherf59bac22008-04-20 13:11:43 +0200149 if not self.current.test(expr):
Armin Ronacher07bc6842008-03-31 14:18:49 +0200150 raise TemplateSyntaxError("expected token %r, got %r" %
Armin Ronacherf59bac22008-04-20 13:11:43 +0200151 (expr, self.current),
Armin Ronacher07bc6842008-03-31 14:18:49 +0200152 self.current.lineno,
153 self.filename)
154 try:
155 return self.current
156 finally:
157 self.next()