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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fe32dcd3e36da0a96d86a6a1a7fd60dd8cef96ebd1e0d1b0d87338e3d7fee85
4
- data.tar.gz: d55a40a0ed66d6d84cf653efba498f01d64fba61e7fd4a9eb87caa04f8dcb396
3
+ metadata.gz: 3991f250f861371d86496a6791f7489bd15e8815d32abde04d8b204fffc63ce1
4
+ data.tar.gz: aa0676a04c31383e91aa7227e339ccb484cff4476a862ed9ecaabd10e4b31337
5
5
  SHA512:
6
- metadata.gz: 8574bf015b701313f58aa5650b963ab35f9119567826606190658640ee32c870ce957ad73e0b2e17b6350c9217fb691383f8794d91d152a4ff2c18873cb18d5b
7
- data.tar.gz: 4a0a6754c29ac19d5654a3a8909a1fd233bfa1831fce155e9c4bee3e25143923693d84a2019079965f507fef34e0502ac412621a671076ba45ce42a312ccbac3
6
+ metadata.gz: c2a3ada3f252f86ce039e3ee5d21159a116733c22ec3bd09155fa403c36c3155148296982ebacb7c3312d982bff94c19ef24b24fd1f2aa0e31fc0619871c1d6c
7
+ data.tar.gz: 4aaccec314475c895a4632588094684ae48459892d9124645219559c9cad58c4a4dfb2fe32dde8877ebd4927ec0b4fe2664dc2a9bf4e5799cbabecc0bbca9c51
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -1,3 +1,16 @@
1
+ === 4.13.0 / 2019-09-24
2
+
3
+ * 4 minor enhancements:
4
+
5
+ * Added Sexp.q (query) and deprecated Sexp.s to distinguish better and match inspect output.
6
+ * Extended Sexp::Matcher::Parser to allow `not?` patterns.
7
+ * Extended Sexp::Matcher::Parser to cover more method names.
8
+ * Split out all pattern-oriented code to sexp_matcher.rb.
9
+
10
+ * 1 bug fix:
11
+
12
+ * Fixed bug w/ ruby's Array#eql? and #hash not looking at ivars.
13
+
1
14
  === 4.12.1 / 2019-06-03
2
15
 
3
16
  * 1 minor enhancement:
@@ -5,6 +5,7 @@ Rakefile
5
5
  lib/composite_sexp_processor.rb
6
6
  lib/pt_testcase.rb
7
7
  lib/sexp.rb
8
+ lib/sexp_matcher.rb
8
9
  lib/sexp_processor.rb
9
10
  lib/strict_sexp.rb
10
11
  lib/unique.rb
@@ -69,6 +69,14 @@ class Sexp < Array # ZenTest FULL
69
69
  obj.class == self.class and super # only because of a bug in ruby
70
70
  end
71
71
 
72
+ def eql? o
73
+ self.class == o.class && super
74
+ end
75
+
76
+ def hash
77
+ [self.class, *self].hash
78
+ end
79
+
72
80
  ##
73
81
  # Returns true if the node_type is +array+ or +args+.
74
82
  #
@@ -369,1055 +377,5 @@ def s *args, &blk
369
377
  Sexp.new(*args)
370
378
  end
371
379
 
