1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 from lepl.lexer.matchers import Token, RestrictTokensBy, EmptyToken
31 from lepl.lexer.lines.lexer import START, END
32 from lepl.lexer.lines.monitor import BlockMonitor
33 from lepl.matchers.support import coerce_, OperatorMatcher, NoMemo
34 from lepl.core.parser import tagged
35 from lepl.support.lib import fmt
36 from lepl.matchers.combine import And
37 from lepl.stream.core import s_key, s_next, s_line
38
39
40 NO_BLOCKS = object()
41 '''
42 Magic initial value for block_offset to disable indentation checks.
43 '''
47
48 - def __init__(self, indent=True, regexp=None, content=None, id_=None,
49 alphabet=None, complete=True, compiled=False):
59
61 '''
62 Read the global indentation level.
63 '''
64 if self.indent:
65 self._current_indent = monitor.indent
66
71
72 @tagged
98
101
102 - def __init__(self, regexp=None, content=None, id_=None, alphabet=None,
103 complete=True, compiled=False):
104 '''
105 Arguments used only to support cloning.
106 '''
107 super(LineEnd, self).__init__(regexp=None, content=None, id_=END,
108 alphabet=None, complete=True,
109 compiled=compiled)
110
111
112 -def Line(matcher, indent=True):
117
128 return factory
129
132 '''
133 Apply the given matcher to a token stream that ignores line endings and
134 starts (so it matches over multiple lines).
135 '''
136 start = LineStart()
137 end = LineEnd()
138 return RestrictTokensBy(end, start)(matcher)
139
140
141
142
143 DEFAULT_TABSIZE = 8
144 '''
145 The default number of spaces for a tab.
146 '''
149 '''
150 Construct a simple policy for `Block` that increments the indent
151 by some fixed number of spaces.
152 '''
153 def policy(current, _indent):
154 '''
155 Increment current by n_spaces
156 '''
157 return current + n_spaces
158 return policy
159
162 '''
163 Another simple policy that matches whatever indent is used.
164 '''
165 return len(indent)
166
169 '''
170 This allows new blocks to be used without any introduction (eg no colon
171 on the preceding line). See the "closed_bug" test for more details.
172 '''
173 new = len(indent)
174 if new <= current:
175 raise StopIteration
176 return new
177
178
179 DEFAULT_POLICY = constant_indent(DEFAULT_TABSIZE)
180 '''
181 By default, expect an indent equivalent to a tab.
182 '''
183
184
185
186 -class Block(OperatorMatcher, NoMemo):
187 '''
188 Set a new indent level for the enclosed matchers (typically `BLine` and
189 `Block` instances).
190
191 In the simplest case, this might increment the global indent by 4, say.
192 In a more complex case it might look at the current token, expecting an
193 `Indent`, and set the global indent at that amount if it is larger
194 than the current value.
195
196 A block will always match an `Indent`, but will not consume it
197 (it will remain in the stream after the block has finished).
198
199 The usual memoization of left recursive calls will not detect problems
200 with nested blocks (because the indentation changes), so instead we
201 track and block nested calls manually.
202 '''
203
204 POLICY = 'policy'
205
206
207
209 '''
210 Lines are invoked in sequence (like `And()`).
211
212 The policy is passed the current level and the indent and must
213 return a new level. Typically it is set globally by rewriting with
214 a default in the configuration. If it is given as an integer then
215 `constant_indent` is used to create a policy from that.
216
217 indent is the matcher used to match indents, and is exposed for
218 rewriting/extension (in other words, ignore it).
219 '''
220 super(Block, self).__init__()
221 self._args(lines=lines)
222 policy = kargs.get(self.POLICY, DEFAULT_POLICY)
223 if isinstance(policy, int):
224 policy = constant_indent(policy)
225 self._karg(policy=policy)
226 self.monitor_class = BlockMonitor
227 self.__monitor = None
228 self.__streams = set()
229
231 '''
232 Store a reference to the monitor which we will update inside _match
233 '''
234 self.__monitor = monitor
235
238
239 @tagged
241 '''
242 Pull indent and call the policy and update the global value,
243 then evaluate the contents.
244 '''
245
246 key = s_key(stream_in)
247 if key in self.__streams:
248 self._debug('Avoided left recursive call to Block.')
249 return
250 self.__streams.add(key)
251 try:
252 ((tokens, token_stream), _) = s_next(stream_in)
253 (indent, _) = s_line(token_stream, True)
254 if START not in tokens:
255 raise StopIteration
256 current = self.__monitor.indent
257 policy = self.policy(current, indent)
258
259 generator = And(*self.lines)._match(stream_in)
260 while True:
261 self.__monitor.push_level(policy)
262 try:
263 results = yield generator
264 finally:
265 self.__monitor.pop_level()
266 yield results
267 finally:
268 self.__streams.remove(key)
269