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