sexp_path 0.4.0

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