sexp_processor 4.9.0 → 4.10.0b1

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.
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