added first working pieces of compiler
[jinja2.git] / jinja2 / datastructure.py
1 # -*- 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 """
11 from operator import itemgetter
12 from collections import deque
13 from jinja2.exceptions import TemplateSyntaxError, TemplateRuntimeError
14
15
16 _missing = object()
17
18
19 class Token(tuple):
20     """
21     Token class.
22     """
23     __slots__ = ()
24     lineno, type, value = (property(itemgetter(x)) for x in range(3))
25
26     def __new__(cls, lineno, type, value):
27         return tuple.__new__(cls, (lineno, intern(str(type)), value))
28
29     def __str__(self):
30         from jinja.lexer import keywords, reverse_operators
31         if self.type in keywords:
32             return self.type
33         elif self.type in reverse_operators:
34             return reverse_operators[self.type]
35         return self.value
36
37     def __repr__(self):
38         return 'Token(%r, %r, %r)' % (
39             self.lineno,
40             self.type,
41             self.value
42         )
43
44
45 class TokenStreamIterator(object):
46     """
47     The iterator for tokenstreams.  Iterate over the stream
48     until the eof token is reached.
49     """
50
51     def __init__(self, stream):
52         self._stream = stream
53
54     def __iter__(self):
55         return self
56
57     def next(self):
58         token = self._stream.current
59         if token.type == 'eof':
60             self._stream.close()
61             raise StopIteration()
62         self._stream.next(False)
63         return token
64
65
66 class TokenStream(object):
67     """
68     A token stream wraps a generator and supports pushing tokens back.
69     It also provides some functions to expect tokens and similar stuff.
70
71     Important note: Do never push more than one token back to the
72                     stream.  Although the stream object won't stop you
73                     from doing so, the behavior is undefined. Multiple
74                     pushed tokens are only used internally!
75     """
76
77     def __init__(self, generator, filename):
78         self._next = generator.next
79         self._pushed = deque()
80         self.current = Token(1, 'initial', '')
81         self.filename = filename
82         self.next()
83
84     def __iter__(self):
85         return TokenStreamIterator(self)
86
87     def __nonzero__(self):
88         """Are we at the end of the tokenstream?"""
89         return bool(self._pushed) or self.current.type != 'eof'
90
91     eos = property(lambda x: not x.__nonzero__(), doc=__nonzero__.__doc__)
92
93     def push(self, token):
94         """Push a token back to the stream."""
95         self._pushed.append(token)
96
97     def look(self):
98         """Look at the next token."""
99         old_token = self.current
100         next = self.next()
101         self.push(old_token)
102         self.push(next)
103         return next
104
105     def skip(self, n):
106         """Got n tokens ahead."""
107         for x in xrange(n):
108             self.next()
109
110     def next(self, skip_eol=True):
111         """Go one token ahead and return the old one"""
112         rv = self.current
113         while 1:
114             if self._pushed:
115                 self.current = self._pushed.popleft()
116             elif self.current.type is not 'eof':
117                 try:
118                     self.current = self._next()
119                 except StopIteration:
120                     self.close()
121             if not skip_eol or self.current.type is not 'eol':
122                 break
123         return rv
124
125     def close(self):
126         """Close the stream."""
127         self.current = Token(self.current.lineno, 'eof', '')
128         self._next = None
129
130     def expect(self, token_type, token_value=_missing):
131         """Expect a given token type and return it"""
132         if self.current.type is not token_type:
133             raise TemplateSyntaxError("expected token %r, got %r" %
134                                       (token_type, self.current.type),
135                                       self.current.lineno,
136                                       self.filename)
137         elif token_value is not _missing and \
138              self.current.value != token_value:
139             raise TemplateSyntaxError("expected %r, got %r" %
140                                       (token_value, self.current.value),
141                                       self.current.lineno,
142                                       self.filename)
143         try:
144             return self.current
145         finally:
146             self.next()