sexp_processor 4.12.1 → 4.13.0

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,1063 @@
1
+ class Sexp #:nodoc:
2
+ ##
3
+ # Verifies that +pattern+ is a Matcher and then dispatches to its
4
+ # #=~ method.
5
+ #
6
+ # See Matcher.=~
7
+
8
+ def =~ pattern
9
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
10
+ pattern =~ self
11
+ end
12
+
13
+ ##
14
+ # Verifies that +pattern+ is a Matcher and then dispatches to its
15
+ # #satisfy? method.
16
+ #
17
+ # TODO: rename match?
18
+
19
+ def satisfy? pattern
20
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
21
+ pattern.satisfy? self
22
+ end
23
+
24
+ ##
25
+ # Verifies that +pattern+ is a Matcher and then dispatches to its #/
26
+ # method.
27
+ #
28
+ # TODO: rename grep? match_all ? find_all ?
29
+
30
+ def / pattern
31
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
32
+ pattern / self
33
+ end
34
+
35
+ ##
36
+ # Recursively searches for the +pattern+ yielding the matches.
37
+
38
+ def search_each pattern, &block # TODO: rename to grep?
39
+ raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
40
+
41
+ return enum_for(:search_each, pattern) unless block_given?
42
+
43
+ if pattern.satisfy? self then
44
+ yield self
45
+ end
46
+
47
+ self.each_sexp do |subset|
48
+ subset.search_each pattern, &block
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Recursively searches for the +pattern+ yielding each match, and
54
+ # replacing it with the result of the block.
55
+ #
56
+
57
+ def replace_sexp pattern, &block # TODO: rename to gsub?
58
+ raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
59
+
60
+ return yield self if pattern.satisfy? self
61
+
62
+ # TODO: Needs #new_from(*new_body) to copy file/line/comment
63
+ self.class.new(*self.map { |subset|
64
+ case subset
65
+ when Sexp then
66
+ subset.replace_sexp pattern, &block
67
+ else
68
+ subset
69
+ end
70
+ })
71
+ end
72
+
73
+ ##
74
+ # Matches an S-Expression.
75
+ #
76
+ # See Matcher for examples.
77
+
78
+ def self.q *args
79
+ Matcher.new(*args)
80
+ end
81
+
82
+ def self.s *args
83
+ where = caller.first.split(/:/, 3).first(2).join ":"
84
+ warn "DEPRECATED: use Sexp.q(...) instead. From %s" % [where]
85
+ q(*args)
86
+ end
87
+
88
+ ##
89
+ # Matches any single item.
90
+ #
91
+ # See Wild for examples.
92
+
93
+ def self._
94
+ Wild.new
95
+ end
96
+
97
+ # TODO: reorder factory methods and classes to match
98
+
99
+ ##
100
+ # Matches all remaining input.
101
+ #
102
+ # See Remaining for examples.
103
+
104
+ def self.___
105
+ Remaining.new
106
+ end
107
+
108
+ ##
109
+ # Matches an expression or any expression that includes the child.
110
+ #
111
+ # See Include for examples.
112
+
113
+ def self.include child # TODO: rename, name is generic ruby
114
+ Include.new(child)
115
+ end
116
+
117
+ ##
118
+ # Matches any atom.
119
+ #
120
+ # See Atom for examples.
121
+
122
+ def self.atom
123
+ Atom.new
124
+ end
125
+
126
+ ##
127
+ # Matches when any of the sub-expressions match.
128
+ #
129
+ # This is also available via Matcher#|.
130
+ #
131
+ # See Any for examples.
132
+
133
+ def self.any *args
134
+ Any.new(*args)
135
+ end
136
+
137
+ ##
138
+ # Matches only when all sub-expressions match.
139
+ #
140
+ # This is also available via Matcher#&.
141
+ #
142
+ # See All for examples.
143
+
144
+ def self.all *args
145
+ All.new(*args)
146
+ end
147
+
148
+ ##
149
+ # Matches when sub-expression does not match.
150
+ #
151
+ # This is also available via Matcher#-@.
152
+ #
153
+ # See Not for examples.
154
+
155
+ def self.not? arg
156
+ Not.new arg
157
+ end
158
+
159
+ # TODO: add Sibling factory method?
160
+
161
+ ##
162
+ # Matches anything that has a child matching the sub-expression.
163
+ #
164
+ # See Child for examples.
165
+
166
+ def self.child child
167
+ Child.new child
168
+ end
169
+
170
+ ##
171
+ # Matches anything having the same sexp_type, which is the first
172
+ # value in a Sexp.
173
+ #
174
+ # See Type for examples.
175
+
176
+ def self.t name
177
+ Type.new name
178
+ end
179
+
180
+ ##
181
+ # Matches any atom who's string representation matches the patterns
182
+ # passed in.
183
+ #
184
+ # See Pattern for examples.
185
+
186
+ def self.m *values
187
+ res = values.map { |value|
188
+ case value
189
+ when Regexp then
190
+ value
191
+ else
192
+ re = Regexp.escape value.to_s
193
+ Regexp.new "\\A%s\\Z" % re
194
+ end
195
+ }
196
+ Pattern.new Regexp.union(*res)
197
+ end
198
+
199
+ ##
200
+ # Defines a family of objects that can be used to match sexps to
201
+ # certain types of patterns, much like regexps can be used on
202
+ # strings. Generally you won't use this class directly.
203
+ #
204
+ # You would normally create a matcher using the top-level #s method,
205
+ # but with a block, calling into the Sexp factory methods. For example:
206
+ #
207
+ # s{ s(:class, m(/^Test/), _, ___) }
208
+ #
209
+ # This creates a matcher for classes whose names start with "Test".
210
+ # It uses Sexp.m to create a Sexp::Matcher::Pattern matcher, Sexp._
211
+ # to create a Sexp::Matcher::Wild matcher, and Sexp.___ to create a
212
+ # Sexp::Matcher::Remaining matcher. It works like this:
213
+ #
214
+ # s{ # start to create a pattern
215
+ # s( # create a sexp matcher
216
+ # :class. # for class nodes
217
+ # m(/^Test/), # matching name slots that start with "Test"
218
+ # _, # any superclass value
219
+ # ___ # and whatever is in the class
220
+ # )
221
+ # }
222
+ #
223
+ # Then you can use that with #=~, #/, Sexp#replace_sexp, and others.
224
+ #
225
+ # For more examples, see the various Sexp class methods, the examples,
226
+ # and the tests supplied with Sexp.
227
+ #
228
+ # * For pattern creation, see factory methods: Sexp::_, Sexp::___, etc.
229
+ # * For matching returning truthy/falsey results, see Sexp#=~.
230
+ # * For case expressions, see Matcher#===.
231
+ # * For getting all subtree matches, see Sexp#/.
232
+ #
233
+ # If rdoc didn't suck, these would all be links.
234
+
235
+ class Matcher < Sexp
236
+ ##
237
+ # Should #=~ match sub-trees?
238
+
239
+ def self.match_subs?
240
+ @@match_subs
241
+ end
242
+
243
+ ##
244
+ # Setter for +match_subs?+.
245
+
246
+ def self.match_subs= o
247
+ @@match_subs = o
248
+ end
249
+
250
+ self.match_subs = true
251
+
252
+ ##
253
+ # Does this matcher actually match +o+? Returns falsey if +o+ is
254
+ # not a Sexp or if any sub-tree of +o+ is not satisfied by or
255
+ # equal to its corresponding sub-matcher.
256
+ #
257
+ #--
258
+ # TODO: push this up to Sexp and make this the workhorse
259
+ # TODO: do the same with ===/satisfy?
260
+
261
+ def satisfy? o
262
+ return unless o.kind_of?(Sexp) &&
263
+ (length == o.length || Matcher === last && last.greedy?)
264
+
265
+ each_with_index.all? { |child, i|
266
+ sexp = o.at i
267
+ if Sexp === child then # TODO: when will this NOT be a matcher?
268
+ sexp = o.sexp_body i if child.respond_to?(:greedy?) && child.greedy?
269
+ child.satisfy? sexp
270
+ else
271
+ child == sexp
272
+ end
273
+ }
274
+ end
275
+
276
+ ##
277
+ # Tree equivalent to String#=~, returns true if +self+ matches
278
+ # +sexp+ as a whole or in a sub-tree (if +match_subs?+).
279
+ #
280
+ # TODO: maybe this should NOT be aliased to === ?
281
+ #
282
+ # TODO: example
283
+
284
+ def =~ sexp
285
+ raise ArgumentError, "Can't both be matchers: %p" % [sexp] if Matcher === sexp
286
+
287
+ self.satisfy?(sexp) ||
288
+ (self.class.match_subs? && sexp.each_sexp.any? { |sub| self =~ sub })
289
+ end
290
+
291
+ alias === =~ # TODO?: alias === satisfy?
292
+
293
+ ##
294
+ # Searches through +sexp+ for all sub-trees that match this
295
+ # matcher and returns a MatchCollection for each match.
296
+ #
297
+ # TODO: redirect?
298
+ # Example:
299
+ # Q{ s(:b) } / s(:a, s(:b)) => [s(:b)]
300
+
301
+ def / sexp
302
+ raise ArgumentError, "can't both be matchers" if Matcher === sexp
303
+
304
+ # TODO: move search_each into matcher?
305
+ MatchCollection.new sexp.search_each(self).to_a
306
+ end
307
+
308
+ ##
309
+ # Combines the Matcher with another Matcher, the resulting one will
310
+ # be satisfied if either Matcher would be satisfied.
311
+ #
312
+ # TODO: redirect
313
+ # Example:
314
+ # s(:a) | s(:b)
315
+
316
+ def | other
317
+ Any.new self, other
318
+ end
319
+
320
+ ##
321
+ # Combines the Matcher with another Matcher, the resulting one will
322
+ # be satisfied only if both Matchers would be satisfied.
323
+ #
324
+ # TODO: redirect
325
+ # Example:
326
+ # t(:a) & include(:b)
327
+
328
+ def & other
329
+ All.new self, other
330
+ end
331
+
332
+ ##
333
+ # Returns a Matcher that matches whenever this Matcher would not have matched
334
+ #
335
+ # Example:
336
+ # -s(:a)
337
+
338
+ def -@
339
+ Not.new self
340
+ end
341
+
342
+ ##
343
+ # Returns a Matcher that matches if this has a sibling +o+
344
+ #
345
+ # Example:
346
+ # s(:a) >> s(:b)
347
+
348
+ def >> other
349
+ Sibling.new self, other
350
+ end
351
+
352
+ ##
353
+ # Is this matcher greedy? Defaults to false.
354
+
355
+ def greedy?
356
+ false
357
+ end
358
+
359
+ def inspect # :nodoc:
360
+ s = super
361
+ s[0] = "q"
362
+ s
363
+ end
364
+
365
+ def pretty_print q # :nodoc:
366
+ q.group 1, "q(", ")" do
367
+ q.seplist self do |v|
368
+ q.pp v
369
+ end
370
+ end
371
+ end
372
+
373
+ ##
374
+ # Parse a lispy string representation of a matcher into a Matcher.
375
+ # See +Parser+.
376
+
377
+ def self.parse s
378
+ Parser.new(s).parse
379
+ end
380
+
381
+ ##
382
+ # Converts from a lispy string to Sexp matchers in a safe manner.
383
+ #
384
+ # "(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }
385
+
386
+ class Parser
387
+
388
+ ##
389
+ # The stream of tokens to parse. See #lex.
390
+
391
+ attr_accessor :tokens
392
+
393
+ ##
394
+ # Create a new Parser instance on +s+
395
+
396
+ def initialize s
397
+ self.tokens = lex s
398
+ end
399
+
400
+ ##
401
+ # Converts +s+ into a stream of tokens and adds them to +tokens+.
402
+
403
+ def lex s
404
+ s.scan %r%[()\[\]]|\"[^"]*\"|/[^/]*/|:?[\w?!=~-]+%
405
+ end
406
+
407
+ ##
408
+ # Returns the next token and removes it from the stream or raises if empty.
409
+
410
+ def next_token
411
+ raise SyntaxError, "unbalanced input" if tokens.empty?
412
+ tokens.shift
413
+ end
414
+
415
+ ##
416
+ # Returns the next token without removing it from the stream.
417
+
418
+ def peek_token
419
+ tokens.first
420
+ end
421
+
422
+ ##
423
+ # Parses tokens and returns a +Matcher+ instance.
424
+
425
+ def parse
426
+ result = parse_sexp until tokens.empty?
427
+ result
428
+ end
429
+
430
+ ##
431
+ # Parses a string into a sexp matcher:
432
+ #
433
+ # SEXP : "(" SEXP:args* ")" => Sexp.q(*args)
434
+ # | "[" CMD:cmd sexp:args* "]" => Sexp.cmd(*args)
435
+ # | "nil" => nil
436
+ # | /\d+/:n => n.to_i
437
+ # | "___" => Sexp.___
438
+ # | "_" => Sexp._
439
+ # | /^\/(.*)\/$/:re => Regexp.new re[0]
440
+ # | /^"(.*)"$/:s => String.new s[0]
441
+ # | NAME:name => name.to_sym
442
+ # NAME : /:?[\w?!=~-]+/
443
+ # CMD : "t" | "m" | "atom" | "not?"
444
+
445
+ def parse_sexp
446
+ token = next_token
447
+
448
+ case token
449
+ when "(" then
450
+ parse_list
451
+ when "[" then
452
+ parse_cmd
453
+ when "nil" then
454
+ nil
455
+ when /^\d+$/ then
456
+ token.to_i
457
+ when "___" then
458
+ Sexp.___
459
+ when "_" then
460
+ Sexp._
461
+ when %r%^/(.*)/$% then
462
+ re = $1
463
+ raise SyntaxError, "Not allowed: /%p/" % [re] unless
464
+ re =~ /\A([\w()|.*+^$]+)\z/
465
+ Regexp.new re
466
+ when /^"(.*)"$/ then
467
+ $1
468
+ when /^:?([\w?!=~-]+)$/ then
469
+ $1.to_sym
470
+ else
471
+ raise SyntaxError, "unhandled token: %p" % [token]
472
+ end
473
+ end
474
+
475
+ ##
476
+ # Parses a balanced list of expressions and returns the
477
+ # equivalent matcher.
478
+
479
+ def parse_list
480
+ result = []
481
+
482
+ result << parse_sexp while peek_token && peek_token != ")"
483
+ next_token # pop off ")"
484
+
485
+ Sexp.q(*result)
486
+ end
487
+
488
+ ##
489
+ # A collection of allowed commands to convert into matchers.
490
+
491
+ ALLOWED = [:t, :m, :atom, :not?].freeze
492
+
493
+ ##
494
+ # Parses a balanced command. A command is denoted by square
495
+ # brackets and must conform to a whitelisted set of allowed
496
+ # commands (see +ALLOWED+).
497
+
498
+ def parse_cmd
499
+ args = []
500
+ args << parse_sexp while peek_token && peek_token != "]"
501
+ next_token # pop off "]"
502
+
503
+ cmd = args.shift
504
+ args = Sexp.q(*args)
505
+
506
+ raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd
507
+
508
+ result = Sexp.send cmd, *args
509
+
510
+ result
511
+ end
512
+ end # class Parser
513
+ end # class Matcher
514
+
515
+ ##
516
+ # Matches any single item.
517
+ #
518
+ # examples:
519
+ #
520
+ # s(:a) / s{ _ } #=> [s(:a)]
521
+ # s(:a, s(s(:b))) / s{ s(_) } #=> [s(s(:b))]
522
+
523
+ class Wild < Matcher
524
+ ##
525
+ # Matches any single element.
526
+
527
+ def satisfy? o
528
+ true
529
+ end
530
+
531
+ def inspect # :nodoc:
532
+ "_"
533
+ end
534
+
535
+ def pretty_print q # :nodoc:
536
+ q.text "_"
537
+ end
538
+ end
539
+
540
+ ##
541
+ # Matches all remaining input. If remaining comes before any other
542
+ # matchers, they will be ignored.
543
+ #
544
+ # examples:
545
+ #
546
+ # s(:a) / s{ s(:a, ___ ) } #=> [s(:a)]
547
+ # s(:a, :b, :c) / s{ s(:a, ___ ) } #=> [s(:a, :b, :c)]
548
+
549
+ class Remaining < Matcher
550
+ ##
551
+ # Always satisfied once this is reached. Think of it as a var arg.
552
+
553
+ def satisfy? o
554
+ true
555
+ end
556
+
557
+ def greedy?
558
+ true
559
+ end
560
+
561
+ def inspect # :nodoc:
562
+ "___"
563
+ end
564
+
565
+ def pretty_print q # :nodoc:
566
+ q.text "___"
567
+ end
568
+ end
569
+
570
+ ##
571
+ # Matches when any of the sub-expressions match.
572
+ #
573
+ # This is also available via Matcher#|.
574
+ #
575
+ # examples:
576
+ #
577
+ # s(:a) / s{ any(s(:a), s(:b)) } #=> [s(:a)]
578
+ # s(:a) / s{ s(:a) | s(:b) } #=> [s(:a)] # same thing via |
579
+ # s(:a) / s{ any(s(:b), s(:c)) } #=> []
580
+
581
+ class Any < Matcher
582
+ ##
583
+ # The collection of sub-matchers to match against.
584
+
585
+ attr_reader :options
586
+
587
+ ##
588
+ # Create an Any matcher which will match any of the +options+.
589
+
590
+ def initialize *options
591
+ @options = options
592
+ end
593
+
594
+ ##
595
+ # Satisfied when any sub expressions match +o+
596
+
597
+ def satisfy? o
598
+ options.any? { |exp|
599
+ Sexp === exp && exp.satisfy?(o) || exp == o
600
+ }
601
+ end
602
+
603
+ def == o # :nodoc:
604
+ super && self.options == o.options
605
+ end
606
+
607
+ def inspect # :nodoc:
608
+ options.map(&:inspect).join(" | ")
609
+ end
610
+
611
+ def pretty_print q # :nodoc:
612
+ q.group 1, "any(", ")" do
613
+ q.seplist options do |v|
614
+ q.pp v
615
+ end
616
+ end
617
+ end
618
+ end
619
+
620
+ ##
621
+ # Matches only when all sub-expressions match.
622
+ #
623
+ # This is also available via Matcher#&.
624
+ #
625
+ # examples:
626
+ #
627
+ # s(:a) / s{ all(s(:a), s(:b)) } #=> []
628
+ # s(:a, :b) / s{ t(:a) & include(:b)) } #=> [s(:a, :b)]
629
+
630
+ class All < Matcher
631
+ ##
632
+ # The collection of sub-matchers to match against.
633
+
634
+ attr_reader :options
635
+
636
+ ##
637
+ # Create an All matcher which will match all of the +options+.
638
+
639
+ def initialize *options
640
+ @options = options
641
+ end
642
+
643
+ ##
644
+ # Satisfied when all sub expressions match +o+
645
+
646
+ def satisfy? o
647
+ options.all? { |exp|
648
+ exp.kind_of?(Sexp) ? exp.satisfy?(o) : exp == o
649
+ }
650
+ end
651
+
652
+ def == o # :nodoc:
653
+ super && self.options == o.options
654
+ end
655
+
656
+ def inspect # :nodoc:
657
+ options.map(&:inspect).join(" & ")
658
+ end
659
+
660
+ def pretty_print q # :nodoc:
661
+ q.group 1, "all(", ")" do
662
+ q.seplist options do |v|
663
+ q.pp v
664
+ end
665
+ end
666
+ end
667
+ end
668
+
669
+ ##
670
+ # Matches when sub-expression does not match.
671
+ #
672
+ # This is also available via Matcher#-@.
673
+ #
674
+ # examples:
675
+ #
676
+ # s(:a) / s{ not?(s(:b)) } #=> [s(:a)]
677
+ # s(:a) / s{ -s(:b) } #=> [s(:a)]
678
+ # s(:a) / s{ s(not? :a) } #=> []
679
+
680
+ class Not < Matcher
681
+
682
+ ##
683
+ # The value to negate in the match.
684
+
685
+ attr_reader :value
686
+
687
+ ##
688
+ # Creates a Matcher which will match any Sexp that does not match the +value+
689
+
690
+ def initialize value
691
+ @value = value
692
+ end
693
+
694
+ def == o # :nodoc:
695
+ super && self.value == o.value
696
+ end
697
+
698
+ ##
699
+ # Satisfied if a +o+ does not match the +value+
700
+
701
+ def satisfy? o
702
+ !(value.kind_of?(Sexp) ? value.satisfy?(o) : value == o)
703
+ end
704
+
705
+ def inspect # :nodoc:
706
+ "not?(%p)" % [value]
707
+ end
708
+
709
+ def pretty_print q # :nodoc:
710
+ q.group 1, "not?(", ")" do
711
+ q.pp value
712
+ end
713
+ end
714
+ end
715
+
716
+ ##
717
+ # Matches anything that has a child matching the sub-expression
718
+ #
719
+ # example:
720
+ #
721
+ # s(s(s(s(s(:a))))) / s{ child(s(:a)) } #=> [s(s(s(s(s(:a))))),
722
+ # s(s(s(s(:a)))),
723
+ # s(s(s(:a))),
724
+ # s(s(:a)),
725
+ # s(:a)]
726
+
727
+ class Child < Matcher
728
+ ##
729
+ # The child to match.
730
+
731
+ attr_reader :child
732
+
733
+ ##
734
+ # Create a Child matcher which will match anything having a
735
+ # descendant matching +child+.
736
+
737
+ def initialize child
738
+ @child = child
739
+ end
740
+
741
+ ##
742
+ # Satisfied if matches +child+ or +o+ has a descendant matching
743
+ # +child+.
744
+
745
+ def satisfy? o
746
+ if child.satisfy? o
747
+ true
748
+ elsif o.kind_of? Sexp
749
+ o.search_each(child).any?
750
+ end
751
+ end
752
+
753
+ def == o # :nodoc:
754
+ super && self.child == o.child
755
+ end
756
+
757
+ def inspect # :nodoc:
758
+ "child(%p)" % [child]
759
+ end
760
+
761
+ def pretty_print q # :nodoc:
762
+ q.group 1, "child(", ")" do
763
+ q.pp child
764
+ end
765
+ end
766
+ end
767
+
768
+ ##
769
+ # Matches any atom (non-Sexp).
770
+ #
771
+ # examples:
772
+ #
773
+ # s(:a) / s{ s(atom) } #=> [s(:a)]
774
+ # s(:a, s(:b)) / s{ s(atom) } #=> [s(:b)]
775
+
776
+ class Atom < Matcher
777
+ ##
778
+ # Satisfied when +o+ is an atom.
779
+
780
+ def satisfy? o
781
+ !(o.kind_of? Sexp)
782
+ end
783
+
784
+ def inspect #:nodoc:
785
+ "atom"
786
+ end
787
+
788
+ def pretty_print q # :nodoc:
789
+ q.text "atom"
790
+ end
791
+ end
792
+
793
+ ##
794
+ # Matches any atom who's string representation matches the patterns
795
+ # passed in.
796
+ #
797
+ # examples:
798
+ #
799
+ # s(:a) / s{ m('a') } #=> [s(:a)]
800
+ # s(:a) / s{ m(/\w/,/\d/) } #=> [s(:a)]
801
+ # s(:tests, s(s(:test_a), s(:test_b))) / s{ m(/test_\w/) } #=> [s(:test_a),
802
+ #
803
+ # TODO: maybe don't require non-sexps? This does respond to =~ now.
804
+
805
+ class Pattern < Matcher
806
+
807
+ ##
808
+ # The regexp to match for the pattern.
809
+
810
+ attr_reader :pattern
811
+
812
+ def == o # :nodoc:
813
+ super && self.pattern == o.pattern
814
+ end
815
+
816
+ ##
817
+ # Create a Patten matcher which will match any atom that either
818
+ # matches the input +pattern+.
819
+
820
+ def initialize pattern
821
+ @pattern = pattern
822
+ end
823
+
824
+ ##
825
+ # Satisfied if +o+ is an atom, and +o+ matches +pattern+
826
+
827
+ def satisfy? o
828
+ !o.kind_of?(Sexp) && o.to_s =~ pattern # TODO: question to_s
829
+ end
830
+
831
+ def inspect # :nodoc:
832
+ "m(%p)" % pattern
833
+ end
834
+
835
+ def pretty_print q # :nodoc:
836
+ q.group 1, "m(", ")" do
837
+ q.pp pattern
838
+ end
839
+ end
840
+
841
+ def eql? o
842
+ super and self.pattern.eql? o.pattern
843
+ end
844
+
845
+ def hash
846
+ [super, pattern].hash
847
+ end
848
+ end
849
+
850
+ ##
851
+ # Matches anything having the same sexp_type, which is the first
852
+ # value in a Sexp.
853
+ #
854
+ # examples:
855
+ #
856
+ # s(:a, :b) / s{ t(:a) } #=> [s(:a, :b)]
857
+ # s(:a, :b) / s{ t(:b) } #=> []
858
+ # s(:a, s(:b, :c)) / s{ t(:b) } #=> [s(:b, :c)]
859
+
860
+ class Type < Matcher
861
+ attr_reader :sexp_type
862
+
863
+ ##
864
+ # Creates a Matcher which will match any Sexp who's type is +type+, where a type is
865
+ # the first element in the Sexp.
866
+
867
+ def initialize type
868
+ @sexp_type = type
869
+ end
870
+
871
+ def == o # :nodoc:
872
+ super && self.sexp_type == o.sexp_type
873
+ end
874
+
875
+ ##
876
+ # Satisfied if the sexp_type of +o+ is +type+.
877
+
878
+ def satisfy? o
879
+ o.kind_of?(Sexp) && o.sexp_type == sexp_type
880
+ end
881
+
882
+ def inspect # :nodoc:
883
+ "t(%p)" % sexp_type
884
+ end
885
+
886
+ def pretty_print q # :nodoc:
887
+ q.group 1, "t(", ")" do
888
+ q.pp sexp_type
889
+ end
890
+ end
891
+ end
892
+
893
+ ##
894
+ # Matches an expression or any expression that includes the child.
895
+ #
896
+ # examples:
897
+ #
898
+ # s(:a, :b) / s{ include(:b) } #=> [s(:a, :b)]
899
+ # s(s(s(:a))) / s{ include(:a) } #=> [s(:a)]
900
+
901
+ class Include < Matcher
902
+ ##
903
+ # The value that should be included in the match.
904
+
905
+ attr_reader :value
906
+
907
+ ##
908
+ # Creates a Matcher which will match any Sexp that contains the
909
+ # +value+.
910
+
911
+ def initialize value
912
+ @value = value
913
+ end
914
+
915
+ ##
916
+ # Satisfied if a +o+ is a Sexp and one of +o+'s elements matches
917
+ # value
918
+
919
+ def satisfy? o
920
+ Sexp === o && o.any? { |c|
921
+ # TODO: switch to respond_to??
922
+ Sexp === value ? value.satisfy?(c) : value == c
923
+ }
924
+ end
925
+
926
+ def == o # :nodoc:
927
+ super && self.value == o.value
928
+ end
929
+
930
+ def inspect # :nodoc:
931
+ "include(%p)" % [value]
932
+ end
933
+
934
+ def pretty_print q # :nodoc:
935
+ q.group 1, "include(", ")" do
936
+ q.pp value
937
+ end
938
+ end
939
+ end
940
+
941
+ ##
942
+ # See Matcher for sibling relations: <,<<,>>,>
943
+
944
+ class Sibling < Matcher
945
+
946
+ ##
947
+ # The LHS of the matcher.
948
+
949
+ attr_reader :subject
950
+
951
+ ##
952
+ # The RHS of the matcher.
953
+
954
+ attr_reader :sibling
955
+
956
+ ##
957
+ # An optional distance requirement for the matcher.
958
+
959
+ attr_reader :distance
960
+
961
+ ##
962
+ # Creates a Matcher which will match any pair of Sexps that are siblings.
963
+ # Defaults to matching the immediate following sibling.
964
+
965
+ def initialize subject, sibling, distance = nil
966
+ @subject = subject
967
+ @sibling = sibling
968
+ @distance = distance
969
+ end
970
+
971
+ ##
972
+ # Satisfied if o contains +subject+ followed by +sibling+
973
+
974
+ def satisfy? o
975
+ # Future optimizations:
976
+ # * Shortcut matching sibling
977
+ subject_matches = index_matches(subject, o)
978
+ return nil if subject_matches.empty?
979
+
980
+ sibling_matches = index_matches(sibling, o)
981
+ return nil if sibling_matches.empty?
982
+
983
+ subject_matches.any? { |i1, _data_1|
984
+ sibling_matches.any? { |i2, _data_2|
985
+ distance ? (i2-i1 == distance) : i2 > i1
986
+ }
987
+ }
988
+ end
989
+
990
+ def == o # :nodoc:
991
+ super &&
992
+ self.subject == o.subject &&
993
+ self.sibling == o.sibling &&
994
+ self.distance == o.distance
995
+ end
996
+
997
+ def inspect # :nodoc:
998
+ "%p >> %p" % [subject, sibling]
999
+ end
1000
+
1001
+ def pretty_print q # :nodoc:
1002
+ if distance then
1003
+ q.group 1, "sibling(", ")" do
1004
+ q.seplist [subject, sibling, distance] do |v|
1005
+ q.pp v
1006
+ end
1007
+ end
1008
+ else
1009
+ q.group 1 do
1010
+ q.pp subject
1011
+ q.text " >> "
1012
+ q.pp sibling
1013
+ end
1014
+ end
1015
+ end
1016
+
1017
+ private
1018
+
1019
+ def index_matches pattern, o
1020
+ indexes = []
1021
+ return indexes unless o.kind_of? Sexp
1022
+
1023
+ o.each_with_index do |e, i|
1024
+ data = {}
1025
+ if pattern.kind_of?(Sexp) ? pattern.satisfy?(e) : pattern == o[i]
1026
+ indexes << [i, data]
1027
+ end
1028
+ end
1029
+
1030
+ indexes
1031
+ end
1032
+ end # class Sibling
1033
+
1034
+ ##
1035
+ # Wraps the results of a Sexp query. MatchCollection defines
1036
+ # MatchCollection#/ so that you can chain queries.
1037
+ #
1038
+ # For instance:
1039
+ # res = s(:a, s(:b)) / s{ s(:a,_) } / s{ s(:b) }
1040
+
1041
+ class MatchCollection < Array
1042
+ ##
1043
+ # See Traverse#search
1044
+
1045
+ def / pattern
1046
+ inject(self.class.new) { |result, match|
1047
+ result.concat match / pattern
1048
+ }
1049
+ end
1050
+
1051
+ def inspect # :nodoc:
1052
+ "MatchCollection.new(%s)" % self.to_a.inspect[1..-2]
1053
+ end
1054
+
1055
+ alias :to_s :inspect # :nodoc:
1056
+
1057
+ def pretty_print q # :nodoc:
1058
+ q.group 1, "MatchCollection.new(", ")" do
1059
+ q.seplist(self) {|v| q.pp v }
1060
+ end
1061
+ end
1062
+ end # class MatchCollection
1063
+ end # class Sexp