sexp_processor 4.9.0 → 4.10.0b1

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
  SHA1:
3
- metadata.gz: c8a4cb1d34e3c86a00ee122db28cf4d3b7d2ef34
4
- data.tar.gz: bec449692c425f44e6e40da22b7dae0192956640
3
+ metadata.gz: 65bf12b262290613b30fc69ea149c71e8943ddb8
4
+ data.tar.gz: 21c0cd86b86276dbf0dc862205b28cb145c2e26a
5
5
  SHA512:
6
- metadata.gz: 7ca8f74ecc9e62dc1dd2d2eb272b0cd2502255fa956c75bf8bdad60990070ec05a03d8806c6acf6f61f6d30915b5d3caea73410c551f1a21c7eb528d085f7553
7
- data.tar.gz: ef4dde66d8a7a6903ddad8520c1bba5ac2885c3fea7a13d54f4d6affd7e42780e78faaebf7c70aa7b26a4aca650673c26d9f39a166bebb1d1cd201e31be25bc0
6
+ metadata.gz: edc58c9bcf7547d5457787581ca887b9ca843fe64224cdf4b416b095b6e577940f62e1014716409d8ea9ef2c273860eed19fd52a4d3a34779ada9319158c69e3
7
+ data.tar.gz: 8bc9add51b1b81eb67f6b651a13f78450bf97e72716eaa346f5c90f782dbc7e733c8e5c45a657903c6e5df4fed3abcb5c6a51e735524cc32eb8d3f63143b3639
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/History.txt CHANGED
@@ -1,3 +1,48 @@
1
+ === 4.10.0b1 / 2017-06-13
2
+
3
+ * 2 major enhancements:
4
+
5
+ * Added experimental pattern matcher to Sexp. Forked from sexp_path.
6
+ * Extended s to take a block and return a matcher: eg s{ s(:defn, atom, _, ___) }
7
+
8
+ * 23 minor enhancements:
9
+
10
+ * Added $STRICT_SEXP to crank down Sexp.[] and friends.
11
+ * Added Matcher#/ w/ real functionality.
12
+ * Added Sexp#/ to search with new patterns.
13
+ * Added Sexp#map to ensure you get a Sexp back.
14
+ * Added Sexp#new to create a new sexp with the same file/line/comment info.
15
+ * Added Sexp#search_each to recursively search w/ new patterns. Returns enum if no block.
16
+ * Added Sexp#sexp_body=
17
+ * Added Sexp::Matcher.match_subs? and .match_subs= to extend =~ so you can match strictly.
18
+ * Added Sexp::Matcher.parse to convert lispy string to safe matcher: "(defn atom _ ___)"
19
+ * Added all mutation methods to STRICT_SEXP >= 4
20
+ * Added deprecation message to Sexp#structure for [s(...)] forms.
21
+ * Added strict_sexp.rb to help you clamp down for future changes. STRICT_SEXP=1+
22
+ * Auto-require strict_sexp if $STRICT_SEXP is > 0.
23
+ * Converted a lot of indexed access to sexp_type/sexp_body, etc.
24
+ * Finally enforced SexpProcessor#process to only process sexps, not bare arrays.
25
+ * Made Sexp#/ double-dispatch to Matcher#/.
26
+ * Made Sexp#gsub work with new patterns.
27
+ * Made Sexp#sub work with new patterns.
28
+ * Made SexpProcessor STRICT_SEXP=4 compliant.
29
+ * Retired SexpMatchSpecial & SexpAny. Never used by anything AFAICT.
30
+ * Sexp#=== goes back to default.
31
+ * Sexp#=~(pat) calls pat =~ self.
32
+ * Sexp#sexp_body now takes optional offset. Use instead of sexp[n..-1].
33
+
34
+ * 9 bug fixes:
35
+
36
+ * Extended Sexp::Matcher::Parser.parse to lex more forms of regexp.
37
+ * Finished off all missing doco.
38
+ * Fixed == methods on all Matcher classes to include ivars.
39
+ * Fixed Child#satisfy? to properly return false if failed.
40
+ * Fixed Sexp#sexp_body to return a sexp using Sexp#new.
41
+ * Fixed map to use Sexp#new.
42
+ * Only try to set c_type if it responds to it. Make STRICT_SEXP safe.
43
+ * R2C has a hack in SexpProcessor to call sexp_type=. Renamed to c_type= in R2C.
44
+ * Removed very obsolete attrset test from pt_testcase.rb
45
+
1
46
  === 4.9.0 / 2017-04-13
2
47
 
3
48
  * 9 minor enhancements:
data/Manifest.txt CHANGED
@@ -6,6 +6,7 @@ lib/composite_sexp_processor.rb
6
6
  lib/pt_testcase.rb
7
7
  lib/sexp.rb
8
8
  lib/sexp_processor.rb
9
+ lib/strict_sexp.rb
9
10
  lib/unique.rb
10
11
  test/test_composite_sexp_processor.rb
11
12
  test/test_environment.rb
data/README.txt CHANGED
@@ -15,21 +15,50 @@ for your language processing pleasure.
15
15
 
16
16
  * Allows you to write very clean filters.
17
17
 
18
+ * Includes MethodBasedSexpProcessor
19
+
20
+ * Makes writing language processors even easier!
21
+
18
22
  * Sexp provides a simple and clean interface to creating and manipulating ASTs.
19
23
 
24
+ * Includes new pattern matching system.
25
+
20
26
  == SYNOPSIS:
21
27
 
22
- class MyProcessor < SexpProcessor
23
- def initialize
24
- super
25
- self.strict = false
28
+ You can use SexpProcessor to do all kinds of language processing. Here
29
+ is a simple example of a simple documentation printer:
30
+
31
+ class ArrrDoc < MethodBasedSexpProcessor
32
+ def process_class exp
33
+ super do
34
+ puts "#{self.klass_name}: #{exp.comments}"
35
+ end
26
36
  end
27
- def process_lit(exp)
28
- val = exp.shift
29
- return val
37
+
38
+ def process_defn exp
39
+ super do
40
+ args, *_body = exp
41
+
42
+ puts "#{self.method_name}(#{process_args args}): #{exp.comments}"
43
+ end
30
44
  end
31
45
  end
32
46
 
47
+ Sexp provides a lot of power with the new pattern matching system.
48
+ Here is an example that parses all the test files using RubyParser and
49
+ then quickly finds all the test methods and prints their names:
50
+
51
+ >> require "ruby_parser";
52
+ >> rp = RubyParser.new;
53
+ >> matcher = Sexp::Matcher.parse "(defn [m /^test_/] ___)"
54
+ => q(:defn, m(/^test_/), ___)
55
+ >> paths = Dir["test/**/*.rb"];
56
+ >> sexps = s(:block, *paths.map { |path| rp.process File.read(path), path });
57
+ >> (sexps / matcher).size
58
+ => 189
59
+ ?> (sexps / matcher).map { |(_, name, *_rest)| name }.sort
60
+ => [:test_all, :test_amp, :test_and_satisfy_eh, :test_any_search, ...]
61
+
33
62
  == REQUIREMENTS:
34
63
 
35
64
  * rubygems
data/Rakefile CHANGED
@@ -5,8 +5,12 @@ require 'hoe'
5
5
 
6
6
  Hoe.plugin :seattlerb
7
7
 
8
+ Hoe.add_include_dirs("../../ruby_parser/dev/lib")
9
+
8
10
  Hoe.spec 'sexp_processor' do
9
11
  developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
12
+
13
+ license "MIT"
10
14
  end
11
15
 
12
16
  # vim: syntax=ruby
@@ -1,4 +1,4 @@
1
- require 'sexp_processor'
1
+ require "sexp_processor"
2
2
 
3
3
  ##
4
4
  # Implements the Composite pattern on SexpProcessor. Need we say more?
