sexp_processor 4.11.0 → 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -3
- data/History.rdoc +88 -0
- data/Manifest.txt +1 -0
- data/Rakefile +2 -0
- data/lib/pt_testcase.rb +22 -19
- data/lib/sexp.rb +32 -1061
- data/lib/sexp_matcher.rb +1100 -0
- data/lib/sexp_processor.rb +2 -2
- data/lib/strict_sexp.rb +26 -4
- data/test/test_sexp.rb +213 -134
- data/test/test_sexp_processor.rb +2 -2
- data.tar.gz.sig +1 -2
- metadata +32 -22
- metadata.gz.sig +0 -0
data/lib/sexp_matcher.rb
ADDED
@@ -0,0 +1,1100 @@
|
|
1
|
+
class Sexp #:nodoc:
|
2
|
+
##
|
3
|
+
# Verifies that +pattern+ is a Matcher and then dispatches to its
|
4
|
+
# #=~ method.
|
5
|
+
#
|
6
|
+
# See Matcher.=~
|
7
|
+
|
8
|
+
def =~ pattern
|
9
|
+
raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
|
10
|
+
pattern =~ self
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Verifies that +pattern+ is a Matcher and then dispatches to its
|
15
|
+
# #satisfy? method.
|
16
|
+
#
|
17
|
+
# TODO: rename match?
|
18
|
+
|
19
|
+
def satisfy? pattern
|
20
|
+
raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
|
21
|
+
pattern.satisfy? self
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Verifies that +pattern+ is a Matcher and then dispatches to its #/
|
26
|
+
# method.
|
27
|
+
#
|
28
|
+
# TODO: rename grep? match_all ? find_all ?
|
29
|
+
|
30
|
+
def / pattern
|
31
|
+
raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
|
32
|
+
pattern / self
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Recursively searches for the +pattern+ yielding the matches.
|
37
|
+
|
38
|
+
def search_each pattern, &block # TODO: rename to grep?
|
39
|
+
raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
|
40
|
+
|
41
|
+
return enum_for(:search_each, pattern) unless block_given?
|
42
|
+
|
43
|
+
if pattern.satisfy? self then
|
44
|
+
yield self
|
45
|
+
end
|
46
|
+
|
47
|
+
self.each_sexp do |subset|
|
48
|
+
subset.search_each pattern, &block
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Recursively searches for the +pattern+ yielding each match, and
|
54
|
+
# replacing it with the result of the block.
|
55
|
+
#
|
56
|
+
|
57
|
+
def replace_sexp pattern, &block # TODO: rename to gsub?
|
58
|
+
raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
|
59
|
+
|
60
|
+
return yield self if pattern.satisfy? self
|
61
|
+
|
62
|
+
# TODO: Needs #new_from(*new_body) to copy file/line/comment
|
63
|
+
self.class.new(*self.map { |subset|
|
64
|
+
case subset
|
65
|
+
when Sexp then
|
66
|
+
subset.replace_sexp pattern, &block
|
67
|
+
else
|
68
|
+
subset
|
69
|
+
end
|
70
|
+
})
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Matches an S-Expression.
|
75
|
+
#
|
76
|
+
# See Matcher for examples.
|
77
|
+
|
78
|
+
def self.q *args
|
79
|
+
Matcher.new(*args)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.s *args
|
83
|
+
where = caller.first.split(/:/, 3).first(2).join ":"
|
84
|
+
warn "DEPRECATED: use Sexp.q(...) instead. From %s" % [where]
|
85
|
+
q(*args)
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Matches any single item.
|
90
|
+
#
|
91
|
+
# See Wild for examples.
|
92
|
+
|
93
|
+
def self._
|
94
|
+
Wild.new
|
95
|
+
end
|
96
|
+
|
97
|
+
# TODO: reorder factory methods and classes to match
|
98
|
+
|
99
|
+
##
|
100
|
+
# Matches all remaining input.
|
101
|
+
#
|
102
|
+
# See Remaining for examples.
|
103
|
+
|
104
|
+
def self.___
|
105
|
+
Remaining.new
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Matches an expression or any expression that includes the child.
|
110
|
+
#
|
111
|
+
# See Include for examples.
|
112
|
+
|
113
|
+
def self.include child # TODO: rename, name is generic ruby
|
114
|
+
Include.new(child)
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Matches any atom.
|
119
|
+
#
|
120
|
+
# See Atom for examples.
|
121
|
+
|
122
|
+
def self.atom
|
123
|
+
Atom.new
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Matches when any of the sub-expressions match.
|
128
|
+
#
|
129
|
+
# This is also available via Matcher#|.
|
130
|
+
#
|
131
|
+
# See Any for examples.
|
132
|
+
|
133
|
+
def self.any *args
|
134
|
+
Any.new(*args)
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Matches only when all sub-expressions match.
|
139
|
+
#
|
140
|
+
# This is also available via Matcher#&.
|
141
|
+
#
|
142
|
+
# See All for examples.
|
143
|
+
|
144
|
+
def self.all *args
|
145
|
+
All.new(*args)
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# Matches when sub-expression does not match.
|
150
|
+
#
|
151
|
+
# This is also available via Matcher#-@.
|
152
|
+
#
|
153
|
+
# See Not for examples.
|
154
|
+
|
155
|
+
def self.not? arg
|
156
|
+
Not.new arg
|
157
|
+
end
|
158
|
+
|
159
|
+
class << self
|
160
|
+
alias - not?
|
161
|
+
end
|
162
|
+
|
163
|
+
# TODO: add Sibling factory method?
|
164
|
+
|
165
|
+
##
|
166
|
+
# Matches anything that has a child matching the sub-expression.
|
167
|
+
#
|
168
|
+
# See Child for examples.
|
169
|
+
|
170
|
+
def self.child child
|
171
|
+
Child.new child
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Matches anything having the same sexp_type, which is the first
|
176
|
+
# value in a Sexp.
|
177
|
+
#
|
178
|
+
# See Type for examples.
|
179
|
+
|
180
|
+
def self.t name
|
181
|
+
Type.new name
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Matches any atom who's string representation matches the patterns
|
186
|
+
# passed in.
|
187
|
+
#
|
188
|
+
# See Pattern for examples.
|
189
|
+
|
190
|
+
def self.m *values
|
191
|
+
res = values.map { |value|
|
192
|
+
case value
|
193
|
+
when Regexp then
|
194
|
+
value
|
195
|
+
else
|
196
|
+
re = Regexp.escape value.to_s
|
197
|
+
Regexp.new "\\A%s\\Z" % re
|
198
|
+
end
|
199
|
+
}
|
200
|
+
Pattern.new Regexp.union(*res)
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Matches an atom of the specified +klass+ (or module).
|
205
|
+
#
|
206
|
+
# See Pattern for examples.
|
207
|
+
|
208
|
+
def self.k klass
|
209
|
+
Klass.new klass
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Defines a family of objects that can be used to match sexps to
|
214
|
+
# certain types of patterns, much like regexps can be used on
|
215
|
+
# strings. Generally you won't use this class directly.
|
216
|
+
#
|
217
|
+
# You would normally create a matcher using the top-level #s method,
|
218
|
+
# but with a block, calling into the Sexp factory methods. For example:
|
219
|
+
#
|
220
|
+
# s{ s(:class, m(/^Test/), _, ___) }
|
221
|
+
#
|
222
|
+
# This creates a matcher for classes whose names start with "Test".
|
223
|
+
# It uses Sexp.m to create a Sexp::Matcher::Pattern matcher, Sexp._
|
224
|
+
# to create a Sexp::Matcher::Wild matcher, and Sexp.___ to create a
|
225
|
+
# Sexp::Matcher::Remaining matcher. It works like this:
|
226
|
+
#
|
227
|
+
# s{ # start to create a pattern
|
228
|
+
# s( # create a sexp matcher
|
229
|
+
# :class. # for class nodes
|
230
|
+
# m(/^Test/), # matching name slots that start with "Test"
|
231
|
+
# _, # any superclass value
|
232
|
+
# ___ # and whatever is in the class
|
233
|
+
# )
|
234
|
+
# }
|
235
|
+
#
|
236
|
+
# Then you can use that with #=~, #/, Sexp#replace_sexp, and others.
|
237
|
+
#
|
238
|
+
# For more examples, see the various Sexp class methods, the examples,
|
239
|
+
# and the tests supplied with Sexp.
|
240
|
+
#
|
241
|
+
# * For pattern creation, see factory methods: Sexp::_, Sexp::___, etc.
|
242
|
+
# * For matching returning truthy/falsey results, see Sexp#=~.
|
243
|
+
# * For case expressions, see Matcher#===.
|
244
|
+
# * For getting all subtree matches, see Sexp#/.
|
245
|
+
#
|
246
|
+
# If rdoc didn't suck, these would all be links.
|
247
|
+
|
248
|
+
class Matcher < Sexp
|
249
|
+
##
|
250
|
+
# Should #=~ match sub-trees?
|
251
|
+
|
252
|
+
def self.match_subs?
|
253
|
+
@@match_subs
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Setter for +match_subs?+.
|
258
|
+
|
259
|
+
def self.match_subs= o
|
260
|
+
@@match_subs = o
|
261
|
+
end
|
262
|
+
|
263
|
+
self.match_subs = true
|
264
|
+
|
265
|
+
##
|
266
|
+
# Does this matcher actually match +o+? Returns falsey if +o+ is
|
267
|
+
# not a Sexp or if any sub-tree of +o+ is not satisfied by or
|
268
|
+
# equal to its corresponding sub-matcher.
|
269
|
+
#
|
270
|
+
#--
|
271
|
+
# TODO: push this up to Sexp and make this the workhorse
|
272
|
+
# TODO: do the same with ===/satisfy?
|
273
|
+
|
274
|
+
def satisfy? o
|
275
|
+
return unless o.kind_of?(Sexp) &&
|
276
|
+
(length == o.length || Matcher === last && last.greedy?)
|
277
|
+
|
278
|
+
each_with_index.all? { |child, i|
|
279
|
+
sexp = o.at i
|
280
|
+
if Sexp === child then # TODO: when will this NOT be a matcher?
|
281
|
+
sexp = o.sexp_body i if child.respond_to?(:greedy?) && child.greedy?
|
282
|
+
child.satisfy? sexp
|
283
|
+
else
|
284
|
+
child == sexp
|
285
|
+
end
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
##
|
290
|
+
# Tree equivalent to String#=~, returns true if +self+ matches
|
291
|
+
# +sexp+ as a whole or in a sub-tree (if +match_subs?+).
|
292
|
+
#
|
293
|
+
# TODO: maybe this should NOT be aliased to === ?
|
294
|
+
#
|
295
|
+
# TODO: example
|
296
|
+
|
297
|
+
def =~ sexp
|
298
|
+
raise ArgumentError, "Can't both be matchers: %p" % [sexp] if Matcher === sexp
|
299
|
+
|
300
|
+
self.satisfy?(sexp) ||
|
301
|
+
(self.class.match_subs? && sexp.each_sexp.any? { |sub| self =~ sub })
|
302
|
+
end
|
303
|
+
|
304
|
+
alias === =~ # TODO?: alias === satisfy?
|
305
|
+
|
306
|
+
##
|
307
|
+
# Searches through +sexp+ for all sub-trees that match this
|
308
|
+
# matcher and returns a MatchCollection for each match.
|
309
|
+
#
|
310
|
+
# TODO: redirect?
|
311
|
+
# Example:
|
312
|
+
# Q{ s(:b) } / s(:a, s(:b)) => [s(:b)]
|
313
|
+
|
314
|
+
def / sexp
|
315
|
+
raise ArgumentError, "can't both be matchers" if Matcher === sexp
|
316
|
+
|
317
|
+
# TODO: move search_each into matcher?
|
318
|
+
MatchCollection.new sexp.search_each(self).to_a
|
319
|
+
end
|
320
|
+
|
321
|
+
##
|
322
|
+
# Combines the Matcher with another Matcher, the resulting one will
|
323
|
+
# be satisfied if either Matcher would be satisfied.
|
324
|
+
#
|
325
|
+
# TODO: redirect
|
326
|
+
# Example:
|
327
|
+
# s(:a) | s(:b)
|
328
|
+
|
329
|
+
def | other
|
330
|
+
Any.new self, other
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
334
|
+
# Combines the Matcher with another Matcher, the resulting one will
|
335
|
+
# be satisfied only if both Matchers would be satisfied.
|
336
|
+
#
|
337
|
+
# TODO: redirect
|
338
|
+
# Example:
|
339
|
+
# t(:a) & include(:b)
|
340
|
+
|
341
|
+
def & other
|
342
|
+
All.new self, other
|
343
|
+
end
|
344
|
+
|
345
|
+
##
|
346
|
+
# Returns a Matcher that matches whenever this Matcher would not have matched
|
347
|
+
#
|
348
|
+
# Example:
|
349
|
+
# -s(:a)
|
350
|
+
|
351
|
+
def -@
|
352
|
+
Not.new self
|
353
|
+
end
|
354
|
+
|
355
|
+
##
|
356
|
+
# Returns a Matcher that matches if this has a sibling +o+
|
357
|
+
#
|
358
|
+
# Example:
|
359
|
+
# s(:a) >> s(:b)
|
360
|
+
|
361
|
+
def >> other
|
362
|
+
Sibling.new self, other
|
363
|
+
end
|
364
|
+
|
365
|
+
##
|
366
|
+
# Is this matcher greedy? Defaults to false.
|
367
|
+
|
368
|
+
def greedy?
|
369
|
+
false
|
370
|
+
end
|
371
|
+
|
372
|
+
def inspect # :nodoc:
|
373
|
+
s = super
|
374
|
+
s[0] = "q"
|
375
|
+
s
|
376
|
+
end
|
377
|
+
|
378
|
+
def pretty_print q # :nodoc:
|
379
|
+
q.group 1, "q(", ")" do
|
380
|
+
q.seplist self do |v|
|
381
|
+
q.pp v
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
##
|
387
|
+
# Parse a lispy string representation of a matcher into a Matcher.
|
388
|
+
# See +Parser+.
|
389
|
+
|
390
|
+
def self.parse s
|
391
|
+
Parser.new(s).parse
|
392
|
+
end
|
393
|
+
|
394
|
+
##
|
395
|
+
# Converts from a lispy string to Sexp matchers in a safe manner.
|
396
|
+
#
|
397
|
+
# "(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }
|
398
|
+
|
399
|
+
class Parser
|
400
|
+
|
401
|
+
##
|
402
|
+
# The stream of tokens to parse. See #lex.
|
403
|
+
|
404
|
+
attr_accessor :tokens
|
405
|
+
|
406
|
+
##
|
407
|
+
# Create a new Parser instance on +s+
|
408
|
+
|
409
|
+
def initialize s
|
410
|
+
self.tokens = lex s
|
411
|
+
end
|
412
|
+
|
413
|
+
##
|
414
|
+
# Converts +s+ into a stream of tokens and adds them to +tokens+.
|
415
|
+
|
416
|
+
def lex s
|
417
|
+
s.scan %r%[()\[\]]|\"[^"]*\"|/[^/]*/|:?[\w?!=~-]+%
|
418
|
+
end
|
419
|
+
|
420
|
+
##
|
421
|
+
# Returns the next token and removes it from the stream or raises if empty.
|
422
|
+
|
423
|
+
def next_token
|
424
|
+
raise SyntaxError, "unbalanced input" if tokens.empty?
|
425
|
+
tokens.shift
|
426
|
+
end
|
427
|
+
|
428
|
+
##
|
429
|
+
# Returns the next token without removing it from the stream.
|
430
|
+
|
431
|
+
def peek_token
|
432
|
+
tokens.first
|
433
|
+
end
|
434
|
+
|
435
|
+
##
|
436
|
+
# Parses tokens and returns a +Matcher+ instance.
|
437
|
+
|
438
|
+
def parse
|
439
|
+
result = parse_sexp until tokens.empty?
|
440
|
+
result
|
441
|
+
end
|
442
|
+
|
443
|
+
##
|
444
|
+
# Parses a string into a sexp matcher:
|
445
|
+
#
|
446
|
+
# SEXP : "(" SEXP:args* ")" => Sexp.q(*args)
|
447
|
+
# | "[" CMD:cmd sexp:args* "]" => Sexp.cmd(*args)
|
448
|
+
# | "nil" => nil
|
449
|
+
# | /\d+/:n => n.to_i
|
450
|
+
# | "___" => Sexp.___
|
451
|
+
# | "_" => Sexp._
|
452
|
+
# | /^\/(.*)\/$/:re => Regexp.new re[0]
|
453
|
+
# | /^"(.*)"$/:s => String.new s[0]
|
454
|
+
# | UP_NAME:name => Object.const_get name
|
455
|
+
# | NAME:name => name.to_sym
|
456
|
+
# UP_NAME: /[A-Z]\w*/
|
457
|
+
# NAME : /:?[\w?!=~-]+/
|
458
|
+
# CMD : t | k | m | atom | not? | - | any | child | include
|
459
|
+
|
460
|
+
def parse_sexp
|
461
|
+
token = next_token
|
462
|
+
|
463
|
+
case token
|
464
|
+
when "(" then
|
465
|
+
parse_list
|
466
|
+
when "[" then
|
467
|
+
parse_cmd
|
468
|
+
when "nil" then
|
469
|
+
nil
|
470
|
+
when /^\d+$/ then
|
471
|
+
token.to_i
|
472
|
+
when "___" then
|
473
|
+
Sexp.___
|
474
|
+
when "_" then
|
475
|
+
Sexp._
|
476
|
+
when %r%^/(.*)/$% then
|
477
|
+
re = $1
|
478
|
+
raise SyntaxError, "Not allowed: /%p/" % [re] unless
|
479
|
+
re =~ /\A([\w()|.*+^$]+)\z/
|
480
|
+
Regexp.new re
|
481
|
+
when /^"(.*)"$/ then
|
482
|
+
$1
|
483
|
+
when /^([A-Z]\w*)$/ then
|
484
|
+
Object.const_get $1
|
485
|
+
when /^:?([\w?!=~-]+)$/ then
|
486
|
+
$1.to_sym
|
487
|
+
else
|
488
|
+
raise SyntaxError, "unhandled token: %p" % [token]
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
##
|
493
|
+
# Parses a balanced list of expressions and returns the
|
494
|
+
# equivalent matcher.
|
495
|
+
|
496
|
+
def parse_list
|
497
|
+
result = []
|
498
|
+
|
499
|
+
result << parse_sexp while peek_token && peek_token != ")"
|
500
|
+
next_token # pop off ")"
|
501
|
+
|
502
|
+
Sexp.q(*result)
|
503
|
+
end
|
504
|
+
|
505
|
+
##
|
506
|
+
# A collection of allowed commands to convert into matchers.
|
507
|
+
|
508
|
+
ALLOWED = [:t, :m, :k, :atom, :not?, :-, :any, :child, :include].freeze
|
509
|
+
|
510
|
+
##
|
511
|
+
# Parses a balanced command. A command is denoted by square
|
512
|
+
# brackets and must conform to a whitelisted set of allowed
|
513
|
+
# commands (see +ALLOWED+).
|
514
|
+
|
515
|
+
def parse_cmd
|
516
|
+
args = []
|
517
|
+
args << parse_sexp while peek_token && peek_token != "]"
|
518
|
+
next_token # pop off "]"
|
519
|
+
|
520
|
+
cmd = args.shift
|
521
|
+
args = Sexp.q(*args)
|
522
|
+
|
523
|
+
raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd
|
524
|
+
|
525
|
+
result = Sexp.send cmd, *args
|
526
|
+
|
527
|
+
result
|
528
|
+
end
|
529
|
+
end # class Parser
|
530
|
+
end # class Matcher
|
531
|
+
|
532
|
+
##
|
533
|
+
# Matches any single item.
|
534
|
+
#
|
535
|
+
# examples:
|
536
|
+
#
|
537
|
+
# s(:a) / s{ _ } #=> [s(:a)]
|
538
|
+
# s(:a, s(s(:b))) / s{ s(_) } #=> [s(s(:b))]
|
539
|
+
|
540
|
+
class Wild < Matcher
|
541
|
+
##
|
542
|
+
# Matches any single element.
|
543
|
+
|
544
|
+
def satisfy? o
|
545
|
+
true
|
546
|
+
end
|
547
|
+
|
548
|
+
def inspect # :nodoc:
|
549
|
+
"_"
|
550
|
+
end
|
551
|
+
|
552
|
+
def pretty_print q # :nodoc:
|
553
|
+
q.text "_"
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
##
|
558
|
+
# Matches all remaining input. If remaining comes before any other
|
559
|
+
# matchers, they will be ignored.
|
560
|
+
#
|
561
|
+
# examples:
|
562
|
+
#
|
563
|
+
# s(:a) / s{ s(:a, ___ ) } #=> [s(:a)]
|
564
|
+
# s(:a, :b, :c) / s{ s(:a, ___ ) } #=> [s(:a, :b, :c)]
|
565
|
+
|
566
|
+
class Remaining < Matcher
|
567
|
+
##
|
568
|
+
# Always satisfied once this is reached. Think of it as a var arg.
|
569
|
+
|
570
|
+
def satisfy? o
|
571
|
+
true
|
572
|
+
end
|
573
|
+
|
574
|
+
def greedy?
|
575
|
+
true
|
576
|
+
end
|
577
|
+
|
578
|
+
def inspect # :nodoc:
|
579
|
+
"___"
|
580
|
+
end
|
581
|
+
|
582
|
+
def pretty_print q # :nodoc:
|
583
|
+
q.text "___"
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
##
|
588
|
+
# Matches when any of the sub-expressions match.
|
589
|
+
#
|
590
|
+
# This is also available via Matcher#|.
|
591
|
+
#
|
592
|
+
# examples:
|
593
|
+
#
|
594
|
+
# s(:a) / s{ any(s(:a), s(:b)) } #=> [s(:a)]
|
595
|
+
# s(:a) / s{ s(:a) | s(:b) } #=> [s(:a)] # same thing via |
|
596
|
+
# s(:a) / s{ any(s(:b), s(:c)) } #=> []
|
597
|
+
|
598
|
+
class Any < Matcher
|
599
|
+
##
|
600
|
+
# The collection of sub-matchers to match against.
|
601
|
+
|
602
|
+
attr_reader :options
|
603
|
+
|
604
|
+
##
|
605
|
+
# Create an Any matcher which will match any of the +options+.
|
606
|
+
|
607
|
+
def initialize *options
|
608
|
+
@options = options
|
609
|
+
end
|
610
|
+
|
611
|
+
##
|
612
|
+
# Satisfied when any sub expressions match +o+
|
613
|
+
|
614
|
+
def satisfy? o
|
615
|
+
options.any? { |exp|
|
616
|
+
Sexp === exp && exp.satisfy?(o) || exp == o
|
617
|
+
}
|
618
|
+
end
|
619
|
+
|
620
|
+
def == o # :nodoc:
|
621
|
+
super && self.options == o.options
|
622
|
+
end
|
623
|
+
|
624
|
+
def inspect # :nodoc:
|
625
|
+
options.map(&:inspect).join(" | ")
|
626
|
+
end
|
627
|
+
|
628
|
+
def pretty_print q # :nodoc:
|
629
|
+
q.group 1, "any(", ")" do
|
630
|
+
q.seplist options do |v|
|
631
|
+
q.pp v
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
##
|
638
|
+
# Matches only when all sub-expressions match.
|
639
|
+
#
|
640
|
+
# This is also available via Matcher#&.
|
641
|
+
#
|
642
|
+
# examples:
|
643
|
+
#
|
644
|
+
# s(:a) / s{ all(s(:a), s(:b)) } #=> []
|
645
|
+
# s(:a, :b) / s{ t(:a) & include(:b)) } #=> [s(:a, :b)]
|
646
|
+
|
647
|
+
class All < Matcher
|
648
|
+
##
|
649
|
+
# The collection of sub-matchers to match against.
|
650
|
+
|
651
|
+
attr_reader :options
|
652
|
+
|
653
|
+
##
|
654
|
+
# Create an All matcher which will match all of the +options+.
|
655
|
+
|
656
|
+
def initialize *options
|
657
|
+
@options = options
|
658
|
+
end
|
659
|
+
|
660
|
+
##
|
661
|
+
# Satisfied when all sub expressions match +o+
|
662
|
+
|
663
|
+
def satisfy? o
|
664
|
+
options.all? { |exp|
|
665
|
+
exp.kind_of?(Sexp) ? exp.satisfy?(o) : exp == o
|
666
|
+
}
|
667
|
+
end
|
668
|
+
|
669
|
+
def == o # :nodoc:
|
670
|
+
super && self.options == o.options
|
671
|
+
end
|
672
|
+
|
673
|
+
def inspect # :nodoc:
|
674
|
+
options.map(&:inspect).join(" & ")
|
675
|
+
end
|
676
|
+
|
677
|
+
def pretty_print q # :nodoc:
|
678
|
+
q.group 1, "all(", ")" do
|
679
|
+
q.seplist options do |v|
|
680
|
+
q.pp v
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
##
|
687
|
+
# Matches when sub-expression does not match.
|
688
|
+
#
|
689
|
+
# This is also available via Matcher#-@.
|
690
|
+
#
|
691
|
+
# examples:
|
692
|
+
#
|
693
|
+
# s(:a) / s{ not?(s(:b)) } #=> [s(:a)]
|
694
|
+
# s(:a) / s{ -s(:b) } #=> [s(:a)]
|
695
|
+
# s(:a) / s{ s(not? :a) } #=> []
|
696
|
+
|
697
|
+
class Not < Matcher
|
698
|
+
|
699
|
+
##
|
700
|
+
# The value to negate in the match.
|
701
|
+
|
702
|
+
attr_reader :value
|
703
|
+
|
704
|
+
##
|
705
|
+
# Creates a Matcher which will match any Sexp that does not match the +value+
|
706
|
+
|
707
|
+
def initialize value
|
708
|
+
@value = value
|
709
|
+
end
|
710
|
+
|
711
|
+
def == o # :nodoc:
|
712
|
+
super && self.value == o.value
|
713
|
+
end
|
714
|
+
|
715
|
+
##
|
716
|
+
# Satisfied if a +o+ does not match the +value+
|
717
|
+
|
718
|
+
def satisfy? o
|
719
|
+
!(value.kind_of?(Sexp) ? value.satisfy?(o) : value == o)
|
720
|
+
end
|
721
|
+
|
722
|
+
def inspect # :nodoc:
|
723
|
+
"not?(%p)" % [value]
|
724
|
+
end
|
725
|
+
|
726
|
+
def pretty_print q # :nodoc:
|
727
|
+
q.group 1, "not?(", ")" do
|
728
|
+
q.pp value
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
##
|
734
|
+
# Matches anything that has a child matching the sub-expression
|
735
|
+
#
|
736
|
+
# example:
|
737
|
+
#
|
738
|
+
# s(s(s(s(s(:a))))) / s{ child(s(:a)) } #=> [s(s(s(s(s(:a))))),
|
739
|
+
# s(s(s(s(:a)))),
|
740
|
+
# s(s(s(:a))),
|
741
|
+
# s(s(:a)),
|
742
|
+
# s(:a)]
|
743
|
+
|
744
|
+
class Child < Matcher
|
745
|
+
##
|
746
|
+
# The child to match.
|
747
|
+
|
748
|
+
attr_reader :child
|
749
|
+
|
750
|
+
##
|
751
|
+
# Create a Child matcher which will match anything having a
|
752
|
+
# descendant matching +child+.
|
753
|
+
|
754
|
+
def initialize child
|
755
|
+
@child = child
|
756
|
+
end
|
757
|
+
|
758
|
+
##
|
759
|
+
# Satisfied if matches +child+ or +o+ has a descendant matching
|
760
|
+
# +child+.
|
761
|
+
|
762
|
+
def satisfy? o
|
763
|
+
child.satisfy?(o) ||
|
764
|
+
(o.kind_of?(Sexp) && o.search_each(child).any?)
|
765
|
+
end
|
766
|
+
|
767
|
+
def == o # :nodoc:
|
768
|
+
super && self.child == o.child
|
769
|
+
end
|
770
|
+
|
771
|
+
def inspect # :nodoc:
|
772
|
+
"child(%p)" % [child]
|
773
|
+
end
|
774
|
+
|
775
|
+
def pretty_print q # :nodoc:
|
776
|
+
q.group 1, "child(", ")" do
|
777
|
+
q.pp child
|
778
|
+
end
|
779
|
+
end
|
780
|
+
end
|
781
|
+
|
782
|
+
##
|
783
|
+
# Matches any atom (non-Sexp).
|
784
|
+
#
|
785
|
+
# examples:
|
786
|
+
#
|
787
|
+
# s(:a) / s{ s(atom) } #=> [s(:a)]
|
788
|
+
# s(:a, s(:b)) / s{ s(atom) } #=> [s(:b)]
|
789
|
+
|
790
|
+
class Atom < Matcher
|
791
|
+
##
|
792
|
+
# Satisfied when +o+ is an atom.
|
793
|
+
|
794
|
+
def satisfy? o
|
795
|
+
!(o.kind_of? Sexp)
|
796
|
+
end
|
797
|
+
|
798
|
+
def inspect #:nodoc:
|
799
|
+
"atom"
|
800
|
+
end
|
801
|
+
|
802
|
+
def pretty_print q # :nodoc:
|
803
|
+
q.text "atom"
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
##
|
808
|
+
# Matches any atom who's string representation matches the patterns
|
809
|
+
# passed in.
|
810
|
+
#
|
811
|
+
# examples:
|
812
|
+
#
|
813
|
+
# s(:a) / s{ m('a') } #=> [s(:a)]
|
814
|
+
# s(:a) / s{ m(/\w/,/\d/) } #=> [s(:a)]
|
815
|
+
# s(:tests, s(s(:test_a), s(:test_b))) / s{ m(/test_\w/) } #=> [s(:test_a),
|
816
|
+
#
|
817
|
+
# TODO: maybe don't require non-sexps? This does respond to =~ now.
|
818
|
+
|
819
|
+
class Pattern < Matcher
|
820
|
+
|
821
|
+
##
|
822
|
+
# The regexp to match for the pattern.
|
823
|
+
|
824
|
+
attr_reader :pattern
|
825
|
+
|
826
|
+
def == o # :nodoc:
|
827
|
+
super && self.pattern == o.pattern
|
828
|
+
end
|
829
|
+
|
830
|
+
##
|
831
|
+
# Create a Patten matcher which will match any atom that either
|
832
|
+
# matches the input +pattern+.
|
833
|
+
|
834
|
+
def initialize pattern
|
835
|
+
@pattern = pattern
|
836
|
+
end
|
837
|
+
|
838
|
+
##
|
839
|
+
# Satisfied if +o+ is an atom, and +o+ matches +pattern+
|
840
|
+
|
841
|
+
def satisfy? o
|
842
|
+
!o.kind_of?(Sexp) && o.to_s =~ pattern # TODO: question to_s
|
843
|
+
end
|
844
|
+
|
845
|
+
def inspect # :nodoc:
|
846
|
+
"m(%p)" % pattern
|
847
|
+
end
|
848
|
+
|
849
|
+
def pretty_print q # :nodoc:
|
850
|
+
q.group 1, "m(", ")" do
|
851
|
+
q.pp pattern
|
852
|
+
end
|
853
|
+
end
|
854
|
+
|
855
|
+
def eql? o
|
856
|
+
super and self.pattern.eql? o.pattern
|
857
|
+
end
|
858
|
+
|
859
|
+
def hash
|
860
|
+
[super, pattern].hash
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
864
|
+
##
|
865
|
+
# Matches any atom that is an instance of the specified class or module.
|
866
|
+
#
|
867
|
+
# examples:
|
868
|
+
#
|
869
|
+
# s(:lit, 6.28) / s{ q(:lit, k(Float)) } #=> [s(:lit, 6.28)]
|
870
|
+
|
871
|
+
class Klass < Pattern
|
872
|
+
def satisfy? o
|
873
|
+
o.kind_of? self.pattern
|
874
|
+
end
|
875
|
+
|
876
|
+
def inspect # :nodoc:
|
877
|
+
"k(%p)" % pattern
|
878
|
+
end
|
879
|
+
|
880
|
+
def pretty_print q # :nodoc:
|
881
|
+
q.group 1, "k(", ")" do
|
882
|
+
q.pp pattern
|
883
|
+
end
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
##
|
888
|
+
# Matches anything having the same sexp_type, which is the first
|
889
|
+
# value in a Sexp.
|
890
|
+
#
|
891
|
+
# examples:
|
892
|
+
#
|
893
|
+
# s(:a, :b) / s{ t(:a) } #=> [s(:a, :b)]
|
894
|
+
# s(:a, :b) / s{ t(:b) } #=> []
|
895
|
+
# s(:a, s(:b, :c)) / s{ t(:b) } #=> [s(:b, :c)]
|
896
|
+
|
897
|
+
class Type < Matcher
|
898
|
+
attr_reader :sexp_type
|
899
|
+
|
900
|
+
##
|
901
|
+
# Creates a Matcher which will match any Sexp who's type is +type+, where a type is
|
902
|
+
# the first element in the Sexp.
|
903
|
+
|
904
|
+
def initialize type
|
905
|
+
@sexp_type = type
|
906
|
+
end
|
907
|
+
|
908
|
+
def == o # :nodoc:
|
909
|
+
super && self.sexp_type == o.sexp_type
|
910
|
+
end
|
911
|
+
|
912
|
+
##
|
913
|
+
# Satisfied if the sexp_type of +o+ is +type+.
|
914
|
+
|
915
|
+
def satisfy? o
|
916
|
+
o.kind_of?(Sexp) && o.sexp_type == sexp_type
|
917
|
+
end
|
918
|
+
|
919
|
+
def inspect # :nodoc:
|
920
|
+
"t(%p)" % sexp_type
|
921
|
+
end
|
922
|
+
|
923
|
+
def pretty_print q # :nodoc:
|
924
|
+
q.group 1, "t(", ")" do
|
925
|
+
q.pp sexp_type
|
926
|
+
end
|
927
|
+
end
|
928
|
+
end
|
929
|
+
|
930
|
+
##
|
931
|
+
# Matches an expression or any expression that includes the child.
|
932
|
+
#
|
933
|
+
# examples:
|
934
|
+
#
|
935
|
+
# s(:a, :b) / s{ include(:b) } #=> [s(:a, :b)]
|
936
|
+
# s(s(s(:a))) / s{ include(:a) } #=> [s(:a)]
|
937
|
+
|
938
|
+
class Include < Matcher
|
939
|
+
##
|
940
|
+
# The value that should be included in the match.
|
941
|
+
|
942
|
+
attr_reader :value
|
943
|
+
|
944
|
+
##
|
945
|
+
# Creates a Matcher which will match any Sexp that contains the
|
946
|
+
# +value+.
|
947
|
+
|
948
|
+
def initialize value
|
949
|
+
@value = value
|
950
|
+
end
|
951
|
+
|
952
|
+
##
|
953
|
+
# Satisfied if a +o+ is a Sexp and one of +o+'s elements matches
|
954
|
+
# value
|
955
|
+
|
956
|
+
def satisfy? o
|
957
|
+
Sexp === o && o.any? { |c|
|
958
|
+
# TODO: switch to respond_to??
|
959
|
+
Sexp === value ? value.satisfy?(c) : value == c
|
960
|
+
}
|
961
|
+
end
|
962
|
+
|
963
|
+
def == o # :nodoc:
|
964
|
+
super && self.value == o.value
|
965
|
+
end
|
966
|
+
|
967
|
+
def inspect # :nodoc:
|
968
|
+
"include(%p)" % [value]
|
969
|
+
end
|
970
|
+
|
971
|
+
def pretty_print q # :nodoc:
|
972
|
+
q.group 1, "include(", ")" do
|
973
|
+
q.pp value
|
974
|
+
end
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
##
|
979
|
+
# See Matcher for sibling relations: <,<<,>>,>
|
980
|
+
|
981
|
+
class Sibling < Matcher
|
982
|
+
|
983
|
+
##
|
984
|
+
# The LHS of the matcher.
|
985
|
+
|
986
|
+
attr_reader :subject
|
987
|
+
|
988
|
+
##
|
989
|
+
# The RHS of the matcher.
|
990
|
+
|
991
|
+
attr_reader :sibling
|
992
|
+
|
993
|
+
##
|
994
|
+
# An optional distance requirement for the matcher.
|
995
|
+
|
996
|
+
attr_reader :distance
|
997
|
+
|
998
|
+
##
|
999
|
+
# Creates a Matcher which will match any pair of Sexps that are siblings.
|
1000
|
+
# Defaults to matching the immediate following sibling.
|
1001
|
+
|
1002
|
+
def initialize subject, sibling, distance = nil
|
1003
|
+
@subject = subject
|
1004
|
+
@sibling = sibling
|
1005
|
+
@distance = distance
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
##
|
1009
|
+
# Satisfied if o contains +subject+ followed by +sibling+
|
1010
|
+
|
1011
|
+
def satisfy? o
|
1012
|
+
# Future optimizations:
|
1013
|
+
# * Shortcut matching sibling
|
1014
|
+
subject_matches = index_matches(subject, o)
|
1015
|
+
return nil if subject_matches.empty?
|
1016
|
+
|
1017
|
+
sibling_matches = index_matches(sibling, o)
|
1018
|
+
return nil if sibling_matches.empty?
|
1019
|
+
|
1020
|
+
subject_matches.any? { |i1, _data_1|
|
1021
|
+
sibling_matches.any? { |i2, _data_2|
|
1022
|
+
distance ? (i2-i1 == distance) : i2 > i1
|
1023
|
+
}
|
1024
|
+
}
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
def == o # :nodoc:
|
1028
|
+
super &&
|
1029
|
+
self.subject == o.subject &&
|
1030
|
+
self.sibling == o.sibling &&
|
1031
|
+
self.distance == o.distance
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
def inspect # :nodoc:
|
1035
|
+
"%p >> %p" % [subject, sibling]
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
def pretty_print q # :nodoc:
|
1039
|
+
if distance then
|
1040
|
+
q.group 1, "sibling(", ")" do
|
1041
|
+
q.seplist [subject, sibling, distance] do |v|
|
1042
|
+
q.pp v
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
else
|
1046
|
+
q.group 1 do
|
1047
|
+
q.pp subject
|
1048
|
+
q.text " >> "
|
1049
|
+
q.pp sibling
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
private
|
1055
|
+
|
1056
|
+
def index_matches pattern, o
|
1057
|
+
indexes = []
|
1058
|
+
return indexes unless o.kind_of? Sexp
|
1059
|
+
|
1060
|
+
o.each_with_index do |e, i|
|
1061
|
+
data = {}
|
1062
|
+
if pattern.kind_of?(Sexp) ? pattern.satisfy?(e) : pattern == o[i]
|
1063
|
+
indexes << [i, data]
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
indexes
|
1068
|
+
end
|
1069
|
+
end # class Sibling
|
1070
|
+
|
1071
|
+
##
|
1072
|
+
# Wraps the results of a Sexp query. MatchCollection defines
|
1073
|
+
# MatchCollection#/ so that you can chain queries.
|
1074
|
+
#
|
1075
|
+
# For instance:
|
1076
|
+
# res = s(:a, s(:b)) / s{ s(:a,_) } / s{ s(:b) }
|
1077
|
+
|
1078
|
+
class MatchCollection < Array
|
1079
|
+
##
|
1080
|
+
# See Traverse#search
|
1081
|
+
|
1082
|
+
def / pattern
|
1083
|
+
inject(self.class.new) { |result, match|
|
1084
|
+
result.concat match / pattern
|
1085
|
+
}
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
def inspect # :nodoc:
|
1089
|
+
"MatchCollection.new(%s)" % self.to_a.inspect[1..-2]
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
alias :to_s :inspect # :nodoc:
|
1093
|
+
|
1094
|
+
def pretty_print q # :nodoc:
|
1095
|
+
q.group 1, "MatchCollection.new(", ")" do
|
1096
|
+
q.seplist(self) {|v| q.pp v }
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
end # class MatchCollection
|
1100
|
+
end # class Sexp
|