My favorites | Sign in
Project Home Downloads Wiki Issues Source
Repository:
Checkout   Browse   Changes   Clones  
Changes to /src/lepl/matchers/combine.py
dfb1a55ce344 vs. cdbfc4c97d39 Compare: vs.  Format:
Revision cdbfc4c97d39
Go to: 
Project members, sign in to write a code review
/src/lepl/matchers/combine.py   dfb1a55ce344 /src/lepl/matchers/combine.py   cdbfc4c97d39
1 1
2 # The contents of this file are subject to the Mozilla Public License 2 # The contents of this file are subject to the Mozilla Public License
3 # (MPL) Version 1.1 (the "License"); you may not use this file except 3 # (MPL) Version 1.1 (the "License"); you may not use this file except
4 # in compliance with the License. You may obtain a copy of the License 4 # in compliance with the License. You may obtain a copy of the License
5 # at http://www.mozilla.org/MPL/ 5 # at http://www.mozilla.org/MPL/
6 # 6 #
7 # Software distributed under the License is distributed on an "AS IS" 7 # Software distributed under the License is distributed on an "AS IS"
8 # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9 # the License for the specific language governing rights and 9 # the License for the specific language governing rights and
10 # limitations under the License. 10 # limitations under the License.
11 # 11 #
12 # The Original Code is LEPL (http://www.acooke.org/lepl) 12 # The Original Code is LEPL (http://www.acooke.org/lepl)
13 # The Initial Developer of the Original Code is Andrew Cooke. 13 # The Initial Developer of the Original Code is Andrew Cooke.
14 # Portions created by the Initial Developer are Copyright (C) 2009-2010 14 # Portions created by the Initial Developer are Copyright (C) 2009-2010
15 # Andrew Cooke (andrew@acooke.org). All Rights Reserved. 15 # Andrew Cooke (andrew@acooke.org). All Rights Reserved.
16 # 16 #
17 # Alternatively, the contents of this file may be used under the terms 17 # Alternatively, the contents of this file may be used under the terms
18 # of the LGPL license (the GNU Lesser General Public License, 18 # of the LGPL license (the GNU Lesser General Public License,
19 # http://www.gnu.org/licenses/lgpl.html), in which case the provisions 19 # http://www.gnu.org/licenses/lgpl.html), in which case the provisions
20 # of the LGPL License are applicable instead of those above. 20 # of the LGPL License are applicable instead of those above.
21 # 21 #
22 # If you wish to allow use of your version of this file only under the 22 # If you wish to allow use of your version of this file only under the
23 # terms of the LGPL License and not to allow others to use your version 23 # terms of the LGPL License and not to allow others to use your version
24 # of this file under the MPL, indicate your decision by deleting the 24 # of this file under the MPL, indicate your decision by deleting the
25 # provisions above and replace them with the notice and other provisions 25 # provisions above and replace them with the notice and other provisions
26 # required by the LGPL License. If you do not delete the provisions 26 # required by the LGPL License. If you do not delete the provisions
27 # above, a recipient may use your version of this file under either the 27 # above, a recipient may use your version of this file under either the
28 # MPL or the LGPL License. 28 # MPL or the LGPL License.
29 29
30 ''' 30 '''
31 Matchers that combine sub-matchers (And, Or etc). 31 Matchers that combine sub-matchers (And, Or etc).
32 ''' 32 '''
33 33
34 # pylint: disable-msg=C0103,W0212 34 # pylint: disable-msg=C0103,W0212
35 # (consistent interfaces) 35 # (consistent interfaces)
36 # pylint: disable-msg=E1101 36 # pylint: disable-msg=E1101
37 # (_args create attributes) 37 # (_args create attributes)
38 # pylint: disable-msg=R0901, R0904, W0142 38 # pylint: disable-msg=R0901, R0904, W0142
39 # lepl conventions 39 # lepl conventions
40 40
41 from abc import ABCMeta 41 from abc import ABCMeta
42 from collections import deque 42 from collections import deque
43 from operator import __add__ 43 from operator import __add__
44 44
45 from lepl.core.parser import tagged 45 from lepl.core.parser import tagged
46 from lepl.core.manager import _GeneratorManager 46 from lepl.core.manager import _GeneratorManager
47 from lepl.matchers.core import Literal 47 from lepl.matchers.core import Literal
48 from lepl.matchers.matcher import add_children 48 from lepl.matchers.matcher import add_children
49 from lepl.matchers.support import coerce_, sequence_matcher_factory, \ 49 from lepl.matchers.support import coerce_, sequence_matcher_factory, \
50 trampoline_matcher_factory, to, OperatorMatcher 50 trampoline_matcher_factory, to, OperatorMatcher
51 from lepl.matchers.transform import Transformable 51 from lepl.matchers.transform import Transformable
52 from lepl.support.lib import lmap, fmt, document 52 from lepl.support.lib import lmap, fmt, document
53 53
54 54
55 # pylint: disable-msg=C0103, W0105 55 # pylint: disable-msg=C0103, W0105
56 # Python 2.6 56 # Python 2.6
57 #class BaseSearch(metaclass=ABCMeta): 57 #class BaseSearch(metaclass=ABCMeta):
58 _BaseSearch = ABCMeta('_BaseSearch', (object, ), {}) 58 _BaseSearch = ABCMeta('_BaseSearch', (object, ), {})
59 ''' 59 '''
60 ABC used to identify matchers. 60 ABC used to identify matchers.
61 61
62 Note that graph traversal assumes subclasses are hashable and iterable. 62 Note that graph traversal assumes subclasses are hashable and iterable.
63 ''' 63 '''
64 64
65 class BaseSearch(_BaseSearch): 65 class BaseSearch(_BaseSearch):
66 pass 66 pass
67 67
68 68
69 def search_factory(factory): 69 def search_factory(factory):
70 ''' 70 '''
71 Add the arg processing common to all searching. 71 Add the arg processing common to all searching.
72 ''' 72 '''
73 def new_factory(first, start, stop, rest=None, reduce=None, 73 def new_factory(first, start, stop, rest=None, reduce=None,
74 generator_manager_queue_len=None): 74 generator_manager_queue_len=None):
75 rest = first if rest is None else rest 75 rest = first if rest is None else rest
76 reduce = reduce if reduce else ([], __add__) 76 reduce = reduce if reduce else ([], __add__)
77 return factory(first, start, stop, rest, reduce, 77 return factory(first, start, stop, rest, reduce,
78 generator_manager_queue_len) 78 generator_manager_queue_len)
79 return document(new_factory, factory) 79 return document(new_factory, factory)
80 80
81 81
82 @trampoline_matcher_factory(first=to(Literal), rest=to(Literal)) 82 @trampoline_matcher_factory(first=to(Literal), rest=to(Literal))
83 @search_factory 83 @search_factory
84 def DepthFirst(first, start, stop, rest, reduce, generator_manager_queue_len): 84 def DepthFirst(first, start, stop, rest, reduce, generator_manager_queue_len):
85 ''' 85 '''
86 (Post order) Depth first repetition (typically used via `Repeat`). 86 (Post order) Depth first repetition (typically used via `Repeat`).
87 ''' 87 '''
88 (zero, join) = reduce 88 (zero, join) = reduce
89 def match(support, stream): 89 def match(support, stream):
90 stack = deque() 90 stack = deque()
91 try: 91 try:
92 stack.append((0, zero, stream, first._match(stream))) 92 stack.append((0, zero, stream, first._match(stream)))
93 stream = None 93 stream = None
94 while stack: 94 while stack:
95 (count1, acc1, stream1, generator) = stack[-1] 95 (count1, acc1, stream1, generator) = stack[-1]
96 extended = False 96 extended = False
97 if stop is None or count1 < stop: 97 if stop is None or count1 < stop:
98 count2 = count1 + 1 98 count2 = count1 + 1
99 try: 99 try:
100 (value, stream2) = yield generator 100 (value, stream2) = yield generator
101 acc2 = join(acc1, value) 101 acc2 = join(acc1, value)
102 stack.append((count2, acc2, stream2, rest._match(stream2))) 102 stack.append((count2, acc2, stream2, rest._match(stream2)))
103 extended = True 103 extended = True
104 except StopIteration: 104 except StopIteration:
105 pass 105 pass
106 if not extended: 106 if not extended:
107 if count1 >= start and (stop is None or count1 <= stop): 107 if count1 >= start and (stop is None or count1 <= stop):
108 yield (acc1, stream1) 108 yield (acc1, stream1)
109 stack.pop() 109 stack.pop()
110 while support.generator_manager_queue_len \ 110 while support.generator_manager_queue_len \
111 and len(stack) > support.generator_manager_queue_len: 111 and len(stack) > support.generator_manager_queue_len:
112 stack.popleft()[3].generator.close() 112 stack.popleft()[3].generator.close()
113 finally: 113 finally:
114 while stack: 114 while stack:
115 stack.popleft()[3].generator.close() 115 stack.popleft()[3].generator.close()
116 116
117 return match 117 return match
118 118
119 119
120 @trampoline_matcher_factory(first=to(Literal), rest=to(Literal)) 120 @trampoline_matcher_factory(first=to(Literal), rest=to(Literal))
121 @search_factory 121 @search_factory
122 def BreadthFirst(first, start, stop, rest, reduce, generator_manager_queue_len): 122 def BreadthFirst(first, start, stop, rest, reduce, generator_manager_queue_len):
123 ''' 123 '''
124 (Level order) Breadth first repetition (typically used via `Repeat`). 124 (Level order) Breadth first repetition (typically used via `Repeat`).
125 ''' 125 '''
126 (zero, join) = reduce 126 (zero, join) = reduce
127 def match(support, stream): 127 def match(support, stream):
128 queue = deque() 128 queue = deque()
129 try: 129 try:
130 queue.append((0, zero, stream, first._match(stream))) 130 queue.append((0, zero, stream, first._match(stream)))
131 stream = None
131 while queue: 132 while queue:
132 (count1, acc1, stream1, generator) = queue.popleft() 133 (count1, acc1, stream1, generator) = queue.popleft()
133 if count1 >= start and (stop is None or count1 <= stop): 134 if count1 >= start and (stop is None or count1 <= stop):
134 yield (acc1, stream1) 135 yield (acc1, stream1)
135 count2 = count1 + 1 136 count2 = count1 + 1
136 try: 137 try:
137 while True: 138 while True:
138 (value, stream2) = yield generator 139 (value, stream2) = yield generator
139 acc2 = join(acc1, value) 140 acc2 = join(acc1, value)
140 if stop is None or count2 <= stop: 141 if stop is None or count2 <= stop:
141 queue.append((count2, acc2, stream2, rest._match(stream2))) 142 queue.append((count2, acc2, stream2, rest._match(stream2)))
142 except StopIteration: 143 except StopIteration:
143 pass 144 pass
144 while support.generator_manager_queue_len \ 145 while support.generator_manager_queue_len \
145 and len(queue) > support.generator_manager_queue_len: 146 and len(queue) > support.generator_manager_queue_len:
146 queue.popleft()[3].generator.close() 147 queue.popleft()[3].generator.close()
147 finally: 148 finally:
148 while queue: 149 while queue:
149 queue.popleft()[3].generator.close() 150 queue.popleft()[3].generator.close()
150 151
151 return match 152 return match
152 153
153 154
154 @trampoline_matcher_factory(matcher=to(Literal)) 155 @trampoline_matcher_factory(matcher=to(Literal))
155 def OrderByResultCount(matcher, ascending=True): 156 def OrderByResultCount(matcher, ascending=True):
156 ''' 157 '''
157 Modify a matcher to return results in length order. 158 Modify a matcher to return results in length order.
158 ''' 159 '''
159 160
160 def match(support, stream): 161 def match(support, stream):
161 ''' 162 '''
162 Attempt to match the stream. 163 Attempt to match the stream.
163 ''' 164 '''
164 generator = matcher._match(stream) 165 generator = matcher._match(stream)
165 results = [] 166 results = []
166 try: 167 try:
167 while True: 168 while True:
168 # syntax error if this on one line?! 169 # syntax error if this on one line?!
169 result = yield generator 170 result = yield generator
170 results.append(result) 171 results.append(result)
171 except StopIteration: 172 except StopIteration:
172 pass 173 pass
173 for result in sorted(results, 174 for result in sorted(results,
174 key=lambda x: len(x[0]), reverse=ascending): 175 key=lambda x: len(x[0]), reverse=ascending):
175 yield result 176 yield result
176 177
177 return match 178 return match
178 179
179 180
180 @sequence_matcher_factory(first=to(Literal), rest=to(Literal)) 181 @sequence_matcher_factory(first=to(Literal), rest=to(Literal))
181 @search_factory 182 @search_factory
182 def DepthNoTrampoline(first, start, stop, rest, reduce, generator_manager_queue_len): 183 def DepthNoTrampoline(first, start, stop, rest, reduce, generator_manager_queue_len):
183 ''' 184 '''
184 A more efficient search when all matchers are functions (so no need to 185 A more efficient search when all matchers are functions (so no need to
185 trampoline). Depth first (greedy). 186 trampoline). Depth first (greedy).
186 ''' 187 '''
187 (zero, join) = reduce 188 (zero, join) = reduce
188 def matcher(support, stream): 189 def matcher(support, stream):
189 stack = deque() 190 stack = deque()
190 try: 191 try:
191 stack.append((0, zero, stream, first._untagged_match(stream))) 192 stack.append((0, zero, stream, first._untagged_match(stream)))
193 stream = None
192 while stack: 194 while stack:
193 (count1, acc1, stream1, generator) = stack[-1] 195 (count1, acc1, stream1, generator) = stack[-1]
194 extended = False 196 extended = False
195 if stop is None or count1 < stop: 197 if stop is None or count1 < stop:
196 count2 = count1 + 1 198 count2 = count1 + 1
197 try: 199 try:
198 (value, stream2) = next(generator) 200 (value, stream2) = next(generator)
199 acc2 = join(acc1, value) 201 acc2 = join(acc1, value)
200 stack.append((count2, acc2, stream2, 202 stack.append((count2, acc2, stream2,
201 rest._untagged_match(stream2))) 203 rest._untagged_match(stream2)))
202 extended = True 204 extended = True
203 except StopIteration: 205 except StopIteration:
204 pass 206 pass
205 if not extended: 207 if not extended:
206 if count1 >= start and (stop is None or count1 <= stop): 208 if count1 >= start and (stop is None or count1 <= stop):
207 yield (acc1, stream1) 209 yield (acc1, stream1)
208 stack.pop() 210 stack.pop()
209 while support.generator_manager_queue_len \ 211 while support.generator_manager_queue_len \
210 and len(stack) > support.generator_manager_queue_len: 212 and len(stack) > support.generator_manager_queue_len:
211 stack.popleft()[3].generator.close() 213 stack.popleft()[3].close()
212 finally: 214 finally:
213 for (_count, _acc, _stream, generator) in stack: 215 while stack:
214 generator.close() 216 stack.popleft()[3].close()
215 217
216 return matcher 218 return matcher
217 219
218 220
219 @sequence_matcher_factory(first=to(Literal), rest=to(Literal)) 221 @sequence_matcher_factory(first=to(Literal), rest=to(Literal))
220 @search_factory 222 @search_factory
221 def BreadthNoTrampoline(first, start, stop, rest, reduce, generator_manager_queue_len): 223 def BreadthNoTrampoline(first, start, stop, rest, reduce, generator_manager_queue_len):
222 ''' 224 '''
223 A more efficient search when all matchers are functions (so no need to 225 A more efficient search when all matchers are functions (so no need to
224 trampoline). Breadth first (non-greedy). 226 trampoline). Breadth first (non-greedy).
225 ''' 227 '''
226 (zero, join) = reduce 228 (zero, join) = reduce
227 def matcher(support, stream): 229 def matcher(support, stream):
228 queue = deque() 230 queue = deque()
229 try: 231 try:
230 queue.append((0, zero, stream, first._untagged_match(stream))) 232 queue.append((0, zero, stream, first._untagged_match(stream)))
233 stream = None
231 while queue: 234 while queue:
232 (count1, acc1, stream1, generator) = queue.popleft() 235 (count1, acc1, stream1, generator) = queue.popleft()
233 if count1 >= start and (stop is None or count1 <= stop): 236 if count1 >= start and (stop is None or count1 <= stop):
234 yield (acc1, stream1) 237 yield (acc1, stream1)
235 count2 = count1 + 1 238 count2 = count1 + 1
236 for (value, stream2) in generator: 239 for (value, stream2) in generator:
237 acc2 = join(acc1, value) 240 acc2 = join(acc1, value)
238 if stop is None or count2 <= stop: 241 if stop is None or count2 <= stop:
239 queue.append((count2, acc2, stream2, 242 queue.append((count2, acc2, stream2,
240 rest._untagged_match(stream2))) 243 rest._untagged_match(stream2)))
241 while support.generator_manager_queue_len \ 244 while support.generator_manager_queue_len \
242 and len(queue) > support.generator_manager_queue_len: 245 and len(queue) > support.generator_manager_queue_len:
243 queue.popleft()[3].generator.close() 246 queue.popleft()[3].close()
244 finally: 247 finally:
245 for (_count, _acc, _stream, generator) in queue: 248 while queue:
246 generator.close() 249 queue.popleft()[3].close()
247 250
248 return matcher 251 return matcher
249 252
250 253
251 add_children(BaseSearch, DepthFirst, BreadthFirst, \ 254 add_children(BaseSearch, DepthFirst, BreadthFirst, \
252 DepthNoTrampoline, BreadthNoTrampoline) 255 DepthNoTrampoline, BreadthNoTrampoline)
253 256
254 257
255 class _BaseCombiner(Transformable): 258 class _BaseCombiner(Transformable):
256 ''' 259 '''
257 Support for `And` and `Or`. 260 Support for `And` and `Or`.
258 ''' 261 '''
259 262
260 def __init__(self, *matchers): 263 def __init__(self, *matchers):
261 super(_BaseCombiner, self).__init__() 264 super(_BaseCombiner, self).__init__()
262 self._args(matchers=lmap(coerce_, matchers)) 265 self._args(matchers=lmap(coerce_, matchers))
263 266
264 def compose(self, wrapper): 267 def compose(self, wrapper):
265 ''' 268 '''
266 Generate a new instance with the composed function from the Transform. 269 Generate a new instance with the composed function from the Transform.
267 ''' 270 '''
268 copy = type(self)(*self.matchers) 271 copy = type(self)(*self.matchers)
269 copy.wrapper = self.wrapper.compose(wrapper) 272 copy.wrapper = self.wrapper.compose(wrapper)
270 return copy 273 return copy
271 274
272 275
273 @trampoline_matcher_factory(args_=to(Literal)) 276 @trampoline_matcher_factory(args_=to(Literal))
274 def And(*matchers): 277 def And(*matchers):
275 ''' 278 '''
276 Match one or more matchers in sequence (**&**). 279 Match one or more matchers in sequence (**&**).
277 It can be used indirectly by placing ``&`` between matchers. 280 It can be used indirectly by placing ``&`` between matchers.
278 ''' 281 '''
279 # matchers = lmap(coerce_, matchers) 282 # matchers = lmap(coerce_, matchers)
280 283
281 def match(support, stream_in): 284 def match(support, stream_in):
282 if matchers: 285 if matchers:
283 stack = deque([([], 286 stack = deque([([],
284 matchers[0]._match(stream_in), 287 matchers[0]._match(stream_in),
285 matchers[1:])]) 288 matchers[1:])])
286 append = stack.append 289 append = stack.append
287 pop = stack.pop 290 pop = stack.pop
288 stream_in = None 291 stream_in = None
289 try: 292 try:
290 while stack: 293 while stack:
291 (result, generator, queued) = pop() 294 (result, generator, queued) = pop()
292 try: 295 try:
293 (value, stream_out) = yield generator 296 (value, stream_out) = yield generator
294 append((result, generator, queued)) 297 append((result, generator, queued))
295 if queued: 298 if queued:
296 append((result+value, 299 append((result+value,
297 queued[0]._match(stream_out), 300 queued[0]._match(stream_out),
298 queued[1:])) 301 queued[1:]))
299 else: 302 else:
300 yield (result+value, stream_out) 303 yield (result+value, stream_out)
301 except StopIteration: 304 except StopIteration:
302 pass 305 pass
303 finally: 306 finally:
304 for (result, generator, queued) in stack: 307 for (result, generator, queued) in stack:
305 generator.generator.close() 308 generator.generator.close()
306 309
307 return match 310 return match
308 311
309 312
310 @sequence_matcher_factory(args_=to(Literal)) 313 @sequence_matcher_factory(args_=to(Literal))
311 def AndNoTrampoline(*matchers): 314 def AndNoTrampoline(*matchers):
312 ''' 315 '''
313 Used as an optimisation when sub-matchers do not require the trampoline. 316 Used as an optimisation when sub-matchers do not require the trampoline.
314 ''' 317 '''
315 def matcher(support, stream_in): 318 def matcher(support, stream_in):
316 if matchers: 319 if matchers:
317 stack = deque([([], matchers[0]._untagged_match(stream_in), matchers[1:])]) 320 stack = deque([([], matchers[0]._untagged_match(stream_in), matchers[1:])])
318 append = stack.append 321 append = stack.append
319 pop = stack.pop 322 pop = stack.pop
320 try: 323 try:
321 while stack: 324 while stack:
322 (result, generator, queued) = pop() 325 (result, generator, queued) = pop()
323 try: 326 try:
324 (value, stream_out) = next(generator) 327 (value, stream_out) = next(generator)
325 append((result, generator, queued)) 328 append((result, generator, queued))
326 if queued: 329 if queued:
327 append((result+value, 330 append((result+value,
328 queued[0]._untagged_match(stream_out), 331 queued[0]._untagged_match(stream_out),
329 queued[1:])) 332 queued[1:]))
330 else: 333 else:
331 yield (result+value, stream_out) 334 yield (result+value, stream_out)
332 except StopIteration: 335 except StopIteration:
333 pass 336 pass
334 finally: 337 finally:
335 for (result, generator, queued) in stack: 338 for (result, generator, queued) in stack:
336 generator.close() 339 generator.close()
337 340
338 return matcher 341 return matcher
339 342
340 343
341 @trampoline_matcher_factory(args_=to(Literal)) 344 @trampoline_matcher_factory(args_=to(Literal))
342 def Or(*matchers): 345 def Or(*matchers):
343 ''' 346 '''
344 Match one of the given matchers (**|**). 347 Match one of the given matchers (**|**).
345 It can be used indirectly by placing ``|`` between matchers. 348 It can be used indirectly by placing ``|`` between matchers.
346 349
347 Matchers are tried from left to right until one succeeds; backtracking 350 Matchers are tried from left to right until one succeeds; backtracking
348 will try more from the same matcher and, once that is exhausted, 351 will try more from the same matcher and, once that is exhausted,
349 continue to the right. String arguments will be coerced to 352 continue to the right. String arguments will be coerced to
350 literal matches. 353 literal matches.
351 ''' 354 '''
352 355
353 def match(support, stream_in): 356 def match(support, stream_in):
354 ''' 357 '''
355 Do the matching (return a generator that provides successive 358 Do the matching (return a generator that provides successive
356 (result, stream) tuples). The result will correspond to one of the 359 (result, stream) tuples). The result will correspond to one of the
357 sub-matchers (starting from the left). 360 sub-matchers (starting from the left).
358 ''' 361 '''
359 for matcher in matchers: 362 for matcher in matchers:
360 generator = matcher._match(stream_in) 363 generator = matcher._match(stream_in)
361 try: 364 try:
362 while True: 365 while True:
363 yield (yield generator) 366 yield (yield generator)
364 except StopIteration: 367 except StopIteration:
365 pass 368 pass
366 369
367 return match 370 return match
368 371
369 372
370 @sequence_matcher_factory(args_=to(Literal)) 373 @sequence_matcher_factory(args_=to(Literal))
371 def OrNoTrampoline(*matchers): 374 def OrNoTrampoline(*matchers):
372 ''' 375 '''
373 Used as an optimisation when sub-matchers do not require the trampoline. 376 Used as an optimisation when sub-matchers do not require the trampoline.
374 ''' 377 '''
375 def match(support, stream_in): 378 def match(support, stream_in):
376 ''' 379 '''
377 Do the matching (return a generator that provides successive 380 Do the matching (return a generator that provides successive
378 (result, stream) tuples). The result will correspond to one of the 381 (result, stream) tuples). The result will correspond to one of the
379 sub-matchers (starting from the left). 382 sub-matchers (starting from the left).
380 ''' 383 '''
381 for matcher in matchers: 384 for matcher in matchers:
382 for result in matcher._untagged_match(stream_in): 385 for result in matcher._untagged_match(stream_in):
383 yield result 386 yield result
384 return match 387 return match
385 388
386 389
387 @trampoline_matcher_factory() 390 @trampoline_matcher_factory()
388 def First(*matchers): 391 def First(*matchers):
389 ''' 392 '''
390 Match the first successful matcher only (**%**). 393 Match the first successful matcher only (**%**).
391 It can be used indirectly by placing ``%`` between matchers. 394 It can be used indirectly by placing ``%`` between matchers.
392 Note that backtracking for the first-selected matcher will still occur. 395 Note that backtracking for the first-selected matcher will still occur.
393 396
394 Matchers are tried from left to right until one succeeds; backtracking 397 Matchers are tried from left to right until one succeeds; backtracking
395 will try more from the same matcher (only). String arguments will be 398 will try more from the same matcher (only). String arguments will be
396 coerced to literal matches. 399 coerced to literal matches.
397 ''' 400 '''
398 def match(support, stream): 401 def match(support, stream):
399 matched = False 402 matched = False
400 for matcher in support.matchers: 403 for matcher in support.matchers:
401 generator = matcher._match(stream) 404 generator = matcher._match(stream)
402 try: 405 try:
403 while True: 406 while True:
404 yield (yield generator) 407 yield (yield generator)
405 matched = True 408 matched = True
406 except StopIteration: 409 except StopIteration:
407 pass 410 pass
408 if matched: 411 if matched:
409 break 412 break
410 413
411 return match 414 return match
412 415
413 416
414 @trampoline_matcher_factory(args_=to(Literal)) 417 @trampoline_matcher_factory(args_=to(Literal))
415 def Difference(matcher, exclude, count=-1): 418 def Difference(matcher, exclude, count=-1):
416 ''' 419 '''
417 Match with `matcher`, but exclude any matches that would be made by 420 Match with `matcher`, but exclude any matches that would be made by
418 `exclude`. This is implemented by comparing the remaining stream after 421 `exclude`. This is implemented by comparing the remaining stream after
419 matching, so will not be affected by any transform associated with 422 matching, so will not be affected by any transform associated with
420 `exclude`. The `count` parameter gives the number of different matches 423 `exclude`. The `count` parameter gives the number of different matches
421 from `exclude`. By default (-1) all matches are used. A positive 424 from `exclude`. By default (-1) all matches are used. A positive
422 value restricts that to the number given. 425 value restricts that to the number given.
423 ''' 426 '''
424 def match(support, stream, count=count): 427 def match(support, stream, count=count):
425 428
426 # by default use a set; fall back to a list for unhashable streams 429 # by default use a set; fall back to a list for unhashable streams
427 bad = [None] 430 bad = [None]
428 grow_bad = None 431 grow_bad = None
429 def append_bad(value, bad=bad): 432 def append_bad(value, bad=bad):
430 bad[0].append(value) 433 bad[0].append(value)
431 def add_bad(value, bad=bad): 434 def add_bad(value, bad=bad):
432 if bad[0] is None: 435 if bad[0] is None:
433 bad[0] = set() 436 bad[0] = set()
434 try: 437 try:
435 bad[0].add(value) 438 bad[0].add(value)
436 except TypeError: 439 except TypeError:
437 assert not bad[0] 440 assert not bad[0]
438 bad[0] = [] 441 bad[0] = []
439 grow_bad = append_bad 442 grow_bad = append_bad
440 grow_bad(value) 443 grow_bad(value)
441 grow_bad = add_bad 444 grow_bad = add_bad
442 445
443 generator = matcher._match(stream) 446 generator = matcher._match(stream)
444 while True: 447 while True:
445 (value, stream1) = yield generator 448 (value, stream1) = yield generator
446 449
447 if bad[0] is None: # build bad on demand, once 450 if bad[0] is None: # build bad on demand, once
448 bad_generator = exclude._match(stream) 451 bad_generator = exclude._match(stream)
449 try: 452 try:
450 while count: 453 while count:
451 (excluded, stream2) = yield bad_generator 454 (excluded, stream2) = yield bad_generator
452 support._debug(fmt('Exclude: {0!r}', excluded)) 455 support._debug(fmt('Exclude: {0!r}', excluded))
453 grow_bad(stream2) 456 grow_bad(stream2)
454 # limit number of matchers, if requested 457 # limit number of matchers, if requested
455 count -= 1 458 count -= 1
456 except StopIteration: 459 except StopIteration:
457 pass # all matches for exclude 460 pass # all matches for exclude
458 461
459 if stream1 not in bad[0]: 462 if stream1 not in bad[0]:
460 yield (value, stream1) 463 yield (value, stream1)
461 else: 464 else:
462 support._debug(fmt('Excluding: {0!r}', value)) 465 support._debug(fmt('Excluding: {0!r}', value))
463 466
464 return match 467 return match
465 468
466 469
467 @trampoline_matcher_factory(args_=to(Literal)) 470 @trampoline_matcher_factory(args_=to(Literal))
468 def Limit(match, count=1): 471 def Limit(match, count=1):
469 ''' 472 '''
470 Limit the backtracking for a given matcher. A negative `count` means no 473 Limit the backtracking for a given matcher. A negative `count` means no
471 limit. 474 limit.
472 ''' 475 '''
473 def matcher(support, stream, count=count): 476 def matcher(support, stream, count=count):
474 generator = match._match(stream) 477 generator = match._match(stream)
475 try: 478 try:
476 while count: 479 while count:
477 yield (yield generator) 480 yield (yield generator)
478 count -= 1 481 count -= 1
479 except StopIteration: 482 except StopIteration:
480 pass 483 pass
481 return matcher 484 return matcher
Powered by Google Project Hosting