@@ -24,7 +24,7 @@ class CompositeSexpProcessor < SexpProcessor
24
24
  ##
25
25
  # Add a +processor+ to the list of processors to run.
26
26
 
27
- def <<(processor)
27
+ def << processor
28
28
  raise ArgumentError, "Can only add sexp processors" unless
29
29
  SexpProcessor === processor || processor.respond_to?(:process)
30
30
  @processors << processor
@@ -34,14 +34,14 @@ class CompositeSexpProcessor < SexpProcessor
34
34
  # Run +exp+ through all of the processors, returning the final
35
35
  # result.
36
36
 
37
- def process(exp)
37
+ def process exp
38
38
  @processors.each do |processor|
39
39
  exp = processor.process(exp)
40
40
  end
41
41
  exp
42
42
  end
43
43
 
44
- def on_error_in(node_type, &block)
44
+ def on_error_in node_type, &block
45
45
  @processors.each do |processor|
46
46
  processor.on_error_in(node_type, &block)
47
47
  end
data/lib/pt_testcase.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # encoding: US-ASCII
2
2
 
3
3
  $TESTING = true
4
+ # :stopdoc:
4
5
 
5
- require 'minitest/test'
6
- require 'sexp_processor' # for deep_clone
6
+ require "minitest/test"
7
+ require "sexp_processor" # for deep_clone
7
8
 
8
9
  # key:
9
10
  # wwtt = what were they thinking?
@@ -12,7 +13,7 @@ class Examples
12
13
  attr_reader :reader
13
14
  attr_writer :writer
14
15
 
15
- def a_method(x); x+1; end
16
+ def a_method x; x+1; end
16
17
  alias an_alias a_method
17
18
 
18
19
  define_method(:bmethod_noargs) do
@@ -136,9 +137,9 @@ class ParseTreeTestCase < Minitest::Test
136
137
  extra_input = []
137
138
 
138
139
  _, expected, extra_expected = *expected if
139
- Array === expected and expected.first == :defx
140
+ Array === expected and expected.sexp_type == :defx
140
141
  _, input, extra_input = *input if
141
- Array === input and input.first == :defx
142
+ Array === input and input.sexp_type == :defx
142
143
 
143
144
  # OMG... I can't believe I have to do this this way. these
144
145
  # hooks are here instead of refactoring this define_method
@@ -167,7 +168,7 @@ class ParseTreeTestCase < Minitest::Test
167
168
  install_missing_reporter
168
169
  clone_same
169
170
 
170
- output_name = klass.name.to_s.sub(/^Test/, '')
171
+ output_name = klass.name.to_s.sub(/^Test/, "")
171
172
 
172
173
  input_name = self.previous(output_name)
173
174
 
@@ -202,7 +203,7 @@ class ParseTreeTestCase < Minitest::Test
202
203
  end
203
204
  end
204
205
 
205
- def self.previous(key, extra=0) # FIX: remove R2C code
206
+ def self.previous key, extra=0 # FIX: remove R2C code
206
207
  idx = @@testcase_order.index(key)
207
208
 
208
209
  raise "Unknown class #{key} in @@testcase_order" if idx.nil?
@@ -307,7 +308,7 @@ class ParseTreeTestCase < Minitest::Test
307
308
  "Ruby2Ruby" => "10")
308
309
 
309
310
  add_18tests("str_question_literal",
310
- "Ruby" => '?a',
311
+ "Ruby" => "?a",
311
312
  "ParseTree" => s(:lit, 97),
312
313
  "Ruby2Ruby" => "97")
313
314
 
@@ -595,7 +596,7 @@ class ParseTreeTestCase < Minitest::Test
595
596
  "ParseTree" => s(:str, "\n"))
596
597
 
597
598
  add_19tests("str_question_literal",
598
- "Ruby" => '?a',
599
+ "Ruby" => "?a",
599
600
  "ParseTree" => s(:str, "a"))
600
601
 
601
602
  add_19tests("unless_post_not",
@@ -748,13 +749,6 @@ class ParseTreeTestCase < Minitest::Test
748
749
  s(:lit, 42), s(:lit, 24))),
749
750
  "Ruby2Ruby" => "a = []\na[42] = 24\n")
750
751
 
751
- add_tests("attrset",
752
- "Ruby" => [Examples, :writer=],
753
- "ParseTree" => s(:defn, :writer=,
754
- s(:args, :arg),
755
- s(:attrset, :@writer)),
756
- "Ruby2Ruby" => "attr_writer :writer")
757
-
758
752
  add_tests("back_ref",
759
753
  "Ruby" => "[$&, $`, $', $+]",
760
754
  "ParseTree" => s(:array,
@@ -1534,15 +1528,15 @@ class ParseTreeTestCase < Minitest::Test
1534
1528
 
1535
1529
  add_tests("dregx_interp",
1536
1530
  "Ruby" => "/#\{@rakefile}/",
1537
- "ParseTree" => s(:dregx, '', s(:evstr, s(:ivar, :@rakefile))))
1531
+ "ParseTree" => s(:dregx, "", s(:evstr, s(:ivar, :@rakefile))))
1538
1532
 
1539
1533
  add_tests("dregx_interp_empty",
1540
1534
  "Ruby" => "/a#\{}b/",
1541
- "ParseTree" => s(:dregx, 'a', s(:evstr), s(:str, "b")))
1535
+ "ParseTree" => s(:dregx, "a", s(:evstr), s(:str, "b")))
1542
1536
 
1543
1537
  add_tests("dregx_n",
1544
1538
  "Ruby" => '/#{1}/n',
1545
- "ParseTree" => s(:dregx, '',
1539
+ "ParseTree" => s(:dregx, "",
1546
1540
  s(:evstr, s(:lit, 1)), /x/n.options))
1547
1541
 
1548
1542
  add_tests("dregx_once",
@@ -1554,7 +1548,7 @@ class ParseTreeTestCase < Minitest::Test
1554
1548
 
1555
1549
  add_tests("dregx_once_n_interp",
1556
1550
  "Ruby" => "/#\{IAC}#\{SB}/no",
1557
- "ParseTree" => s(:dregx_once, '',
1551
+ "ParseTree" => s(:dregx_once, "",
1558
1552
  s(:evstr, s(:const, :IAC)),
1559
1553
  s(:evstr, s(:const, :SB)), /x/n.options))
1560
1554
 
@@ -1624,7 +1618,7 @@ class ParseTreeTestCase < Minitest::Test
1624
1618
  add_tests("dstr_heredoc_windoze_sucks",
1625
1619
  "Ruby" => "<<-EOF\r\ndef test_#\{action}_valid_feed\r\n EOF\r\n",
1626
1620
  "ParseTree" => s(:dstr,
1627
- 'def test_',
1621
+ "def test_",
1628
1622
  s(:evstr, s(:call, nil, :action)),
1629
1623
  s(:str, "_valid_feed\n")),
1630
1624
  "Ruby2Ruby" => "\"def test_#\{action}_valid_feed\\n\"")
@@ -1681,7 +1675,7 @@ class ParseTreeTestCase < Minitest::Test
1681
1675
  "Ruby" => "t = 5\n`touch #\{t}`\n",
1682
1676
  "ParseTree" => s(:block,
1683
1677
  s(:lasgn, :t, s(:lit, 5)),
1684
- s(:dxstr, 'touch ', s(:evstr, s(:lvar, :t)))))
1678
+ s(:dxstr, "touch ", s(:evstr, s(:lvar, :t)))))
1685
1679
 
1686
1680
  add_tests("ensure",
1687
1681
  "Ruby" => "begin\n (1 + 1)\nrescue SyntaxError => e1\n 2\nrescue Exception => e2\n 3\nelse\n 4\nensure\n 5\nend",
@@ -2164,7 +2158,7 @@ class ParseTreeTestCase < Minitest::Test
2164
2158
  "ParseTree" => s(:lit, /x/))
