sexp_processor 4.12.0 → 4.15.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 +0 -0
- data.tar.gz.sig +0 -0
- data/History.rdoc +47 -0
- data/Manifest.txt +1 -0
- data/Rakefile +2 -0
- data/lib/pt_testcase.rb +13 -15
- data/lib/sexp.rb +14 -1053
- data/lib/sexp_matcher.rb +1100 -0
- data/lib/sexp_processor.rb +2 -2
- data/test/test_sexp.rb +189 -132
- data/test/test_sexp_processor.rb +2 -2
- metadata +18 -16
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dae436d9eece3ad19a5d390da622ee2b73db2db5e73a4a6b06fe351e8759d45f
|
4
|
+
data.tar.gz: 64c4cda4d2f25f759812de2dea4bd7d85e948a6d9cba8507a7d64318c358d116
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8647ce87dca77073d067bd2027b139499fa8db297ea524c453d709f5e52f057c9355b8b2f49e265aa09aef7c1a29dc6038409fb5ba19d33c9fb2eeb972b4c210
|
7
|
+
data.tar.gz: 38eb466cf27e2eeb30a14c9eabae202ab3b1d7f119b7b2a715c848fdcf98b8e2e130d656e05336781572d7ca9ff74ea8185683e065e6af557cac50f483b92436
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/History.rdoc
CHANGED
@@ -1,3 +1,50 @@
|
|
1
|
+
=== 4.15.0 / 2020-06-09
|
2
|
+
|
3
|
+
* 1 minor enhancement:
|
4
|
+
|
5
|
+
* Added `child` and `include` to Sexp::Matcher.parse language.
|
6
|
+
|
7
|
+
=== 4.14.1 / 2020-02-09
|
8
|
+
|
9
|
+
* 2 bug fixes:
|
10
|
+
|
11
|
+
* Declared support for ruby 2.2+ to gemspec.
|
12
|
+
* Fixed alias for `not?` to `-` for ruby <= 2.4. (nard-tech).
|
13
|
+
|
14
|
+
=== 4.14.0 / 2020-02-06
|
15
|
+
|
16
|
+
* 4 minor enhancements:
|
17
|
+
|
18
|
+
* Added '-' as an alias for the 'not?' pattern matching command.
|
19
|
+
* Added Klass matcher to match on types.
|
20
|
+
* Added `k` shortcut for Klass & hooked into Sexp::Matcher.parse.
|
21
|
+
* Added any matcher to pattern parser.
|
22
|
+
|
23
|
+
=== 4.13.0 / 2019-09-24
|
24
|
+
|
25
|
+
* 4 minor enhancements:
|
26
|
+
|
27
|
+
* Added Sexp.q (query) and deprecated Sexp.s to distinguish better and match inspect output.
|
28
|
+
* Extended Sexp::Matcher::Parser to allow `not?` patterns.
|
29
|
+
* Extended Sexp::Matcher::Parser to cover more method names.
|
30
|
+
* Split out all pattern-oriented code to sexp_matcher.rb.
|
31
|
+
|
32
|
+
* 1 bug fix:
|
33
|
+
|
34
|
+
* Fixed bug w/ ruby's Array#eql? and #hash not looking at ivars.
|
35
|
+
|
36
|
+
=== 4.12.1 / 2019-06-03
|
37
|
+
|
38
|
+
* 1 minor enhancement:
|
39
|
+
|
40
|
+
* Sexp#line now raises if setting w/ non-integer (eg nil).
|
41
|
+
|
42
|
+
* 3 bug fixes:
|
43
|
+
|
44
|
+
* Fixed pt_testcase.rb for block args w/ trailing commas.
|
45
|
+
* Fixed pt_testcase.rb for stabby proc sexps.
|
46
|
+
* Simple fixes for STRICT_SEXP=1.
|
47
|
+
|
1
48
|
=== 4.12.0 / 2019-03-12
|
2
49
|
|
3
50
|
* 3 bug fixes:
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
data/lib/pt_testcase.rb
CHANGED
@@ -363,26 +363,26 @@ class ParseTreeTestCase < Minitest::Test
|
|
363
363
|
###
|
364
364
|
# 1.9 specific tests
|
365
365
|
|
366
|
-
add_19edgecases("
|
366
|
+
add_19edgecases("-> () { (x + 1) }",
|
367
367
|
s(:iter,
|
368
|
-
s(:
|
368
|
+
s(:lambda),
|
369
369
|
s(:args),
|
370
370
|
s(:call, s(:call, nil, :x), :+, s(:lit, 1))),
|
371
371
|
"stabby_args" => "->() { (x + 1) }",
|
372
372
|
"stabby_args_doend" => "->() do (x + 1) end")
|
373
373
|
|
374
|
-
add_19edgecases("
|
374
|
+
add_19edgecases("-> { (x + 1) }",
|
375
375
|
s(:iter,
|
376
|
-
s(:
|
376
|
+
s(:lambda),
|
377
377
|
0,
|
378
378
|
s(:call, s(:call, nil, :x), :+, s(:lit, 1))),
|
379
379
|
"stabby_args_0_no_parens" => "-> { (x + 1) }",
|
380
380
|
"stabby_args_0_no_parens_doend" => "-> do (x + 1) end",
|
381
381
|
"stabby_args_0_spacebar_broken" => "->{x+1}") # I hate you
|
382
382
|
|
383
|
-
add_19edgecases("
|
383
|
+
add_19edgecases("-> (x, y) { (x + y) }",
|
384
384
|
s(:iter,
|
385
|
-
s(:
|
385
|
+
s(:lambda),
|
386
386
|
s(:args, :x, :y),
|
387
387
|
s(:call, s(:lvar, :x), :+, s(:lvar, :y))),
|
388
388
|
"stabby_args_2" => "->(x, y) { (x + y) }",
|
@@ -390,9 +390,9 @@ class ParseTreeTestCase < Minitest::Test
|
|
390
390
|
"stabby_args_2_no_parens" => "-> x, y { (x + y) }",
|
391
391
|
"stabby_args_2_no_parens_doend" => "-> x, y do (x + y) end")
|
392
392
|
|
393
|
-
add_19edgecases("
|
393
|
+
add_19edgecases("-> (x) { (x + 1) }",
|
394
394
|
s(:iter,
|
395
|
-
s(:
|
395
|
+
s(:lambda),
|
396
396
|
s(:args, :x),
|
397
397
|
s(:call, s(:lvar, :x), :+, s(:lit, 1))),
|
398
398
|
"stabby_args_1" => "->(x) { (x + 1) }",
|
@@ -2080,17 +2080,15 @@ class ParseTreeTestCase < Minitest::Test
|
|
2080
2080
|
"Ruby" => "lambda { |a,| a }",
|
2081
2081
|
"ParseTree" => s(:iter,
|
2082
2082
|
s(:call, nil, :lambda),
|
2083
|
-
s(:args, :a),
|
2084
|
-
s(:lvar, :a))
|
2085
|
-
"Ruby2Ruby" => "lambda { |a| a }")
|
2083
|
+
s(:args, :a, nil),
|
2084
|
+
s(:lvar, :a)))
|
2086
2085
|
|
2087
2086
|
add_tests("lambda_args_norm_comma2",
|
2088
|
-
"Ruby" => "lambda { |a,b,| a }",
|
2087
|
+
"Ruby" => "lambda { |a, b,| a }",
|
2089
2088
|
"ParseTree" => s(:iter,
|
2090
2089
|
s(:call, nil, :lambda),
|
2091
|
-
s(:args, :a, :b),
|
2092
|
-
s(:lvar, :a))
|
2093
|
-
"Ruby2Ruby" => "lambda { |a, b| a }")
|
2090
|
+
s(:args, :a, :b, nil),
|
2091
|
+
s(:lvar, :a)))
|
2094
2092
|
|
2095
2093
|
add_tests("lambda_args_norm_star",
|
2096
2094
|
"Ruby" => "lambda { |a, *star| star }",
|
data/lib/sexp.rb
CHANGED
@@ -69,6 +69,14 @@ class Sexp < Array # ZenTest FULL
|
|
69
69
|
obj.class == self.class and super # only because of a bug in ruby
|
70
70
|
end
|
71
71
|
|
72
|
+
def eql? o
|
73
|
+
self.class == o.class && super
|
74
|
+
end
|
75
|
+
|
76
|
+
def hash
|
77
|
+
[self.class, *self].hash
|
78
|
+
end
|
79
|
+
|
72
80
|
##
|
73
81
|
# Returns true if the node_type is +array+ or +args+.
|
74
82
|
#
|
@@ -202,13 +210,16 @@ class Sexp < Array # ZenTest FULL
|
|
202
210
|
each_sexp.find_all { |sexp| sexp.sexp_type == name }
|
203
211
|
end
|
204
212
|
|
213
|
+
UNASSIGNED = Object.new
|
214
|
+
|
205
215
|
##
|
206
216
|
# If passed a line number, sets the line and returns self. Otherwise
|
207
217
|
# returns the line number. This allows you to do message cascades
|
208
218
|
# and still get the sexp back.
|
209
219
|
|
210
|
-
def line n =
|
211
|
-
if n then
|
220
|
+
def line n = UNASSIGNED
|
221
|
+
if n != UNASSIGNED then
|
222
|
+
raise ArgumentError, "setting %p.line %p" % [self, n] unless Integer === n
|
212
223
|
@line = n
|
213
224
|
self
|
214
225
|
else
|
@@ -366,1055 +377,5 @@ def s *args, &blk
|
|
366
377
|
Sexp.new(*args)
|
367
378
|
end
|
368
379
|
|
369
|
-
|
370
|
-
##
|
371
|
-
# Verifies that +pattern+ is a Matcher and then dispatches to its
|
372
|
-
# #=~ method.
|
373
|
-
#
|
374
|
-
# See Matcher.=~
|
375
|
-
|
376
|
-
def =~ pattern
|
377
|
-
raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
|
378
|
-
pattern =~ self
|
379
|
-
end
|
380
|
-
|
381
|
-
##
|
382
|
-
# Verifies that +pattern+ is a Matcher and then dispatches to its
|
383
|
-
# #satisfy? method.
|
384
|
-
#
|
385
|
-
# TODO: rename match?
|
386
|
-
|
387
|
-
def satisfy? pattern
|
388
|
-
raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
|
389
|
-
pattern.satisfy? self
|
390
|
-
end
|
391
|
-
|
392
|
-
##
|
393
|
-
# Verifies that +pattern+ is a Matcher and then dispatches to its #/
|
394
|
-
# method.
|
395
|
-
#
|
396
|
-
# TODO: rename grep? match_all ? find_all ?
|
397
|
-
|
398
|
-
def / pattern
|
399
|
-
raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
|
400
|
-
pattern / self
|
401
|
-
end
|
402
|
-
|
403
|
-
##
|
404
|
-
# Recursively searches for the +pattern+ yielding the matches.
|
405
|
-
|
406
|
-
def search_each pattern, &block # TODO: rename to grep?
|
407
|
-
raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
|
408
|
-
|
409
|
-
return enum_for(:search_each, pattern) unless block_given?
|
410
|
-
|
411
|
-
if pattern.satisfy? self then
|
412
|
-
yield self
|
413
|
-
end
|
414
|
-
|
415
|
-
self.each_sexp do |subset|
|
416
|
-
subset.search_each pattern, &block
|
417
|
-
end
|
418
|
-
end
|
419
|
-
|
420
|
-
##
|
421
|
-
# Recursively searches for the +pattern+ yielding each match, and
|
422
|
-
# replacing it with the result of the block.
|
423
|
-
#
|
424
|
-
|
425
|
-
def replace_sexp pattern, &block # TODO: rename to gsub?
|
426
|
-
raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
|
427
|
-
|
428
|
-
return yield self if pattern.satisfy? self
|
429
|
-
|
430
|
-
# TODO: Needs #new_from(*new_body) to copy file/line/comment
|
431
|
-
self.class.new(*self.map { |subset|
|
432
|
-
case subset
|
433
|
-
when Sexp then
|
434
|
-
subset.replace_sexp pattern, &block
|
435
|
-
else
|
436
|
-
subset
|
437
|
-
end
|
438
|
-
})
|
439
|
-
end
|
440
|
-
|
441
|
-
##
|
442
|
-
# Matches an S-Expression.
|
443
|
-
#
|
444
|
-
# See Matcher for examples.
|
445
|
-
|
446
|
-
def self.s *args
|
447
|
-
Matcher.new(*args)
|
448
|
-
end
|
449
|
-
|
450
|
-
##
|
451
|
-
# Matches any single item.
|
452
|
-
#
|
453
|
-
# See Wild for examples.
|
454
|
-
|
455
|
-
def self._
|
456
|
-
Wild.new
|
457
|
-
end
|
458
|
-
|
459
|
-
# TODO: reorder factory methods and classes to match
|
460
|
-
|
461
|
-
##
|
462
|
-
# Matches all remaining input.
|
463
|
-
#
|
464
|
-
# See Remaining for examples.
|
465
|
-
|
466
|
-
def self.___
|
467
|
-
Remaining.new
|
468
|
-
end
|
469
|
-
|
470
|
-
##
|
471
|
-
# Matches an expression or any expression that includes the child.
|
472
|
-
#
|
473
|
-
# See Include for examples.
|
474
|
-
|
475
|
-
def self.include child # TODO: rename, name is generic ruby
|
476
|
-
Include.new(child)
|
477
|
-
end
|
478
|
-
|
479
|
-
##
|
480
|
-
# Matches any atom.
|
481
|
-
#
|
482
|
-
# See Atom for examples.
|
483
|
-
|
484
|
-
def self.atom
|
485
|
-
Atom.new
|
486
|
-
end
|
487
|
-
|
488
|
-
##
|
489
|
-
# Matches when any of the sub-expressions match.
|
490
|
-
#
|
491
|
-
# This is also available via Matcher#|.
|
492
|
-
#
|
493
|
-
# See Any for examples.
|
494
|
-
|
495
|
-
def self.any *args
|
496
|
-
Any.new(*args)
|
497
|
-
end
|
498
|
-
|
499
|
-
##
|
500
|
-
# Matches only when all sub-expressions match.
|
501
|
-
#
|
502
|
-
# This is also available via Matcher#&.
|
503
|
-
#
|
504
|
-
# See All for examples.
|
505
|
-
|
506
|
-
def self.all *args
|
507
|
-
All.new(*args)
|
508
|
-
end
|
509
|
-
|
510
|
-
##
|
511
|
-
# Matches when sub-expression does not match.
|
512
|
-
#
|
513
|
-
# This is also available via Matcher#-@.
|
514
|
-
#
|
515
|
-
# See Not for examples.
|
516
|
-
|
517
|
-
def self.not? arg
|
518
|
-
Not.new arg
|
519
|
-
end
|
520
|
-
|
521
|
-
# TODO: add Sibling factory method?
|
522
|
-
|
523
|
-
##
|
524
|
-
# Matches anything that has a child matching the sub-expression.
|
525
|
-
#
|
526
|
-
# See Child for examples.
|
527
|
-
|
528
|
-
def self.child child
|
529
|
-
Child.new child
|
530
|
-
end
|
531
|
-
|
532
|
-
##
|
533
|
-
# Matches anything having the same sexp_type, which is the first
|
534
|
-
# value in a Sexp.
|
535
|
-
#
|
536
|
-
# See Type for examples.
|
537
|
-
|
538
|
-
def self.t name
|
539
|
-
Type.new name
|
540
|
-
end
|
541
|
-
|
542
|
-
##
|
543
|
-
# Matches any atom who's string representation matches the patterns
|
544
|
-
# passed in.
|
545
|
-
#
|
546
|
-
# See Pattern for examples.
|
547
|
-
|
548
|
-
def self.m *values
|
549
|
-
res = values.map { |value|
|
550
|
-
case value
|
551
|
-
when Regexp then
|
552
|
-
value
|
553
|
-
else
|
554
|
-
re = Regexp.escape value.to_s
|
555
|
-
Regexp.new "\\A%s\\Z" % re
|
556
|
-
end
|
557
|
-
}
|
558
|
-
Pattern.new Regexp.union(*res)
|
559
|
-
end
|
560
|
-
|
561
|
-
##
|
562
|
-
# Defines a family of objects that can be used to match sexps to
|
563
|
-
# certain types of patterns, much like regexps can be used on
|
564
|
-
# strings. Generally you won't use this class directly.
|
565
|
-
#
|
566
|
-
# You would normally create a matcher using the top-level #s method,
|
567
|
-
# but with a block, calling into the Sexp factory methods. For example:
|
568
|
-
#
|
569
|
-
# s{ s(:class, m(/^Test/), _, ___) }
|
570
|
-
#
|
571
|
-
# This creates a matcher for classes whose names start with "Test".
|
572
|
-
# It uses Sexp.m to create a Sexp::Matcher::Pattern matcher, Sexp._
|
573
|
-
# to create a Sexp::Matcher::Wild matcher, and Sexp.___ to create a
|
574
|
-
# Sexp::Matcher::Remaining matcher. It works like this:
|
575
|
-
#
|
576
|
-
# s{ # start to create a pattern
|
577
|
-
# s( # create a sexp matcher
|
578
|
-
# :class. # for class nodes
|
579
|
-
# m(/^Test/), # matching name slots that start with "Test"
|
580
|
-
# _, # any superclass value
|
581
|
-
# ___ # and whatever is in the class
|
582
|
-
# )
|
583
|
-
# }
|
584
|
-
#
|
585
|
-
# Then you can use that with #=~, #/, Sexp#replace_sexp, and others.
|
586
|
-
#
|
587
|
-
# For more examples, see the various Sexp class methods, the examples,
|
588
|
-
# and the tests supplied with Sexp.
|
589
|
-
#
|
590
|
-
# * For pattern creation, see factory methods: Sexp::_, Sexp::___, etc.
|
591
|
-
# * For matching returning truthy/falsey results, see Sexp#=~.
|
592
|
-
# * For case expressions, see Matcher#===.
|
593
|
-
# * For getting all subtree matches, see Sexp#/.
|
594
|
-
#
|
595
|
-
# If rdoc didn't suck, these would all be links.
|
596
|
-
|
597
|
-
class Matcher < Sexp
|
598
|
-
##
|
599
|
-
# Should #=~ match sub-trees?
|
600
|
-
|
601
|
-
def self.match_subs?
|
602
|
-
@@match_subs
|
603
|
-
end
|
604
|
-
|
605
|
-
##
|
606
|
-
# Setter for +match_subs?+.
|
607
|
-
|
608
|
-
def self.match_subs= o
|
609
|
-
@@match_subs = o
|
610
|
-
end
|
611
|
-
|
612
|
-
self.match_subs = true
|
613
|
-
|
614
|
-
##
|
615
|
-
# Does this matcher actually match +o+? Returns falsey if +o+ is
|
616
|
-
# not a Sexp or if any sub-tree of +o+ is not satisfied by or
|
617
|
-
# equal to its corresponding sub-matcher.
|
618
|
-
#
|
619
|
-
#--
|
620
|
-
# TODO: push this up to Sexp and make this the workhorse
|
621
|
-
# TODO: do the same with ===/satisfy?
|
622
|
-
|
623
|
-
def satisfy? o
|
624
|
-
return unless o.kind_of?(Sexp) &&
|
625
|
-
(length == o.length || Matcher === last && last.greedy?)
|
626
|
-
|
627
|
-
each_with_index.all? { |child, i|
|
628
|
-
sexp = o.at i
|
629
|
-
if Sexp === child then # TODO: when will this NOT be a matcher?
|
630
|
-
sexp = o.sexp_body i if child.respond_to?(:greedy?) && child.greedy?
|
631
|
-
child.satisfy? sexp
|
632
|
-
else
|
633
|
-
child == sexp
|
634
|
-
end
|
635
|
-
}
|
636
|
-
end
|
637
|
-
|
638
|
-
##
|
639
|
-
# Tree equivalent to String#=~, returns true if +self+ matches
|
640
|
-
# +sexp+ as a whole or in a sub-tree (if +match_subs?+).
|
641
|
-
#
|
642
|
-
# TODO: maybe this should NOT be aliased to === ?
|
643
|
-
#
|
644
|
-
# TODO: example
|
645
|
-
|
646
|
-
def =~ sexp
|
647
|
-
raise ArgumentError, "Can't both be matchers: %p" % [sexp] if Matcher === sexp
|
648
|
-
|
649
|
-
self.satisfy?(sexp) ||
|
650
|
-
(self.class.match_subs? && sexp.each_sexp.any? { |sub| self =~ sub })
|
651
|
-
end
|
652
|
-
|
653
|
-
alias === =~ # TODO?: alias === satisfy?
|
654
|
-
|
655
|
-
##
|
656
|
-
# Searches through +sexp+ for all sub-trees that match this
|
657
|
-
# matcher and returns a MatchCollection for each match.
|
658
|
-
#
|
659
|
-
# TODO: redirect?
|
660
|
-
# Example:
|
661
|
-
# Q{ s(:b) } / s(:a, s(:b)) => [s(:b)]
|
662
|
-
|
663
|
-
def / sexp
|
664
|
-
raise ArgumentError, "can't both be matchers" if Matcher === sexp
|
665
|
-
|
666
|
-
# TODO: move search_each into matcher?
|
667
|
-
MatchCollection.new sexp.search_each(self).to_a
|
668
|
-
end
|
669
|
-
|
670
|
-
##
|
671
|
-
# Combines the Matcher with another Matcher, the resulting one will
|
672
|
-
# be satisfied if either Matcher would be satisfied.
|
673
|
-
#
|
674
|
-
# TODO: redirect
|
675
|
-
# Example:
|
676
|
-
# s(:a) | s(:b)
|
677
|
-
|
678
|
-
def | other
|
679
|
-
Any.new self, other
|
680
|
-
end
|
681
|
-
|
682
|
-
##
|
683
|
-
# Combines the Matcher with another Matcher, the resulting one will
|
684
|
-
# be satisfied only if both Matchers would be satisfied.
|
685
|
-
#
|
686
|
-
# TODO: redirect
|
687
|
-
# Example:
|
688
|
-
# t(:a) & include(:b)
|
689
|
-
|
690
|
-
def & other
|
691
|
-
All.new self, other
|
692
|
-
end
|
693
|
-
|
694
|
-
##
|
695
|
-
# Returns a Matcher that matches whenever this Matcher would not have matched
|
696
|
-
#
|
697
|
-
# Example:
|
698
|
-
# -s(:a)
|
699
|
-
|
700
|
-
def -@
|
701
|
-
Not.new self
|
702
|
-
end
|
703
|
-
|
704
|
-
##
|
705
|
-
# Returns a Matcher that matches if this has a sibling +o+
|
706
|
-
#
|
707
|
-
# Example:
|
708
|
-
# s(:a) >> s(:b)
|
709
|
-
|
710
|
-
def >> other
|
711
|
-
Sibling.new self, other
|
712
|
-
end
|
713
|
-
|
714
|
-
##
|
715
|
-
# Is this matcher greedy? Defaults to false.
|
716
|
-
|
717
|
-
def greedy?
|
718
|
-
false
|
719
|
-
end
|
720
|
-
|
721
|
-
def inspect # :nodoc:
|
722
|
-
s = super
|
723
|
-
s[0] = "q"
|
724
|
-
s
|
725
|
-
end
|
726
|
-
|
727
|
-
def pretty_print q # :nodoc:
|
728
|
-
q.group 1, "q(", ")" do
|
729
|
-
q.seplist self do |v|
|
730
|
-
q.pp v
|
731
|
-
end
|
732
|
-
end
|
733
|
-
end
|
734
|
-
|
735
|
-
##
|
736
|
-
# Parse a lispy string representation of a matcher into a Matcher.
|
737
|
-
# See +Parser+.
|
738
|
-
|
739
|
-
def self.parse s
|
740
|
-
Parser.new(s).parse
|
741
|
-
end
|
742
|
-
|
743
|
-
##
|
744
|
-
# Converts from a lispy string to Sexp matchers in a safe manner.
|
745
|
-
#
|
746
|
-
# "(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }
|
747
|
-
|
748
|
-
class Parser
|
749
|
-
|
750
|
-
##
|
751
|
-
# The stream of tokens to parse. See #lex.
|
752
|
-
|
753
|
-
attr_accessor :tokens
|
754
|
-
|
755
|
-
##
|
756
|
-
# Create a new Parser instance on +s+
|
757
|
-
|
758
|
-
def initialize s
|
759
|
-
self.tokens = []
|
760
|
-
lex s
|
761
|
-
end
|
762
|
-
|
763
|
-
##
|
764
|
-
# Converts +s+ into a stream of tokens and adds them to +tokens+.
|
765
|
-
|
766
|
-
def lex s
|
767
|
-
tokens.concat s.scan(%r%[()\[\]]|\"[^"]*\"|/[^/]*/|[\w-]+%) # "
|
768
|
-
end
|
769
|
-
|
770
|
-
##
|
771
|
-
# Returns the next token and removes it from the stream or raises if empty.
|
772
|
-
|
773
|
-
def next_token
|
774
|
-
raise SyntaxError, "unbalanced input" if tokens.empty?
|
775
|
-
tokens.shift
|
776
|
-
end
|
777
|
-
|
778
|
-
##
|
779
|
-
# Returns the next token without removing it from the stream.
|
780
|
-
|
781
|
-
def peek_token
|
782
|
-
tokens.first
|
783
|
-
end
|
784
|
-
|
785
|
-
##
|
786
|
-
# Parses tokens and returns a +Matcher+ instance.
|
787
|
-
|
788
|
-
def parse
|
789
|
-
result = parse_sexp until tokens.empty?
|
790
|
-
result
|
791
|
-
end
|
792
|
-
|
793
|
-
##
|
794
|
-
# Parses a string into a sexp matcher:
|
795
|
-
#
|
796
|
-
# SEXP : "(" SEXP:args* ")" => Sexp.q(*args)
|
797
|
-
# | "[" CMD:cmd sexp:args* "]" => Sexp.cmd(*args)
|
798
|
-
# | "nil" => nil
|
799
|
-
# | /\d+/:n => n.to_i
|
800
|
-
# | "___" => Sexp.___
|
801
|
-
# | "_" => Sexp._
|
802
|
-
# | /^\/(.*)\/$/:re => Regexp.new re[0]
|
803
|
-
# | /^"(.*)"$/:s => String.new s[0]
|
804
|
-
# | NAME:name => name.to_sym
|
805
|
-
# NAME : /\w+/
|
806
|
-
# CMD : "t" | "m" | "atom"
|
807
|
-
|
808
|
-
def parse_sexp
|
809
|
-
token = next_token
|
810
|
-
|
811
|
-
case token
|
812
|
-
when "(" then
|
813
|
-
parse_list
|
814
|
-
when "[" then
|
815
|
-
parse_cmd
|
816
|
-
when "nil" then
|
817
|
-
nil
|
818
|
-
when /^\d+$/ then
|
819
|
-
token.to_i
|
820
|
-
when "___" then
|
821
|
-
Sexp.___
|
822
|
-
when "_" then
|
823
|
-
Sexp._
|
824
|
-
when %r%^/(.*)/$% then
|
825
|
-
re = $1
|
826
|
-
raise SyntaxError, "Not allowed: /%p/" % [re] unless
|
827
|
-
re =~ /\A([\w()|.*+^$]+)\z/
|
828
|
-
Regexp.new re
|
829
|
-
when /^"(.*)"$/ then
|
830
|
-
$1
|
831
|
-
when /^\w+$/ then
|
832
|
-
token.to_sym
|
833
|
-
else
|
834
|
-
raise SyntaxError, "unhandled token: %p" % [token]
|
835
|
-
end
|
836
|
-
end
|
837
|
-
|
838
|
-
##
|
839
|
-
# Parses a balanced list of expressions and returns the
|
840
|
-
# equivalent matcher.
|
841
|
-
|
842
|
-
def parse_list
|
843
|
-
result = []
|
844
|
-
|
845
|
-
result << parse_sexp while peek_token && peek_token != ")"
|
846
|
-
next_token # pop off ")"
|
847
|
-
|
848
|
-
Sexp.s(*result)
|
849
|
-
end
|
850
|
-
|
851
|
-
##
|
852
|
-
# A collection of allowed commands to convert into matchers.
|
853
|
-
|
854
|
-
ALLOWED = [:t, :m, :atom].freeze
|
855
|
-
|
856
|
-
##
|
857
|
-
# Parses a balanced command. A command is denoted by square
|
858
|
-
# brackets and must conform to a whitelisted set of allowed
|
859
|
-
# commands (see +ALLOWED+).
|
860
|
-
|
861
|
-
def parse_cmd
|
862
|
-
args = []
|
863
|
-
args << parse_sexp while peek_token && peek_token != "]"
|
864
|
-
next_token # pop off "]"
|
865
|
-
|
866
|
-
cmd = args.shift
|
867
|
-
args = Sexp.s(*args)
|
868
|
-
|
869
|
-
raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd
|
870
|
-
|
871
|
-
result = Sexp.send cmd, *args
|
872
|
-
|
873
|
-
result
|
874
|
-
end
|
875
|
-
end # class Parser
|
876
|
-
end # class Matcher
|
877
|
-
|
878
|
-
##
|
879
|
-
# Matches any single item.
|
880
|
-
#
|
881
|
-
# examples:
|
882
|
-
#
|
883
|
-
# s(:a) / s{ _ } #=> [s(:a)]
|
884
|
-
# s(:a, s(s(:b))) / s{ s(_) } #=> [s(s(:b))]
|
885
|
-
|
886
|
-
class Wild < Matcher
|
887
|
-
##
|
888
|
-
# Matches any single element.
|
889
|
-
|
890
|
-
def satisfy? o
|
891
|
-
true
|
892
|
-
end
|
893
|
-
|
894
|
-
def inspect # :nodoc:
|
895
|
-
"_"
|
896
|
-
end
|
897
|
-
|
898
|
-
def pretty_print q # :nodoc:
|
899
|
-
q.text "_"
|
900
|
-
end
|
901
|
-
end
|
902
|
-
|
903
|
-
##
|
904
|
-
# Matches all remaining input. If remaining comes before any other
|
905
|
-
# matchers, they will be ignored.
|
906
|
-
#
|
907
|
-
# examples:
|
908
|
-
#
|
909
|
-
# s(:a) / s{ s(:a, ___ ) } #=> [s(:a)]
|
910
|
-
# s(:a, :b, :c) / s{ s(:a, ___ ) } #=> [s(:a, :b, :c)]
|
911
|
-
|
912
|
-
class Remaining < Matcher
|
913
|
-
##
|
914
|
-
# Always satisfied once this is reached. Think of it as a var arg.
|
915
|
-
|
916
|
-
def satisfy? o
|
917
|
-
true
|
918
|
-
end
|
919
|
-
|
920
|
-
def greedy?
|
921
|
-
true
|
922
|
-
end
|
923
|
-
|
924
|
-
def inspect # :nodoc:
|
925
|
-
"___"
|
926
|
-
end
|
927
|
-
|
928
|
-
def pretty_print q # :nodoc:
|
929
|
-
q.text "___"
|
930
|
-
end
|
931
|
-
end
|
932
|
-
|
933
|
-
##
|
934
|
-
# Matches when any of the sub-expressions match.
|
935
|
-
#
|
936
|
-
# This is also available via Matcher#|.
|
937
|
-
#
|
938
|
-
# examples:
|
939
|
-
#
|
940
|
-
# s(:a) / s{ any(s(:a), s(:b)) } #=> [s(:a)]
|
941
|
-
# s(:a) / s{ s(:a) | s(:b) } #=> [s(:a)] # same thing via |
|
942
|
-
# s(:a) / s{ any(s(:b), s(:c)) } #=> []
|
943
|
-
|
944
|
-
class Any < Matcher
|
945
|
-
##
|
946
|
-
# The collection of sub-matchers to match against.
|
947
|
-
|
948
|
-
attr_reader :options
|
949
|
-
|
950
|
-
##
|
951
|
-
# Create an Any matcher which will match any of the +options+.
|
952
|
-
|
953
|
-
def initialize *options
|
954
|
-
@options = options
|
955
|
-
end
|
956
|
-
|
957
|
-
##
|
958
|
-
# Satisfied when any sub expressions match +o+
|
959
|
-
|
960
|
-
def satisfy? o
|
961
|
-
options.any? { |exp|
|
962
|
-
Sexp === exp && exp.satisfy?(o) || exp == o
|
963
|
-
}
|
964
|
-
end
|
965
|
-
|
966
|
-
def == o # :nodoc:
|
967
|
-
super && self.options == o.options
|
968
|
-
end
|
969
|
-
|
970
|
-
def inspect # :nodoc:
|
971
|
-
options.map(&:inspect).join(" | ")
|
972
|
-
end
|
973
|
-
|
974
|
-
def pretty_print q # :nodoc:
|
975
|
-
q.group 1, "any(", ")" do
|
976
|
-
q.seplist options do |v|
|
977
|
-
q.pp v
|
978
|
-
end
|
979
|
-
end
|
980
|
-
end
|
981
|
-
end
|
982
|
-
|
983
|
-
##
|
984
|
-
# Matches only when all sub-expressions match.
|
985
|
-
#
|
986
|
-
# This is also available via Matcher#&.
|
987
|
-
#
|
988
|
-
# examples:
|
989
|
-
#
|
990
|
-
# s(:a) / s{ all(s(:a), s(:b)) } #=> []
|
991
|
-
# s(:a, :b) / s{ t(:a) & include(:b)) } #=> [s(:a, :b)]
|
992
|
-
|
993
|
-
class All < Matcher
|
994
|
-
##
|
995
|
-
# The collection of sub-matchers to match against.
|
996
|
-
|
997
|
-
attr_reader :options
|
998
|
-
|
999
|
-
##
|
1000
|
-
# Create an All matcher which will match all of the +options+.
|
1001
|
-
|
1002
|
-
def initialize *options
|
1003
|
-
@options = options
|
1004
|
-
end
|
1005
|
-
|
1006
|
-
##
|
1007
|
-
# Satisfied when all sub expressions match +o+
|
1008
|
-
|
1009
|
-
def satisfy? o
|
1010
|
-
options.all? { |exp|
|
1011
|
-
exp.kind_of?(Sexp) ? exp.satisfy?(o) : exp == o
|
1012
|
-
}
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
def == o # :nodoc:
|
1016
|
-
super && self.options == o.options
|
1017
|
-
end
|
1018
|
-
|
1019
|
-
def inspect # :nodoc:
|
1020
|
-
options.map(&:inspect).join(" & ")
|
1021
|
-
end
|
1022
|
-
|
1023
|
-
def pretty_print q # :nodoc:
|
1024
|
-
q.group 1, "all(", ")" do
|
1025
|
-
q.seplist options do |v|
|
1026
|
-
q.pp v
|
1027
|
-
end
|
1028
|
-
end
|
1029
|
-
end
|
1030
|
-
end
|
1031
|
-
|
1032
|
-
##
|
1033
|
-
# Matches when sub-expression does not match.
|
1034
|
-
#
|
1035
|
-
# This is also available via Matcher#-@.
|
1036
|
-
#
|
1037
|
-
# examples:
|
1038
|
-
#
|
1039
|
-
# s(:a) / s{ not?(s(:b)) } #=> [s(:a)]
|
1040
|
-
# s(:a) / s{ -s(:b) } #=> [s(:a)]
|
1041
|
-
# s(:a) / s{ s(not? :a) } #=> []
|
1042
|
-
|
1043
|
-
class Not < Matcher
|
1044
|
-
|
1045
|
-
##
|
1046
|
-
# The value to negate in the match.
|
1047
|
-
|
1048
|
-
attr_reader :value
|
1049
|
-
|
1050
|
-
##
|
1051
|
-
# Creates a Matcher which will match any Sexp that does not match the +value+
|
1052
|
-
|
1053
|
-
def initialize value
|
1054
|
-
@value = value
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
def == o # :nodoc:
|
1058
|
-
super && self.value == o.value
|
1059
|
-
end
|
1060
|
-
|
1061
|
-
##
|
1062
|
-
# Satisfied if a +o+ does not match the +value+
|
1063
|
-
|
1064
|
-
def satisfy? o
|
1065
|
-
!(value.kind_of?(Sexp) ? value.satisfy?(o) : value == o)
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
def inspect # :nodoc:
|
1069
|
-
"not?(%p)" % [value]
|
1070
|
-
end
|
1071
|
-
|
1072
|
-
def pretty_print q # :nodoc:
|
1073
|
-
q.group 1, "not?(", ")" do
|
1074
|
-
q.pp value
|
1075
|
-
end
|
1076
|
-
end
|
1077
|
-
end
|
1078
|
-
|
1079
|
-
##
|
1080
|
-
# Matches anything that has a child matching the sub-expression
|
1081
|
-
#
|
1082
|
-
# example:
|
1083
|
-
#
|
1084
|
-
# s(s(s(s(s(:a))))) / s{ child(s(:a)) } #=> [s(s(s(s(s(:a))))),
|
1085
|
-
# s(s(s(s(:a)))),
|
1086
|
-
# s(s(s(:a))),
|
1087
|
-
# s(s(:a)),
|
1088
|
-
# s(:a)]
|
1089
|
-
|
1090
|
-
class Child < Matcher
|
1091
|
-
##
|
1092
|
-
# The child to match.
|
1093
|
-
|
1094
|
-
attr_reader :child
|
1095
|
-
|
1096
|
-
##
|
1097
|
-
# Create a Child matcher which will match anything having a
|
1098
|
-
# descendant matching +child+.
|
1099
|
-
|
1100
|
-
def initialize child
|
1101
|
-
@child = child
|
1102
|
-
end
|
1103
|
-
|
1104
|
-
##
|
1105
|
-
# Satisfied if matches +child+ or +o+ has a descendant matching
|
1106
|
-
# +child+.
|
1107
|
-
|
1108
|
-
def satisfy? o
|
1109
|
-
if child.satisfy? o
|
1110
|
-
true
|
1111
|
-
elsif o.kind_of? Sexp
|
1112
|
-
o.search_each(child).any?
|
1113
|
-
end
|
1114
|
-
end
|
1115
|
-
|
1116
|
-
def == o # :nodoc:
|
1117
|
-
super && self.child == o.child
|
1118
|
-
end
|
1119
|
-
|
1120
|
-
def inspect # :nodoc:
|
1121
|
-
"child(%p)" % [child]
|
1122
|
-
end
|
1123
|
-
|
1124
|
-
def pretty_print q # :nodoc:
|
1125
|
-
q.group 1, "child(", ")" do
|
1126
|
-
q.pp child
|
1127
|
-
end
|
1128
|
-
end
|
1129
|
-
end
|
1130
|
-
|
1131
|
-
##
|
1132
|
-
# Matches any atom (non-Sexp).
|
1133
|
-
#
|
1134
|
-
# examples:
|
1135
|
-
#
|
1136
|
-
# s(:a) / s{ s(atom) } #=> [s(:a)]
|
1137
|
-
# s(:a, s(:b)) / s{ s(atom) } #=> [s(:b)]
|
1138
|
-
|
1139
|
-
class Atom < Matcher
|
1140
|
-
##
|
1141
|
-
# Satisfied when +o+ is an atom.
|
1142
|
-
|
1143
|
-
def satisfy? o
|
1144
|
-
!(o.kind_of? Sexp)
|
1145
|
-
end
|
1146
|
-
|
1147
|
-
def inspect #:nodoc:
|
1148
|
-
"atom"
|
1149
|
-
end
|
1150
|
-
|
1151
|
-
def pretty_print q # :nodoc:
|
1152
|
-
q.text "atom"
|
1153
|
-
end
|
1154
|
-
end
|
1155
|
-
|
1156
|
-
##
|
1157
|
-
# Matches any atom who's string representation matches the patterns
|
1158
|
-
# passed in.
|
1159
|
-
#
|
1160
|
-
# examples:
|
1161
|
-
#
|
1162
|
-
# s(:a) / s{ m('a') } #=> [s(:a)]
|
1163
|
-
# s(:a) / s{ m(/\w/,/\d/) } #=> [s(:a)]
|
1164
|
-
# s(:tests, s(s(:test_a), s(:test_b))) / s{ m(/test_\w/) } #=> [s(:test_a),
|
1165
|
-
#
|
1166
|
-
# TODO: maybe don't require non-sexps? This does respond to =~ now.
|
1167
|
-
|
1168
|
-
class Pattern < Matcher
|
1169
|
-
|
1170
|
-
##
|
1171
|
-
# The regexp to match for the pattern.
|
1172
|
-
|
1173
|
-
attr_reader :pattern
|
1174
|
-
|
1175
|
-
def == o # :nodoc:
|
1176
|
-
super && self.pattern == o.pattern
|
1177
|
-
end
|
1178
|
-
|
1179
|
-
##
|
1180
|
-
# Create a Patten matcher which will match any atom that either
|
1181
|
-
# matches the input +pattern+.
|
1182
|
-
|
1183
|
-
def initialize pattern
|
1184
|
-
@pattern = pattern
|
1185
|
-
end
|
1186
|
-
|
1187
|
-
##
|
1188
|
-
# Satisfied if +o+ is an atom, and +o+ matches +pattern+
|
1189
|
-
|
1190
|
-
def satisfy? o
|
1191
|
-
!o.kind_of?(Sexp) && o.to_s =~ pattern # TODO: question to_s
|
1192
|
-
end
|
1193
|
-
|
1194
|
-
def inspect # :nodoc:
|
1195
|
-
"m(%p)" % pattern
|
1196
|
-
end
|
1197
|
-
|
1198
|
-
def pretty_print q # :nodoc:
|
1199
|
-
q.group 1, "m(", ")" do
|
1200
|
-
q.pp pattern
|
1201
|
-
end
|
1202
|
-
end
|
1203
|
-
end
|
1204
|
-
|
1205
|
-
##
|
1206
|
-
# Matches anything having the same sexp_type, which is the first
|
1207
|
-
# value in a Sexp.
|
1208
|
-
#
|
1209
|
-
# examples:
|
1210
|
-
#
|
1211
|
-
# s(:a, :b) / s{ t(:a) } #=> [s(:a, :b)]
|
1212
|
-
# s(:a, :b) / s{ t(:b) } #=> []
|
1213
|
-
# s(:a, s(:b, :c)) / s{ t(:b) } #=> [s(:b, :c)]
|
1214
|
-
|
1215
|
-
class Type < Matcher
|
1216
|
-
attr_reader :sexp_type
|
1217
|
-
|
1218
|
-
##
|
1219
|
-
# Creates a Matcher which will match any Sexp who's type is +type+, where a type is
|
1220
|
-
# the first element in the Sexp.
|
1221
|
-
|
1222
|
-
def initialize type
|
1223
|
-
@sexp_type = type
|
1224
|
-
end
|
1225
|
-
|
1226
|
-
def == o # :nodoc:
|
1227
|
-
super && self.sexp_type == o.sexp_type
|
1228
|
-
end
|
1229
|
-
|
1230
|
-
##
|
1231
|
-
# Satisfied if the sexp_type of +o+ is +type+.
|
1232
|
-
|
1233
|
-
def satisfy? o
|
1234
|
-
o.kind_of?(Sexp) && o.sexp_type == sexp_type
|
1235
|
-
end
|
1236
|
-
|
1237
|
-
def inspect # :nodoc:
|
1238
|
-
"t(%p)" % sexp_type
|
1239
|
-
end
|
1240
|
-
|
1241
|
-
def pretty_print q # :nodoc:
|
1242
|
-
q.group 1, "t(", ")" do
|
1243
|
-
q.pp sexp_type
|
1244
|
-
end
|
1245
|
-
end
|
1246
|
-
end
|
1247
|
-
|
1248
|
-
##
|
1249
|
-
# Matches an expression or any expression that includes the child.
|
1250
|
-
#
|
1251
|
-
# examples:
|
1252
|
-
#
|
1253
|
-
# s(:a, :b) / s{ include(:b) } #=> [s(:a, :b)]
|
1254
|
-
# s(s(s(:a))) / s{ include(:a) } #=> [s(:a)]
|
1255
|
-
|
1256
|
-
class Include < Matcher
|
1257
|
-
##
|
1258
|
-
# The value that should be included in the match.
|
1259
|
-
|
1260
|
-
attr_reader :value
|
1261
|
-
|
1262
|
-
##
|
1263
|
-
# Creates a Matcher which will match any Sexp that contains the
|
1264
|
-
# +value+.
|
1265
|
-
|
1266
|
-
def initialize value
|
1267
|
-
@value = value
|
1268
|
-
end
|
1269
|
-
|
1270
|
-
##
|
1271
|
-
# Satisfied if a +o+ is a Sexp and one of +o+'s elements matches
|
1272
|
-
# value
|
1273
|
-
|
1274
|
-
def satisfy? o
|
1275
|
-
Sexp === o && o.any? { |c|
|
1276
|
-
# TODO: switch to respond_to??
|
1277
|
-
Sexp === value ? value.satisfy?(c) : value == c
|
1278
|
-
}
|
1279
|
-
end
|
1280
|
-
|
1281
|
-
def == o # :nodoc:
|
1282
|
-
super && self.value == o.value
|
1283
|
-
end
|
1284
|
-
|
1285
|
-
def inspect # :nodoc:
|
1286
|
-
"include(%p)" % [value]
|
1287
|
-
end
|
1288
|
-
|
1289
|
-
def pretty_print q # :nodoc:
|
1290
|
-
q.group 1, "include(", ")" do
|
1291
|
-
q.pp value
|
1292
|
-
end
|
1293
|
-
end
|
1294
|
-
end
|
1295
|
-
|
1296
|
-
##
|
1297
|
-
# See Matcher for sibling relations: <,<<,>>,>
|
1298
|
-
|
1299
|
-
class Sibling < Matcher
|
1300
|
-
|
1301
|
-
##
|
1302
|
-
# The LHS of the matcher.
|
1303
|
-
|
1304
|
-
attr_reader :subject
|
1305
|
-
|
1306
|
-
##
|
1307
|
-
# The RHS of the matcher.
|
1308
|
-
|
1309
|
-
attr_reader :sibling
|
1310
|
-
|
1311
|
-
##
|
1312
|
-
# An optional distance requirement for the matcher.
|
1313
|
-
|
1314
|
-
attr_reader :distance
|
1315
|
-
|
1316
|
-
##
|
1317
|
-
# Creates a Matcher which will match any pair of Sexps that are siblings.
|
1318
|
-
# Defaults to matching the immediate following sibling.
|
1319
|
-
|
1320
|
-
def initialize subject, sibling, distance = nil
|
1321
|
-
@subject = subject
|
1322
|
-
@sibling = sibling
|
1323
|
-
@distance = distance
|
1324
|
-
end
|
1325
|
-
|
1326
|
-
##
|
1327
|
-
# Satisfied if o contains +subject+ followed by +sibling+
|
1328
|
-
|
1329
|
-
def satisfy? o
|
1330
|
-
# Future optimizations:
|
1331
|
-
# * Shortcut matching sibling
|
1332
|
-
subject_matches = index_matches(subject, o)
|
1333
|
-
return nil if subject_matches.empty?
|
1334
|
-
|
1335
|
-
sibling_matches = index_matches(sibling, o)
|
1336
|
-
return nil if sibling_matches.empty?
|
1337
|
-
|
1338
|
-
subject_matches.any? { |i1, _data_1|
|
1339
|
-
sibling_matches.any? { |i2, _data_2|
|
1340
|
-
distance ? (i2-i1 == distance) : i2 > i1
|
1341
|
-
}
|
1342
|
-
}
|
1343
|
-
end
|
1344
|
-
|
1345
|
-
def == o # :nodoc:
|
1346
|
-
super &&
|
1347
|
-
self.subject == o.subject &&
|
1348
|
-
self.sibling == o.sibling &&
|
1349
|
-
self.distance == o.distance
|
1350
|
-
end
|
1351
|
-
|
1352
|
-
def inspect # :nodoc:
|
1353
|
-
"%p >> %p" % [subject, sibling]
|
1354
|
-
end
|
1355
|
-
|
1356
|
-
def pretty_print q # :nodoc:
|
1357
|
-
if distance then
|
1358
|
-
q.group 1, "sibling(", ")" do
|
1359
|
-
q.seplist [subject, sibling, distance] do |v|
|
1360
|
-
q.pp v
|
1361
|
-
end
|
1362
|
-
end
|
1363
|
-
else
|
1364
|
-
q.group 1 do
|
1365
|
-
q.pp subject
|
1366
|
-
q.text " >> "
|
1367
|
-
q.pp sibling
|
1368
|
-
end
|
1369
|
-
end
|
1370
|
-
end
|
1371
|
-
|
1372
|
-
private
|
1373
|
-
|
1374
|
-
def index_matches pattern, o
|
1375
|
-
indexes = []
|
1376
|
-
return indexes unless o.kind_of? Sexp
|
1377
|
-
|
1378
|
-
o.each_with_index do |e, i|
|
1379
|
-
data = {}
|
1380
|
-
if pattern.kind_of?(Sexp) ? pattern.satisfy?(e) : pattern == o[i]
|
1381
|
-
indexes << [i, data]
|
1382
|
-
end
|
1383
|
-
end
|
1384
|
-
|
1385
|
-
indexes
|
1386
|
-
end
|
1387
|
-
end # class Sibling
|
1388
|
-
|
1389
|
-
##
|
1390
|
-
# Wraps the results of a Sexp query. MatchCollection defines
|
1391
|
-
# MatchCollection#/ so that you can chain queries.
|
1392
|
-
#
|
1393
|
-
# For instance:
|
1394
|
-
# res = s(:a, s(:b)) / s{ s(:a,_) } / s{ s(:b) }
|
1395
|
-
|
1396
|
-
class MatchCollection < Array
|
1397
|
-
##
|
1398
|
-
# See Traverse#search
|
1399
|
-
|
1400
|
-
def / pattern
|
1401
|
-
inject(self.class.new) { |result, match|
|
1402
|
-
result.concat match / pattern
|
1403
|
-
}
|
1404
|
-
end
|
1405
|
-
|
1406
|
-
def inspect # :nodoc:
|
1407
|
-
"MatchCollection.new(%s)" % self.to_a.inspect[1..-2]
|
1408
|
-
end
|
1409
|
-
|
1410
|
-
alias :to_s :inspect # :nodoc:
|
1411
|
-
|
1412
|
-
def pretty_print q # :nodoc:
|
1413
|
-
q.group 1, "MatchCollection.new(", ")" do
|
1414
|
-
q.seplist(self) {|v| q.pp v }
|
1415
|
-
end
|
1416
|
-
end
|
1417
|
-
end # class MatchCollection
|
1418
|
-
end
|
1419
|
-
|
380
|
+
require "sexp_matcher" unless defined? Sexp::Matcher
|
1420
381
|
require "strict_sexp" if ENV["STRICT_SEXP"].to_i > 0
|