sexp_processor 4.12.1 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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