2165
2159
 
2166
2160
  add_tests("lit_regexp_i_wwtt",
2167
- "Ruby" => 'str.split(//i)',
2161
+ "Ruby" => "str.split(//i)",
2168
2162
  "ParseTree" => s(:call,
2169
2163
  s(:call, nil, :str),
2170
2164
  :split,
@@ -2369,7 +2363,7 @@ class ParseTreeTestCase < Minitest::Test
2369
2363
  s(:call,
2370
2364
  s(:call, nil, :d),
2371
2365
  :e,
2372
- s(:str, 'f')))))
2366
+ s(:str, "f")))))
2373
2367
 
2374
2368
  add_tests("match",
2375
2369
  "Ruby" => "1 if /x/",
@@ -3083,7 +3077,7 @@ class ParseTreeTestCase < Minitest::Test
3083
3077
 
3084
3078
  add_tests("xstr",
3085
3079
  "Ruby" => "`touch 5`",
3086
- "ParseTree" => s(:xstr, 'touch 5'))
3080
+ "ParseTree" => s(:xstr, "touch 5"))
3087
3081
 
3088
3082
  add_tests("yield_0",
3089
3083
  "Ruby" => "yield",
@@ -3125,3 +3119,5 @@ class ParseTreeTestCase < Minitest::Test
3125
3119
  # end
3126
3120
 
3127
3121
  end
3122
+
3123
+ # :startdoc:
data/lib/sexp.rb CHANGED
@@ -7,66 +7,66 @@ $TESTING ||= false # unless defined $TESTING
7
7
  # dispatch the Sexp to for processing.
8
8
 
9
9
  class Sexp < Array # ZenTest FULL
10
+ ##
11
+ # A setter for the line this sexp was found on. Usually set by ruby_parser.
10
12
 
11
13
  attr_writer :line
12
- attr_accessor :file, :comments
13
14
 
14
- @@array_types = [ :array, :args, ]
15
+ ##
16
+ # Accessors for the file. Usually set by ruby_parser.
17
+
18
+ attr_accessor :file
19
+
20
+ ##
21
+ # Optional comments above/aside this sexp. Usually set by ruby_parser.
22
+
23
+ attr_accessor :comments
24
+
25
+ @@array_types = [ :array, :args ] # TODO: remove
15
26
 
16
27
  ##
17
28
  # Create a new Sexp containing +args+.
18
29
 
19
- def initialize(*args)
30
+ def initialize *args
20
31
  super(args)
21
32
  end
22
33
 
23
34
  ##
24
35
  # Creates a new Sexp from Array +a+.
25
36
 
26
- def self.from_array(a)
37
+ def self.from_array a
27
38
  ary = Array === a ? a : [a]
28
39
 
29
- result = self.new
30
-
31
- ary.each do |x|
32
- case x
33
- when Sexp
34
- result << x
35
- when Array
36
- result << self.from_array(x)
37
- else
38
- result << x
39
- end
40
- end
41
-
42
- result
43
- end
44
-
45
- def ==(obj) # :nodoc:
46
- obj.class == self.class and super
40
+ self.new(*ary.map { |x|
41
+ case x
42
+ when Sexp
43
+ x
44
+ when Array
45
+ self.from_array(x)
46
+ else
47
+ x
48
+ end
49
+ })
47
50
  end
48
51
 
49
52
  ##
50
- # Returns true if this Sexp's pattern matches +sexp+.
51
-
52
- def ===(sexp)
53
- return nil unless Sexp === sexp
54
- pattern = self # this is just for my brain
55
-
56
- return true if pattern == sexp
57
-
58
- sexp.each do |subset|
59
- return true if pattern === subset
60
- end
61
-
62
- return nil
53
+ # Creates a new sexp with the new contents of +body+, but with the
54
+ # same +file+, +line+, and +comment+ as self.
55
+
56
+ def new(*body)
57
+ r = self.class.new(*body) # ensures a sexp from map
58
+ r.file = self.file if self.file
59
+ r.line = self.line if self.line
60
+ r.comments = self.comments if self.comments
61
+ r
63
62
  end
64
63
 
65
- ##
66
- # Returns true if this Sexp matches +pattern+. (Opposite of #===.)
64
+ def map &blk # :nodoc:
65
+ self.new(*super(&blk)) # ensures a sexp from map
66
+ end
67
67
 
68
- def =~(pattern)
69
- return pattern === self
68
+ def == obj # :nodoc:
69
+ obj.class == self.class and super # only because of a bug in ruby
70
70
  end
71
71
 
72
72
  ##
@@ -75,18 +75,19 @@ class Sexp < Array # ZenTest FULL
75
75
  # REFACTOR: to TypedSexp - we only care when we have units.
76
76
 
77
77
  def array_type?
78
- type = self.first
78
+ warn "DEPRECATED: please file an issue if you actually use this. from #{caller.first}"
79
+ type = self.sexp_type
79
80
  @@array_types.include? type
80
81
  end
81
82
 
82
83
  def compact # :nodoc:
83
- self.delete_if { |o| o.nil? }
84
+ self.delete_if(&:nil?)
84
85
  end
85
86
 
86
87
  ##
87
88
  # Recursively enumerates the sexp yielding to +block+ for every element.
88
89
 
89
- def deep_each(&block)
90
+ def deep_each &block
90
91
  return enum_for(:deep_each) unless block_given?
91
92
 
92
93
  self.each_sexp do |sexp|
@@ -95,6 +96,9 @@ class Sexp < Array # ZenTest FULL
95
96
  end
96
97
  end
97
98
 
99
+ ##
100
+ # Return the maximum depth of the sexp. One-based.
101
+
98
102
  def depth
99
103
  1 + (each_sexp.map(&:depth).max || 0)
100
104
  end
@@ -102,14 +106,12 @@ class Sexp < Array # ZenTest FULL
102
106
  ##
103
107
  # Enumeratates the sexp yielding to +b+ when the node_type == +t+.
104
108
 
105
- def each_of_type(t, &b)
109
+ def each_of_type t, &b
106
110
  return enum_for(:each_of_type) unless block_given?
107
111
 
108
- each do | elem |
109
- if Sexp === elem then
110
- elem.each_of_type(t, &b)
111
- b.call(elem) if elem.first == t
112
- end
112
+ each_sexp do | sexp |
113
+ sexp.each_of_type(t, &b)
114
+ yield sexp if sexp.sexp_type == t
113
115
  end
114
116
  end
115
117
 
@@ -130,12 +132,12 @@ class Sexp < Array # ZenTest FULL
130
132
  # Replaces all elements whose node_type is +from+ with +to+. Used
131
133
  # only for the most trivial of rewrites.
132
134
 
133
- def find_and_replace_all(from, to)
135
+ def find_and_replace_all from, to
134
136
  each_with_index do | elem, index |
135
137
  if Sexp === elem then
136
138
  elem.find_and_replace_all(from, to)
137
- else
138
- self[index] = to if elem == from
139
+ elsif elem == from
140
+ self[index] = to
139
141
  end
140
142
  end
141
143
  end
@@ -143,31 +145,35 @@ class Sexp < Array # ZenTest FULL
143
145
  ##
144
146
  # Replaces all Sexps matching +pattern+ with Sexp +repl+.
145
147
 
146
- def gsub(pattern, repl)
148
+ def gsub pattern, repl
147
149
  return repl if pattern == self
148
150
 
149
- new = self.map do |subset|
151
+ new = self.map { |subset|
150
152
  case subset
151
153
  when Sexp then
152
- subset.gsub(pattern, repl)
154
+ if Matcher === pattern && pattern.satisfy?(subset) then # TODO: make === be satisfy? maybe?
155
+ repl.dup rescue repl
156
+ else
157
+ subset.gsub pattern, repl
158
+ end
153
159
  else
154
160
  subset
155
161
  end
156
- end
162
+ }
157
163
 
158
- return Sexp.from_array(new)
164
+ Sexp.from_array new
159
165
  end
160
166
 
161
167
  def inspect # :nodoc:
162
- sexp_str = self.map {|x|x.inspect}.join(', ')
163
- if ENV['VERBOSE'] && line then
168
+ sexp_str = self.map(&:inspect).join ", "
169
+ if ENV["VERBOSE"] && line then
164
170
  "s(#{sexp_str}).line(#{line})"
165
171
  else
166
172
  "s(#{sexp_str})"
167
173
  end
168
174
  end
169
175
 
170
- def find_node name, delete = false
176
+ def find_node name, delete = false # :nodoc:
171
177
  matches = find_nodes name
172
178
 
173
179
  case matches.size
@@ -186,7 +192,7 @@ class Sexp < Array # ZenTest FULL
186
192
  # Find every node with type +name+.
187
193
 
188
194
  def find_nodes name
189
- find_all { | sexp | Sexp === sexp and sexp.first == name }
195
+ each_sexp.find_all { |sexp| sexp.sexp_type == name }
190
196
  end
191
197
 
192
198
  ##
@@ -194,7 +200,7 @@ class Sexp < Array # ZenTest FULL
194
200
  # returns the line number. This allows you to do message cascades
195
201
  # and still get the sexp back.
196
202
 
197
- def line(n=nil)
203
+ def line n = nil
198
204
  if n then
199
205
  @line = n
200
206
  self
@@ -214,14 +220,7 @@ class Sexp < Array # ZenTest FULL
214
220
  # Returns the size of the sexp, flattened.
215
221
 
216
222
  def mass
217
- @mass ||=
218
- inject(1) { |t, s|
219
- if Sexp === s then
220
- t + s.mass
221
- else
222
- t
223
- end
224
- }
223
+ @mass ||= inject(1) { |t, s| Sexp === s ? t + s.mass : t }
225
224
  end
226
225
 
227
226
  ##
@@ -244,11 +243,11 @@ class Sexp < Array # ZenTest FULL
244
243
  super
245
244
  end
246
245
 
247
- def pretty_print(q) # :nodoc:
248
- nnd = ')'
249
- nnd << ".line(#{line})" if line && ENV['VERBOSE']
246
+ def pretty_print q # :nodoc:
247
+ nnd = ")"
248
+ nnd << ".line(#{line})" if line && ENV["VERBOSE"]
250
249
 
251
- q.group(1, 's(', nnd) do
250
+ q.group(1, "s(", nnd) do
252
251
  q.seplist(self) {|v| q.pp v }
253
252
  end
254
253
  end
@@ -267,11 +266,19 @@ class Sexp < Array # ZenTest FULL
267
266
  self[0] = v
268
267
  end
269
268
 
269
+ ##
270
+ # Returns the Sexp body (starting at +from+, defaulting to 1), ie
271
+ # the values without the node type.
272
+
273
+ def sexp_body from = 1
274
+ self.new(*self[from..-1])
275
+ end
276
+
270
277
  ##
271
278
  # Returns the Sexp body, ie the values without the node type.
272
279
 
273
- def sexp_body
274
- self[1..-1]
280
+ def sexp_body= v
281
+ self[1..-1] = v
275
282
  end
276
283
 
277
284
  alias :head :sexp_type
@@ -284,29 +291,27 @@ class Sexp < Array # ZenTest FULL
284
291
  def shift
285
292
  raise "I'm empty" if self.empty?
286
293
  super
287
- end if ($DEBUG or $TESTING) unless (defined?(RUBY_ENGINE) and RUBY_ENGINE == "maglev")
294
+ end if ($DEBUG or $TESTING)
288
295
 
289
296
  ##
290
297
  # Returns the bare bones structure of the sexp.
291
298
  # s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))
292
299
 
293
300
  def structure
294
- if Array === self.first then
301
+ if Array === self.sexp_type then
302
+ warn "NOTE: form s(s(:subsexp)).structure is deprecated. Removing in 5.0"
295
303
  s(:bogus, *self).structure # TODO: remove >= 4.2.0
296
304
  else
297
- result = s(self.first)
298
- self.each do |subexp|
299
- result << subexp.structure if Sexp === subexp
300
- end
301
- result
305
+ s(self.sexp_type, *each_sexp.map(&:structure))
302
306
  end
303
307
  end
304
308
 
305
309
  ##
306
310
  # Replaces the Sexp matching +pattern+ with +repl+.
307
311
 
308
- def sub(pattern, repl)
312
+ def sub pattern, repl
309
313
  return repl.dup if pattern == self
314
+ return repl.dup if Matcher === pattern && pattern.satisfy?(self)
310
315
 
311
316
  done = false
312
317
 
@@ -318,12 +323,12 @@ class Sexp < Array # ZenTest FULL
318
323
  when Sexp then
319
324
  if pattern == subset then
320
325
  done = true
321
- repl.dup
322
- elsif pattern === subset then
326
+ repl.dup rescue repl
327
+ elsif Matcher === pattern && pattern.satisfy?(subset) then
323
328
  done = true
324
- subset.sub pattern, repl
329
+ repl.dup rescue repl
325
330
  else
326
- subset
331
+ subset.sub pattern, repl
327
332
  end
328
333
  else
329
334
  subset
@@ -331,41 +336,1079 @@ class Sexp < Array # ZenTest FULL
331
336
  end
332
337
  end
333
338
 
334
- return Sexp.from_array(new)
339
+ Sexp.from_array new
335
340
  end
336
341
 
337
342
  def to_a # :nodoc:
338
343
  self.map { |o| Sexp === o ? o.to_a : o }
339
344
  end
340
345
 
341
- def to_s # :nodoc:
342
- inspect
343
- end
346
+ alias to_s inspect # :nodoc:
344
347
  end
345
348
 
346
- class SexpMatchSpecial < Sexp; end
349
+ ##
350
+ # This is a very important shortcut to make using Sexps much more awesome.
351
+ #
352
+ # In its normal form +s(...)+, creates a Sexp instance. If passed a
353
+ # block, it creates a Sexp::Matcher using the factory methods on Sexp.
354
+ #
355
+ # See Matcher and other factory class methods on Sexp.
356
+
357
+ def s *args, &blk
358
+ return Sexp.class_eval(&blk) if blk
359
+ Sexp.new(*args)
360
+ end
347
361
 
348
- class SexpAny < SexpMatchSpecial
349
- def ==(o)
350
- Sexp === o
362
+ class Sexp #:nodoc:
363
+ ##
364
+ # Verifies that +pattern+ is a Matcher and then dispatches to its
365
+ # #=~ method.
366
+ #
367
+ # See Matcher.=~
368
+
369
+ def =~ pattern
370
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
371
+ pattern =~ self
351
372
  end
352
373
 
353
- def ===(o)
354
- return Sexp === o
374
+ ##
375
+ # Verifies that +pattern+ is a Matcher and then dispatches to its
376
+ # #satisfy? method.
377
+ #
378
+ # TODO: rename match?
379
+
380
+ def satisfy? pattern
381
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
382
+ pattern.satisfy? self
355
383
  end
356
384
 
357
- def inspect
358
- "ANY"
385
+ ##
386
+ # Verifies that +pattern+ is a Matcher and then dispatches to its #/
387
+ # method.
388
+ #
389
+ # TODO: rename grep? match_all ? find_all ?
390
+
391
+ def / pattern
392
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
393
+ pattern / self
359
394
  end
360
- end
361
395
 
362
- module SexpMatchSpecials
363
- def ANY(); return SexpAny.new; end
364
- end
396
+ ##
397
+ # Recursively searches for the +pattern+ yielding the matches.
365
398
 
366
- ##
367
- # This is a very important shortcut to make using Sexps much more awesome.
399
+ def search_each pattern, &block # TODO: rename to grep?
400
+ raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
368
401
 
369
- def s(*args)
370
- Sexp.new(*args)
402
+ return enum_for(:search_each, pattern) unless block_given?
403
+
404
+ if pattern.satisfy? self then
405
+ yield self
406
+ end
407
+
408
+ self.each_sexp do |subset|
409
+ subset.search_each pattern, &block
410
+ end
411
+ end
412
+
413
+ ##
414
+ # Recursively searches for the +pattern+ yielding each match, and
415
+ # replacing it with the result of the block.
416
+ #
417
+
418
+ def replace_sexp pattern, &block # TODO: rename to gsub?
419
+ raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
420
+
421
+ return yield self if pattern.satisfy? self
422
+
423
+ # TODO: Needs #new_from(*new_body) to copy file/line/comment
424
+ self.class.new(*self.map { |subset|
425
+ case subset
426
+ when Sexp then
427
+ subset.replace_sexp pattern, &block
428
+ else
429
+ subset
430
+ end
431
+ })
432
+ end
433
+
434
+ ##
435
+ # Matches an S-Expression.
436
+ #
437
+ # See Matcher for examples.
438
+
439
+ def self.s *args
440
+ Matcher.new(*args)
441
+ end
442
+
443
+ ##
444
+ # Matches any single item.
445
+ #
446
+ # See Wild for examples.
447
+
448
+ def self._
449
+ Wild.new
450
+ end
451
+
452
+ # TODO: reorder factory methods and classes to match
453
+
454
+ ##
455
+ # Matches all remaining input.
456
+ #
457
+ # See Remaining for examples.
458
+
459
+ def self.___
460
+ Remaining.new
461
+ end
462
+
463
+ ##
464
+ # Matches an expression or any expression that includes the child.
465
+ #
466
+ # See Include for examples.
467
+
468
+ def self.include child # TODO: rename, name is generic ruby
469
+ Include.new(child)
470
+ end
471
+
472
+ ##
473
+ # Matches any atom.
474
+ #
475
+ # See Atom for examples.
476
+
477
+ def self.atom
478
+ Atom.new
479
+ end
480
+
481
+ ##
482
+ # Matches when any of the sub-expressions match.
483
+ #
484
+ # This is also available via Matcher#|.
485
+ #
486
+ # See Any for examples.
487
+
488
+ def self.any *args
489
+ Any.new(*args)
490
+ end
491
+
492
+ ##
493
+ # Matches only when all sub-expressions match.
494
+ #
495
+ # This is also available via Matcher#&.
496
+ #
497
+ # See All for examples.
498
+
499
+ def self.all *args
500
+ All.new(*args)
501
+ end
502
+
503
+ ##
504
+ # Matches when sub-expression does not match.
505
+ #
506
+ # This is also available via Matcher#-@.
507
+ #
508
+ # See Not for examples.
509
+
510
+ def self.not? arg
511
+ Not.new arg
512
+ end
513
+
514
+ # TODO: add Sibling factory method?
515
+
516
+ ##
517
+ # Matches anything that has a child matching the sub-expression.
518
+ #
519
+ # See Child for examples.
520
+
521
+ def self.child child
522
+ Child.new child
523
+ end
524
+
525
+ ##
526
+ # Matches anything having the same sexp_type, which is the first
527
+ # value in a Sexp.
528
+ #
529
+ # See Type for examples.
530
+
531
+ def self.t name
532
+ Type.new name
533
+ end
534
+
535
+ ##
536
+ # Matches any atom who's string representation matches the patterns
537
+ # passed in.
538
+ #
539
+ # See Pattern for examples.
540
+
541
+ def self.m *values
542
+ res = values.map { |value|
543
+ case value
544
+ when Regexp then
545
+ value
546
+ else
547
+ re = Regexp.escape value.to_s
548
+ Regexp.new "\\A%s\\Z" % re
549
+ end
550
+ }
551
+ Pattern.new Regexp.union(*res)
552
+ end
553
+
554
+ ##
555
+ # Defines a family of objects that can be used to match sexps to
556
+ # certain types of patterns, much like regexps can be used on
557
+ # strings. Generally you won't use this class directly.
558
+ #
559
+ # You would normally create a matcher using the top-level #s method,
560
+ # but with a block, calling into the Sexp factory methods. For example:
561
+ #
562
+ # s{ s(:class, m(/^Test/), _, ___) }
563
+ #
564
+ # This creates a matcher for classes whose names start with "Test".
565
+ # It uses Sexp.m to create a Sexp::Matcher::Pattern matcher, Sexp._
566
+ # to create a Sexp::Matcher::Wild matcher, and Sexp.___ to create a
567
+ # Sexp::Matcher::Remaining matcher. It works like this:
568
+ #
569
+ # s{ # start to create a pattern
570
+ # s( # create a sexp matcher
571
+ # :class. # for class nodes
572
+ # m(/^Test/), # matching name slots that start with "Test"
573
+ # _, # any superclass value
574
+ # ___ # and whatever is in the class
575
+ # )
576
+ # }
577
+ #
578
+ # Then you can use that with #=~, #/, Sexp#replace_sexp, and others.
579
+ #
580
+ # For more examples, see the various Sexp class methods, the examples,
581
+ # and the tests supplied with Sexp.
582
+ #
583
+ # * For pattern creation, see factory methods: Sexp::_, Sexp::___, etc.
584
+ # * For matching returning truthy/falsey results, see Sexp#=~.
585
+ # * See Sexp#=~ for matching returning truthy/falsey results.
586
+ # * For case expressions, see Matcher#===.
587
+ # * For getting all subtree matches, see Sexp#/.
588
+ #
589
+ # If rdoc didn't suck, these would all be links.
590
+
591
+ class Matcher < Sexp
592
+ ##
593
+ # Should #=~ match sub-trees?
594
+
595
+ def self.match_subs?
596
+ @@match_subs
597
+ end
598
+
599
+ ##
600
+ # Setter for +match_subs?+.
601
+
602
+ def self.match_subs= o
603
+ @@match_subs = o
604
+ end
605
+
606
+ self.match_subs = true
607
+
608
+ ##
609
+ # Does this matcher actually match +o+? Returns falsey if +o+ is
610
+ # not a Sexp or if any sub-tree of +o+ is not satisfied by or
611
+ # equal to its corresponding sub-matcher.
612
+ #
613
+ #--
614
+ # TODO: push this up to Sexp and make this the workhorse
615
+ # TODO: do the same with ===/satisfy?
616
+
617
+ def satisfy? o
618
+ return unless o.kind_of?(Sexp) &&
619
+ (length == o.length || Matcher === last && last.greedy?)
620
+
621
+ each_with_index.all? { |child, i|
622
+ sexp = o.at i
623
+ if Sexp === child then # TODO: when will this NOT be a matcher?
624
+ sexp = o.sexp_body i if child.respond_to?(:greedy?) && child.greedy?
625
+ child.satisfy? sexp
626
+ else
627
+ child == sexp
628
+ end
629
+ }
630
+ end
631
+
632
+ ##
633
+ # Tree equivalent to String#=~, returns true if +self+ matches
634
+ # +sexp+ as a whole or in a sub-tree (if +match_subs?+).
635
+ #
636
+ # TODO: maybe this should NOT be aliased to === ?
637
+ #
638
+ # TODO: example
639
+
640
+ def =~ sexp
641
+ raise ArgumentError, "Can't both be matchers: %p" % [sexp] if Matcher === sexp
642
+
643
+ self.satisfy?(sexp) ||
644
+ (self.class.match_subs? && sexp.each_sexp.any? { |sub| self =~ sub })
645
+ end
646
+
647
+ alias === =~ # TODO?: alias === satisfy?
648
+
649
+ ##
650
+ # Searches through +sexp+ for all sub-trees that match this
651
+ # matcher and returns a MatchCollection for each match.
652
+ #
653
+ # TODO: redirect?
654
+ # Example:
655
+ # Q{ s(:b) } / s(:a, s(:b)) => [s(:b)]
656
+
657
+ def / sexp
658
+ raise ArgumentError, "can't both be matchers" if Matcher === sexp
659
+
660
+ # TODO: move search_each into matcher?
661
+ MatchCollection.new sexp.search_each(self).to_a
662
+ end
663
+
664
+ ##
665
+ # Combines the Matcher with another Matcher, the resulting one will
666
+ # be satisfied if either Matcher would be satisfied.
667
+ #
668
+ # TODO: redirect
669
+ # Example:
670
+ # s(:a) | s(:b)
671
+
672
+ def | other
673
+ Any.new self, other
674
+ end
675
+
676
+ ##
677
+ # Combines the Matcher with another Matcher, the resulting one will
678
+ # be satisfied only if both Matchers would be satisfied.
679
+ #
680
+ # TODO: redirect
681
+ # Example:
682
+ # t(:a) & include(:b)
683
+
684
+ def & other
685
+ All.new self, other
686
+ end
687
+
688
+ ##
689
+ # Returns a Matcher that matches whenever this Matcher would not have matched
690
+ #
691
+ # Example:
692
+ # -s(:a)
693
+
694
+ def -@
695
+ Not.new self
696
+ end
697
+
698
+ ##
699
+ # Returns a Matcher that matches if this has a sibling +o+
700
+ #
701
+ # Example:
702
+ # s(:a) >> s(:b)
703
+
704
+ def >> other
705
+ Sibling.new self, other
706
+ end
707
+
708
+ ##
709
+ # Is this matcher greedy? Defaults to false.
710
+
711
+ def greedy?
712
+ false
713
+ end
714
+
715
+ def inspect # :nodoc:
716
+ s = super
717
+ s[0] = "q"
718
+ s
719
+ end
720
+
721
+ def pretty_print q # :nodoc:
722
+ q.group 1, "q(", ")" do
723
+ q.seplist self do |v|
724
+ q.pp v
725
+ end
726
+ end
727
+ end
728
+
729
+ ##
730
+ # Parse a lispy string representation of a matcher into a Matcher.
731
+ # See +Parser+.
732
+
733
+ def self.parse s
734
+ Parser.new(s).parse
735
+ end
736
+
737
+ ##
738
+ # Converts from a lispy string to Sexp matchers in a safe manner.
739
+ #
740
+ # "(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }
741
+
742
+ class Parser
743
+
744
+ ##
745
+ # The stream of tokens to parse. See #lex.
746
+
747
+ attr_accessor :tokens
748
+
749
+ ##
750
+ # Create a new Parser instance on +s+
751
+
752
+ def initialize s
753
+ self.tokens = []
754
+ lex s
755
+ end
756
+
757
+ ##
758
+ # Converts +s+ into a stream of tokens and adds them to +tokens+.
759
+
760
+ def lex s
761
+ tokens.concat s.scan(%r%[()\[\]]|\"[^"]*\"|/[^/]*/|[\w-]+%)
762
+ end
763
+
764
+ ##
765
+ # Returns the next token and removes it from the stream or raises if empty.
766
+
767
+ def next_token
768
+ raise SyntaxError, "unbalanced input" if tokens.empty?
769
+ tokens.shift
770
+ end
771
+
772
+ ##
773
+ # Returns the next token without removing it from the stream.
774
+
775
+ def peek_token
776
+ tokens.first
777
+ end
778
+
779
+ ##
780
+ # Parses tokens and returns a +Matcher+ instance.
781
+
782
+ def parse
783
+ result = parse_sexp until tokens.empty?
784
+ result
785
+ end
786
+
787
+ ##
788
+ # Parses a string into a sexp matcher:
789
+ #
790
+ # SEXP : "(" SEXP:args* ")" => Sexp.q(*args)
791
+ # | "[" CMD:cmd sexp:args* "]" => Sexp.cmd(*args)
792
+ # | "nil" => nil
793
+ # | /\d+/:n => n.to_i
794
+ # | "___" => Sexp.___
795
+ # | "_" => Sexp._
796
+ # | /^\/(.*)\/$/:re => Regexp.new re[0]
797
+ # | /^"(.*)"$/:s => String.new s[0]
798
+ # | NAME:name => name.to_sym
799
+ # NAME : /\w+/
800
+ # CMD : "t" | "m" | "atom"
801
+
802
+ def parse_sexp
803
+ token = next_token
804
+
805
+ case token
806
+ when "(" then
807
+ parse_list
808
+ when "[" then
809
+ parse_cmd
810
+ when "nil" then
811
+ nil
812
+ when /^\d+$/ then
813
+ token.to_i
814
+ when "___" then
815
+ Sexp.___
816
+ when "_" then
817
+ Sexp._
818
+ when %r%^/(.*)/$% then
819
+ re = $1
820
+ raise SyntaxError, "Not allowed: /%p/" % [re] unless
821
+ re =~ /\A([\w()|.*+^$]+)\z/
822
+ Regexp.new re
823
+ when /^"(.*)"$/ then
824
+ $1
825
+ when /^\w+$/ then
826
+ token.to_sym
827
+ else
828
+ raise SyntaxError, "unhandled token: %p" % [token]
829
+ end
830
+ end
831
+
832
+ ##
833
+ # Parses a balanced list of expressions and returns the
834
+ # equivalent matcher.
835
+
836
+ def parse_list
837
+ result = []
838
+
839
+ result << parse_sexp while peek_token && peek_token != ")"
840
+ next_token # pop off ")"
841
+
842
+ Sexp.s(*result)
843
+ end
844
+
845
+ ##
846
+ # A collection of allowed commands to convert into matchers.
847
+
848
+ ALLOWED = [:t, :m, :atom].freeze
849
+
850
+ ##
851
+ # Parses a balanced command. A command is denoted by square
852
+ # brackets and must conform to a whitelisted set of allowed
853
+ # commands (see +ALLOWED+).
854
+
855
+ def parse_cmd
856
+ args = []
857
+ args << parse_sexp while peek_token && peek_token != "]"
858
+ next_token # pop off "]"
859
+
860
+ cmd = args.shift
861
+ args = Sexp.s(*args)
862
+
863
+ raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd
864
+
865
+ result = Sexp.send cmd, *args
866
+
867
+ result
868
+ end
869
+ end # class Parser
870
+ end # class Matcher
871
+
872
+ ##
873
+ # Matches any single item.
874
+ #
875
+ # examples:
876
+ #
877
+ # s(:a) / s{ _ } #=> [s(:a)]
878
+ # s(:a, s(s(:b))) / s{ s(_) } #=> [s(s(:b))]
879
+
880
+ class Wild < Matcher
881
+ ##
882
+ # Matches any single element.
883
+
884
+ def satisfy? o
885
+ true
886
+ end
887
+
888
+ def inspect # :nodoc:
889
+ "_"
890
+ end
891
+
892
+ def pretty_print q # :nodoc:
893
+ q.text "_"
894
+ end
895
+ end
896
+
897
+ ##
898
+ # Matches all remaining input. If remaining comes before any other
899
+ # matchers, they will be ignored.
900
+ #
901
+ # examples:
902
+ #
903
+ # s(:a) / s{ s(:a, ___ ) } #=> [s(:a)]
904
+ # s(:a, :b, :c) / s{ s(:a, ___ ) } #=> [s(:a, :b, :c)]
905
+
906
+ class Remaining < Matcher
907
+ ##
908
+ # Always satisfied once this is reached. Think of it as a var arg.
909
+
910
+ def satisfy? o
911
+ true
912
+ end
913
+
914
+ def greedy?
915
+ true
916
+ end
917
+
918
+ def inspect # :nodoc:
919
+ "___"
920
+ end
921
+
922
+ def pretty_print q # :nodoc:
923
+ q.text "___"
924
+ end
925
+ end
926
+
927
+ ##
928
+ # Matches when any of the sub-expressions match.
929
+ #
930
+ # This is also available via Matcher#|.
931
+ #
932
+ # examples:
933
+ #
934
+ # s(:a) / s{ any(s(:a), s(:b)) } #=> [s(:a)]
935
+ # s(:a) / s{ s(:a) | s(:b) } #=> [s(:a)] # same thing via |
936
+ # s(:a) / s{ any(s(:b), s(:c)) } #=> []
937
+
938
+ class Any < Matcher
939
+ ##
940
+ # The collection of sub-matchers to match against.
941
+
942
+ attr_reader :options
943
+
944
+ ##
945
+ # Create an Any matcher which will match any of the +options+.
946
+
947
+ def initialize *options
948
+ @options = options
949
+ end
950
+
951
+ ##
952
+ # Satisfied when any sub expressions match +o+
953
+
954
+ def satisfy? o
955
+ options.any? { |exp|
956
+ Sexp === exp && exp.satisfy?(o) || exp == o
957
+ }
958
+ end
959
+
960
+ def == o # :nodoc:
961
+ super && self.options == o.options
962
+ end
963
+
964
+ def inspect # :nodoc:
965
+ options.map(&:inspect).join(" | ")
966
+ end
967
+
968
+ def pretty_print q # :nodoc:
969
+ q.group 1, "any(", ")" do
970
+ q.seplist options do |v|
971
+ q.pp v
972
+ end
973
+ end
974
+ end
975
+ end
976
+
977
+ ##
978
+ # Matches only when all sub-expressions match.
979
+ #
980
+ # This is also available via Matcher#&.
981
+ #
982
+ # examples:
983
+ #
984
+ # s(:a) / s{ all(s(:a), s(:b)) } #=> []
985
+ # s(:a, :b) / s{ t(:a) & include(:b)) } #=> [s(:a, :b)]
986
+
987
+ class All < Matcher
988
+ ##
989
+ # The collection of sub-matchers to match against.
990
+
991
+ attr_reader :options
992
+
993
+ ##
994
+ # Create an All matcher which will match all of the +options+.
995
+
996
+ def initialize *options
997
+ @options = options
998
+ end
999
+
1000
+ ##
1001
+ # Satisfied when all sub expressions match +o+
1002
+
1003
+ def satisfy? o
1004
+ options.all? { |exp|
1005
+ exp.kind_of?(Sexp) ? exp.satisfy?(o) : exp == o
1006
+ }
1007
+ end
1008
+
1009
+ def == o # :nodoc:
1010
+ super && self.options == o.options
1011
+ end
1012
+
1013
+ def inspect # :nodoc:
1014
+ options.map(&:inspect).join(" & ")
1015
+ end
1016
+
1017
+ def pretty_print q # :nodoc:
1018
+ q.group 1, "all(", ")" do
1019
+ q.seplist options do |v|
1020
+ q.pp v
1021
+ end
1022
+ end
1023
+ end
1024
+ end
1025
+
1026
+ ##
1027
+ # Matches when sub-expression does not match.
1028
+ #
1029
+ # This is also available via Matcher#-@.
1030
+ #
1031
+ # examples:
1032
+ #
1033
+ # s(:a) / s{ not?(s(:b)) } #=> [s(:a)]
1034
+ # s(:a) / s{ -s(:b) } #=> [s(:a)]
1035
+ # s(:a) / s{ s(not? :a) } #=> []
1036
+
1037
+ class Not < Matcher
1038
+
1039
+ ##
1040
+ # The value to negate in the match.
1041
+
1042
+ attr_reader :value
1043
+
1044
+ ##
1045
+ # Creates a Matcher which will match any Sexp that does not match the +value+
1046
+
1047
+ def initialize value
1048
+ @value = value
1049
+ end
1050
+
1051
+ def == o # :nodoc:
1052
+ super && self.value == o.value
1053
+ end
1054
+
1055
+ ##
1056
+ # Satisfied if a +o+ does not match the +value+
1057
+
1058
+ def satisfy? o
1059
+ !(value.kind_of?(Sexp) ? value.satisfy?(o) : value == o)
1060
+ end
1061
+
1062
+ def inspect # :nodoc:
1063
+ "not?(%p)" % [value]
1064
+ end
1065
+
1066
+ def pretty_print q # :nodoc:
1067
+ q.group 1, "not?(", ")" do
1068
+ q.pp value
1069
+ end
1070
+ end
1071
+ end
1072
+
1073
+ ##
1074
+ # Matches anything that has a child matching the sub-expression
1075
+ #
1076
+ # example:
1077
+ #
1078
+ # s(s(s(s(s(:a))))) / s{ child(s(:a)) } #=> [s(s(s(s(s(:a))))),
1079
+ # s(s(s(s(:a)))),
1080
+ # s(s(s(:a))),
1081
+ # s(s(:a)),
1082
+ # s(:a)]
1083
+
1084
+ class Child < Matcher
1085
+ ##
1086
+ # The child to match.
1087
+
1088
+ attr_reader :child
1089
+
1090
+ ##
1091
+ # Create a Child matcher which will match anything having a
1092
+ # descendant matching +child+.
1093
+
1094
+ def initialize child
1095
+ @child = child
1096
+ end
1097
+
1098
+ ##
1099
+ # Satisfied if matches +child+ or +o+ has a descendant matching
1100
+ # +child+.
1101
+
1102
+ def satisfy? o
1103
+ if child.satisfy? o
1104
+ true
1105
+ elsif o.kind_of? Sexp
1106
+ o.search_each(child).any?
1107
+ end
1108
+ end
1109
+
1110
+ def == o # :nodoc:
1111
+ super && self.child == o.child
1112
+ end
1113
+
1114
+ def inspect # :nodoc:
1115
+ "child(%p)" % [child]
1116
+ end
1117
+
1118
+ def pretty_print q # :nodoc:
1119
+ q.group 1, "child(", ")" do
1120
+ q.pp child
1121
+ end
1122
+ end
1123
+ end
1124
+
1125
+ ##
1126
+ # Matches any atom (non-Sexp).
1127
+ #
1128
+ # examples:
1129
+ #
1130
+ # s(:a) / s{ s(atom) } #=> [s(:a)]
1131
+ # s(:a, s(:b)) / s{ s(atom) } #=> [s(:b)]
1132
+
1133
+ class Atom < Matcher
1134
+ ##
1135
+ # Satisfied when +o+ is an atom.
1136
+
1137
+ def satisfy? o
1138
+ !(o.kind_of? Sexp)
1139
+ end
1140
+
1141
+ def inspect #:nodoc:
1142
+ "atom"
1143
+ end
1144
+
1145
+ def pretty_print q # :nodoc:
1146
+ q.text "atom"
1147
+ end
1148
+ end
1149
+
1150
+ ##
1151
+ # Matches any atom who's string representation matches the patterns
1152
+ # passed in.
1153
+ #
1154
+ # examples:
1155
+ #
1156
+ # s(:a) / s{ m('a') } #=> [s(:a)]
1157
+ # s(:a) / s{ m(/\w/,/\d/) } #=> [s(:a)]
1158
+ # s(:tests, s(s(:test_a), s(:test_b))) / s{ m(/test_\w/) } #=> [s(:test_a),
1159
+ #
1160
+ # TODO: maybe don't require non-sexps? This does respond to =~ now.
1161
+
1162
+ class Pattern < Matcher
1163
+
1164
+ ##
1165
+ # The regexp to match for the pattern.
1166
+
1167
+ attr_reader :pattern
1168
+
1169
+ def == o # :nodoc:
1170
+ super && self.pattern == o.pattern
1171
+ end
1172
+
1173
+ ##
1174
+ # Create a Patten matcher which will match any atom that either
1175
+ # matches the input +pattern+.
1176
+
1177
+ def initialize pattern
1178
+ @pattern = pattern
1179
+ end
1180
+
1181
+ ##
1182
+ # Satisfied if +o+ is an atom, and +o+ matches +pattern+
1183
+
1184
+ def satisfy? o
1185
+ !o.kind_of?(Sexp) && o.to_s =~ pattern # TODO: question to_s
1186
+ end
1187
+
1188
+ def inspect # :nodoc:
1189
+ "m(%p)" % pattern
1190
+ end
1191
+
1192
+ def pretty_print q # :nodoc:
1193
+ q.group 1, "m(", ")" do
1194
+ q.pp pattern
1195
+ end
1196
+ end
1197
+ end
1198
+
1199
+ ##
1200
+ # Matches anything having the same sexp_type, which is the first
1201
+ # value in a Sexp.
1202
+ #
1203
+ # examples:
1204
+ #
1205
+ # s(:a, :b) / s{ t(:a) } #=> [s(:a, :b)]
1206
+ # s(:a, :b) / s{ t(:b) } #=> []
1207
+ # s(:a, s(:b, :c)) / s{ t(:b) } #=> [s(:b, :c)]
1208
+
1209
+ class Type < Matcher
1210
+ attr_reader :sexp_type
1211
+
1212
+ ##
1213
+ # Creates a Matcher which will match any Sexp who's type is +type+, where a type is
1214
+ # the first element in the Sexp.
1215
+
1216
+ def initialize type
1217
+ @sexp_type = type
1218
+ end
1219
+
1220
+ def == o # :nodoc:
1221
+ super && self.sexp_type == o.sexp_type
1222
+ end
1223
+
1224
+ ##
1225
+ # Satisfied if the sexp_type of +o+ is +type+.
1226
+
1227
+ def satisfy? o
1228
+ o.kind_of?(Sexp) && o.sexp_type == sexp_type
1229
+ end
1230
+
1231
+ def inspect # :nodoc:
1232
+ "t(%p)" % sexp_type
1233
+ end
1234
+
1235
+ def pretty_print q # :nodoc:
1236
+ q.group 1, "t(", ")" do
1237
+ q.pp sexp_type
1238
+ end
1239
+ end
1240
+ end
1241
+
1242
+ ##
1243
+ # Matches an expression or any expression that includes the child.
1244
+ #
1245
+ # examples:
1246
+ #
1247
+ # s(:a, :b) / s{ include(:b) } #=> [s(:a, :b)]
1248
+ # s(s(s(:a))) / s{ include(:a) } #=> [s(:a)]
1249
+
1250
+ class Include < Matcher
1251
+ ##
1252
+ # The value that should be included in the match.
1253
+
1254
+ attr_reader :value
1255
+
1256
+ ##
1257
+ # Creates a Matcher which will match any Sexp that contains the
1258
+ # +value+.
1259
+
1260
+ def initialize value
1261
+ @value = value
1262
+ end
1263
+
1264
+ ##
1265
+ # Satisfied if a +o+ is a Sexp and one of +o+'s elements matches
1266
+ # value
1267
+
1268
+ def satisfy? o
1269
+ Sexp === o && o.any? { |c|
1270
+ # TODO: switch to respond_to??
1271
+ Sexp === value ? value.satisfy?(c) : value == c
1272
+ }
1273
+ end
1274
+
1275
+ def == o # :nodoc:
1276
+ super && self.value == o.value
1277
+ end
1278
+
1279
+ def inspect # :nodoc:
1280
+ "include(%p)" % [value]
1281
+ end
1282
+
1283
+ def pretty_print q # :nodoc:
1284
+ q.group 1, "include(", ")" do
1285
+ q.pp value
1286
+ end
1287
+ end
1288
+ end
1289
+
1290
+ ##
1291
+ # See Matcher for sibling relations: <,<<,>>,>
1292
+
1293
+ class Sibling < Matcher
1294
+
1295
+ ##
1296
+ # The LHS of the matcher.
1297
+
1298
+ attr_reader :subject
1299
+
1300
+ ##
1301
+ # The RHS of the matcher.
1302
+
1303
+ attr_reader :sibling
1304
+
1305
+ ##
1306
+ # An optional distance requirement for the matcher.
1307
+
1308
+ attr_reader :distance
1309
+
1310
+ ##
1311
+ # Creates a Matcher which will match any pair of Sexps that are siblings.
1312
+ # Defaults to matching the immediate following sibling.
1313
+
1314
+ def initialize subject, sibling, distance = nil
1315
+ @subject = subject
1316
+ @sibling = sibling
1317
+ @distance = distance
1318
+ end
1319
+
1320
+ ##
1321
+ # Satisfied if o contains +subject+ followed by +sibling+
1322
+
1323
+ def satisfy? o
1324
+ # Future optimizations:
1325
+ # * Shortcut matching sibling
1326
+ subject_matches = index_matches(subject, o)
1327
+ return nil if subject_matches.empty?
1328
+
1329
+ sibling_matches = index_matches(sibling, o)
1330
+ return nil if sibling_matches.empty?
1331
+
1332
+ subject_matches.any? { |i1, _data_1|
1333
+ sibling_matches.any? { |i2, _data_2|
1334
+ distance ? (i2-i1 == distance) : i2 > i1
1335
+ }
1336
+ }
1337
+ end
1338
+
1339
+ def == o # :nodoc:
1340
+ super &&
1341
+ self.subject == o.subject &&
1342
+ self.sibling == o.sibling &&
1343
+ self.distance == o.distance
1344
+ end
1345
+
1346
+ def inspect # :nodoc:
1347
+ "%p >> %p" % [subject, sibling]
1348
+ end
1349
+
1350
+ def pretty_print q # :nodoc:
1351
+ if distance then
1352
+ q.group 1, "sibling(", ")" do
1353
+ q.seplist [subject, sibling, distance] do |v|
1354
+ q.pp v
1355
+ end
1356
+ end
1357
+ else
1358
+ q.group 1 do
1359
+ q.pp subject
1360
+ q.text " >> "
1361
+ q.pp sibling
1362
+ end
1363
+ end
1364
+ end
1365
+
1366
+ private
1367
+
1368
+ def index_matches pattern, o
1369
+ indexes = []
1370
+ return indexes unless o.kind_of? Sexp
1371
+
1372
+ o.each_with_index do |e, i|
1373
+ data = {}
1374
+ if pattern.kind_of?(Sexp) ? pattern.satisfy?(e) : pattern == o[i]
1375
+ indexes << [i, data]
1376
+ end
1377
+ end
1378
+
1379
+ indexes
1380
+ end
1381
+ end # class Sibling
1382
+
1383
+ ##
1384
+ # Wraps the results of a Sexp query. MatchCollection defines
1385
+ # MatchCollection#/ so that you can chain queries.
1386
+ #
1387
+ # For instance:
1388
+ # res = s(:a, s(:b)) / s{ s(:a,_) } / s{ s(:b) }
1389
+
1390
+ class MatchCollection < Array
1391
+ ##
1392
+ # See Traverse#search
1393
+
1394
+ def / pattern
1395
+ inject(self.class.new) { |result, match|
1396
+ result.concat match / pattern
1397
+ }
1398
+ end
1399
+
1400
+ def inspect # :nodoc:
1401
+ "MatchCollection.new(%s)" % self.to_a.inspect[1..-2]
1402
+ end
1403
+
1404
+ alias :to_s :inspect # :nodoc:
1405
+
1406
+ def pretty_print q # :nodoc:
1407
+ q.group 1, "MatchCollection.new(", ")" do
1408
+ q.seplist(self) {|v| q.pp v }
1409
+ end
1410
+ end
1411
+ end # class MatchCollection
371
1412
  end
1413
+
1414
+ require "strict_sexp" if ENV["STRICT_SEXP"].to_i > 0