372
- class Sexp #:nodoc:
373
- ##
374
- # Verifies that +pattern+ is a Matcher and then dispatches to its
375
- # #=~ method.
376
- #
377
- # See Matcher.=~
378
-
379
- def =~ pattern
380
- raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
381
- pattern =~ self
382
- end
383
-
384
- ##
385
- # Verifies that +pattern+ is a Matcher and then dispatches to its
386
- # #satisfy? method.
387
- #
388
- # TODO: rename match?
389
-
390
- def satisfy? pattern
391
- raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
392
- pattern.satisfy? self
393
- end
394
-
395
- ##
396
- # Verifies that +pattern+ is a Matcher and then dispatches to its #/
397
- # method.
398
- #
399
- # TODO: rename grep? match_all ? find_all ?
400
-
401
- def / pattern
402
- raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
403
- pattern / self
404
- end
405
-
406
- ##
407
- # Recursively searches for the +pattern+ yielding the matches.
408
-
409
- def search_each pattern, &block # TODO: rename to grep?
410
- raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
411
-
412
- return enum_for(:search_each, pattern) unless block_given?
413
-
414
- if pattern.satisfy? self then
415
- yield self
416
- end
417
-
418
- self.each_sexp do |subset|
419
- subset.search_each pattern, &block
420
- end
421
- end
422
-
423
- ##
424
- # Recursively searches for the +pattern+ yielding each match, and
425
- # replacing it with the result of the block.
426
- #
427
-
428
- def replace_sexp pattern, &block # TODO: rename to gsub?
429
- raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
430
-
431
- return yield self if pattern.satisfy? self
432
-
433
- # TODO: Needs #new_from(*new_body) to copy file/line/comment
434
- self.class.new(*self.map { |subset|
435
- case subset
436
- when Sexp then
437
- subset.replace_sexp pattern, &block
438
- else
439
- subset
440
- end
441
- })
442
- end
443
-
444
- ##
445
- # Matches an S-Expression.
446
- #
447
- # See Matcher for examples.
448
-
449
- def self.s *args
450
- Matcher.new(*args)
451
- end
452
-
453
- ##
454
- # Matches any single item.
455
- #
456
- # See Wild for examples.
457
-
458
- def self._
459
- Wild.new
460
- end
461
-
462
- # TODO: reorder factory methods and classes to match
463
-
464
- ##
465
- # Matches all remaining input.
466
- #
467
- # See Remaining for examples.
468
-
469
- def self.___
470
- Remaining.new
471
- end
472
-
473
- ##
474
- # Matches an expression or any expression that includes the child.
475
- #
476
- # See Include for examples.
477
-
478
- def self.include child # TODO: rename, name is generic ruby
479
- Include.new(child)
480
- end
481
-
482
- ##
483
- # Matches any atom.
484
- #
485
- # See Atom for examples.
486
-
487
- def self.atom
488
- Atom.new
489
- end
490
-
491
- ##
492
- # Matches when any of the sub-expressions match.
493
- #
494
- # This is also available via Matcher#|.
495
- #
496
- # See Any for examples.
497
-
498
- def self.any *args
499
- Any.new(*args)
500
- end
501
-
502
- ##
503
- # Matches only when all sub-expressions match.
504
- #
505
- # This is also available via Matcher#&.
506
- #
507
- # See All for examples.
508
-
509
- def self.all *args
510
- All.new(*args)
511
- end
512
-
513
- ##
514
- # Matches when sub-expression does not match.
515
- #
516
- # This is also available via Matcher#-@.
517
- #
518
- # See Not for examples.
519
-
520
- def self.not? arg
521
- Not.new arg
522
- end
523
-
524
- # TODO: add Sibling factory method?
525
-
526
- ##
527
- # Matches anything that has a child matching the sub-expression.
528
- #
529
- # See Child for examples.
530
-
531
- def self.child child
532
- Child.new child
533
- end
534
-
535
- ##
536
- # Matches anything having the same sexp_type, which is the first
537
- # value in a Sexp.
538
- #
539
- # See Type for examples.
540
-
541
- def self.t name
542
- Type.new name
543
- end
544
-
545
- ##
546
- # Matches any atom who's string representation matches the patterns
547
- # passed in.
548
- #
549
- # See Pattern for examples.
550
-
551
- def self.m *values
552
- res = values.map { |value|
553
- case value
554
- when Regexp then
555
- value
556
- else
557
- re = Regexp.escape value.to_s
558
- Regexp.new "\\A%s\\Z" % re
559
- end
560
- }
561
- Pattern.new Regexp.union(*res)
562
- end
563
-
564
- ##
565
- # Defines a family of objects that can be used to match sexps to
566
- # certain types of patterns, much like regexps can be used on
567
- # strings. Generally you won't use this class directly.
568
- #
569
- # You would normally create a matcher using the top-level #s method,
570
- # but with a block, calling into the Sexp factory methods. For example:
571
- #
572
- # s{ s(:class, m(/^Test/), _, ___) }
573
- #
574
- # This creates a matcher for classes whose names start with "Test".
575
- # It uses Sexp.m to create a Sexp::Matcher::Pattern matcher, Sexp._
576
- # to create a Sexp::Matcher::Wild matcher, and Sexp.___ to create a
577
- # Sexp::Matcher::Remaining matcher. It works like this:
578
- #
579
- # s{ # start to create a pattern
580
- # s( # create a sexp matcher
581
- # :class. # for class nodes
582
- # m(/^Test/), # matching name slots that start with "Test"
583
- # _, # any superclass value
584
- # ___ # and whatever is in the class
585
- # )
586
- # }
587
- #
588
- # Then you can use that with #=~, #/, Sexp#replace_sexp, and others.
589
- #
590
- # For more examples, see the various Sexp class methods, the examples,
591
- # and the tests supplied with Sexp.
592
- #
593
- # * For pattern creation, see factory methods: Sexp::_, Sexp::___, etc.
594
- # * For matching returning truthy/falsey results, see Sexp#=~.
595
- # * For case expressions, see Matcher#===.
596
- # * For getting all subtree matches, see Sexp#/.
597
- #
598
- # If rdoc didn't suck, these would all be links.
599
-
600
- class Matcher < Sexp
601
- ##
602
- # Should #=~ match sub-trees?
603
-
604
- def self.match_subs?
605
- @@match_subs
606
- end
607
-
608
- ##
609
- # Setter for +match_subs?+.
610
-
611
- def self.match_subs= o
612
- @@match_subs = o
613
- end
614
-
615
- self.match_subs = true
616
-
617
- ##
618
- # Does this matcher actually match +o+? Returns falsey if +o+ is
619
- # not a Sexp or if any sub-tree of +o+ is not satisfied by or
620
- # equal to its corresponding sub-matcher.
621
- #
622
- #--
623
- # TODO: push this up to Sexp and make this the workhorse
624
- # TODO: do the same with ===/satisfy?
625
-
626
- def satisfy? o
627
- return unless o.kind_of?(Sexp) &&
628
- (length == o.length || Matcher === last && last.greedy?)
629
-
630
- each_with_index.all? { |child, i|
631
- sexp = o.at i
632
- if Sexp === child then # TODO: when will this NOT be a matcher?
633
- sexp = o.sexp_body i if child.respond_to?(:greedy?) && child.greedy?
634
- child.satisfy? sexp
635
- else
636
- child == sexp
637
- end
638
- }
639
- end
640
-
641
- ##
642
- # Tree equivalent to String#=~, returns true if +self+ matches
643
- # +sexp+ as a whole or in a sub-tree (if +match_subs?+).
644
- #
645
- # TODO: maybe this should NOT be aliased to === ?
646
- #
647
- # TODO: example
648
-
649
- def =~ sexp
650
- raise ArgumentError, "Can't both be matchers: %p" % [sexp] if Matcher === sexp
651
-
652
- self.satisfy?(sexp) ||
653
- (self.class.match_subs? && sexp.each_sexp.any? { |sub| self =~ sub })
654
- end
655
-
656
- alias === =~ # TODO?: alias === satisfy?
657
-
658
- ##
659
- # Searches through +sexp+ for all sub-trees that match this
660
- # matcher and returns a MatchCollection for each match.
661
- #
662
- # TODO: redirect?
663
- # Example:
664
- # Q{ s(:b) } / s(:a, s(:b)) => [s(:b)]
665
-
666
- def / sexp
667
- raise ArgumentError, "can't both be matchers" if Matcher === sexp
668
-
669
- # TODO: move search_each into matcher?
670
- MatchCollection.new sexp.search_each(self).to_a
671
- end
672
-
673
- ##
674
- # Combines the Matcher with another Matcher, the resulting one will
675
- # be satisfied if either Matcher would be satisfied.
676
- #
677
- # TODO: redirect
678
- # Example:
679
- # s(:a) | s(:b)
680
-
681
- def | other
682
- Any.new self, other
683
- end
684
-
685
- ##
686
- # Combines the Matcher with another Matcher, the resulting one will
687
- # be satisfied only if both Matchers would be satisfied.
688
- #
689
- # TODO: redirect
690
- # Example:
691
- # t(:a) & include(:b)
692
-
693
- def & other
694
- All.new self, other
695
- end
696
-
697
- ##
698
- # Returns a Matcher that matches whenever this Matcher would not have matched
699
- #
700
- # Example:
701
- # -s(:a)
702
-
703
- def -@
704
- Not.new self
705
- end
706
-
707
- ##
708
- # Returns a Matcher that matches if this has a sibling +o+
709
- #
710
- # Example:
711
- # s(:a) >> s(:b)
712
-
713
- def >> other
714
- Sibling.new self, other
715
- end
716
-
717
- ##
718
- # Is this matcher greedy? Defaults to false.
719
-
720
- def greedy?
721
- false
722
- end
723
-
724
- def inspect # :nodoc:
725
- s = super
726
- s[0] = "q"
727
- s
728
- end
729
-
730
- def pretty_print q # :nodoc:
731
- q.group 1, "q(", ")" do
732
- q.seplist self do |v|
733
- q.pp v
734
- end
735
- end
736
- end
737
-
738
- ##
739
- # Parse a lispy string representation of a matcher into a Matcher.
740
- # See +Parser+.
741
-
742
- def self.parse s
743
- Parser.new(s).parse
744
- end
745
-
746
- ##
747
- # Converts from a lispy string to Sexp matchers in a safe manner.
748
- #
749
- # "(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }
750
-
751
- class Parser
752
-
753
- ##
754
- # The stream of tokens to parse. See #lex.
755
-
756
- attr_accessor :tokens
757
-
758
- ##
759
- # Create a new Parser instance on +s+
760
-
761
- def initialize s
762
- self.tokens = []
763
- lex s
764
- end
765
-
766
- ##
767
- # Converts +s+ into a stream of tokens and adds them to +tokens+.
768
-
769
- def lex s
770
- tokens.concat s.scan(%r%[()\[\]]|\"[^"]*\"|/[^/]*/|[\w-]+%) # "
771
- end
772
-
773
- ##
774
- # Returns the next token and removes it from the stream or raises if empty.
775
-
776
- def next_token
777
- raise SyntaxError, "unbalanced input" if tokens.empty?
778
- tokens.shift
779
- end
780
-
781
- ##
782
- # Returns the next token without removing it from the stream.
783
-
784
- def peek_token
785
- tokens.first
786
- end
787
-
788
- ##
789
- # Parses tokens and returns a +Matcher+ instance.
790
-
791
- def parse
792
- result = parse_sexp until tokens.empty?
793
- result
794
- end
795
-
796
- ##
797
- # Parses a string into a sexp matcher:
798
- #
799
- # SEXP : "(" SEXP:args* ")" => Sexp.q(*args)
800
- # | "[" CMD:cmd sexp:args* "]" => Sexp.cmd(*args)
801
- # | "nil" => nil
802
- # | /\d+/:n => n.to_i
803
- # | "___" => Sexp.___
804
- # | "_" => Sexp._
805
- # | /^\/(.*)\/$/:re => Regexp.new re[0]
806
- # | /^"(.*)"$/:s => String.new s[0]
807
- # | NAME:name => name.to_sym
808
- # NAME : /\w+/
809
- # CMD : "t" | "m" | "atom"
810
-
811
- def parse_sexp
812
- token = next_token
813
-
814
- case token
815
- when "(" then
816
- parse_list
817
- when "[" then
818
- parse_cmd
819
- when "nil" then
820
- nil
821
- when /^\d+$/ then
822
- token.to_i
823
- when "___" then
824
- Sexp.___
825
- when "_" then
826
- Sexp._
827
- when %r%^/(.*)/$% then
828
- re = $1
829
- raise SyntaxError, "Not allowed: /%p/" % [re] unless
830
- re =~ /\A([\w()|.*+^$]+)\z/
831
- Regexp.new re
832
- when /^"(.*)"$/ then
833
- $1
834
- when /^\w+$/ then
835
- token.to_sym
836
- else
837
- raise SyntaxError, "unhandled token: %p" % [token]
838
- end
839
- end
840
-
841
- ##
842
- # Parses a balanced list of expressions and returns the
843
- # equivalent matcher.
844
-
845
- def parse_list
846
- result = []
847
-
848
- result << parse_sexp while peek_token && peek_token != ")"
849
- next_token # pop off ")"
850
-
851
- Sexp.s(*result)
852
- end
853
-
854
- ##
855
- # A collection of allowed commands to convert into matchers.
856
-
857
- ALLOWED = [:t, :m, :atom].freeze
858
-
859
- ##
860
- # Parses a balanced command. A command is denoted by square
861
- # brackets and must conform to a whitelisted set of allowed
862
- # commands (see +ALLOWED+).
863
-
864
- def parse_cmd
865
- args = []
866
- args << parse_sexp while peek_token && peek_token != "]"
867
- next_token # pop off "]"
868
-
869
- cmd = args.shift
870
- args = Sexp.s(*args)
871
-
872
- raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd
873
-
874
- result = Sexp.send cmd, *args
875
-
876
- result
877
- end
878
- end # class Parser
879
- end # class Matcher
880
-
881
- ##
882
- # Matches any single item.
883
- #
884
- # examples:
885
- #
886
- # s(:a) / s{ _ } #=> [s(:a)]
887
- # s(:a, s(s(:b))) / s{ s(_) } #=> [s(s(:b))]
888
-
889
- class Wild < Matcher
890
- ##
891
- # Matches any single element.
892
-
893
- def satisfy? o
894
- true
895
- end
896
-
897
- def inspect # :nodoc:
898
- "_"
899
- end
900
-
901
- def pretty_print q # :nodoc:
902
- q.text "_"
903
- end
904
- end
905
-
906
- ##
907
- # Matches all remaining input. If remaining comes before any other
908
- # matchers, they will be ignored.
909
- #
910
- # examples:
911
- #
912
- # s(:a) / s{ s(:a, ___ ) } #=> [s(:a)]
913
- # s(:a, :b, :c) / s{ s(:a, ___ ) } #=> [s(:a, :b, :c)]
914
-
915
- class Remaining < Matcher
916
- ##
917
- # Always satisfied once this is reached. Think of it as a var arg.
918
-
919
- def satisfy? o
920
- true
921
- end
922
-
923
- def greedy?
924
- true
925
- end
926
-
927
- def inspect # :nodoc:
928
- "___"
929
- end
930
-
931
- def pretty_print q # :nodoc:
932
- q.text "___"
933
- end
934
- end
935
-
936
- ##
937
- # Matches when any of the sub-expressions match.
938
- #
939
- # This is also available via Matcher#|.
940
- #
941
- # examples:
942
- #
943
- # s(:a) / s{ any(s(:a), s(:b)) } #=> [s(:a)]
944
- # s(:a) / s{ s(:a) | s(:b) } #=> [s(:a)] # same thing via |
945
- # s(:a) / s{ any(s(:b), s(:c)) } #=> []
946
-
947
- class Any < Matcher
948
- ##
949
- # The collection of sub-matchers to match against.
950
-
951
- attr_reader :options
952
-
953
- ##
954
- # Create an Any matcher which will match any of the +options+.
955
-
956
- def initialize *options
957
- @options = options
958
- end
959
-
960
- ##
961
- # Satisfied when any sub expressions match +o+
962
-
963
- def satisfy? o
964
- options.any? { |exp|
965
- Sexp === exp && exp.satisfy?(o) || exp == o
966
- }
967
- end
968
-
969
- def == o # :nodoc:
970
- super && self.options == o.options
971
- end
972
-
973
- def inspect # :nodoc:
974
- options.map(&:inspect).join(" | ")
975
- end
976
-
977
- def pretty_print q # :nodoc:
978
- q.group 1, "any(", ")" do
979
- q.seplist options do |v|
980
- q.pp v
981
- end
982
- end
983
- end
984
- end
985
-
986
- ##
987
- # Matches only when all sub-expressions match.
988
- #
989
- # This is also available via Matcher#&.
990
- #
991
- # examples:
992
- #
993
- # s(:a) / s{ all(s(:a), s(:b)) } #=> []
994
- # s(:a, :b) / s{ t(:a) & include(:b)) } #=> [s(:a, :b)]
995
-
996
- class All < Matcher
997
- ##
998
- # The collection of sub-matchers to match against.
999
-
1000
- attr_reader :options
1001
-
1002
- ##
1003
- # Create an All matcher which will match all of the +options+.
1004
-
1005
- def initialize *options
1006
- @options = options
1007
- end
1008
-
1009
- ##
1010
- # Satisfied when all sub expressions match +o+
1011
-
1012
- def satisfy? o
1013
- options.all? { |exp|
1014
- exp.kind_of?(Sexp) ? exp.satisfy?(o) : exp == o
1015
- }
1016
- end
1017
-
1018
- def == o # :nodoc:
1019
- super && self.options == o.options
1020
- end
1021
-
1022
- def inspect # :nodoc:
1023
- options.map(&:inspect).join(" & ")
1024
- end
1025
-
1026
- def pretty_print q # :nodoc:
1027
- q.group 1, "all(", ")" do
1028
- q.seplist options do |v|
1029
- q.pp v
1030
- end
1031
- end
1032
- end
1033
- end
1034
-
1035
- ##
1036
- # Matches when sub-expression does not match.
1037
- #
1038
- # This is also available via Matcher#-@.
1039
- #
1040
- # examples:
1041
- #
1042
- # s(:a) / s{ not?(s(:b)) } #=> [s(:a)]
1043
- # s(:a) / s{ -s(:b) } #=> [s(:a)]
1044
- # s(:a) / s{ s(not? :a) } #=> []
1045
-
1046
- class Not < Matcher
1047
-
1048
- ##
1049
- # The value to negate in the match.
1050
-
1051
- attr_reader :value
1052
-
1053
- ##
1054
- # Creates a Matcher which will match any Sexp that does not match the +value+
1055
-
1056
- def initialize value
1057
- @value = value
1058
- end
1059
-
1060
- def == o # :nodoc:
1061
- super && self.value == o.value
1062
- end
1063
-
1064
- ##
1065
- # Satisfied if a +o+ does not match the +value+
1066
-
1067
- def satisfy? o
1068
- !(value.kind_of?(Sexp) ? value.satisfy?(o) : value == o)
1069
- end
1070
-
1071
- def inspect # :nodoc:
1072
- "not?(%p)" % [value]
1073
- end
1074
-
1075
- def pretty_print q # :nodoc:
1076
- q.group 1, "not?(", ")" do
1077
- q.pp value
1078
- end
1079
- end
1080
- end
1081
-
1082
- ##
1083
- # Matches anything that has a child matching the sub-expression
1084
- #
1085
- # example:
1086
- #
1087
- # s(s(s(s(s(:a))))) / s{ child(s(:a)) } #=> [s(s(s(s(s(:a))))),
1088
- # s(s(s(s(:a)))),
1089
- # s(s(s(:a))),
1090
- # s(s(:a)),
1091
- # s(:a)]
1092
-
1093
- class Child < Matcher
1094
- ##
1095
- # The child to match.
1096
-
1097
- attr_reader :child
1098
-
1099
- ##
1100
- # Create a Child matcher which will match anything having a
1101
- # descendant matching +child+.
1102
-
1103
- def initialize child
1104
- @child = child
1105
- end
1106
-
1107
- ##
1108
- # Satisfied if matches +child+ or +o+ has a descendant matching
1109
- # +child+.
1110
-
1111
- def satisfy? o
1112
- if child.satisfy? o
1113
- true
1114
- elsif o.kind_of? Sexp
1115
- o.search_each(child).any?
1116
- end
1117
- end
1118
-
1119
- def == o # :nodoc:
1120
- super && self.child == o.child
1121
- end
1122
-
1123
- def inspect # :nodoc:
1124
- "child(%p)" % [child]
1125
- end
1126
-
1127
- def pretty_print q # :nodoc:
1128
- q.group 1, "child(", ")" do
1129
- q.pp child
1130
- end
1131
- end
1132
- end
1133
-
1134
- ##
1135
- # Matches any atom (non-Sexp).
1136
- #
1137
- # examples:
1138
- #
1139
- # s(:a) / s{ s(atom) } #=> [s(:a)]
1140
- # s(:a, s(:b)) / s{ s(atom) } #=> [s(:b)]
1141
-
1142
- class Atom < Matcher
1143
- ##
1144
- # Satisfied when +o+ is an atom.
1145
-
1146
- def satisfy? o
1147
- !(o.kind_of? Sexp)
1148
- end
1149
-
1150
- def inspect #:nodoc:
1151
- "atom"
1152
- end
1153
-
1154
- def pretty_print q # :nodoc:
1155
- q.text "atom"
1156
- end
1157
- end
1158
-
1159
- ##
1160
- # Matches any atom who's string representation matches the patterns
1161
- # passed in.
1162
- #
1163
- # examples:
1164
- #
1165
- # s(:a) / s{ m('a') } #=> [s(:a)]
1166
- # s(:a) / s{ m(/\w/,/\d/) } #=> [s(:a)]
1167
- # s(:tests, s(s(:test_a), s(:test_b))) / s{ m(/test_\w/) } #=> [s(:test_a),
1168
- #
1169
- # TODO: maybe don't require non-sexps? This does respond to =~ now.
1170
-
1171
- class Pattern < Matcher
1172
-
1173
- ##
1174
- # The regexp to match for the pattern.
1175
-
1176
- attr_reader :pattern
1177
-
1178
- def == o # :nodoc:
1179
- super && self.pattern == o.pattern
1180
- end
1181
-
1182
- ##
1183
- # Create a Patten matcher which will match any atom that either
1184
- # matches the input +pattern+.
1185
-
1186
- def initialize pattern
1187
- @pattern = pattern
1188
- end
1189
-
1190
- ##
1191
- # Satisfied if +o+ is an atom, and +o+ matches +pattern+
1192
-
1193
- def satisfy? o
1194
- !o.kind_of?(Sexp) && o.to_s =~ pattern # TODO: question to_s
1195
- end
1196
-
1197
- def inspect # :nodoc:
1198
- "m(%p)" % pattern
1199
- end
1200
-
1201
- def pretty_print q # :nodoc:
1202
- q.group 1, "m(", ")" do
1203
- q.pp pattern
1204
- end
1205
- end
1206
- end
1207
-
1208
- ##
1209
- # Matches anything having the same sexp_type, which is the first
1210
- # value in a Sexp.
1211
- #
1212
- # examples:
1213
- #
1214
- # s(:a, :b) / s{ t(:a) } #=> [s(:a, :b)]
1215
- # s(:a, :b) / s{ t(:b) } #=> []
1216
- # s(:a, s(:b, :c)) / s{ t(:b) } #=> [s(:b, :c)]
1217
-
1218
- class Type < Matcher
1219
- attr_reader :sexp_type
1220
-
1221
- ##
1222
- # Creates a Matcher which will match any Sexp who's type is +type+, where a type is
1223
- # the first element in the Sexp.
1224
-
1225
- def initialize type
1226
- @sexp_type = type
1227
- end
1228
-
1229
- def == o # :nodoc:
1230
- super && self.sexp_type == o.sexp_type
1231
- end
1232
-
1233
- ##
1234
- # Satisfied if the sexp_type of +o+ is +type+.
1235
-
1236
- def satisfy? o
1237
- o.kind_of?(Sexp) && o.sexp_type == sexp_type
1238
- end
1239
-
1240
- def inspect # :nodoc:
1241
- "t(%p)" % sexp_type
1242
- end
1243
-
1244
- def pretty_print q # :nodoc:
1245
- q.group 1, "t(", ")" do
1246
- q.pp sexp_type
1247
- end
1248
- end
1249
- end
1250
-
1251
- ##
1252
- # Matches an expression or any expression that includes the child.
1253
- #
1254
- # examples:
1255
- #
1256
- # s(:a, :b) / s{ include(:b) } #=> [s(:a, :b)]
1257
- # s(s(s(:a))) / s{ include(:a) } #=> [s(:a)]
1258
-
1259
- class Include < Matcher
1260
- ##
1261
- # The value that should be included in the match.
1262
-
1263
- attr_reader :value
1264
-
1265
- ##
1266
- # Creates a Matcher which will match any Sexp that contains the
1267
- # +value+.
1268
-
1269
- def initialize value
1270
- @value = value
1271
- end
1272
-
1273
- ##
1274
- # Satisfied if a +o+ is a Sexp and one of +o+'s elements matches
1275
- # value
1276
-
1277
- def satisfy? o
1278
- Sexp === o && o.any? { |c|
1279
- # TODO: switch to respond_to??
1280
- Sexp === value ? value.satisfy?(c) : value == c
1281
- }
1282
- end
1283
-
1284
- def == o # :nodoc:
1285
- super && self.value == o.value
1286
- end
1287
-
1288
- def inspect # :nodoc:
1289
- "include(%p)" % [value]
1290
- end
1291
-
1292
- def pretty_print q # :nodoc:
1293
- q.group 1, "include(", ")" do
1294
- q.pp value
1295
- end
1296
- end
1297
- end
1298
-
1299
- ##
1300
- # See Matcher for sibling relations: <,<<,>>,>
1301
-
1302
- class Sibling < Matcher
1303
-
1304
- ##
1305
- # The LHS of the matcher.
1306
-
1307
- attr_reader :subject
1308
-
1309
- ##
1310
- # The RHS of the matcher.
1311
-
1312
- attr_reader :sibling
1313
-
1314
- ##
1315
- # An optional distance requirement for the matcher.
1316
-
1317
- attr_reader :distance
1318
-
1319
- ##
1320
- # Creates a Matcher which will match any pair of Sexps that are siblings.
1321
- # Defaults to matching the immediate following sibling.
1322
-
1323
- def initialize subject, sibling, distance = nil
1324
- @subject = subject
1325
- @sibling = sibling
1326
- @distance = distance
1327
- end
1328
-
1329
- ##
1330
- # Satisfied if o contains +subject+ followed by +sibling+
1331
-
1332
- def satisfy? o
1333
- # Future optimizations:
1334
- # * Shortcut matching sibling
1335
- subject_matches = index_matches(subject, o)
1336
- return nil if subject_matches.empty?
1337
-
1338
- sibling_matches = index_matches(sibling, o)
1339
- return nil if sibling_matches.empty?
1340
-
1341
- subject_matches.any? { |i1, _data_1|
1342
- sibling_matches.any? { |i2, _data_2|
1343
- distance ? (i2-i1 == distance) : i2 > i1
1344
- }
1345
- }
1346
- end
1347
-
1348
- def == o # :nodoc:
1349
- super &&
1350
- self.subject == o.subject &&
1351
- self.sibling == o.sibling &&
1352
- self.distance == o.distance
1353
- end
1354
-
1355
- def inspect # :nodoc:
1356
- "%p >> %p" % [subject, sibling]
1357
- end
1358
-
1359
- def pretty_print q # :nodoc:
1360
- if distance then
1361
- q.group 1, "sibling(", ")" do
1362
- q.seplist [subject, sibling, distance] do |v|
1363
- q.pp v
1364
- end
1365
- end
1366
- else
1367
- q.group 1 do
1368
- q.pp subject
1369
- q.text " >> "
1370
- q.pp sibling
1371
- end
1372
- end
1373
- end
1374
-
1375
- private
1376
-
1377
- def index_matches pattern, o
1378
- indexes = []
1379
- return indexes unless o.kind_of? Sexp
1380
-
1381
- o.each_with_index do |e, i|
1382
- data = {}
1383
- if pattern.kind_of?(Sexp) ? pattern.satisfy?(e) : pattern == o[i]
1384
- indexes << [i, data]
1385
- end
1386
- end
1387
-
1388
- indexes
1389
- end
1390
- end # class Sibling
1391
-
1392
- ##
1393
- # Wraps the results of a Sexp query. MatchCollection defines
1394
- # MatchCollection#/ so that you can chain queries.
1395
- #
1396
- # For instance:
1397
- # res = s(:a, s(:b)) / s{ s(:a,_) } / s{ s(:b) }
1398
-
1399
- class MatchCollection < Array
1400
- ##
1401
- # See Traverse#search
1402
-
1403
- def / pattern
1404
- inject(self.class.new) { |result, match|
1405
- result.concat match / pattern
1406
- }
1407
- end
1408
-
1409
- def inspect # :nodoc:
1410
- "MatchCollection.new(%s)" % self.to_a.inspect[1..-2]
1411
- end
1412
-
1413
- alias :to_s :inspect # :nodoc:
1414
-
1415
- def pretty_print q # :nodoc:
1416
- q.group 1, "MatchCollection.new(", ")" do
1417
- q.seplist(self) {|v| q.pp v }
1418
- end
1419
- end
1420
- end # class MatchCollection
1421
- end
1422
-
380
+ require "sexp_matcher" unless defined? Sexp::Matcher
1423
381
  require "strict_sexp" if ENV["STRICT_SEXP"].to_i > 0