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