tb 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,569 @@
1
+ # lib/tb/pathfinder.rb - pattern matcher for two-dimensional array.
2
+ #
3
+ # Copyright (C) 2011 Tanaka Akira <akr@fsij.org>
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # 3. The name of the author may not be used to endorse or promote products
14
+ # derived from this software without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ # OF SUCH DAMAGE.
26
+
27
+ module Tb::Pathfinder
28
+ module_function
29
+
30
+ def strary_to_aa(strary)
31
+ aa = []
32
+ strary.each_with_index {|str, y|
33
+ aa[y] = []
34
+ str.each_char.with_index {|ch, x|
35
+ aa[y][x] = ch
36
+ }
37
+ }
38
+ aa
39
+ end
40
+
41
+ def match(pat, aa, start=nil)
42
+ each_match(pat, aa, start) {|spos, epos, cap|
43
+ return spos, epos, cap
44
+ }
45
+ end
46
+
47
+ def each_match(pat, aa, spos=nil)
48
+ if spos
49
+ run {
50
+ try(pat, aa, Tb::Pathfinder::EmptyState.merge(:pos => spos)) {|st2|
51
+ yield spos, st2.fetch(:pos), st2.reject {|k,v| k == :pos }
52
+ nil
53
+ }
54
+ }
55
+ else
56
+ aa.each_with_index {|a, y|
57
+ a.each_index {|x|
58
+ spos = [x,y]
59
+ run {
60
+ try(pat, aa, Tb::Pathfinder::EmptyState.merge(:pos => spos)) {|st2|
61
+ yield spos, st2.fetch(:pos), st2.reject {|k,v| k == :pos }
62
+ nil
63
+ }
64
+ }
65
+ }
66
+ }
67
+ end
68
+ nil
69
+ end
70
+
71
+ def run(&b)
72
+ stack = [b]
73
+ while !stack.empty?
74
+ last = stack.pop
75
+ v = last.call
76
+ case v
77
+ when nil
78
+ when Array
79
+ v.each {|e|
80
+ raise TypeError, "result array contains non-proc: #{last.inspect}" unless Proc === e
81
+ }
82
+ stack.concat v.reverse
83
+ when Proc
84
+ stack << v
85
+ else
86
+ raise TypeError, "unexpected: #{v.inspect}"
87
+ end
88
+ end
89
+ end
90
+
91
+ def try(pat, aa, st, &b)
92
+ case pat
93
+ when nil; lambda { yield st }
94
+ when String; try_lit(pat, aa, st, &b)
95
+ when Regexp; try_regexp(pat, aa, st, &b)
96
+ when :n, :north; try_rmove(:north, aa, st, &b)
97
+ when :s, :south; try_rmove(:south, aa, st, &b)
98
+ when :e, :east; try_rmove(:east, aa, st, &b)
99
+ when :w, :west; try_rmove(:west, aa, st, &b)
100
+ when :debug_print_state; p st; yield st
101
+ when Array
102
+ case pat[0]
103
+ when :rmove; _, dir = pat; try_rmove(dir, aa, st, &b)
104
+ when :lit; _, val = pat; try_lit(val, aa, st, &b)
105
+ when :regexp; _, re = pat; try_regexp(re, aa, st, &b)
106
+ when :cat; _, *ps = pat; try_cat(ps, aa, st, &b)
107
+ when :alt; _, *ps = pat; try_alt(ps, aa, st, &b)
108
+ when :rep; _, *ps = pat; try_rep_generic(nil, 0, nil, true, ps, aa, st, &b)
109
+ when :rep1; _, *ps = pat; try_rep_generic(nil, 1, nil, true, ps, aa, st, &b)
110
+ when :rep_nongreedy; _, *ps = pat; try_rep_generic(nil, 0, nil, false, ps, aa, st, &b)
111
+ when :rep1_nongreedy; _, *ps = pat; try_rep_generic(nil, 1, nil, false, ps, aa, st, &b)
112
+ when :opt; _, *ps = pat; try_rep_generic(nil, 0, 1, true, ps, aa, st, &b)
113
+ when :opt_nongreedy; _, *ps = pat; try_rep_generic(nil, 0, 1, false, ps, aa, st, &b)
114
+ when :repn; _, num, *ps = pat; try_rep_generic(nil, num, num, true, ps, aa, st, &b)
115
+ when :repeat; _, var, min, max, greedy, *ps = pat; try_rep_generic(var, min, max, greedy, ps, aa, st, &b)
116
+ when :bfs; _, keys, *ps = pat; try_bfs(keys, ps, aa, st, &b)
117
+ when :grid; _, *arys = pat; try_grid(arys, aa, st, &b)
118
+ when :capval; _, n = pat; try_capval(n, aa, st, &b)
119
+ when :refval; _, n = pat; try_refval(n, aa, st, &b)
120
+ when :tmp_pos; _, dx, dy, *ps = pat; try_tmp_pos(dx, dy, ps, aa, st, &b)
121
+ when :save_pos; _, n = pat; try_save_pos(n, aa, st, &b)
122
+ when :push_pos; _, n = pat; try_push_pos(n, aa, st, &b)
123
+ when :pop_pos; _, n = pat; try_pop_pos(n, aa, st, &b)
124
+ when :update; _, pr = pat; try_update(pr, aa, st, &b)
125
+ when :assert; _, pr = pat; try_assert(pr, aa, st, &b)
126
+ else raise "unexpected: #{pat.inspect}"
127
+ end
128
+ else
129
+ raise TypeError, "unexpected pattern: #{pat.inspect}"
130
+ end
131
+ end
132
+
133
+ def try_rmove(dir, aa, st)
134
+ x, y = st.fetch(:pos)
135
+ case dir
136
+ when :east then x += 1
137
+ when :west then x -= 1
138
+ when :north then y -= 1
139
+ when :south then y += 1
140
+ end
141
+ if 0 <= y && y < aa.length &&
142
+ 0 <= x && x < aa[y].length
143
+ lambda { yield st.merge(:pos => [x,y]) }
144
+ else
145
+ nil
146
+ end
147
+ end
148
+
149
+ def try_lit(val, aa, st)
150
+ x, y = st.fetch(:pos)
151
+ if 0 <= y && y < aa.length &&
152
+ 0 <= x && x < aa[y].length &&
153
+ aa[y][x] == val
154
+ lambda { yield st }
155
+ else
156
+ nil
157
+ end
158
+ end
159
+
160
+ def try_regexp(re, aa, st)
161
+ x, y = st.fetch(:pos)
162
+ if 0 <= y && y < aa.length &&
163
+ 0 <= x && x < aa[y].length &&
164
+ re =~ aa[y][x]
165
+ lambda { yield st }
166
+ else
167
+ nil
168
+ end
169
+ end
170
+
171
+ # p1 p2 ...
172
+ def try_cat(ps, aa, st, &block)
173
+ if ps.empty?
174
+ lambda { yield st }
175
+ else
176
+ pat, *rest = ps
177
+ try(pat, aa, st) {|st2|
178
+ try_cat(rest, aa, st2, &block)
179
+ }
180
+ end
181
+ end
182
+
183
+ # p1 | p2 | ...
184
+ def try_alt(ps, aa, st, &block)
185
+ ps.map {|pat|
186
+ lambda { try(pat, aa, st, &block) }
187
+ }
188
+ end
189
+
190
+ # (p1 p2 ...)*
191
+ # (p1 p2 ...){min,}
192
+ # (p1 p2 ...){min,max}
193
+ # (p1 p2 ...)*?
194
+ # (p1 p2 ...){min,}?
195
+ # (p1 p2 ...){min,max}?
196
+ def try_rep_generic(var, min, max, greedy, ps, aa, st, visit_keys=[:pos], visited={}, &block)
197
+ min = st[min].to_int if Symbol === min
198
+ max = st[max].to_int if Symbol === max
199
+ visited2 = visited.dup
200
+ visited2[st.values_at(*visit_keys)] = true
201
+ result = []
202
+ if min <= 0 && !greedy
203
+ result << lambda {
204
+ st = st.merge(var => visited.size) if var
205
+ yield st
206
+ }
207
+ end
208
+ if max.nil? || 0 < max
209
+ min2 = min <= 0 ? 0 : min-1
210
+ max2 = max ? max-1 : nil
211
+ result << lambda {
212
+ try_cat(ps, aa, st) {|st2|
213
+ if !visited2[st2.values_at(*visit_keys)]
214
+ try_rep_generic(var, min2, max2, greedy, ps, aa, st2, visit_keys, visited2, &block)
215
+ else
216
+ nil
217
+ end
218
+ }
219
+ }
220
+ end
221
+ if min <= 0 && greedy
222
+ result << lambda {
223
+ st = st.merge(var => visited.size) if var
224
+ yield st
225
+ }
226
+ end
227
+ result
228
+ end
229
+
230
+ def try_bfs(keys, ps, aa, st, &b)
231
+ queue = [st]
232
+ visited = {st.values_at(*keys) => true}
233
+ try_bfs_loop(queue, visited, keys, ps, aa, &b)
234
+ end
235
+
236
+ def try_bfs_loop(queue, visited, keys, ps, aa, &b)
237
+ if queue.empty?
238
+ nil
239
+ else
240
+ st = queue.shift
241
+ result = []
242
+ result << lambda {
243
+ yield st
244
+ }
245
+ result << lambda {
246
+ try_cat(ps, aa, st) {|st2|
247
+ k = st2.values_at(*keys)
248
+ if !visited[k]
249
+ visited[k] = true
250
+ queue << st2
251
+ end
252
+ nil
253
+ }
254
+ }
255
+ result << lambda {
256
+ try_bfs_loop(queue, visited, keys, ps, aa, &b)
257
+ }
258
+ result
259
+ end
260
+ end
261
+
262
+ def try_grid(arys, aa, st)
263
+ start = nil
264
+ goal = nil
265
+ newarys = []
266
+ arys.each_with_index {|ary, y|
267
+ newarys << []
268
+ ary.each_with_index {|pat, x|
269
+ if pat == :start
270
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
271
+ start = [x,y]
272
+ newarys.last << nil
273
+ elsif Array === pat && pat[0] == :start
274
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
275
+ start = [x,y]
276
+ newarys.last << [:cat, *pat[1..-1]]
277
+ elsif pat == :goal
278
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
279
+ goal = [x,y]
280
+ newarys.last << nil
281
+ elsif Array === pat && pat[0] == :goal
282
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
283
+ goal = [x,y]
284
+ newarys.last << [:cat, *pat[1..-1]]
285
+ elsif pat == :origin
286
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
287
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
288
+ start = goal = [x,y]
289
+ newarys.last << nil
290
+ elsif Array === pat && pat[0] == :origin
291
+ raise ArgumentError, "multiple starts: #{arys.inspect}" if start
292
+ raise ArgumentError, "multiple goals: #{arys.inspect}" if goal
293
+ start = goal = [x,y]
294
+ newarys.last << [:cat, *pat[1..-1]]
295
+ else
296
+ newarys.last << pat
297
+ end
298
+ }
299
+ }
300
+ raise ArgumentError, "no start" if !start
301
+ raise ArgumentError, "no goal" if !goal
302
+ pos = st.fetch(:pos)
303
+ try_grid_rect(newarys, aa, st.merge(:pos => [pos[0]-start[0], pos[1]-start[1]])) {|st2|
304
+ lambda { yield st2.merge(:pos => [pos[0]-start[0]+goal[0], pos[1]-start[1]+goal[1]]) }
305
+ }
306
+ end
307
+
308
+ def try_grid_rect(arys, aa, st)
309
+ if arys.empty?
310
+ lambda { yield st }
311
+ else
312
+ ary, *rest = arys
313
+ x, y = st.fetch(:pos)
314
+ pos1 = [x, y+1]
315
+ try_grid_row(ary, aa, st) {|st2|
316
+ try_grid_rect(rest, aa, st2.merge(:pos => pos1)) {|st3|
317
+ lambda { yield st3.merge(:pos => st.fetch(:pos)) }
318
+ }
319
+ }
320
+ end
321
+ end
322
+
323
+ def try_grid_row(ary, aa, st)
324
+ if ary.empty?
325
+ lambda { yield st }
326
+ else
327
+ pat, *rest = ary
328
+ x, y = st.fetch(:pos)
329
+ pos1 = [x+1, y]
330
+ try(pat, aa, st) {|st2|
331
+ try_grid_row(rest, aa, st2.merge(:pos => pos1)) {|st3|
332
+ lambda { yield st3.merge(:pos => st.fetch(:pos)) }
333
+ }
334
+ }
335
+ end
336
+ end
337
+
338
+ def try_capval(n, aa, st)
339
+ x, y = st.fetch(:pos)
340
+ if 0 <= y && y < aa.length &&
341
+ 0 <= x && x < aa[y].length &&
342
+ val = aa[y][x]
343
+ else
344
+ val = nil
345
+ end
346
+ st2 = st.merge(n => val)
347
+ lambda { yield st2 }
348
+ end
349
+
350
+ def try_refval(n, aa, st)
351
+ x, y = st.fetch(:pos)
352
+ if 0 <= y && y < aa.length &&
353
+ 0 <= x && x < aa[y].length
354
+ val = aa[y][x]
355
+ else
356
+ val = nil
357
+ end
358
+ if val == st[n]
359
+ lambda { yield st }
360
+ else
361
+ nil
362
+ end
363
+ end
364
+
365
+ def try_tmp_pos(dx, dy, ps, aa, st, &b)
366
+ x, y = st.fetch(:pos)
367
+ try_cat(ps, aa, st.merge(:pos => [x+dx, y+dy])) {|st2|
368
+ lambda { yield st2.merge(:pos => st.fetch(:pos)) }
369
+ }
370
+ end
371
+
372
+ def try_save_pos(n, aa, st, &b)
373
+ st2 = st.merge(n => st.fetch(:pos))
374
+ lambda { yield st2 }
375
+ end
376
+
377
+ def try_push_pos(n, aa, st, &b)
378
+ ary = (st[n] || []) + [st.fetch(:pos)]
379
+ st2 = st.merge(n => ary)
380
+ lambda { yield st2 }
381
+ end
382
+
383
+ def try_pop_pos(n, aa, st, &b)
384
+ ary = st[n]
385
+ raise TypeError, "array expected: #{ary.inspect} (#{n.inspect})" unless Array === ary
386
+ raise TypeError, "empty array (#{n.inspect})" if ary.empty?
387
+ pos2 = ary.last
388
+ raise TypeError, "array expected: #{pos2.inspect} (#{n.inspect})" unless Array === pos2
389
+ raise TypeError, "two-element array expected: #{pos2.inspect} (#{n.inspect})" if pos2.length != 2
390
+ raise TypeError, "integer elements expected: #{pos2.inspect} (#{n.inspect})" unless pos2.all? {|v| Integer === v }
391
+ st2 = st.merge(:pos => pos2, n => ary[0...-1])
392
+ lambda { yield st2 }
393
+ end
394
+
395
+ def try_update(pr, aa, st)
396
+ st2 = pr.call(st)
397
+ lambda { yield st2 }
398
+ end
399
+
400
+ def try_assert(pr, aa, st)
401
+ if pr.call(st)
402
+ lambda { yield st }
403
+ else
404
+ nil
405
+ end
406
+ end
407
+ end
408
+
409
+ module Tb::Pathfinder::EmptyState
410
+ module_function
411
+
412
+ def empty?
413
+ true
414
+ end
415
+
416
+ def each
417
+ end
418
+
419
+ def fetch(k, *rest)
420
+ if block_given?
421
+ yield k
422
+ elsif !rest.empty?
423
+ return rest[0]
424
+ else
425
+ raise KeyError, "key not found: #{k}"
426
+ end
427
+ end
428
+
429
+ def [](k)
430
+ nil
431
+ end
432
+
433
+ def values_at(*ks)
434
+ ks.map {|k| nil }
435
+ end
436
+
437
+ def keys
438
+ []
439
+ end
440
+
441
+ def merge(h)
442
+ pairs = self
443
+ h.reverse_each {|k, v|
444
+ pairs = Tb::Pathfinder::State.new(k, v, pairs)
445
+ }
446
+ pairs
447
+ end
448
+
449
+ def reject
450
+ self
451
+ end
452
+
453
+ def inspect
454
+ "\#<Tb::Pathfinder::EmptyState>"
455
+ end
456
+ end
457
+
458
+ class Tb::Pathfinder::State
459
+ def initialize(key, val, tail=nil)
460
+ @key = key
461
+ @val = val
462
+ @tail = tail
463
+ end
464
+ attr_reader :key, :val, :tail
465
+
466
+ def empty?
467
+ false
468
+ end
469
+
470
+ def each
471
+ pairs = self
472
+ while !pairs.empty?
473
+ yield [pairs.key, pairs.val]
474
+ pairs = pairs.tail
475
+ end
476
+ nil
477
+ end
478
+
479
+ def fetch(k, *rest)
480
+ pairs = self
481
+ while !pairs.empty?
482
+ return pairs.val if k == pairs.key
483
+ pairs = pairs.tail
484
+ end
485
+ if block_given?
486
+ yield k
487
+ elsif !rest.empty?
488
+ return rest[0]
489
+ else
490
+ raise KeyError, "key not found: #{k}"
491
+ end
492
+ end
493
+
494
+ def [](k)
495
+ fetch(k, nil)
496
+ end
497
+
498
+ def values_at(*ks)
499
+ ks.map {|k| self[k] }
500
+ end
501
+
502
+ def keys
503
+ result = []
504
+ pairs = self
505
+ while !pairs.empty?
506
+ result << pairs.key
507
+ pairs = pairs.tail
508
+ end
509
+ result
510
+ end
511
+
512
+ def merge(h)
513
+ return self if h.empty?
514
+ n = 0
515
+ pairs = self
516
+ ary = []
517
+ result = self
518
+ needs_copy = 0
519
+ while !pairs.empty?
520
+ if h.include? pairs.key
521
+ needs_copy = ary.length
522
+ result = pairs.tail
523
+ n += 1
524
+ break if n == h.size
525
+ else
526
+ ary << pairs
527
+ end
528
+ pairs = pairs.tail
529
+ end
530
+ (needs_copy-1).downto(0) {|i|
531
+ pairs = ary[i]
532
+ result = Tb::Pathfinder::State.new(pairs.key, pairs.val, result)
533
+ }
534
+ h.reverse_each {|k, v|
535
+ result = Tb::Pathfinder::State.new(k, v, result)
536
+ }
537
+ result
538
+ end
539
+
540
+ def reject
541
+ ary = []
542
+ pairs = self
543
+ while !pairs.empty?
544
+ unless yield pairs.key, pairs.val
545
+ ary << pairs
546
+ end
547
+ pairs = pairs.tail
548
+ end
549
+ result = Tb::Pathfinder::EmptyState
550
+ ary.reverse_each {|pairs|
551
+ result = Tb::Pathfinder::State.new(pairs.key, pairs.val, result)
552
+ }
553
+ result
554
+ end
555
+
556
+ def inspect
557
+ h = {}
558
+ pairs = self
559
+ str = ''
560
+ while !pairs.empty?
561
+ str << pairs.key.inspect << "=>" << pairs.val.inspect << ", "
562
+ pairs = pairs.tail
563
+ end
564
+ str.sub!(/, \z/, '')
565
+ "\#<#{self.class}: #{str}>"
566
+ end
567
+
568
+ end
569
+