Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 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 |
Armin Ronacher | 82b3f3d | 2008-03-31 20:01:08 +0200 | [diff] [blame] | 12 | from collections import deque |
| 13 | from jinja2.exceptions import TemplateSyntaxError, TemplateRuntimeError |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 14 | |
| 15 | |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 16 | class Token(tuple): |
| 17 | """ |
| 18 | Token class. |
| 19 | """ |
| 20 | __slots__ = () |
Armin Ronacher | 82b3f3d | 2008-03-31 20:01:08 +0200 | [diff] [blame] | 21 | lineno, type, value = (property(itemgetter(x)) for x in range(3)) |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 22 | |
| 23 | def __new__(cls, lineno, type, value): |
Armin Ronacher | 82b3f3d | 2008-03-31 20:01:08 +0200 | [diff] [blame] | 24 | return tuple.__new__(cls, (lineno, intern(str(type)), value)) |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 25 | |
| 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 Ronacher | f59bac2 | 2008-04-20 13:11:43 +0200 | [diff] [blame] | 32 | 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 Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 53 | |
| 54 | def __repr__(self): |
| 55 | return 'Token(%r, %r, %r)' % ( |
| 56 | self.lineno, |
| 57 | self.type, |
| 58 | self.value |
| 59 | ) |
| 60 | |
| 61 | |
| 62 | class 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 Ronacher | e791c2a | 2008-04-07 18:39:54 +0200 | [diff] [blame] | 79 | self._stream.next(False) |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 80 | return token |
| 81 | |
| 82 | |
| 83 | class 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 Ronacher | 82b3f3d | 2008-03-31 20:01:08 +0200 | [diff] [blame] | 96 | self._pushed = deque() |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 97 | self.current = Token(1, 'initial', '') |
| 98 | self.filename = filename |
| 99 | self.next() |
| 100 | |
| 101 | def __iter__(self): |
| 102 | return TokenStreamIterator(self) |
| 103 | |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 104 | 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 Ronacher | 82b3f3d | 2008-03-31 20:01:08 +0200 | [diff] [blame] | 114 | def look(self): |
| 115 | """Look at the next token.""" |
Armin Ronacher | 8efc522 | 2008-04-08 14:47:40 +0200 | [diff] [blame] | 116 | old_token = self.next() |
| 117 | result = self.current |
| 118 | self.push(result) |
| 119 | self.current = old_token |
| 120 | return result |
Armin Ronacher | 82b3f3d | 2008-03-31 20:01:08 +0200 | [diff] [blame] | 121 | |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 122 | def skip(self, n): |
| 123 | """Got n tokens ahead.""" |
| 124 | for x in xrange(n): |
| 125 | self.next() |
| 126 | |
Armin Ronacher | e791c2a | 2008-04-07 18:39:54 +0200 | [diff] [blame] | 127 | 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 Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 141 | |
| 142 | def close(self): |
| 143 | """Close the stream.""" |
| 144 | self.current = Token(self.current.lineno, 'eof', '') |
| 145 | self._next = None |
| 146 | |
Armin Ronacher | f59bac2 | 2008-04-20 13:11:43 +0200 | [diff] [blame] | 147 | def expect(self, expr): |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 148 | """Expect a given token type and return it""" |
Armin Ronacher | f59bac2 | 2008-04-20 13:11:43 +0200 | [diff] [blame] | 149 | if not self.current.test(expr): |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 150 | raise TemplateSyntaxError("expected token %r, got %r" % |
Armin Ronacher | f59bac2 | 2008-04-20 13:11:43 +0200 | [diff] [blame] | 151 | (expr, self.current), |
Armin Ronacher | 07bc684 | 2008-03-31 14:18:49 +0200 | [diff] [blame] | 152 | self.current.lineno, |
| 153 | self.filename) |
| 154 | try: |
| 155 | return self.current |
| 156 | finally: |
| 157 | self.next() |