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.
- data/README.rdoc +167 -0
- data/Rakefile +41 -0
- data/TODO +4 -0
- data/VERSION.yml +4 -0
- data/examples/print_methods.rb +28 -0
- data/examples/sexp_grep.rb +46 -0
- data/lib/sexp_path.rb +57 -0
- data/lib/sexp_path/line_numbering_processor.rb +60 -0
- data/lib/sexp_path/matcher/all.rb +20 -0
- data/lib/sexp_path/matcher/any.rb +20 -0
- data/lib/sexp_path/matcher/atom.rb +14 -0
- data/lib/sexp_path/matcher/base.rb +54 -0
- data/lib/sexp_path/matcher/block.rb +16 -0
- data/lib/sexp_path/matcher/child.rb +24 -0
- data/lib/sexp_path/matcher/include.rb +22 -0
- data/lib/sexp_path/matcher/not.rb +20 -0
- data/lib/sexp_path/matcher/pattern.rb +20 -0
- data/lib/sexp_path/matcher/sibling.rb +54 -0
- data/lib/sexp_path/matcher/type.rb +21 -0
- data/lib/sexp_path/matcher/wild.rb +12 -0
- data/lib/sexp_path/sexp_collection.rb +16 -0
- data/lib/sexp_path/sexp_query_builder.rb +137 -0
- data/lib/sexp_path/sexp_result.rb +21 -0
- data/lib/sexp_path/traverse.rb +72 -0
- data/test/line_numbering_processor_test.rb +55 -0
- data/test/sample.rb +25 -0
- data/test/sexp_path_capture_test.rb +131 -0
- data/test/sexp_path_matching_test.rb +211 -0
- data/test/sexp_replacement_test.rb +20 -0
- data/test/use_case_test.rb +127 -0
- metadata +108 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
module SexpPath
|
2
|
+
module Traverse
|
3
|
+
# Searches for the +pattern+ returning a SexpCollection containing
|
4
|
+
# a SexpResult for each match.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
# s(:a, s(:b)) / Q{ s(:b) } => [s(:b)]
|
8
|
+
def search(pattern, data={})
|
9
|
+
collection = SexpCollection.new
|
10
|
+
search_each(pattern,data){|match| collection << match}
|
11
|
+
collection
|
12
|
+
end
|
13
|
+
alias_method :/, :search
|
14
|
+
|
15
|
+
# Searches for the +pattern+ yielding a SexpResult
|
16
|
+
# for each match.
|
17
|
+
#
|
18
|
+
def search_each(pattern, data={}, &block)
|
19
|
+
return false unless pattern.is_a? Sexp
|
20
|
+
|
21
|
+
if pattern.satisfy?(self, data)
|
22
|
+
block.call(SexpResult.new(self, data))
|
23
|
+
end
|
24
|
+
|
25
|
+
self.each do |subset|
|
26
|
+
case subset
|
27
|
+
when Sexp then subset.search_each(pattern, data, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Searches for the +pattern+ yielding a SexpResult
|
33
|
+
# for each match, and replacing it with the result of
|
34
|
+
# the block.
|
35
|
+
#
|
36
|
+
# There is no guarantee that the result will or will not
|
37
|
+
# be the same object passed in, meaning this mutates, or replaces
|
38
|
+
# the original sexp.
|
39
|
+
def replace_sexp(pattern, data={}, &block)
|
40
|
+
return self unless pattern.is_a? Sexp
|
41
|
+
|
42
|
+
if pattern.satisfy?(self, data)
|
43
|
+
return block.call(SexpResult.new(self, data))
|
44
|
+
end
|
45
|
+
|
46
|
+
self.each_with_index do |subset, i|
|
47
|
+
case subset
|
48
|
+
when Sexp then self[i] = (subset.replace_sexp(pattern, data, &block))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return self
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sets a named capture for the Matcher. If a SexpResult is returned
|
56
|
+
# any named captures will be available it.
|
57
|
+
def capture_as(name)
|
58
|
+
@capture_name = name
|
59
|
+
self
|
60
|
+
end
|
61
|
+
alias_method :%, :capture_as
|
62
|
+
|
63
|
+
private
|
64
|
+
def capture_match(matching_object, data)
|
65
|
+
if @capture_name
|
66
|
+
data[@capture_name] = matching_object
|
67
|
+
end
|
68
|
+
|
69
|
+
data
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/sexp_path'
|
3
|
+
require 'parse_tree'
|
4
|
+
|
5
|
+
class LineNumberingProcessorTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@path = File.dirname(__FILE__) + '/sample.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_processing_a_file_fills_in_all_the_line_numbers
|
11
|
+
sexp = LineNumberingProcessor.rewrite_file(@path)
|
12
|
+
assert !sexp.empty?
|
13
|
+
sexp.search_each(Q?{_}) do |s|
|
14
|
+
assert !sexp.line.nil?, "Expected a line number for: #{s.sexp.inspect}"
|
15
|
+
assert !sexp.file.nil?, "Expected a file for: #{s.sexp.inspect}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# This test may break if sample.rb changes
|
20
|
+
def test_finding_known_lines
|
21
|
+
sexp = LineNumberingProcessor.rewrite_file(@path)
|
22
|
+
lines = open(@path,'r'){|io| io.readlines}
|
23
|
+
|
24
|
+
assert_line_numbers_equal(
|
25
|
+
lines, 'def test_b()',
|
26
|
+
sexp, Q?{ s(:defn, :test_b, _) }
|
27
|
+
)
|
28
|
+
|
29
|
+
assert_line_numbers_equal(
|
30
|
+
lines, '[apples, oranges, cakes]',
|
31
|
+
sexp, Q?{ s(:array, s(:lvar, :apples), s(:lvar, :oranges), s(:lvar, :cakes)) }
|
32
|
+
)
|
33
|
+
|
34
|
+
assert_line_numbers_equal(
|
35
|
+
lines, "require 'test/unit'",
|
36
|
+
sexp, Q?{ s(:fcall, :require, s(:array, s(:str, "test/unit"))) }
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def assert_line_numbers_equal(lines, code, sexp, pattern)
|
42
|
+
string_line = find_line(lines, code)
|
43
|
+
sexp_line = (sexp / pattern).first.sexp.line
|
44
|
+
|
45
|
+
assert_equal string_line, sexp_line, "Expected to find #{code} at line #{string_line}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_line(lines, code)
|
49
|
+
|
50
|
+
lines.each_with_index do |line,i|
|
51
|
+
return i+1 if line.index(code) # 1 based indexing
|
52
|
+
end
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
end
|
data/test/sample.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/testcase'
|
3
|
+
|
4
|
+
class ExampleTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
1 + 2
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_a
|
10
|
+
assert_equal 1+2, 4
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_b()
|
14
|
+
# assert 1+1
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_a
|
18
|
+
assert_equal 1+2, 3
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def helper_method apples, oranges, cakes=nil
|
23
|
+
[apples, oranges, cakes].compact.map{|food| food.to_s.upcase}
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/sexp_path'
|
3
|
+
|
4
|
+
class SexpPathCaptureTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@ast_sexp = # Imagine it looks like a ruby AST
|
7
|
+
s(:class, :cake,
|
8
|
+
s(
|
9
|
+
s(:defn, :foo,
|
10
|
+
s(:add, :a, :b)
|
11
|
+
),
|
12
|
+
s(:defn, :bar,
|
13
|
+
s(:sub, :a, :b)
|
14
|
+
)
|
15
|
+
)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_match_with_no_capture_defined_should_not_capture
|
20
|
+
sexp = s(:a, :b, :c)
|
21
|
+
assert_equal({}, Q?{ sexp }.satisfy?(sexp))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_match_with_capture_defined_should_capture
|
25
|
+
sexp = s(:a, :b, :c)
|
26
|
+
assert res = Q?{ sexp % 'cake' }.satisfy?(sexp)
|
27
|
+
assert_equal(sexp, res['cake'])
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_match_with_multiple_captures
|
31
|
+
assert res = Q?{ s(:add, atom % 'A', atom % 'B') }.satisfy?( s(:add, :a, :b) )
|
32
|
+
assert_equal(:a, res['A'])
|
33
|
+
assert_equal(:b, res['B'])
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_deep_matches
|
37
|
+
assert res = Q?{ s(:class, atom % 'name', s( _ % 'def1', _ % 'def2')) }.satisfy?( @ast_sexp )
|
38
|
+
assert_equal(:cake, res['name'])
|
39
|
+
assert_equal(s(:defn, :foo, s(:add, :a, :b)), res['def1'])
|
40
|
+
assert_equal(s(:defn, :bar, s(:sub, :a, :b)), res['def2'])
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_simple_searching
|
44
|
+
res = @ast_sexp / Q?{ s(:class, atom % 'name', _) }
|
45
|
+
assert_equal 1, res.length
|
46
|
+
assert_equal :cake, res.first['name']
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_iterative_searching
|
50
|
+
result = @ast_sexp / Q?{ s(:class, atom % 'class', _) } / Q?{ s(:defn, atom % 'method', _) }
|
51
|
+
assert_equal 2, result.length, "Should have matched both defn nodes"
|
52
|
+
|
53
|
+
result.each do |match_data|
|
54
|
+
assert match_data['method'], "Should have captured to 'method'"
|
55
|
+
assert match_data['class'], "Should have propogated 'class' capture"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_capturing_any_matchers
|
60
|
+
sexp = s(:add, :a, :b)
|
61
|
+
assert res = Q?{ any(s(:add, :a, :b), s(:sub, :a, :b)) % 'match' }.satisfy?( sexp )
|
62
|
+
assert_equal sexp, res['match']
|
63
|
+
|
64
|
+
assert res = Q?{ any(s(atom % 'name', :a, :b), s(:sub, :a, :b)) % 'match' }.satisfy?( sexp )
|
65
|
+
assert_equal sexp, res['match']
|
66
|
+
assert_equal :add, res['name']
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_capturing_all_matchers
|
70
|
+
sexp = s(:add, :a, :b)
|
71
|
+
assert res = Q?{ all(s(_, :a, :b), s(atom, :a, :b)) % 'match' }.satisfy?( sexp )
|
72
|
+
assert_equal sexp, res['match']
|
73
|
+
|
74
|
+
assert res = Q?{ all(s(_ % 'wild', :a, :b), s(atom % 'atom', :a, :b)) % 'match' }.satisfy?( sexp )
|
75
|
+
assert_equal sexp, res['match']
|
76
|
+
assert_equal :add, res['wild']
|
77
|
+
assert_equal :add, res['atom']
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_capturing_type_matches
|
81
|
+
sexp = s(:add, :a, :b)
|
82
|
+
assert res = Q?{ t(:add) % 'match' }.satisfy?( sexp )
|
83
|
+
assert_equal sexp, res['match']
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_capturing_child_matches
|
87
|
+
sexp = s(:a, s(:b, s(:c)))
|
88
|
+
assert res = Q?{ s(:a, child( s(atom % 'atom') ) % 'child' ) }.satisfy?( sexp )
|
89
|
+
assert_equal s(:b, s(:c)), res['child']
|
90
|
+
assert_equal :c, res['atom']
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_catpuring_pattern_matches
|
94
|
+
sexp = s(:add, :a, :b)
|
95
|
+
assert res = Q?{ s(m(/a../) % 'regexp', :a, :b) }.satisfy?( sexp )
|
96
|
+
assert_equal :add, res['regexp']
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_catpuring_include_matches
|
100
|
+
sexp = s(:add, :a, :b)
|
101
|
+
assert res = Q?{ include(:a) % 'include' }.satisfy?( sexp )
|
102
|
+
assert_equal sexp, res['include']
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_catpuring_nested_include_matches
|
106
|
+
sexp = s(:add, s(:a), :b)
|
107
|
+
assert res = Q?{ include(s(atom % 'atom' )) % 'include' }.satisfy?( sexp )
|
108
|
+
assert_equal sexp, res['include']
|
109
|
+
assert_equal :a, res['atom']
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_capturing_negations
|
113
|
+
sexp = s(:b)
|
114
|
+
assert res = Q?{ (-s(:a)) % 'not' }.satisfy?( sexp )
|
115
|
+
assert_equal s(:b), res['not']
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_capturing_negation_contents
|
119
|
+
sexp = s(:a, :b)
|
120
|
+
assert res = Q?{ -((include(:b) % 'b') & t(:c)) }.satisfy?( sexp )
|
121
|
+
assert !res['b'], 'b should not be included'
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_capturing_siblings
|
125
|
+
sexp = s(s(:a), s(s(:b)), s(:c))
|
126
|
+
assert res = Q?{ (s(atom) % 'a') >> (s(atom) % 'c') }.satisfy?( sexp )
|
127
|
+
assert_equal s(:a), res['a']
|
128
|
+
assert_equal s(:c), res['c']
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/sexp_path'
|
3
|
+
|
4
|
+
class SexpMatchingPathTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@ast_sexp = # Imagine it looks like a ruby AST
|
7
|
+
s(:class, :cake,
|
8
|
+
s(
|
9
|
+
s(:defn, :foo,
|
10
|
+
s(:add, :a, :b)
|
11
|
+
),
|
12
|
+
s(:defn, :bar,
|
13
|
+
s(:sub, :a, :b)
|
14
|
+
)
|
15
|
+
)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_searching_simple_examples
|
20
|
+
assert_search_count @ast_sexp, :class, 0,
|
21
|
+
"Literal should match nothing"
|
22
|
+
|
23
|
+
assert_search_count @ast_sexp, Q?{s(:class)}, 0,
|
24
|
+
"Should not exactly match anything"
|
25
|
+
|
26
|
+
assert_search_count @ast_sexp, Q?{s(:add, :a, :b)}, 1,
|
27
|
+
"Should exactly match once"
|
28
|
+
|
29
|
+
assert_search_count s(:a, s(:b, s(:c))), Q?{s(:b, s(:c))}, 1,
|
30
|
+
"Should match an exact subset"
|
31
|
+
|
32
|
+
assert_search_count s(:a, s(:b, s(:c))), Q?{s(:a, s(:c))}, 0,
|
33
|
+
"Should not match the child s(:c)"
|
34
|
+
|
35
|
+
assert_search_count @ast_sexp, Q?{s(:defn, :bar, s(:sub, :a, :b))}, 1,
|
36
|
+
"Nested sexp should exactly match once"
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_equality_of_atom
|
40
|
+
a = SexpPath::Matcher::Atom.new
|
41
|
+
assert a.satisfy?(:a), "Should match a symbol"
|
42
|
+
assert a.satisfy?(1), "Should match a number"
|
43
|
+
assert a.satisfy?(nil), "Should match nil"
|
44
|
+
assert !a.satisfy?(s()), "Should not match an sexp"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_searching_with_atom
|
48
|
+
assert_search_count s(:add, :a, :b), Q?{s(:add, atom, :b)} , 1,
|
49
|
+
"atom should match :a"
|
50
|
+
|
51
|
+
assert_search_count @ast_sexp, Q?{s(:defn, atom, s(atom, :a, :b) )}, 2,
|
52
|
+
"atoms should match :foo/:bar and :add/:sub"
|
53
|
+
|
54
|
+
assert_search_count s(:a, s()), Q?{s(:a, atom)}, 0,
|
55
|
+
"atom should not match s()"
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_searching_with_any
|
59
|
+
assert_search_count s(:foo, s(:a), s(:b)), Q?{s(any(:a,:b))}, 2,
|
60
|
+
"should not match either :a or :b"
|
61
|
+
|
62
|
+
assert_search_count s(:foo, s(:a), s(:b)), Q?{any( s(:a) ,s(:c))}, 1,
|
63
|
+
"sexp should not match s(:a)"
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_equality_of_wildacard
|
67
|
+
w = SexpPath::Matcher::Wild.new
|
68
|
+
assert w.satisfy?(:a ), "Should match a symbol"
|
69
|
+
assert w.satisfy?(1 ), "Should match a number"
|
70
|
+
assert w.satisfy?(nil ), "Should match nil"
|
71
|
+
assert w.satisfy?([] ), "Should match an array"
|
72
|
+
assert w.satisfy?(s() ), "Should match an sexp"
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_searching_with_wildcard
|
76
|
+
assert_search_count s(:add, :a, :b), Q?{s(:add, wild, :b)} , 1,
|
77
|
+
"wild should match :a"
|
78
|
+
|
79
|
+
assert_search_count @ast_sexp, Q?{s(:defn, :bar, _)}, 1,
|
80
|
+
"should match s(:defn, :bar, s(..))"
|
81
|
+
|
82
|
+
assert_search_count @ast_sexp, Q?{s(:defn, _, s(_, :a, :b) )}, 2,
|
83
|
+
"wilds should match :foo/:bar and :add/:sub"
|
84
|
+
|
85
|
+
assert_search_count s(:a, s()), Q?{s(:a, _)}, 1,
|
86
|
+
"wild should match s()"
|
87
|
+
|
88
|
+
assert_search_count s(:a, :b, :c), Q?{s(_,_,_)}, 1,
|
89
|
+
"multiple wilds should work"
|
90
|
+
|
91
|
+
assert_search_count @ast_sexp, Q?{wild}, 6,
|
92
|
+
"wild should match every sub expression"
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_searching_with_include
|
96
|
+
assert_search_count s(:add, :a, :b), Q?{include(:a)} , 1,
|
97
|
+
"Sexp should include atom :a"
|
98
|
+
|
99
|
+
assert_search_count @ast_sexp, Q?{include(:bar)}, 1,
|
100
|
+
"Sexp should include atom :bar"
|
101
|
+
|
102
|
+
assert_search_count @ast_sexp, Q?{s(:defn, atom, include(:a))}, 2,
|
103
|
+
"Sexp should match :defn with an sexp including :a"
|
104
|
+
|
105
|
+
assert_search_count @ast_sexp, Q?{include(:a)}, 2,
|
106
|
+
"Sexp should match an sexp including :a"
|
107
|
+
|
108
|
+
assert_search_count s(:a, s(:b, s(:c))), Q?{s(:a, include(:c))}, 0,
|
109
|
+
"Include should not descend"
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_or_matcher
|
113
|
+
assert Q?{s(:a) | s(:b)}.satisfy?( s(:a) ), "q(:a) should match s(:a)"
|
114
|
+
assert !Q?{s(:a) | s(:b)}.satisfy?( s(:c) ), "Should not match s(:c)"
|
115
|
+
|
116
|
+
assert_search_count s(:a, s(:b, :c), s(:b, :d)), Q?{s(:b, :c) | s(:b, :d)}, 2,
|
117
|
+
"Should match both (:b, :c) and (:b, :d)"
|
118
|
+
|
119
|
+
assert_search_count @ast_sexp, Q?{s(:add, :a, :b) | s(:defn, :bar, _)}, 2,
|
120
|
+
"Should match at any level"
|
121
|
+
end
|
122
|
+
|
123
|
+
# For symetry, kind of silly examples
|
124
|
+
def test_and_matcher
|
125
|
+
assert !Q?{s(:a) & s(:b)}.satisfy?(s(:a)), "s(:a) is not both s(:a) and s(:b)"
|
126
|
+
assert Q?{s(:a) & s(atom)}.satisfy?(s(:a)), "s(:a) matches both criteria"
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_child_matcher
|
130
|
+
assert_search_count @ast_sexp, Q?{s(:class, :cake, child( s(:add, :a, :b) ) )}, 1,
|
131
|
+
"Should match s(:class, :cake ...) and descend to find s(:add, :a, :b)"
|
132
|
+
|
133
|
+
assert_search_count @ast_sexp, Q?{s(:class, :cake, child(include(:a)))}, 1,
|
134
|
+
"Should match once since there exists a child which includes :a"
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_not_matcher
|
138
|
+
assert !Q?{-wild}.satisfy?(s(:a)), "wild should match s(:a)"
|
139
|
+
assert Q?{-(s(:b))}.satisfy?(s(:a)), "s(:b) should not match s(:b)"
|
140
|
+
assert Q?{is_not(s(:b))}.satisfy?(s(:a)),"should behave the same as unary minus"
|
141
|
+
assert !Q?{-(s(atom))}.satisfy?(s(:a)), "should not match, :a is an atom"
|
142
|
+
assert Q?{s(is_not(:b))}.satisfy?(s(:a)), "should match s(:a) since the atom is not :b"
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_sibling_matcher
|
146
|
+
assert_equal SexpPath::Matcher::Sibling, Q?{(s(:a) >> s(:b))}.class
|
147
|
+
|
148
|
+
assert Q?{s(:a) >> s(:b)}.satisfy?( s(s(:a), s(:b)) ), "should match s(:a) has an immediate sibling s(:b)"
|
149
|
+
assert Q?{s(:a) >> s(:b)}.satisfy?( s(s(:a), s(:b), s(:c)) ), "should match s(:a) has an immediate sibling s(:b)"
|
150
|
+
assert Q?{s(:a) >> s(:c)}.satisfy?( s(s(:a), s(:b), s(:c)) ), "should match s(:a) a sibling s(:b)"
|
151
|
+
assert !Q?{s(:c) >> s(:a)}.satisfy?( s(s(:a), s(:b), s(:c)) ), "should not match s(:a) does not follow s(:c)"
|
152
|
+
assert !Q?{s(:a) >> s(:a)}.satisfy?( s(s(:a)) ), "should not match s(:a) has no siblings"
|
153
|
+
assert Q?{s(:a) >> s(:a)}.satisfy?( s(s(:a), s(:b), s(:a)) ), "should match s(:a) has another sibling s(:a)"
|
154
|
+
|
155
|
+
assert_search_count @ast_sexp, Q?{t(:defn) >> t(:defn)}, 1,
|
156
|
+
"Should match s(:add, :a, :b) followed by s(:sub, :a, :b)"
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_pattern_matcher
|
160
|
+
assert Q?{m(/a/)}.satisfy?(:a), "Should match :a"
|
161
|
+
assert Q?{m(/^test/)}.satisfy?(:test_case), "Should match :test_case"
|
162
|
+
assert Q?{m('test')}.satisfy?(:test), "Should match :test #{Q?{m('test')}.inspect}"
|
163
|
+
assert !Q?{m('test')}.satisfy?(:test_case), "Should only match whole word 'test'"
|
164
|
+
assert !Q?{m(/a/)}.satisfy?(s(:a)), "Should not match s(:a)"
|
165
|
+
|
166
|
+
assert_search_count @ast_sexp, Q?{s(m(/\w{3}/), :a, :b)}, 2,
|
167
|
+
"Should match s(:add, :a, :b) and s(:sub, :a, :b)"
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_search_method
|
171
|
+
assert_equal 1, @ast_sexp.search( s(:sub, :a, :b)).length
|
172
|
+
assert_equal 2, @ast_sexp.search( Q?{s(:defn, atom, wild)} ).length
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_search_collection
|
176
|
+
# test method
|
177
|
+
assert_equal SexpPath::SexpCollection, @ast_sexp.search( s(:sub, :a, :b)).class
|
178
|
+
# test binary operator
|
179
|
+
assert_equal SexpPath::SexpCollection, (@ast_sexp / s(:sub, :a, :b)).class
|
180
|
+
# test sub searches
|
181
|
+
collection = @ast_sexp / Q?{s(:defn, atom, _)} / Q?{s(atom, :a, :b)}
|
182
|
+
assert_equal SexpPath::SexpCollection, collection.class
|
183
|
+
assert_equal 2, collection.length
|
184
|
+
assert_equal [s(:add, :a, :b), s(:sub, :a, :b)], collection.map{|m| m.sexp}
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_sexp_type_matching
|
188
|
+
assert Q?{t(:a)}.satisfy?( s(:a) )
|
189
|
+
assert Q?{t(:a)}.satisfy?( s(:a, :b, s(:oh_hai), :d) )
|
190
|
+
assert_search_count @ast_sexp, Q?{t(:defn)}, 2,
|
191
|
+
"Should match s(:defn, _, _)"
|
192
|
+
end
|
193
|
+
|
194
|
+
# Still not sure if I like this
|
195
|
+
def test_block_matching
|
196
|
+
sb = SexpPath::Matcher::Block
|
197
|
+
|
198
|
+
assert sb.new{|o| o == s(:a)}.satisfy?(s(:a)), "Should match simple equality"
|
199
|
+
assert sb.new{|o| o.length == 1}.satisfy?(s(:a)), "Should match length check"
|
200
|
+
|
201
|
+
assert_search_count s(:a, s(:b), s(:c), s(:d,:t) ), sb.new{|o| o.length == 2 }, 1,
|
202
|
+
"Should match s(:d, :t)"
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
def assert_search_count(sexp, example, count, message)
|
207
|
+
i = 0
|
208
|
+
sexp.search_each(example){|match| i += 1}
|
209
|
+
assert_equal count, i, message + "\nSearching for: #{example.inspect}\nIn: #{sexp.inspect}"
|
210
|
+
end
|
211
|
+
end
|