sexp_path 0.4.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,14 @@
1
+ # See SexpQueryBuilder.atom
2
+ class SexpPath::Matcher::Atom < SexpPath::Matcher::Base
3
+ # Satisfied when +o+ is an atom (anything that is not an S-Expression)
4
+ def satisfy?(o, data={})
5
+ return nil if o.is_a? Sexp
6
+
7
+ capture_match o, data
8
+ end
9
+
10
+ # Prints as +atom+
11
+ def inspect
12
+ "atom"
13
+ end
14
+ end
@@ -0,0 +1,54 @@
1
+ # This is the base class for all Sexp matchers.
2
+ #
3
+ # A matcher should implement the following methods:
4
+ #
5
+ # * satisfy?
6
+ # * inspect
7
+ #
8
+ # +satisfy?+ determines whether the matcher matches a given input,
9
+ # and +inspect+ will print the matcher nicely in a user's console.
10
+ #
11
+ # The base matcher is created with the SexpQueryBuilder as follows
12
+ # Q?{ s() }
13
+ class SexpPath::Matcher::Base < Sexp
14
+ # Combines the Matcher with another Matcher, the resulting one will
15
+ # be satisfied if either Matcher would be satisfied.
16
+ #
17
+ # Example:
18
+ # s(:a) | s(:b)
19
+ def | o
20
+ SexpPath::Matcher::Any.new(self, o)
21
+ end
22
+
23
+ # Combines the Matcher with another Matcher, the resulting one will
24
+ # be satisfied only if both Matchers would be satisfied.
25
+ #
26
+ # Example:
27
+ # t(:a) & include(:b)
28
+ def & o
29
+ SexpPath::Matcher::All.new(self, o)
30
+ end
31
+
32
+ # Returns a Matcher that matches whenever this Matcher would not have matched
33
+ #
34
+ # Example:
35
+ # -s(:a)
36
+ def -@
37
+ SexpPath::Matcher::Not.new(self)
38
+ end
39
+
40
+ # Returns a Matcher that matches if this has a sibling +o+
41
+ #
42
+ # Example:
43
+ # s(:a) >> s(:b)
44
+ def >> o
45
+ SexpPath::Matcher::Sibling.new(self, o)
46
+ end
47
+
48
+ # Formats the matcher as:
49
+ # q(:a, :b)
50
+ def inspect
51
+ children = map{|e| e.inspect}.join(', ')
52
+ "q(#{children})"
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ class SexpPath::Matcher::Block < SexpPath::Matcher::Base
2
+ attr_reader :exp
3
+ def initialize &block
4
+ @exp = block
5
+ end
6
+
7
+ def satisfy?(o, data={})
8
+ return nil unless @exp[o]
9
+
10
+ capture_match o, data
11
+ end
12
+
13
+ def inspect
14
+ "<custom>"
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ # See SexpQueryBuilder.child
2
+ class SexpPath::Matcher::Child < SexpPath::Matcher::Base
3
+ attr_reader :child
4
+
5
+ # Create a Child matcher which will match anything having a descendant matching +child+.
6
+ def initialize(child)
7
+ @child = child
8
+ end
9
+
10
+ # Satisfied if matches +child+ or +o+ has a descendant matching +child+.
11
+ def satisfy?(o, data={})
12
+ if child.satisfy?(o,data)
13
+ capture_match o, data
14
+ elsif o.is_a? Sexp
15
+ o.search_each(child,data) do
16
+ return capture_match(o, data)
17
+ end
18
+ end
19
+ end
20
+
21
+ def inspect
22
+ "child(#{child.inspect})"
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # See SexpQueryBuilder.include
2
+ class SexpPath::Matcher::Include < SexpPath::Matcher::Base
3
+ attr_reader :value
4
+
5
+ # Creates a Matcher which will match any Sexp that contains the +value+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ # Satisfied if a +o+ is a Sexp and one of +o+'s elements matches value
11
+ def satisfy?(o, data={})
12
+ if o.is_a? Sexp
13
+ return nil unless o.any?{|c| value.is_a?(Sexp) ? value.satisfy?(c, data) : value == c}
14
+ end
15
+
16
+ capture_match o, data
17
+ end
18
+
19
+ def inspect
20
+ "include(#{value.inspect})"
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ # See SexpQueryBuilder.not
2
+ class SexpPath::Matcher::Not < SexpPath::Matcher::Base
3
+ attr_reader :value
4
+
5
+ # Creates a Matcher which will match any Sexp that does not match the +value+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ # Satisfied if a +o+ does not match the +value+
11
+ def satisfy?(o, data={})
12
+ return nil if value.is_a?(Sexp) ? value.satisfy?(o, data) : value == o
13
+
14
+ capture_match o, {}
15
+ end
16
+
17
+ def inspect
18
+ "is_not(#{value.inspect})"
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # See SexpQueryBuilder.m
2
+ class SexpPath::Matcher::Pattern < SexpPath::Matcher::Base
3
+ attr_reader :pattern
4
+
5
+ # Create a Patten matcher which will match any atom that either matches the input +pattern+.
6
+ def initialize(pattern)
7
+ @pattern = pattern
8
+ end
9
+
10
+ # Satisfied if +o+ is an atom, and +o+ matches +pattern+
11
+ def satisfy?(o, data={})
12
+ return nil unless !o.is_a?(Sexp) && o.to_s =~ pattern
13
+
14
+ capture_match o, data
15
+ end
16
+
17
+ def inspect
18
+ "m(#{pattern.inspect})"
19
+ end
20
+ end
@@ -0,0 +1,54 @@
1
+ # See SexpPath::Matcher::Base for sibling relations: <,<<,>>,>
2
+ #
3
+ class SexpPath::Matcher::Sibling < SexpPath::Matcher::Base
4
+ attr_reader :subject, :sibling, :distance
5
+
6
+ # Creates a Matcher which will match any pair of Sexps that are siblings.
7
+ # Defaults to matching the immediate following sibling.
8
+ def initialize(subject, sibling, distance=nil)
9
+ @subject = subject
10
+ @sibling = sibling
11
+ @distance = distance
12
+ end
13
+
14
+ # Satisfied if o contains +subject+ followed by +sibling+
15
+ def satisfy?(o, data={})
16
+ # Future optimizations:
17
+ # * Shortcut matching sibling
18
+ subject_matches = index_matches(subject, o)
19
+ return nil if subject_matches.empty?
20
+
21
+ sibling_matches = index_matches(sibling, o)
22
+ return nil if sibling_matches.empty?
23
+
24
+ subject_matches.each do |i1, data_1|
25
+ sibling_matches.each do |i2, data_2|
26
+ if (distance ? (i2-i1 == distance) : i2 > i1)
27
+ data = data.merge(data_1).merge(data_2)
28
+ return capture_match(o, data)
29
+ end
30
+ end
31
+ end
32
+
33
+ nil
34
+ end
35
+
36
+ def inspect
37
+ "#{subject.inspect} >> #{sibling.inspect}"
38
+ end
39
+
40
+ private
41
+ def index_matches(pattern, o)
42
+ indexes = []
43
+ return indexes unless o.is_a? Sexp
44
+
45
+ o.each_with_index do |e,i|
46
+ data = {}
47
+ if pattern.is_a?(Sexp) ? pattern.satisfy?(o[i],data) : pattern == o[i]
48
+ indexes << [i, data]
49
+ end
50
+ end
51
+
52
+ indexes
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ # See SexpQueryBuilder.t
2
+ class SexpPath::Matcher::Type < SexpPath::Matcher::Base
3
+ attr_reader :sexp_type
4
+
5
+ # Creates a Matcher which will match any Sexp who's type is +type+, where a type is
6
+ # the first element in the Sexp.
7
+ def initialize(type)
8
+ @sexp_type = type
9
+ end
10
+
11
+ # Satisfied if the sexp_type of +o+ is +type+.
12
+ def satisfy?(o, data={})
13
+ return nil unless o.is_a?(Sexp) && o.sexp_type == sexp_type
14
+
15
+ capture_match o, data
16
+ end
17
+
18
+ def inspect
19
+ "t(#{sexp_type.inspect})"
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ # See SexpQueryBuilder.wild and SexpQueryBuilder._
2
+ class SexpPath::Matcher::Wild < SexpPath::Matcher::Base
3
+
4
+ # Matches any single element.
5
+ def satisfy?(o, data={})
6
+ capture_match o, data
7
+ end
8
+
9
+ def inspect
10
+ "wild"
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module SexpPath
2
+ # Wraps the results of a SexpPath query.
3
+ # SexpCollection defines SexpCollection#search so that you can
4
+ # chain queries.
5
+ #
6
+ # For instance:
7
+ # res = s(:a, s(:b)) / Q?{ s(:a,_) } / Q?{ s(:b) }
8
+ #
9
+ class SexpCollection < Array
10
+ # See Traverse#search
11
+ def search(pattern)
12
+ inject(SexpCollection.new){|collection, match| collection.concat match.sexp.search(pattern, match) }
13
+ end
14
+ alias_method :/, :search
15
+ end
16
+ end
@@ -0,0 +1,137 @@
1
+ module SexpPath
2
+
3
+ # The SexpQueryBuilder is the simplest way to build SexpPath queries.
4
+ #
5
+ # Typically, one access the SexpQueryBuilder through the helper method
6
+ # Q? which accepts a block, and will return a SexpPath matcher.
7
+ #
8
+ # For example here is a SexpPath query that looks for s(:a):
9
+ #
10
+ # query = Q?{ s(:a) }
11
+ #
12
+ # A more interesting query might look for classes with names starting
13
+ # in Test:
14
+ #
15
+ # query = Q?{ s(:class, m(/^Test\w+/ % 'name', _, _)) }
16
+ #
17
+ # This makes use of a SexpPath::Matcher::Pattern, two SexpPath::Matcher::Wild
18
+ # matchers and SexpPath::Traverse#capture_as for capturing the name to a
19
+ # variable 'name'.
20
+ #
21
+ # For more examples, see the various SexpQueryBuilder class methods, the
22
+ # examples, and the tests supplied with SexpPath.
23
+ #
24
+ class SexpQueryBuilder
25
+ class << self
26
+
27
+ # This is the longhand method for create a SexpPath query, normally
28
+ # one would use Q?{ ... }, however it is also possible to do:
29
+ #
30
+ # SexpPath::SexpQueryBuilder.do{ s() }
31
+ #
32
+ def do(&block)
33
+ instance_eval(&block)
34
+ end
35
+
36
+ # Matches an S-Expression.
37
+ #
38
+ # example
39
+ # s(:a) / Q?{ s(:a) } #=> [s(:a)]
40
+ # s(:a) / Q?{ s() } #=> []
41
+ # s(:a, s(:b) / Q?{ s(:b) } #=> [s(:b)]
42
+ #
43
+ def s(*args)
44
+ SexpPath::Matcher::Base.new(*args)
45
+ end
46
+
47
+ # Matches anything.
48
+ #
49
+ # example:
50
+ # s(:a) / Q?{ _ } #=> [s(:a)]
51
+ # s(:a, s(s(:b))) / Q?{ s(_) } #=> [s(s(:b))]
52
+ #
53
+ # Can also be called with +wild+
54
+ # s(:a) / Q?{ wild } #=> [s(:a)]
55
+ #
56
+ def wild()
57
+ SexpPath::Matcher::Wild.new
58
+ end
59
+ alias_method :_, :wild
60
+
61
+ def include(child)
62
+ SexpPath::Matcher::Include.new(child)
63
+ end
64
+
65
+ # Matches any atom.
66
+ #
67
+ # example:
68
+ # s(:a) / Q?{ s(atom) } #=> [s(:a)]
69
+ # s(:a, s(:b)) / Q?{ s(atom) } #=> [s(:b)]
70
+ #
71
+ def atom
72
+ SexpPath::Matcher::Atom.new
73
+ end
74
+
75
+ # Matches when any sub expression match
76
+ #
77
+ # example:
78
+ # s(:a) / Q?{ any(s(:a), s(:b)) } #=> [s(:a)]
79
+ # s(:a) / Q?{ any(s(:b), s(:c)) } #=> []
80
+ #
81
+ def any(*args)
82
+ SexpPath::Matcher::Any.new(*args)
83
+ end
84
+
85
+ # Matches when all sub expression match
86
+ #
87
+ # example:
88
+ # s(:a) / Q?{ all(s(:a), s(:b)) } #=> []
89
+ # s(:a,:b) / Q?{ t(:a), include(:b)) } #=> [s(:a,:b)]
90
+ #
91
+ def all(*args)
92
+ SexpPath::Matcher::All.new(*args)
93
+ end
94
+
95
+ # Matches when sub expression does not match, see SexpPath::Matcher::Base#-@
96
+ #
97
+ # example:
98
+ # s(:a) / Q?{ is_not(s(:b)) } #=> [s(:a)]
99
+ # s(:a) / Q?{ s(is_not :a) } #=> []
100
+ #
101
+ def is_not(arg)
102
+ SexpPath::Matcher::Not.new(arg)
103
+ end
104
+
105
+ # Matches anything that has a child matching the sub expression
106
+ #
107
+ # example:
108
+ # s(s(s(s(s(:a))))) / Q?{ child(s(:a)) } #=> [s(s(s(s(s(:a)))))]
109
+ #
110
+ def child(child)
111
+ SexpPath::Matcher::Child.new(child)
112
+ end
113
+
114
+ # Matches anything having the same sexp_type, which is the first value in a Sexp.
115
+ #
116
+ # example:
117
+ # s(:a, :b) / Q?{ t(:a) } #=> [s(:a, :b)]
118
+ # s(:a, :b) / Q?{ t(:b) } #=> []
119
+ # s(:a, s(:b, :c)) / Q?{ t(:b) } #=> [s(:b, :c)]
120
+ def t(name)
121
+ SexpPath::Matcher::Type.new(name)
122
+ end
123
+
124
+ # Matches any atom who's string representation matches the patterns passed in.
125
+ #
126
+ # example:
127
+ # s(:a) / Q?{ m('a') } #=> [s(:a)]
128
+ # s(:a) / Q?{ m(/\w/,/\d/) } #=> [s(:a)]
129
+ # s(:tests, s(s(:test_a), s(:test_b))) / Q?{ m(/test_\w/) } #=> [s(:test_a), s(:test_b)]
130
+ def m(* patterns)
131
+ patterns = patterns.map{|p| p.is_a?(Regexp) ? p : Regexp.new("\\A"+Regexp.escape(p.to_s)+"\\Z")}
132
+ regexp = Regexp.union(*patterns)
133
+ SexpPath::Matcher::Pattern.new(regexp)
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,21 @@
1
+ module SexpPath
2
+
3
+ # Wraps the results of a SexpPath query. The matching Sexp
4
+ # is placed in SexpResult#sexp. Any named captures will be
5
+ # available with SexpResult#[].
6
+ #
7
+ # For instance:
8
+ # res = s(:a) / Q?{ s( _ % 'name') }
9
+ #
10
+ # res.first.sexp == s(:a)
11
+ # res.first['name'] == :a
12
+ #
13
+ class SexpResult < Hash
14
+ attr_accessor :sexp # Matched Sexp
15
+
16
+ def initialize(sexp, data={})
17
+ @sexp = sexp
18
+ merge! data
19
+ end
20
+ end
21
+ end