Skip to content

Instantly share code, notes, and snippets.

@jrafanie
Created October 2, 2017 15:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jrafanie/ebdbbec2802eec082d4f3e4aadef77d6 to your computer and use it in GitHub Desktop.
Save jrafanie/ebdbbec2802eec082d4f3e4aadef77d6 to your computer and use it in GitHub Desktop.
Diff of sexp_processor 4.9.0 vs 4.10.0
diff --git a/sexp_processor-4.9.0/History.txt b/sexp_processor-4.10.0/History.txt
index b8e355f..23c21e2 100644
--- a/sexp_processor-4.9.0/History.txt
+++ b/sexp_processor-4.10.0/History.txt
@@ -1,3 +1,52 @@
+=== 4.10.0 / 2017-07-17
+
+* 2 major enhancements:
+
+ * Added experimental pattern matcher to Sexp. Forked from sexp_path.
+ * Extended s to take a block and return a matcher: eg s{ s(:defn, atom, _, ___) }
+
+* 23 minor enhancements:
+
+ * Added $STRICT_SEXP to crank down Sexp.[] and friends.
+ * Added Matcher#/ w/ real functionality.
+ * Added Sexp#/ to search with new patterns.
+ * Added Sexp#map to ensure you get a Sexp back.
+ * Added Sexp#new to create a new sexp with the same file/line/comment info.
+ * Added Sexp#search_each to recursively search w/ new patterns. Returns enum if no block.
+ * Added Sexp#sexp_body=
+ * Added Sexp::Matcher.match_subs? and .match_subs= to extend =~ so you can match strictly.
+ * Added Sexp::Matcher.parse to convert lispy string to safe matcher: "(defn atom _ ___)"
+ * Added all mutation methods to STRICT_SEXP >= 4
+ * Added deprecation message to Sexp#structure for [s(...)] forms.
+ * Added strict_sexp.rb to help you clamp down for future changes. STRICT_SEXP=1+
+ * Auto-require strict_sexp if $STRICT_SEXP is > 0.
+ * Converted a lot of indexed access to sexp_type/sexp_body, etc.
+ * Finally enforced SexpProcessor#process to only process sexps, not bare arrays.
+ * Made Sexp#/ double-dispatch to Matcher#/.
+ * Made Sexp#gsub work with new patterns.
+ * Made Sexp#sub work with new patterns.
+ * Made SexpProcessor STRICT_SEXP=4 compliant.
+ * Retired SexpMatchSpecial & SexpAny. Never used by anything AFAICT.
+ * Sexp#=== goes back to default.
+ * Sexp#=~(pat) calls pat =~ self.
+ * Sexp#sexp_body now takes optional offset. Use instead of sexp[n..-1].
+
+* 9 bug fixes:
+
+ * Extended Sexp::Matcher::Parser.parse to lex more forms of regexp.
+ * Finished off all missing doco.
+ * Fixed == methods on all Matcher classes to include ivars.
+ * Fixed Child#satisfy? to properly return false if failed.
+ * Fixed Sexp#sexp_body to return a sexp using Sexp#new.
+ * Fixed map to use Sexp#new.
+ * Only try to set c_type if it responds to it. Make STRICT_SEXP safe.
+ * R2C has a hack in SexpProcessor to call sexp_type=. Renamed to c_type= in R2C.
+ * Removed very obsolete attrset test from pt_testcase.rb
+
+=== 4.10.0b1 / 2017-06-13
+
+Beta of the above.
+
=== 4.9.0 / 2017-04-13
* 9 minor enhancements:
diff --git a/sexp_processor-4.9.0/Manifest.txt b/sexp_processor-4.10.0/Manifest.txt
index 61bf9ca..db0f37e 100644
--- a/sexp_processor-4.9.0/Manifest.txt
+++ b/sexp_processor-4.10.0/Manifest.txt
@@ -6,6 +6,7 @@ lib/composite_sexp_processor.rb
lib/pt_testcase.rb
lib/sexp.rb
lib/sexp_processor.rb
+lib/strict_sexp.rb
lib/unique.rb
test/test_composite_sexp_processor.rb
test/test_environment.rb
diff --git a/sexp_processor-4.9.0/README.txt b/sexp_processor-4.10.0/README.txt
index 289a06d..4c58a1b 100644
--- a/sexp_processor-4.9.0/README.txt
+++ b/sexp_processor-4.10.0/README.txt
@@ -15,21 +15,50 @@ for your language processing pleasure.
* Allows you to write very clean filters.
+* Includes MethodBasedSexpProcessor
+
+ * Makes writing language processors even easier!
+
* Sexp provides a simple and clean interface to creating and manipulating ASTs.
+ * Includes new pattern matching system.
+
== SYNOPSIS:
- class MyProcessor < SexpProcessor
- def initialize
- super
- self.strict = false
+You can use SexpProcessor to do all kinds of language processing. Here
+is a simple example of a simple documentation printer:
+
+ class ArrrDoc < MethodBasedSexpProcessor
+ def process_class exp
+ super do
+ puts "#{self.klass_name}: #{exp.comments}"
+ end
end
- def process_lit(exp)
- val = exp.shift
- return val
+
+ def process_defn exp
+ super do
+ args, *_body = exp
+
+ puts "#{self.method_name}(#{process_args args}): #{exp.comments}"
+ end
end
end
+Sexp provides a lot of power with the new pattern matching system.
+Here is an example that parses all the test files using RubyParser and
+then quickly finds all the test methods and prints their names:
+
+ >> require "ruby_parser";
+ >> rp = RubyParser.new;
+ >> matcher = Sexp::Matcher.parse "(defn [m /^test_/] ___)"
+ => q(:defn, m(/^test_/), ___)
+ >> paths = Dir["test/**/*.rb"];
+ >> sexps = s(:block, *paths.map { |path| rp.process File.read(path), path });
+ >> (sexps / matcher).size
+ => 189
+ ?> (sexps / matcher).map { |(_, name, *_rest)| name }.sort
+ => [:test_all, :test_amp, :test_and_satisfy_eh, :test_any_search, ...]
+
== REQUIREMENTS:
* rubygems
diff --git a/sexp_processor-4.9.0/Rakefile b/sexp_processor-4.10.0/Rakefile
index 115a45e..f753bc0 100644
--- a/sexp_processor-4.9.0/Rakefile
+++ b/sexp_processor-4.10.0/Rakefile
@@ -5,8 +5,12 @@ require 'hoe'
Hoe.plugin :seattlerb
+Hoe.add_include_dirs("../../ruby_parser/dev/lib")
+
Hoe.spec 'sexp_processor' do
developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
+
+ license "MIT"
end
# vim: syntax=ruby
diff --git a/sexp_processor-4.9.0/lib/composite_sexp_processor.rb b/sexp_processor-4.10.0/lib/composite_sexp_processor.rb
index caebe0e..d59848f 100644
--- a/sexp_processor-4.9.0/lib/composite_sexp_processor.rb
+++ b/sexp_processor-4.10.0/lib/composite_sexp_processor.rb
@@ -1,4 +1,4 @@
-require 'sexp_processor'
+require "sexp_processor"
##
# Implements the Composite pattern on SexpProcessor. Need we say more?
@@ -24,7 +24,7 @@ class CompositeSexpProcessor < SexpProcessor
##
# Add a +processor+ to the list of processors to run.
- def <<(processor)
+ def << processor
raise ArgumentError, "Can only add sexp processors" unless
SexpProcessor === processor || processor.respond_to?(:process)
@processors << processor
@@ -34,14 +34,14 @@ class CompositeSexpProcessor < SexpProcessor
# Run +exp+ through all of the processors, returning the final
# result.
- def process(exp)
+ def process exp
@processors.each do |processor|
exp = processor.process(exp)
end
exp
end
- def on_error_in(node_type, &block)
+ def on_error_in node_type, &block
@processors.each do |processor|
processor.on_error_in(node_type, &block)
end
diff --git a/sexp_processor-4.9.0/lib/pt_testcase.rb b/sexp_processor-4.10.0/lib/pt_testcase.rb
index 43d6e66..6a19b0b 100644
--- a/sexp_processor-4.9.0/lib/pt_testcase.rb
+++ b/sexp_processor-4.10.0/lib/pt_testcase.rb
@@ -1,9 +1,10 @@
# encoding: US-ASCII
$TESTING = true
+# :stopdoc:
-require 'minitest/test'
-require 'sexp_processor' # for deep_clone
+require "minitest/test"
+require "sexp_processor" # for deep_clone
# key:
# wwtt = what were they thinking?
@@ -12,7 +13,7 @@ class Examples
attr_reader :reader
attr_writer :writer
- def a_method(x); x+1; end
+ def a_method x; x+1; end
alias an_alias a_method
define_method(:bmethod_noargs) do
@@ -136,9 +137,9 @@ class ParseTreeTestCase < Minitest::Test
extra_input = []
_, expected, extra_expected = *expected if
- Array === expected and expected.first == :defx
+ Array === expected and expected.sexp_type == :defx
_, input, extra_input = *input if
- Array === input and input.first == :defx
+ Array === input and input.sexp_type == :defx
# OMG... I can't believe I have to do this this way. these
# hooks are here instead of refactoring this define_method
@@ -167,7 +168,7 @@ class ParseTreeTestCase < Minitest::Test
install_missing_reporter
clone_same
- output_name = klass.name.to_s.sub(/^Test/, '')
+ output_name = klass.name.to_s.sub(/^Test/, "")
input_name = self.previous(output_name)
@@ -202,7 +203,7 @@ class ParseTreeTestCase < Minitest::Test
end
end
- def self.previous(key, extra=0) # FIX: remove R2C code
+ def self.previous key, extra=0 # FIX: remove R2C code
idx = @@testcase_order.index(key)
raise "Unknown class #{key} in @@testcase_order" if idx.nil?
@@ -307,7 +308,7 @@ class ParseTreeTestCase < Minitest::Test
"Ruby2Ruby" => "10")
add_18tests("str_question_literal",
- "Ruby" => '?a',
+ "Ruby" => "?a",
"ParseTree" => s(:lit, 97),
"Ruby2Ruby" => "97")
@@ -595,7 +596,7 @@ class ParseTreeTestCase < Minitest::Test
"ParseTree" => s(:str, "\n"))
add_19tests("str_question_literal",
- "Ruby" => '?a',
+ "Ruby" => "?a",
"ParseTree" => s(:str, "a"))
add_19tests("unless_post_not",
@@ -748,13 +749,6 @@ class ParseTreeTestCase < Minitest::Test
s(:lit, 42), s(:lit, 24))),
"Ruby2Ruby" => "a = []\na[42] = 24\n")
- add_tests("attrset",
- "Ruby" => [Examples, :writer=],
- "ParseTree" => s(:defn, :writer=,
- s(:args, :arg),
- s(:attrset, :@writer)),
- "Ruby2Ruby" => "attr_writer :writer")
-
add_tests("back_ref",
"Ruby" => "[$&, $`, $', $+]",
"ParseTree" => s(:array,
@@ -1534,15 +1528,15 @@ class ParseTreeTestCase < Minitest::Test
add_tests("dregx_interp",
"Ruby" => "/#\{@rakefile}/",
- "ParseTree" => s(:dregx, '', s(:evstr, s(:ivar, :@rakefile))))
+ "ParseTree" => s(:dregx, "", s(:evstr, s(:ivar, :@rakefile))))
add_tests("dregx_interp_empty",
"Ruby" => "/a#\{}b/",
- "ParseTree" => s(:dregx, 'a', s(:evstr), s(:str, "b")))
+ "ParseTree" => s(:dregx, "a", s(:evstr), s(:str, "b")))
add_tests("dregx_n",
"Ruby" => '/#{1}/n',
- "ParseTree" => s(:dregx, '',
+ "ParseTree" => s(:dregx, "",
s(:evstr, s(:lit, 1)), /x/n.options))
add_tests("dregx_once",
@@ -1554,7 +1548,7 @@ class ParseTreeTestCase < Minitest::Test
add_tests("dregx_once_n_interp",
"Ruby" => "/#\{IAC}#\{SB}/no",
- "ParseTree" => s(:dregx_once, '',
+ "ParseTree" => s(:dregx_once, "",
s(:evstr, s(:const, :IAC)),
s(:evstr, s(:const, :SB)), /x/n.options))
@@ -1624,7 +1618,7 @@ class ParseTreeTestCase < Minitest::Test
add_tests("dstr_heredoc_windoze_sucks",
"Ruby" => "<<-EOF\r\ndef test_#\{action}_valid_feed\r\n EOF\r\n",
"ParseTree" => s(:dstr,
- 'def test_',
+ "def test_",
s(:evstr, s(:call, nil, :action)),
s(:str, "_valid_feed\n")),
"Ruby2Ruby" => "\"def test_#\{action}_valid_feed\\n\"")
@@ -1681,7 +1675,7 @@ class ParseTreeTestCase < Minitest::Test
"Ruby" => "t = 5\n`touch #\{t}`\n",
"ParseTree" => s(:block,
s(:lasgn, :t, s(:lit, 5)),
- s(:dxstr, 'touch ', s(:evstr, s(:lvar, :t)))))
+ s(:dxstr, "touch ", s(:evstr, s(:lvar, :t)))))
add_tests("ensure",
"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
"ParseTree" => s(:lit, /x/))
add_tests("lit_regexp_i_wwtt",
- "Ruby" => 'str.split(//i)',
+ "Ruby" => "str.split(//i)",
"ParseTree" => s(:call,
s(:call, nil, :str),
:split,
@@ -2369,7 +2363,7 @@ class ParseTreeTestCase < Minitest::Test
s(:call,
s(:call, nil, :d),
:e,
- s(:str, 'f')))))
+ s(:str, "f")))))
add_tests("match",
"Ruby" => "1 if /x/",
@@ -3083,7 +3077,7 @@ class ParseTreeTestCase < Minitest::Test
add_tests("xstr",
"Ruby" => "`touch 5`",
- "ParseTree" => s(:xstr, 'touch 5'))
+ "ParseTree" => s(:xstr, "touch 5"))
add_tests("yield_0",
"Ruby" => "yield",
@@ -3125,3 +3119,5 @@ class ParseTreeTestCase < Minitest::Test
# end
end
+
+# :startdoc:
diff --git a/sexp_processor-4.9.0/lib/sexp.rb b/sexp_processor-4.10.0/lib/sexp.rb
index 57046bb..6415f5c 100644
--- a/sexp_processor-4.9.0/lib/sexp.rb
+++ b/sexp_processor-4.10.0/lib/sexp.rb
@@ -7,66 +7,66 @@ $TESTING ||= false # unless defined $TESTING
# dispatch the Sexp to for processing.
class Sexp < Array # ZenTest FULL
+ ##
+ # A setter for the line this sexp was found on. Usually set by ruby_parser.
attr_writer :line
- attr_accessor :file, :comments
- @@array_types = [ :array, :args, ]
+ ##
+ # Accessors for the file. Usually set by ruby_parser.
+
+ attr_accessor :file
+
+ ##
+ # Optional comments above/aside this sexp. Usually set by ruby_parser.
+
+ attr_accessor :comments
+
+ @@array_types = [ :array, :args ] # TODO: remove
##
# Create a new Sexp containing +args+.
- def initialize(*args)
+ def initialize *args
super(args)
end
##
# Creates a new Sexp from Array +a+.
- def self.from_array(a)
+ def self.from_array a
ary = Array === a ? a : [a]
- result = self.new
-
- ary.each do |x|
- case x
- when Sexp
- result << x
- when Array
- result << self.from_array(x)
- else
- result << x
- end
- end
-
- result
- end
-
- def ==(obj) # :nodoc:
- obj.class == self.class and super
+ self.new(*ary.map { |x|
+ case x
+ when Sexp
+ x
+ when Array
+ self.from_array(x)
+ else
+ x
+ end
+ })
end
##
- # Returns true if this Sexp's pattern matches +sexp+.
-
- def ===(sexp)
- return nil unless Sexp === sexp
- pattern = self # this is just for my brain
-
- return true if pattern == sexp
-
- sexp.each do |subset|
- return true if pattern === subset
- end
-
- return nil
+ # Creates a new sexp with the new contents of +body+, but with the
+ # same +file+, +line+, and +comment+ as self.
+
+ def new(*body)
+ r = self.class.new(*body) # ensures a sexp from map
+ r.file = self.file if self.file
+ r.line = self.line if self.line
+ r.comments = self.comments if self.comments
+ r
end
- ##
- # Returns true if this Sexp matches +pattern+. (Opposite of #===.)
+ def map &blk # :nodoc:
+ self.new(*super(&blk)) # ensures a sexp from map
+ end
- def =~(pattern)
- return pattern === self
+ def == obj # :nodoc:
+ obj.class == self.class and super # only because of a bug in ruby
end
##
@@ -75,18 +75,19 @@ class Sexp < Array # ZenTest FULL
# REFACTOR: to TypedSexp - we only care when we have units.
def array_type?
- type = self.first
+ warn "DEPRECATED: please file an issue if you actually use this. from #{caller.first}"
+ type = self.sexp_type
@@array_types.include? type
end
def compact # :nodoc:
- self.delete_if { |o| o.nil? }
+ self.delete_if(&:nil?)
end
##
# Recursively enumerates the sexp yielding to +block+ for every element.
- def deep_each(&block)
+ def deep_each &block
return enum_for(:deep_each) unless block_given?
self.each_sexp do |sexp|
@@ -95,6 +96,9 @@ class Sexp < Array # ZenTest FULL
end
end
+ ##
+ # Return the maximum depth of the sexp. One-based.
+
def depth
1 + (each_sexp.map(&:depth).max || 0)
end
@@ -102,14 +106,12 @@ class Sexp < Array # ZenTest FULL
##
# Enumeratates the sexp yielding to +b+ when the node_type == +t+.
- def each_of_type(t, &b)
+ def each_of_type t, &b
return enum_for(:each_of_type) unless block_given?
- each do | elem |
- if Sexp === elem then
- elem.each_of_type(t, &b)
- b.call(elem) if elem.first == t
- end
+ each_sexp do | sexp |
+ sexp.each_of_type(t, &b)
+ yield sexp if sexp.sexp_type == t
end
end
@@ -130,12 +132,12 @@ class Sexp < Array # ZenTest FULL
# Replaces all elements whose node_type is +from+ with +to+. Used
# only for the most trivial of rewrites.
- def find_and_replace_all(from, to)
+ def find_and_replace_all from, to
each_with_index do | elem, index |
if Sexp === elem then
elem.find_and_replace_all(from, to)
- else
- self[index] = to if elem == from
+ elsif elem == from
+ self[index] = to
end
end
end
@@ -143,31 +145,35 @@ class Sexp < Array # ZenTest FULL
##
# Replaces all Sexps matching +pattern+ with Sexp +repl+.
- def gsub(pattern, repl)
+ def gsub pattern, repl
return repl if pattern == self
- new = self.map do |subset|
+ new = self.map { |subset|
case subset
when Sexp then
- subset.gsub(pattern, repl)
+ if Matcher === pattern && pattern.satisfy?(subset) then # TODO: make === be satisfy? maybe?
+ repl.dup rescue repl
+ else
+ subset.gsub pattern, repl
+ end
else
subset
end
- end
+ }
- return Sexp.from_array(new)
+ Sexp.from_array new
end
def inspect # :nodoc:
- sexp_str = self.map {|x|x.inspect}.join(', ')
- if ENV['VERBOSE'] && line then
+ sexp_str = self.map(&:inspect).join ", "
+ if ENV["VERBOSE"] && line then
"s(#{sexp_str}).line(#{line})"
else
"s(#{sexp_str})"
end
end
- def find_node name, delete = false
+ def find_node name, delete = false # :nodoc:
matches = find_nodes name
case matches.size
@@ -186,7 +192,7 @@ class Sexp < Array # ZenTest FULL
# Find every node with type +name+.
def find_nodes name
- find_all { | sexp | Sexp === sexp and sexp.first == name }
+ each_sexp.find_all { |sexp| sexp.sexp_type == name }
end
##
@@ -194,7 +200,7 @@ class Sexp < Array # ZenTest FULL
# returns the line number. This allows you to do message cascades
# and still get the sexp back.
- def line(n=nil)
+ def line n = nil
if n then
@line = n
self
@@ -214,14 +220,7 @@ class Sexp < Array # ZenTest FULL
# Returns the size of the sexp, flattened.
def mass
- @mass ||=
- inject(1) { |t, s|
- if Sexp === s then
- t + s.mass
- else
- t
- end
- }
+ @mass ||= inject(1) { |t, s| Sexp === s ? t + s.mass : t }
end
##
@@ -244,11 +243,11 @@ class Sexp < Array # ZenTest FULL
super
end
- def pretty_print(q) # :nodoc:
- nnd = ')'
- nnd << ".line(#{line})" if line && ENV['VERBOSE']
+ def pretty_print q # :nodoc:
+ nnd = ")"
+ nnd << ".line(#{line})" if line && ENV["VERBOSE"]
- q.group(1, 's(', nnd) do
+ q.group(1, "s(", nnd) do
q.seplist(self) {|v| q.pp v }
end
end
@@ -268,10 +267,18 @@ class Sexp < Array # ZenTest FULL
end
##
+ # Returns the Sexp body (starting at +from+, defaulting to 1), ie
+ # the values without the node type.
+
+ def sexp_body from = 1
+ self.new(*self[from..-1])
+ end
+
+ ##
# Returns the Sexp body, ie the values without the node type.
- def sexp_body
- self[1..-1]
+ def sexp_body= v
+ self[1..-1] = v
end
alias :head :sexp_type
@@ -284,29 +291,27 @@ class Sexp < Array # ZenTest FULL
def shift
raise "I'm empty" if self.empty?
super
- end if ($DEBUG or $TESTING) unless (defined?(RUBY_ENGINE) and RUBY_ENGINE == "maglev")
+ end if ($DEBUG or $TESTING)
##
# Returns the bare bones structure of the sexp.
# s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))
def structure
- if Array === self.first then
+ if Array === self.sexp_type then
+ warn "NOTE: form s(s(:subsexp)).structure is deprecated. Removing in 5.0"
s(:bogus, *self).structure # TODO: remove >= 4.2.0
else
- result = s(self.first)
- self.each do |subexp|
- result << subexp.structure if Sexp === subexp
- end
- result
+ s(self.sexp_type, *each_sexp.map(&:structure))
end
end
##
# Replaces the Sexp matching +pattern+ with +repl+.
- def sub(pattern, repl)
+ def sub pattern, repl
return repl.dup if pattern == self
+ return repl.dup if Matcher === pattern && pattern.satisfy?(self)
done = false
@@ -318,12 +323,12 @@ class Sexp < Array # ZenTest FULL
when Sexp then
if pattern == subset then
done = true
- repl.dup
- elsif pattern === subset then
+ repl.dup rescue repl
+ elsif Matcher === pattern && pattern.satisfy?(subset) then
done = true
- subset.sub pattern, repl
+ repl.dup rescue repl
else
- subset
+ subset.sub pattern, repl
end
else
subset
@@ -331,41 +336,1079 @@ class Sexp < Array # ZenTest FULL
end
end
- return Sexp.from_array(new)
+ Sexp.from_array new
end
def to_a # :nodoc:
self.map { |o| Sexp === o ? o.to_a : o }
end
- def to_s # :nodoc:
- inspect
- end
+ alias to_s inspect # :nodoc:
end
-class SexpMatchSpecial < Sexp; end
+##
+# This is a very important shortcut to make using Sexps much more awesome.
+#
+# In its normal form +s(...)+, creates a Sexp instance. If passed a
+# block, it creates a Sexp::Matcher using the factory methods on Sexp.
+#
+# See Matcher and other factory class methods on Sexp.
+
+def s *args, &blk
+ return Sexp.class_eval(&blk) if blk
+ Sexp.new(*args)
+end
-class SexpAny < SexpMatchSpecial
- def ==(o)
- Sexp === o
+class Sexp #:nodoc:
+ ##
+ # Verifies that +pattern+ is a Matcher and then dispatches to its
+ # #=~ method.
+ #
+ # See Matcher.=~
+
+ def =~ pattern
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
+ pattern =~ self
end
- def ===(o)
- return Sexp === o
+ ##
+ # Verifies that +pattern+ is a Matcher and then dispatches to its
+ # #satisfy? method.
+ #
+ # TODO: rename match?
+
+ def satisfy? pattern
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
+ pattern.satisfy? self
end
- def inspect
- "ANY"
+ ##
+ # Verifies that +pattern+ is a Matcher and then dispatches to its #/
+ # method.
+ #
+ # TODO: rename grep? match_all ? find_all ?
+
+ def / pattern
+ raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
+ pattern / self
end
-end
-module SexpMatchSpecials
- def ANY(); return SexpAny.new; end
-end
+ ##
+ # Recursively searches for the +pattern+ yielding the matches.
-##
-# This is a very important shortcut to make using Sexps much more awesome.
+ def search_each pattern, &block # TODO: rename to grep?
+ raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
-def s(*args)
- Sexp.new(*args)
+ return enum_for(:search_each, pattern) unless block_given?
+
+ if pattern.satisfy? self then
+ yield self
+ end
+
+ self.each_sexp do |subset|
+ subset.search_each pattern, &block
+ end
+ end
+
+ ##
+ # Recursively searches for the +pattern+ yielding each match, and
+ # replacing it with the result of the block.
+ #
+
+ def replace_sexp pattern, &block # TODO: rename to gsub?
+ raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher
+
+ return yield self if pattern.satisfy? self
+
+ # TODO: Needs #new_from(*new_body) to copy file/line/comment
+ self.class.new(*self.map { |subset|
+ case subset
+ when Sexp then
+ subset.replace_sexp pattern, &block
+ else
+ subset
+ end
+ })
+ end
+
+ ##
+ # Matches an S-Expression.
+ #
+ # See Matcher for examples.
+
+ def self.s *args
+ Matcher.new(*args)
+ end
+
+ ##
+ # Matches any single item.
+ #
+ # See Wild for examples.
+
+ def self._
+ Wild.new
+ end
+
+ # TODO: reorder factory methods and classes to match
+
+ ##
+ # Matches all remaining input.
+ #
+ # See Remaining for examples.
+
+ def self.___
+ Remaining.new
+ end
+
+ ##
+ # Matches an expression or any expression that includes the child.
+ #
+ # See Include for examples.
+
+ def self.include child # TODO: rename, name is generic ruby
+ Include.new(child)
+ end
+
+ ##
+ # Matches any atom.
+ #
+ # See Atom for examples.
+
+ def self.atom
+ Atom.new
+ end
+
+ ##
+ # Matches when any of the sub-expressions match.
+ #
+ # This is also available via Matcher#|.
+ #
+ # See Any for examples.
+
+ def self.any *args
+ Any.new(*args)
+ end
+
+ ##
+ # Matches only when all sub-expressions match.
+ #
+ # This is also available via Matcher#&.
+ #
+ # See All for examples.
+
+ def self.all *args
+ All.new(*args)
+ end
+
+ ##
+ # Matches when sub-expression does not match.
+ #
+ # This is also available via Matcher#-@.
+ #
+ # See Not for examples.
+
+ def self.not? arg
+ Not.new arg
+ end
+
+ # TODO: add Sibling factory method?
+
+ ##
+ # Matches anything that has a child matching the sub-expression.
+ #
+ # See Child for examples.
+
+ def self.child child
+ Child.new child
+ end
+
+ ##
+ # Matches anything having the same sexp_type, which is the first
+ # value in a Sexp.
+ #
+ # See Type for examples.
+
+ def self.t name
+ Type.new name
+ end
+
+ ##
+ # Matches any atom who's string representation matches the patterns
+ # passed in.
+ #
+ # See Pattern for examples.
+
+ def self.m *values
+ res = values.map { |value|
+ case value
+ when Regexp then
+ value
+ else
+ re = Regexp.escape value.to_s
+ Regexp.new "\\A%s\\Z" % re
+ end
+ }
+ Pattern.new Regexp.union(*res)
+ end
+
+ ##
+ # Defines a family of objects that can be used to match sexps to
+ # certain types of patterns, much like regexps can be used on
+ # strings. Generally you won't use this class directly.
+ #
+ # You would normally create a matcher using the top-level #s method,
+ # but with a block, calling into the Sexp factory methods. For example:
+ #
+ # s{ s(:class, m(/^Test/), _, ___) }
+ #
+ # This creates a matcher for classes whose names start with "Test".
+ # It uses Sexp.m to create a Sexp::Matcher::Pattern matcher, Sexp._
+ # to create a Sexp::Matcher::Wild matcher, and Sexp.___ to create a
+ # Sexp::Matcher::Remaining matcher. It works like this:
+ #
+ # s{ # start to create a pattern
+ # s( # create a sexp matcher
+ # :class. # for class nodes
+ # m(/^Test/), # matching name slots that start with "Test"
+ # _, # any superclass value
+ # ___ # and whatever is in the class
+ # )
+ # }
+ #
+ # Then you can use that with #=~, #/, Sexp#replace_sexp, and others.
+ #
+ # For more examples, see the various Sexp class methods, the examples,
+ # and the tests supplied with Sexp.
+ #
+ # * For pattern creation, see factory methods: Sexp::_, Sexp::___, etc.
+ # * For matching returning truthy/falsey results, see Sexp#=~.
+ # * See Sexp#=~ for matching returning truthy/falsey results.
+ # * For case expressions, see Matcher#===.
+ # * For getting all subtree matches, see Sexp#/.
+ #
+ # If rdoc didn't suck, these would all be links.
+
+ class Matcher < Sexp
+ ##
+ # Should #=~ match sub-trees?
+
+ def self.match_subs?
+ @@match_subs
+ end
+
+ ##
+ # Setter for +match_subs?+.
+
+ def self.match_subs= o
+ @@match_subs = o
+ end
+
+ self.match_subs = true
+
+ ##
+ # Does this matcher actually match +o+? Returns falsey if +o+ is
+ # not a Sexp or if any sub-tree of +o+ is not satisfied by or
+ # equal to its corresponding sub-matcher.
+ #
+ #--
+ # TODO: push this up to Sexp and make this the workhorse
+ # TODO: do the same with ===/satisfy?
+
+ def satisfy? o
+ return unless o.kind_of?(Sexp) &&
+ (length == o.length || Matcher === last && last.greedy?)
+
+ each_with_index.all? { |child, i|
+ sexp = o.at i
+ if Sexp === child then # TODO: when will this NOT be a matcher?
+ sexp = o.sexp_body i if child.respond_to?(:greedy?) && child.greedy?
+ child.satisfy? sexp
+ else
+ child == sexp
+ end
+ }
+ end
+
+ ##
+ # Tree equivalent to String#=~, returns true if +self+ matches
+ # +sexp+ as a whole or in a sub-tree (if +match_subs?+).
+ #
+ # TODO: maybe this should NOT be aliased to === ?
+ #
+ # TODO: example
+
+ def =~ sexp
+ raise ArgumentError, "Can't both be matchers: %p" % [sexp] if Matcher === sexp
+
+ self.satisfy?(sexp) ||
+ (self.class.match_subs? && sexp.each_sexp.any? { |sub| self =~ sub })
+ end
+
+ alias === =~ # TODO?: alias === satisfy?
+
+ ##
+ # Searches through +sexp+ for all sub-trees that match this
+ # matcher and returns a MatchCollection for each match.
+ #
+ # TODO: redirect?
+ # Example:
+ # Q{ s(:b) } / s(:a, s(:b)) => [s(:b)]
+
+ def / sexp
+ raise ArgumentError, "can't both be matchers" if Matcher === sexp
+
+ # TODO: move search_each into matcher?
+ MatchCollection.new sexp.search_each(self).to_a
+ end
+
+ ##
+ # Combines the Matcher with another Matcher, the resulting one will
+ # be satisfied if either Matcher would be satisfied.
+ #
+ # TODO: redirect
+ # Example:
+ # s(:a) | s(:b)
+
+ def | other
+ Any.new self, other
+ end
+
+ ##
+ # Combines the Matcher with another Matcher, the resulting one will
+ # be satisfied only if both Matchers would be satisfied.
+ #
+ # TODO: redirect
+ # Example:
+ # t(:a) & include(:b)
+
+ def & other
+ All.new self, other
+ end
+
+ ##
+ # Returns a Matcher that matches whenever this Matcher would not have matched
+ #
+ # Example:
+ # -s(:a)
+
+ def -@
+ Not.new self
+ end
+
+ ##
+ # Returns a Matcher that matches if this has a sibling +o+
+ #
+ # Example:
+ # s(:a) >> s(:b)
+
+ def >> other
+ Sibling.new self, other
+ end
+
+ ##
+ # Is this matcher greedy? Defaults to false.
+
+ def greedy?
+ false
+ end
+
+ def inspect # :nodoc:
+ s = super
+ s[0] = "q"
+ s
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "q(", ")" do
+ q.seplist self do |v|
+ q.pp v
+ end
+ end
+ end
+
+ ##
+ # Parse a lispy string representation of a matcher into a Matcher.
+ # See +Parser+.
+
+ def self.parse s
+ Parser.new(s).parse
+ end
+
+ ##
+ # Converts from a lispy string to Sexp matchers in a safe manner.
+ #
+ # "(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }
+
+ class Parser
+
+ ##
+ # The stream of tokens to parse. See #lex.
+
+ attr_accessor :tokens
+
+ ##
+ # Create a new Parser instance on +s+
+
+ def initialize s
+ self.tokens = []
+ lex s
+ end
+
+ ##
+ # Converts +s+ into a stream of tokens and adds them to +tokens+.
+
+ def lex s
+ tokens.concat s.scan(%r%[()\[\]]|\"[^"]*\"|/[^/]*/|[\w-]+%)
+ end
+
+ ##
+ # Returns the next token and removes it from the stream or raises if empty.
+
+ def next_token
+ raise SyntaxError, "unbalanced input" if tokens.empty?
+ tokens.shift
+ end
+
+ ##
+ # Returns the next token without removing it from the stream.
+
+ def peek_token
+ tokens.first
+ end
+
+ ##
+ # Parses tokens and returns a +Matcher+ instance.
+
+ def parse
+ result = parse_sexp until tokens.empty?
+ result
+ end
+
+ ##
+ # Parses a string into a sexp matcher:
+ #
+ # SEXP : "(" SEXP:args* ")" => Sexp.q(*args)
+ # | "[" CMD:cmd sexp:args* "]" => Sexp.cmd(*args)
+ # | "nil" => nil
+ # | /\d+/:n => n.to_i
+ # | "___" => Sexp.___
+ # | "_" => Sexp._
+ # | /^\/(.*)\/$/:re => Regexp.new re[0]
+ # | /^"(.*)"$/:s => String.new s[0]
+ # | NAME:name => name.to_sym
+ # NAME : /\w+/
+ # CMD : "t" | "m" | "atom"
+
+ def parse_sexp
+ token = next_token
+
+ case token
+ when "(" then
+ parse_list
+ when "[" then
+ parse_cmd
+ when "nil" then
+ nil
+ when /^\d+$/ then
+ token.to_i
+ when "___" then
+ Sexp.___
+ when "_" then
+ Sexp._
+ when %r%^/(.*)/$% then
+ re = $1
+ raise SyntaxError, "Not allowed: /%p/" % [re] unless
+ re =~ /\A([\w()|.*+^$]+)\z/
+ Regexp.new re
+ when /^"(.*)"$/ then
+ $1
+ when /^\w+$/ then
+ token.to_sym
+ else
+ raise SyntaxError, "unhandled token: %p" % [token]
+ end
+ end
+
+ ##
+ # Parses a balanced list of expressions and returns the
+ # equivalent matcher.
+
+ def parse_list
+ result = []
+
+ result << parse_sexp while peek_token && peek_token != ")"
+ next_token # pop off ")"
+
+ Sexp.s(*result)
+ end
+
+ ##
+ # A collection of allowed commands to convert into matchers.
+
+ ALLOWED = [:t, :m, :atom].freeze
+
+ ##
+ # Parses a balanced command. A command is denoted by square
+ # brackets and must conform to a whitelisted set of allowed
+ # commands (see +ALLOWED+).
+
+ def parse_cmd
+ args = []
+ args << parse_sexp while peek_token && peek_token != "]"
+ next_token # pop off "]"
+
+ cmd = args.shift
+ args = Sexp.s(*args)
+
+ raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd
+
+ result = Sexp.send cmd, *args
+
+ result
+ end
+ end # class Parser
+ end # class Matcher
+
+ ##
+ # Matches any single item.
+ #
+ # examples:
+ #
+ # s(:a) / s{ _ } #=> [s(:a)]
+ # s(:a, s(s(:b))) / s{ s(_) } #=> [s(s(:b))]
+
+ class Wild < Matcher
+ ##
+ # Matches any single element.
+
+ def satisfy? o
+ true
+ end
+
+ def inspect # :nodoc:
+ "_"
+ end
+
+ def pretty_print q # :nodoc:
+ q.text "_"
+ end
+ end
+
+ ##
+ # Matches all remaining input. If remaining comes before any other
+ # matchers, they will be ignored.
+ #
+ # examples:
+ #
+ # s(:a) / s{ s(:a, ___ ) } #=> [s(:a)]
+ # s(:a, :b, :c) / s{ s(:a, ___ ) } #=> [s(:a, :b, :c)]
+
+ class Remaining < Matcher
+ ##
+ # Always satisfied once this is reached. Think of it as a var arg.
+
+ def satisfy? o
+ true
+ end
+
+ def greedy?
+ true
+ end
+
+ def inspect # :nodoc:
+ "___"
+ end
+
+ def pretty_print q # :nodoc:
+ q.text "___"
+ end
+ end
+
+ ##
+ # Matches when any of the sub-expressions match.
+ #
+ # This is also available via Matcher#|.
+ #
+ # examples:
+ #
+ # s(:a) / s{ any(s(:a), s(:b)) } #=> [s(:a)]
+ # s(:a) / s{ s(:a) | s(:b) } #=> [s(:a)] # same thing via |
+ # s(:a) / s{ any(s(:b), s(:c)) } #=> []
+
+ class Any < Matcher
+ ##
+ # The collection of sub-matchers to match against.
+
+ attr_reader :options
+
+ ##
+ # Create an Any matcher which will match any of the +options+.
+
+ def initialize *options
+ @options = options
+ end
+
+ ##
+ # Satisfied when any sub expressions match +o+
+
+ def satisfy? o
+ options.any? { |exp|
+ Sexp === exp && exp.satisfy?(o) || exp == o
+ }
+ end
+
+ def == o # :nodoc:
+ super && self.options == o.options
+ end
+
+ def inspect # :nodoc:
+ options.map(&:inspect).join(" | ")
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "any(", ")" do
+ q.seplist options do |v|
+ q.pp v
+ end
+ end
+ end
+ end
+
+ ##
+ # Matches only when all sub-expressions match.
+ #
+ # This is also available via Matcher#&.
+ #
+ # examples:
+ #
+ # s(:a) / s{ all(s(:a), s(:b)) } #=> []
+ # s(:a, :b) / s{ t(:a) & include(:b)) } #=> [s(:a, :b)]
+
+ class All < Matcher
+ ##
+ # The collection of sub-matchers to match against.
+
+ attr_reader :options
+
+ ##
+ # Create an All matcher which will match all of the +options+.
+
+ def initialize *options
+ @options = options
+ end
+
+ ##
+ # Satisfied when all sub expressions match +o+
+
+ def satisfy? o
+ options.all? { |exp|
+ exp.kind_of?(Sexp) ? exp.satisfy?(o) : exp == o
+ }
+ end
+
+ def == o # :nodoc:
+ super && self.options == o.options
+ end
+
+ def inspect # :nodoc:
+ options.map(&:inspect).join(" & ")
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "all(", ")" do
+ q.seplist options do |v|
+ q.pp v
+ end
+ end
+ end
+ end
+
+ ##
+ # Matches when sub-expression does not match.
+ #
+ # This is also available via Matcher#-@.
+ #
+ # examples:
+ #
+ # s(:a) / s{ not?(s(:b)) } #=> [s(:a)]
+ # s(:a) / s{ -s(:b) } #=> [s(:a)]
+ # s(:a) / s{ s(not? :a) } #=> []
+
+ class Not < Matcher
+
+ ##
+ # The value to negate in the match.
+
+ attr_reader :value
+
+ ##
+ # Creates a Matcher which will match any Sexp that does not match the +value+
+
+ def initialize value
+ @value = value
+ end
+
+ def == o # :nodoc:
+ super && self.value == o.value
+ end
+
+ ##
+ # Satisfied if a +o+ does not match the +value+
+
+ def satisfy? o
+ !(value.kind_of?(Sexp) ? value.satisfy?(o) : value == o)
+ end
+
+ def inspect # :nodoc:
+ "not?(%p)" % [value]
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "not?(", ")" do
+ q.pp value
+ end
+ end
+ end
+
+ ##
+ # Matches anything that has a child matching the sub-expression
+ #
+ # example:
+ #
+ # s(s(s(s(s(:a))))) / s{ child(s(:a)) } #=> [s(s(s(s(s(:a))))),
+ # s(s(s(s(:a)))),
+ # s(s(s(:a))),
+ # s(s(:a)),
+ # s(:a)]
+
+ class Child < Matcher
+ ##
+ # The child to match.
+
+ attr_reader :child
+
+ ##
+ # Create a Child matcher which will match anything having a
+ # descendant matching +child+.
+
+ def initialize child
+ @child = child
+ end
+
+ ##
+ # Satisfied if matches +child+ or +o+ has a descendant matching
+ # +child+.
+
+ def satisfy? o
+ if child.satisfy? o
+ true
+ elsif o.kind_of? Sexp
+ o.search_each(child).any?
+ end
+ end
+
+ def == o # :nodoc:
+ super && self.child == o.child
+ end
+
+ def inspect # :nodoc:
+ "child(%p)" % [child]
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "child(", ")" do
+ q.pp child
+ end
+ end
+ end
+
+ ##
+ # Matches any atom (non-Sexp).
+ #
+ # examples:
+ #
+ # s(:a) / s{ s(atom) } #=> [s(:a)]
+ # s(:a, s(:b)) / s{ s(atom) } #=> [s(:b)]
+
+ class Atom < Matcher
+ ##
+ # Satisfied when +o+ is an atom.
+
+ def satisfy? o
+ !(o.kind_of? Sexp)
+ end
+
+ def inspect #:nodoc:
+ "atom"
+ end
+
+ def pretty_print q # :nodoc:
+ q.text "atom"
+ end
+ end
+
+ ##
+ # Matches any atom who's string representation matches the patterns
+ # passed in.
+ #
+ # examples:
+ #
+ # s(:a) / s{ m('a') } #=> [s(:a)]
+ # s(:a) / s{ m(/\w/,/\d/) } #=> [s(:a)]
+ # s(:tests, s(s(:test_a), s(:test_b))) / s{ m(/test_\w/) } #=> [s(:test_a),
+ #
+ # TODO: maybe don't require non-sexps? This does respond to =~ now.
+
+ class Pattern < Matcher
+
+ ##
+ # The regexp to match for the pattern.
+
+ attr_reader :pattern
+
+ def == o # :nodoc:
+ super && self.pattern == o.pattern
+ end
+
+ ##
+ # Create a Patten matcher which will match any atom that either
+ # matches the input +pattern+.
+
+ def initialize pattern
+ @pattern = pattern
+ end
+
+ ##
+ # Satisfied if +o+ is an atom, and +o+ matches +pattern+
+
+ def satisfy? o
+ !o.kind_of?(Sexp) && o.to_s =~ pattern # TODO: question to_s
+ end
+
+ def inspect # :nodoc:
+ "m(%p)" % pattern
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "m(", ")" do
+ q.pp pattern
+ end
+ end
+ end
+
+ ##
+ # Matches anything having the same sexp_type, which is the first
+ # value in a Sexp.
+ #
+ # examples:
+ #
+ # s(:a, :b) / s{ t(:a) } #=> [s(:a, :b)]
+ # s(:a, :b) / s{ t(:b) } #=> []
+ # s(:a, s(:b, :c)) / s{ t(:b) } #=> [s(:b, :c)]
+
+ class Type < Matcher
+ attr_reader :sexp_type
+
+ ##
+ # Creates a Matcher which will match any Sexp who's type is +type+, where a type is
+ # the first element in the Sexp.
+
+ def initialize type
+ @sexp_type = type
+ end
+
+ def == o # :nodoc:
+ super && self.sexp_type == o.sexp_type
+ end
+
+ ##
+ # Satisfied if the sexp_type of +o+ is +type+.
+
+ def satisfy? o
+ o.kind_of?(Sexp) && o.sexp_type == sexp_type
+ end
+
+ def inspect # :nodoc:
+ "t(%p)" % sexp_type
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "t(", ")" do
+ q.pp sexp_type
+ end
+ end
+ end
+
+ ##
+ # Matches an expression or any expression that includes the child.
+ #
+ # examples:
+ #
+ # s(:a, :b) / s{ include(:b) } #=> [s(:a, :b)]
+ # s(s(s(:a))) / s{ include(:a) } #=> [s(:a)]
+
+ class Include < Matcher
+ ##
+ # The value that should be included in the match.
+
+ attr_reader :value
+
+ ##
+ # Creates a Matcher which will match any Sexp that contains the
+ # +value+.
+
+ def initialize value
+ @value = value
+ end
+
+ ##
+ # Satisfied if a +o+ is a Sexp and one of +o+'s elements matches
+ # value
+
+ def satisfy? o
+ Sexp === o && o.any? { |c|
+ # TODO: switch to respond_to??
+ Sexp === value ? value.satisfy?(c) : value == c
+ }
+ end
+
+ def == o # :nodoc:
+ super && self.value == o.value
+ end
+
+ def inspect # :nodoc:
+ "include(%p)" % [value]
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 1, "include(", ")" do
+ q.pp value
+ end
+ end
+ end
+
+ ##
+ # See Matcher for sibling relations: <,<<,>>,>
+
+ class Sibling < Matcher
+
+ ##
+ # The LHS of the matcher.
+
+ attr_reader :subject
+
+ ##
+ # The RHS of the matcher.
+
+ attr_reader :sibling
+
+ ##
+ # An optional distance requirement for the matcher.
+
+ attr_reader :distance
+
+ ##
+ # Creates a Matcher which will match any pair of Sexps that are siblings.
+ # Defaults to matching the immediate following sibling.
+
+ def initialize subject, sibling, distance = nil
+ @subject = subject
+ @sibling = sibling
+ @distance = distance
+ end
+
+ ##
+ # Satisfied if o contains +subject+ followed by +sibling+
+
+ def satisfy? o
+ # Future optimizations:
+ # * Shortcut matching sibling
+ subject_matches = index_matches(subject, o)
+ return nil if subject_matches.empty?
+
+ sibling_matches = index_matches(sibling, o)
+ return nil if sibling_matches.empty?
+
+ subject_matches.any? { |i1, _data_1|
+ sibling_matches.any? { |i2, _data_2|
+ distance ? (i2-i1 == distance) : i2 > i1
+ }
+ }
+ end
+
+ def == o # :nodoc:
+ super &&
+ self.subject == o.subject &&
+ self.sibling == o.sibling &&
+ self.distance == o.distance
+ end
+
+ def inspect # :nodoc:
+ "%p >> %p" % [subject, sibling]
+ end
+
+ def pretty_print q # :nodoc:
+ if distance then
+ q.group 1, "sibling(", ")" do
+ q.seplist [subject, sibling, distance] do |v|
+ q.pp v
+ end
+ end
+ else
+ q.group 1 do
+ q.pp subject
+ q.text " >> "
+ q.pp sibling
+ end
+ end
+ end
+
+ private
+
+ def index_matches pattern, o
+ indexes = []
+ return indexes unless o.kind_of? Sexp
+
+ o.each_with_index do |e, i|
+ data = {}
+ if pattern.kind_of?(Sexp) ? pattern.satisfy?(e) : pattern == o[i]
+ indexes << [i, data]
+ end
+ end
+
+ indexes
+ end
+ end # class Sibling
+
+ ##
+ # Wraps the results of a Sexp query. MatchCollection defines
+ # MatchCollection#/ so that you can chain queries.
+ #
+ # For instance:
+ # res = s(:a, s(:b)) / s{ s(:a,_) } / s{ s(:b) }
+
+ class MatchCollection < Array
+ ##
+ # See Traverse#search
+
+ def / pattern
+ inject(self.class.new) { |result, match|
+ result.concat match / pattern
+ }
+ end
+
+ def inspect # :nodoc:
+ "MatchCollection.new(%s)" % self.to_a.inspect[1..-2]
+ end
+
+ alias :to_s :inspect # :nodoc:
+
+ def pretty_print q # :nodoc:
+ q.group 1, "MatchCollection.new(", ")" do
+ q.seplist(self) {|v| q.pp v }
+ end
+ end
+ end # class MatchCollection
end
+
+require "strict_sexp" if ENV["STRICT_SEXP"].to_i > 0
diff --git a/sexp_processor-4.9.0/lib/sexp_processor.rb b/sexp_processor-4.10.0/lib/sexp_processor.rb
index 66d2441..ecfe39e 100644
--- a/sexp_processor-4.9.0/lib/sexp_processor.rb
+++ b/sexp_processor-4.10.0/lib/sexp_processor.rb
@@ -1,6 +1,6 @@
$TESTING = false unless defined? $TESTING
-require 'sexp'
+require "sexp"
##
# SexpProcessor provides a uniform interface to process Sexps.
@@ -33,7 +33,8 @@ require 'sexp'
class SexpProcessor
- VERSION = "4.9.0"
+ # duh
+ VERSION = "4.10.0"
##
# Automatically shifts off the Sexp type before handing the
@@ -102,7 +103,7 @@ class SexpProcessor
dirs.flatten.map { |p|
if File.directory? p then
- Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")]
+ Dir[File.join(p, "**", "*.{#{extensions.join ","}}")]
else
p
end
@@ -160,7 +161,10 @@ class SexpProcessor
end
end
- def assert_empty(meth, exp, exp_orig)
+ ##
+ # Raise if +exp+ is not empty.
+
+ def assert_empty meth, exp, exp_orig
unless exp.empty? then
msg = "exp not empty after #{self.class}.#{meth} on #{exp.inspect}"
msg += " from #{exp_orig.inspect}" if $DEBUG
@@ -168,30 +172,39 @@ class SexpProcessor
end
end
- def rewrite(exp)
- type = exp.first
+ ##
+ # Rewrite +exp+ using rewrite_* method for +exp+'s sexp_type, if one
+ # exists.
+
+ def rewrite exp
+ type = exp.sexp_type
+
+ comments = exp.comments
- if @debug.has_key? type then
+ if @debug.key? type then
str = exp.inspect
puts "// DEBUG (original ): #{str}" if str =~ @debug[type]
end
in_context type do
- exp.map! { |sub| Array === sub ? rewrite(sub) : sub }
+ exp = exp.map { |sub| Array === sub ? rewrite(sub) : sub }
end
- begin
+ loop do
meth = @rewriters[type]
exp = self.send(meth, exp) if meth
break unless Sexp === exp
- if @debug.has_key? type then
+ if @debug.key? type then
str = exp.inspect
puts "// DEBUG (rewritten): #{str}" if str =~ @debug[type]
end
- old_type, type = type, exp.first
- end until old_type == type
+ old_type, type = type, exp.sexp_type
+ break if old_type == type
+ end
+
+ exp.comments = comments
exp
end
@@ -201,8 +214,13 @@ class SexpProcessor
# the Sexp type given. Performs additional checks as specified by
# the initializer.
- def process(exp)
+ def process exp
return nil if exp.nil?
+
+ unless Sexp === exp then
+ raise SexpTypeError, "exp must be a Sexp, was #{exp.class}:#{exp.inspect}"
+ end
+
if self.context.empty? then
p :rewriting unless debug.empty?
exp = self.rewrite(exp)
@@ -210,7 +228,7 @@ class SexpProcessor
end
unless @unsupported_checked then
- m = public_methods.grep(/^process_/) { |o| o.to_s.sub(/^process_/, '').to_sym }
+ m = public_methods.grep(/^process_/) { |o| o.to_s.sub(/^process_/, "").to_sym }
supported = m - (m - @unsupported)
raise UnsupportedNodeError, "#{supported.inspect} shouldn't be in @unsupported" unless supported.empty?
@@ -220,19 +238,19 @@ class SexpProcessor
result = self.expected.new
- type = exp.first
+ type = exp.sexp_type
raise "type should be a Symbol, not: #{exp.first.inspect}" unless
Symbol === type
in_context type do
- if @debug.has_key? type then
+ if @debug.key? type then
str = exp.inspect
puts "// DEBUG:(original ): #{str}" if str =~ @debug[type]
end
exp_orig = nil
exp_orig = exp.deep_clone if $DEBUG or
- @debug.has_key? type or @exceptions.has_key?(type)
+ @debug.key? type or @exceptions.key?(type)
raise UnsupportedNodeError, "'#{type}' is not a supported node type" if
@unsupported.include? type
@@ -245,40 +263,44 @@ class SexpProcessor
warn "WARNING: Using default method #{meth} for #{type}"
end
- exp.shift if @auto_shift_type and meth != @default_method
+ exp = exp.sexp_body if @auto_shift_type and meth != @default_method # HACK
- result = error_handler(type, exp_orig) do
- self.send(meth, exp)
- end
+ result = error_handler(type, exp_orig) {
+ self.send meth, exp
+ }
- if @debug.has_key? type then
+ if @debug.key? type then
str = exp.inspect
puts "// DEBUG (processed): #{str}" if str =~ @debug[type]
end
- raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result
+ raise SexpTypeError, "Result of #{type} must be a #{@expected}, was #{result.class}:#{result.inspect}" unless
+ @expected === result
self.assert_empty(meth, exp, exp_orig) if @require_empty
else
unless @strict then
until exp.empty? do
- sub_exp = exp.shift
+ sub_exp, *exp = exp # HACK
sub_result = nil
if Array === sub_exp then
sub_result = error_handler(type, exp_orig) do
process(sub_exp)
end
raise "Result is a bad type" unless Array === sub_exp
- raise "Result does not have a type in front: #{sub_exp.inspect}" unless Symbol === sub_exp.first unless sub_exp.empty?
+ raise "Result does not have a type in front: #{sub_exp.inspect}" unless
+ Symbol === sub_exp.sexp_type unless
+ sub_exp.empty?
else
sub_result = sub_exp
end
- result << sub_result
+ # result << sub_result
+ result = result.class.new(*result, sub_result) # HACK
end
# NOTE: this is costly, but we are in the generic processor
# so we shouldn't hit it too much with RubyToC stuff at least.
- result.sexp_type = exp.sexp_type if Sexp === exp and exp.sexp_type
+ result.c_type ||= exp.c_type if Sexp === exp and exp.respond_to?(:c_type)
else
msg = "Bug! Unknown node-type #{type.inspect} to #{self.class}"
msg += " in #{exp_orig.inspect} from #{caller.inspect}" if $DEBUG
@@ -293,28 +315,26 @@ class SexpProcessor
##
# Raises unless the Sexp type for +list+ matches +typ+
- def assert_type(list, typ)
+ def assert_type list, typ
raise SexpTypeError, "Expected type #{typ.inspect} in #{list.inspect}" if
not Array === list or list.first != typ
end
- def error_handler(type, exp=nil) # :nodoc:
- begin
- return yield
- rescue StandardError => err
- if @exceptions.has_key? type then
- return @exceptions[type].call(self, exp, err)
- else
- warn "#{err.class} Exception thrown while processing #{type} for sexp #{exp.inspect} #{caller.inspect}" if $DEBUG
- raise
- end
- end
+ def error_handler type, exp = nil # :nodoc:
+ yield
+ rescue StandardError => err
+ return @exceptions[type].call self, exp, err if @exceptions.key? type
+
+ warn "#{err.class} Exception thrown while processing #{type} for sexp #{exp.inspect} #{caller.inspect}" if
+ $DEBUG
+
+ raise
end
##
# Registers an error handler for +node+
- def on_error_in(node_type, &block)
+ def on_error_in node_type, &block
@exceptions[node_type] = block
end
@@ -329,13 +349,9 @@ class SexpProcessor
# return s(:dummy, process(exp), s(:extra, 42))
# end
- def process_dummy(exp)
+ def process_dummy exp
result = @expected.new(:dummy) rescue @expected.new
-
- until exp.empty? do
- result << self.process(exp.shift)
- end
-
+ result << self.process(exp.shift) until exp.empty?
result
end
@@ -362,11 +378,16 @@ class SexpProcessor
env.scope(&block)
end
+ ##
+ # Track a stack of contexts that the processor is in, pushing on
+ # +type+ yielding, and then removing the context from the stack.
+
def in_context type
self.context.unshift type
yield
+ ensure
self.context.shift
end
@@ -376,35 +397,55 @@ class SexpProcessor
# itches too much...
class Environment
- def initialize
+ def initialize #:nodoc:
@env = []
@env.unshift({})
end
+ ##
+ # Flatten out all scopes and return all key/value pairs.
+
def all
@env.reverse.inject { |env, scope| env.merge scope }
end
+ ##
+ # Return the current number of scopes.
+
def depth
@env.length
end
# TODO: depth_of
+ ##
+ # Get +name+ from env at whatever scope it is defined in, or return nil.
+
def [] name
- hash = @env.find { |closure| closure.has_key? name }
+ hash = @env.find { |closure| closure.key? name }
hash[name] if hash
end
+ ##
+ # If +name+ exists in the env, set it to +val+ in whatever scope
+ # it is in. If it doesn't exist, set +name+ to +val+ in the
+ # current scope.
+
def []= name, val
- hash = @env.find { |closure| closure.has_key? name } || current
+ hash = @env.find { |closure| closure.key? name } || current
hash[name] = val
end
+ ##
+ # Get the current/top environment.
+
def current
@env.first
end
+ ##
+ # Create a new scope and yield to the block passed.
+
def scope
@env.unshift({})
begin
@@ -423,7 +464,7 @@ end
# AKA, an interpreter.
class SexpInterpreter < SexpProcessor
- def initialize
+ def initialize #:nodoc:
super
self.expected = Object
@@ -442,9 +483,30 @@ class MethodBasedSexpProcessor < SexpProcessor
@@no_class = :main
@@no_method = :none
- attr_reader :class_stack, :method_stack, :sclass, :method_locations
+ ##
+ # A stack of the classes/modules that are being processed
- def initialize
+ attr_reader :class_stack
+
+ ##
+ # A stack of the methods that are being processed. You'd think it'd
+ # only ever be 1 deep, but you'd be wrong. People do terrible things
+ # in/to ruby.
+
+ attr_reader :method_stack
+
+ ##
+ # A stack of the singleton classes that are being processed.
+
+ attr_reader :sclass
+
+ ##
+ # A lookup table of all the method locations that have been
+ # processed so far.
+
+ attr_reader :method_locations
+
+ def initialize #:nodoc:
super
@sclass = []
@class_stack = []
@@ -458,7 +520,7 @@ class MethodBasedSexpProcessor < SexpProcessor
def in_klass name
if Sexp === name then
- name = case name.first
+ name = case name.sexp_type
when :colon2 then
name = name.flatten
name.delete :const
@@ -483,7 +545,7 @@ class MethodBasedSexpProcessor < SexpProcessor
##
# Adds name to the method stack, for the duration of the block
- def in_method name, file, line, line_max=nil
+ def in_method name, file, line, line_max = nil
method_name = Regexp === name ? name.inspect : name.to_s
@method_stack.unshift method_name
line_max = "-#{line_max}" if line_max
@@ -514,10 +576,10 @@ class MethodBasedSexpProcessor < SexpProcessor
def klass_name
name = @class_stack.first
- if Sexp === name then
- raise "you shouldn't see me"
- elsif @class_stack.any?
- @class_stack.reverse.join("::").sub(/\([^\)]+\)$/, '')
+ raise "you shouldn't see me" if Sexp === name
+
+ if @class_stack.any?
+ @class_stack.reverse.join("::").sub(/\([^\)]+\)$/, "")
else
@@no_class
end
@@ -535,10 +597,10 @@ class MethodBasedSexpProcessor < SexpProcessor
##
# Process a class node until empty. Tracks all nesting. If you have
- # to subclass and override this method, you can clall super with a
+ # to subclass and override this method, you can call super with a
# block.
- def process_class(exp)
+ def process_class exp
exp.shift unless auto_shift_type # node type
in_klass exp.shift do
if block_given? then
@@ -555,7 +617,7 @@ class MethodBasedSexpProcessor < SexpProcessor
# have to subclass and override this method, you can clall super
# with a block.
- def process_defn(exp)
+ def process_defn exp
exp.shift unless auto_shift_type # node type
name = @sclass.empty? ? exp.shift : "::#{exp.shift}"
@@ -574,7 +636,7 @@ class MethodBasedSexpProcessor < SexpProcessor
# If you have to subclass and override this method, you can clall
# super with a block.
- def process_defs(exp)
+ def process_defs exp
exp.shift unless auto_shift_type # node type
process exp.shift # recv
in_method "::#{exp.shift}", exp.file, exp.line, exp.line_max do
@@ -592,7 +654,7 @@ class MethodBasedSexpProcessor < SexpProcessor
# to subclass and override this method, you can clall super with a
# block.
- def process_module(exp)
+ def process_module exp
exp.shift unless auto_shift_type # node type
in_klass exp.shift do
if block_given? then
@@ -609,7 +671,7 @@ class MethodBasedSexpProcessor < SexpProcessor
# you have to subclass and override this method, you can clall super
# with a block.
- def process_sclass(exp)
+ def process_sclass exp
exp.shift unless auto_shift_type # node type
in_sklass do
if block_given? then
@@ -651,7 +713,7 @@ class MethodBasedSexpProcessor < SexpProcessor
end
end
-class Object
+class Object # :nodoc:
##
# deep_clone is the usual Marshalling hack to make a deep copy.
diff --git a/sexp_processor-4.10.0/lib/strict_sexp.rb b/sexp_processor-4.10.0/lib/strict_sexp.rb
new file mode 100644
index 0000000..285ed54
--- /dev/null
+++ b/sexp_processor-4.10.0/lib/strict_sexp.rb
@@ -0,0 +1,126 @@
+# :stopdoc:
+
+##
+# I'm starting to warm up to this idea!
+# ENV["STRICT_SEXP"] turns on various levels of conformance checking
+#
+# 1 = sexp[0] => sexp_type
+# 1 = sexp.first => sexp_type
+# 1 = sexp[0] = x => sexp_type = x
+# 1 = sexp[1..-1] => sexp_body
+# 1 = sexp[1..-1] = x => sexp_body = x
+# 1 = sexp[-1] => last
+# 2 = sexp[1] => no
+# 2 = sexp[1] = x => no
+# 3 = sexp[n] => no
+# 3 = sexp[n] = x => no
+# 3 = sexp.node_name => no (ie, method_missing)
+# 4 = sexp.replace x => no
+# 4 = sexp.concat x => no
+# 4 = sexp.collect! => no
+# 4 = sexp.compact! => no
+# 4 = sexp.flatten! => no
+# 4 = sexp.map! => no
+# 4 = sexp.reject! => no
+# 4 = sexp.reverse! => no
+# 4 = sexp.rotate! => no
+# 4 = sexp.select! => no
+# 4 = sexp.shuffle! => no
+# 4 = sexp.slice! => no
+# 4 = sexp.sort! => no
+# 4 = sexp.sort_by! => no
+# 4 = sexp.uniq! => no
+# 4 = sexp.unshift => no
+# 4 = sexp.push => no
+# 4 = sexp.pop => no
+# 4 = sexp << => no
+
+class Sexp
+ alias :safe_idx :[]
+ alias :safe_asgn :[]=
+ alias :sexp_type= :sexp_type=
+ alias :sexp_body= :sexp_body=
+ alias :shift :shift
+
+ def self.nuke_method name, level
+ define_method name do |*args|
+ raise "no: %p.%s %p" % [self, name, args]
+ end if __strict >= level
+ end
+
+ def self.__strict
+ ENV["STRICT_SEXP"].to_i
+ end
+
+ def __strict
+ self.class.__strict
+ end
+
+ undef_method :method_missing if __strict > 2
+
+ def method_missing msg, *args
+ raise "don't call method_missing on Sexps: %p.(%s)" % [msg, args.inspect[1..-2]]
+ end if __strict > 2
+
+ def [] i
+ raise "no idx: #{inspect}[#{i}]" if __strict > 2
+ raise "no idx>1: #{inspect}[#{i}]" if Integer === i && i > 1 if __strict > 1
+ raise "use sexp_type" if i == 0
+ raise "use sexp_body" if i == (1..-1)
+ raise "use last" if i == -1
+ self.safe_idx i
+ end
+
+ def []= i, v
+ raise "use sexp_type=" if i == 0
+ raise "use sexp_body=" if i == (1..-1)
+ raise "no asgn>1: #{inspect}[#{i}] = #{v.inspect}" if Integer === i && i > 1 if
+ __strict > 1
+ raise "no asgn: #{inspect}[#{i}] = #{v.inspect}" if
+ __strict > 2
+ self.safe_asgn i, v
+ end
+
+ def first
+ raise "use sexp_type"
+ end
+
+ nuke_method :collect!, 4
+ nuke_method :compact!, 4
+ nuke_method :concat, 4
+ nuke_method :flatten!, 4
+ nuke_method :map!, 4
+ nuke_method :pop, 4
+ nuke_method :push, 4
+ nuke_method :reject!, 4
+ nuke_method :replace, 4
+ nuke_method :reverse!, 4
+ nuke_method :rotate!, 4
+ nuke_method :select!, 4
+ nuke_method :shuffle!, 4
+ nuke_method :slice!, 4
+ nuke_method :sort!, 4
+ nuke_method :sort_by!, 4
+ nuke_method :uniq!, 4
+ nuke_method :unshift, 4
+ nuke_method :<<, 5
+ nuke_method :shift, 5
+
+ def sexp_type
+ safe_idx 0
+ end
+
+ def sexp_body from = 1
+ safe_idx from..-1
+ end
+
+ def sexp_type= v
+ self.safe_asgn 0, v
+ end
+
+ def sexp_body= v
+ self.safe_asgn 1..-1, v
+ end
+end unless Sexp.new.respond_to? :safe_asgn if ENV["STRICT_SEXP"]
+
+# :startdoc:
diff --git a/sexp_processor-4.9.0/lib/unique.rb b/sexp_processor-4.10.0/lib/unique.rb
index 7ce2db1..c5725d2 100644
--- a/sexp_processor-4.9.0/lib/unique.rb
+++ b/sexp_processor-4.10.0/lib/unique.rb
@@ -2,10 +2,16 @@
# Unique creates unique variable names.
class Unique
- def self.reset # mostly for testing
+ ##
+ # Reset current count back to zero. Mainly used for testing.
+
+ def self.reset
@@curr = 0
end
+ ##
+ # Get the next unique variable name.
+
def self.next
@@curr += 1
"temp_#{@@curr}".intern
diff --git a/sexp_processor-4.9.0/test/test_composite_sexp_processor.rb b/sexp_processor-4.10.0/test/test_composite_sexp_processor.rb
index b807a1a..b3beb24 100755
--- a/sexp_processor-4.9.0/test/test_composite_sexp_processor.rb
+++ b/sexp_processor-4.10.0/test/test_composite_sexp_processor.rb
@@ -1,7 +1,7 @@
$TESTING = true
-require 'composite_sexp_processor'
-require 'minitest/autorun'
+require "composite_sexp_processor"
+require "minitest/autorun"
class FakeProcessor1 < SexpProcessor # ZenTest SKIP
@@ -9,16 +9,13 @@ class FakeProcessor1 < SexpProcessor # ZenTest SKIP
super
self.warn_on_default = false
self.default_method = :default_processor
+ self.require_empty = false
self.expected = Array
end
- def default_processor(exp)
- result = []
- result << exp.shift
- until exp.empty? do
- result << exp.shift.to_s + " woot"
- end
- result
+ def default_processor exp
+ t, *rest = exp
+ s(t, *rest.map { |s| "#{s} woot" })
end
end
@@ -29,20 +26,20 @@ class TestCompositeSexpProcessor < Minitest::Test
end
def test_process_default
- data = [1, 2, 3]
+ data = s(1, 2, 3)
result = @p.process(data.dup)
assert_equal(data.dup, result)
end
def test_process_fake1
- data = [:x, 1, 2, 3]
+ data = s(:x, 1, 2, 3)
@p << FakeProcessor1.new
result = @p.process(data.dup)
assert_equal [:x, "1 woot", "2 woot", "3 woot"], result
end
def test_process_fake1_twice
- data = [:x, 1, 2, 3]
+ data = s(:x, 1, 2, 3)
@p << FakeProcessor1.new
@p << FakeProcessor1.new
result = @p.process(data.dup)
diff --git a/sexp_processor-4.9.0/test/test_environment.rb b/sexp_processor-4.10.0/test/test_environment.rb
index 08635ad..cb56e6a 100755
--- a/sexp_processor-4.9.0/test/test_environment.rb
+++ b/sexp_processor-4.10.0/test/test_environment.rb
@@ -1,7 +1,7 @@
$TESTING = true
-require 'minitest/autorun'
-require 'sexp_processor'
+require "minitest/autorun"
+require "sexp_processor"
class TestEnvironment < Minitest::Test
diff --git a/sexp_processor-4.9.0/test/test_sexp.rb b/sexp_processor-4.10.0/test/test_sexp.rb
index 85a5e41..e9f6c34 100755
--- a/sexp_processor-4.9.0/test/test_sexp.rb
+++ b/sexp_processor-4.10.0/test/test_sexp.rb
@@ -1,10 +1,19 @@
$TESTING = true
-require 'minitest/autorun'
+if ENV["COV"]
+ require "simplecov"
+ SimpleCov.start do
+ add_filter "lib/sexp_processor"
+ add_filter "test"
+ end
+ warn "Running simplecov"
+end
+
+require "minitest/autorun"
+require "minitest/hell" # beat these up
require "minitest/benchmark" if ENV["BENCH"]
-require 'sexp_processor'
-require 'stringio'
-require 'pp'
+require "sexp_processor" # for deep_clone (TODO: why is that on SP and not S?)
+require "sexp"
def pyramid_sexp max
# s(:array,
@@ -20,49 +29,119 @@ def pyramid_sexp max
end
class SexpTestCase < Minitest::Test
+ M = Sexp::Matcher
+ MC = Sexp::MatchCollection
+ MR = Sexp # ::MatchResult
+
+ CLASS_SEXP = s(:class, :cake, nil,
+ s(:defn, :foo, s(:args), s(:add, :a, :b)),
+ s(:defn, :bar, s(:args), s(:sub, :a, :b)))
+
+ def skip_if_strict n = 1
+ strict = ENV["STRICT_SEXP"].to_i
+
+ skip "Can't pass on STRICT_SEXP mode" if strict >= n
+ end
+
# KEY for regex tests
# :a == no change
# :b == will change (but sometimes ONLY once)
# :c == change to
- include SexpMatchSpecials
+ def assert_equal3 x, y
+ skip_if_strict
- def util_equals(x, y)
- result = x == y
- refute_nil result, "#{x.inspect} does not === #{y.inspect}"
+ assert_operator x, :===, y
end
- def util_equals3(x, y)
- result = x === y
- refute_nil result, "#{x.inspect} does not === #{y.inspect}"
+ def refute_equal3 x, y
+ refute_operator x, :===, y
end
- def setup
- @any = ANY()
+ def assert_pretty_print expect, input
+ assert_equal expect, input.pretty_inspect.chomp
end
-end
-class TestSexp < SexpTestCase # ZenTest FULL
+ def assert_inspect expect, input
+ assert_equal expect, input.inspect
+ end
- class SexpFor
- def method
- 1
- end
+ def assert_search count, sexp, pattern
+ assert_equal count, sexp.search_each(pattern).count
+ end
+
+ def assert_satisfy pattern, sexp
+ assert_operator pattern, :satisfy?, sexp
+ end
+
+ def refute_satisfy pattern, sexp
+ refute_operator pattern, :satisfy?, sexp
+ end
+end # class SexpTestCase
+
+class MatcherTestCase < SexpTestCase
+ def self.abstract_test_case! klass = self # REFACTOR: push this up to minitest
+ extend Module.new {
+ define_method :run do |*args|
+ super(*args) unless self == klass
+ end
+ }
+ end
+
+ abstract_test_case!
+
+ def matcher
+ raise "Subclass responsibility"
+ end
+
+ def inspect_str
+ raise "Subclass responsibility"
+ end
+
+ def pretty_str
+ inspect_str
+ end
+
+ def sexp
+ s(:a)
+ end
+
+ def bad_sexp
+ s(:b)
+ end
+
+ def test_satisfy_eh
+ assert_equal3 matcher, sexp
+ end
+
+ def test_satisfy_eh_fail
+ skip "not applicable" unless bad_sexp
+ refute_equal3 matcher, bad_sexp
+ end
+
+ def test_greedy
+ refute_operator matcher, :greedy?
end
- def util_pretty_print(expect, input)
- io = StringIO.new
- PP.pp(input, io)
- io.rewind
- assert_equal(expect, io.read.chomp)
+ def test_inspect
+ assert_inspect inspect_str, matcher
end
+ def test_pretty_print
+ assert_pretty_print pretty_str, matcher
+ end
+end # class MatcherTestCase
+
+class TestSexp < SexpTestCase # ZenTest FULL
def setup
super
@sexp_class = Object.const_get(self.class.name[4..-1])
- @processor = SexpProcessor.new
@sexp = @sexp_class.new(1, 2, 3)
@basic_sexp = s(:lasgn, :var, s(:lit, 42).line(1)).line(1)
+ @basic_sexp.each_sexp do |s|
+ s.file = "file.rb"
+ end
+
@complex_sexp = s(:block,
s(:lasgn, :foo, s(:str, "foo").line(1)).line(1),
s(:if, s(:and, s(:true).line(2), s(:lit, 1).line(2)).line(2),
@@ -76,38 +155,50 @@ class TestSexp < SexpTestCase # ZenTest FULL
@bad1 = s(:blah, 42)
end
+ def assert_from_array exp, input
+ assert_equal exp, Sexp.from_array(input)
+ end
+
def test_class_from_array
- skip 'Need to write test_class_from_array'
+ assert_from_array s(), []
+ assert_from_array s(:s), [:s]
+ assert_from_array s(:s, s(:m)), [:s, [:m]]
+ assert_from_array s(:s, s(:m)), [:s, s(:m)]
+ assert_from_array s(:s, s(:m, [:not_converted])), [:s, s(:m, [:not_converted])]
end
- def test_class_index
- skip 'Need to write test_class_index'
+ def test_compact
+ input = s(:a, nil, :b)
+
+ actual = input.compact
+
+ assert_equal s(:a, :b), actual
+ assert_same input, actual # returns mutated result
end
def test_array_type_eh
- assert_equal false, @sexp.array_type?
- @sexp.unshift :array
- assert_equal true, @sexp.array_type?
+ capture_io do # HACK
+ assert_equal false, s(:lit, 42).array_type?
+ assert_equal true, s(:array, 42).array_type?
+ end
end
def test_each_of_type
# TODO: huh... this tests fails if top level sexp :b is removed
@sexp = s(:b, s(:a, s(:b, s(:a), :a, s(:b, :a), s(:b, s(:a)))))
count = 0
- @sexp.each_of_type(:a) do |exp|
+ @sexp.each_of_type :a do
count += 1
end
assert_equal(3, count, "must find 3 a's in #{@sexp.inspect}")
end
def test_equals2_array
- # can't use assert_equals because it uses array as receiver
- refute_equal(@sexp, [1, 2, 3],
- "Sexp must not be equal to equivalent array")
- # both directions just in case
- # HACK - this seems to be a bug in ruby as far as I'm concerned
- # assert_not_equal([1, 2, 3], @sexp,
- # "Sexp must not be equal to equivalent array")
+ refute_equal @sexp, [1, 2, 3] # Sexp == Array
+ assert_raises Minitest::Assertion do # Array == Sexp.
+ refute_equal [1, 2, 3], @sexp # This is a bug in ruby:
+ end
+ # TODO: test if it is calling to_ary first? seems not to
end
def test_equals2_not_body
@@ -124,81 +215,107 @@ class TestSexp < SexpTestCase # ZenTest FULL
end
end
- def test_equals3_any
- util_equals3 @any, s()
- util_equals3 @any, s(:a)
- util_equals3 @any, s(:a, :b, s(:c))
+ def test_equal3_full_match
+ assert_equal3 s(), s() # 0
+ assert_equal3 s(:blah), s(:blah) # 1
+ assert_equal3 s(:a, :b), s(:a, :b) # 2
+ assert_equal3 @basic_sexp, @basic_sexp.dup # deeper structure
end
- def test_equals3_full_match
- util_equals3 s(), s() # 0
- util_equals3 s(:blah), s(:blah) # 1
- util_equals3 s(:a, :b), s(:a, :b) # 2
- util_equals3 @basic_sexp, @basic_sexp.dup # deeper structure
+ def test_equal3_mismatch
+ refute_equal3 s(), s(:a)
+ refute_equal3 s(:a), s()
+ refute_equal3 s(:blah1), s(:blah2)
+ refute_equal3 s(:a), s(:a, :b)
+ refute_equal3 s(:a, :b), s(:a)
+ refute_equal3 s(:a1, :b), s(:a2, :b)
+ refute_equal3 s(:a, :b1), s(:a, :b2)
end
- def test_equals3_mismatch
- assert_nil s() === s(:a)
- assert_nil s(:a) === s()
- assert_nil s(:blah1) === s(:blah2)
- assert_nil s(:a) === s(:a, :b)
- assert_nil s(:a, :b) === s(:a)
- assert_nil s(:a1, :b) === s(:a2, :b)
- assert_nil s(:a, :b1) === s(:a, :b2)
- assert_nil @basic_sexp === @basic_sexp.dup.push(42)
- assert_nil @basic_sexp.dup.push(42) === @basic_sexp
+ def test_equal3_subset_match
+ assert_match s{s(:a)}, s(s(:a), s(:b)) # left - =~
+ assert_equal3 s{s(:a)}, s(s(:a), s(:b)) # left - ===
+ assert_equal3 s{s(:a)}, s(:blah, s(:a ), s(:b)) # mid 1
+ assert_equal3 s{s(:a, 1)}, s(:blah, s(:a, 1), s(:b)) # mid 2
+ assert_equal3 s{s(:a)}, s(:blah, s(:blah, s(:a))) # left deeper
end
- def test_equals3_subset_match
- util_equals3 s(:a), s(s(:a), s(:b)) # left
- util_equals3 s(:a), s(:blah, s(:a ), s(:b)) # mid 1
- util_equals3 s(:a, 1), s(:blah, s(:a, 1), s(:b)) # mid 2
- util_equals3 @basic_sexp, s(:blah, @basic_sexp.dup, s(:b)) # mid deeper
- util_equals3 @basic_sexp, s(@basic_sexp.dup, s(:a), s(:b)) # left deeper
-
- util_equals3 s(:a), s(:blah, s(:blah, s(:a))) # left deeper
- end
+ def test_equalstilde_fancy
+ assert_match s{ s(:b) }, s(:a, s(:b), :c)
+ assert_match s(:a, s(:b), :c), s{ s(:b) }
-# def test_equalstilde_any
-# result = @basic_sexp =~ s(:lit, ANY())
-# p result
-# assert result
-# end
+ e = assert_raises ArgumentError do
+ s(:b) =~ s(:a, s(:b), :c)
+ end
+ assert_equal "Not a pattern: s(:a, s(:b), :c)", e.message
- def test_equalstilde_fancy
- assert_nil s(:b) =~ s(:a, s(:b), :c)
- refute_nil s(:a, s(:b), :c) =~ s(:b)
+ e = assert_raises ArgumentError do
+ s(:a, s(:b), :c) =~ s(:b)
+ end
+ assert_equal "Not a pattern: s(:b)", e.message
end
def test_equalstilde_plain
- result = @basic_sexp =~ @re
- assert result
+ s{ s(:re) } =~ s(:data) # pattern on LHS
+ s(:data) =~ s{ s(:re) } # pattern on RHS
+
+ e = assert_raises ArgumentError do
+ s(:re) =~ s(:data) # no pattern
+ end
+
+ assert_equal "Not a pattern: s(:data)", e.message
end
def test_find_and_replace_all
- @sexp = s(:a, s(:b, s(:a), s(:b), s(:b, s(:a))))
- expected = s(:a, s(:a, s(:a), s(:a), s(:a, s(:a))))
+ skip_if_strict 2
+
+ @sexp = s(:a, s(:a, :b, s(:a, :b), s(:a), :b, s(:a, s(:a))))
+ expected = s(:a, s(:a, :a, s(:a, :a), s(:a), :a, s(:a, s(:a))))
@sexp.find_and_replace_all(:b, :a)
assert_equal(expected, @sexp)
end
+ def assert_gsub exp, sexp, from, to
+ assert_equal exp, sexp.gsub(from, to)
+ end
+
def test_gsub
- assert_equal s(:c), s(:b). gsub(s(:b), s(:c))
- assert_equal s(:a), s(:a). gsub(s(:b), s(:c))
- assert_equal s(:a, s(:c)), s(:a, s(:b)).gsub(s(:b), s(:c))
+ assert_gsub s(:c), s(:b), s(:b), s(:c)
+ assert_gsub s(:a), s(:a), s(:b), s(:c)
+ assert_gsub s(:a, s(:c)), s(:a, s(:b)), s(:b), s(:c)
end
def test_gsub_empty
- assert_equal s(:c), s().gsub(s(), s(:c))
+ assert_gsub s(:c), s(), s(), s(:c)
end
def test_gsub_multiple
- assert_equal(s(:a, s(:c), s(:c)),
- s(:a, s(:b), s(:b)). gsub(s(:b), s(:c)))
- assert_equal(s(:a, s(:c), s(:a, s(:c))),
- s(:a, s(:b), s(:a, s(:b))). gsub(s(:b), s(:c)))
+ assert_gsub s(:a, s(:c), s(:c)), s(:a, s(:b), s(:b)), s(:b), s(:c)
+ assert_gsub s(:a, s(:c), s(:a, s(:c))), s(:a, s(:b), s(:a, s(:b))), s(:b), s(:c)
+ end
+
+ def test_gsub_matcher
+ assert_gsub s(:a, :b, :c), s(:a, s(:b, 42), :c), s{ s(:b, _) }, :b
+ assert_gsub s(:a, s(:b), :c), s(:a, s(:b), :c), s{ s(:b, _) }, :b
+ assert_gsub s(:a, s(:c, :b), :d), s(:a, s(:c, s(:b, 42)), :d), s{ s(:b, _) }, :b
+ assert_gsub s(:a, s(:q), :c), s(:a, s(:q), :c), s{ s(:b, _) }, :b
+ end
+
+ def with_env key
+ old_val, ENV[key] = ENV[key], "1"
+ yield
+ ensure
+ ENV[key] = old_val
+ end
+
+ def with_verbose &block
+ with_env "VERBOSE", &block
+ end
+
+ def with_debug &block
+ with_env "DEBUG", &block
end
def test_inspect
@@ -212,6 +329,17 @@ class TestSexp < SexpTestCase # ZenTest FULL
k.new(:a, :b).inspect)
assert_equal("#{n}(:a, #{n}(:b))",
k.new(:a, k.new(:b)).inspect)
+
+ with_verbose do
+ assert_equal("#{n}().line(42)",
+ k.new().line(42).inspect)
+ assert_equal("#{n}(:a).line(42)",
+ k.new(:a).line(42).inspect)
+ assert_equal("#{n}(:a, :b).line(42)",
+ k.new(:a, :b).line(42).inspect)
+ assert_equal("#{n}(:a, #{n}(:b).line(43)).line(42)",
+ k.new(:a, k.new(:b).line(43)).line(42).inspect)
+ end
end
def test_line
@@ -226,6 +354,54 @@ class TestSexp < SexpTestCase # ZenTest FULL
assert_equal 5, @complex_sexp.line_max
end
+ def test_new
+ file = "file.rb"
+
+ old = s(:lasgn, :var, s(:lit, 42).line(2)).line(1)
+ old.file = file
+ old.each_sexp do |x|
+ x.file = file
+ end
+ old.comments = "do the thing"
+
+ assert_same file, old.file
+ assert_equal 1, old.line
+ assert_same file, old.last.file
+ assert_equal 2, old.last.line
+
+ new = old.new(1, 2, 3)
+
+ assert_equal s(1, 2, 3), new
+
+ assert_same file, new.file
+ assert_equal 1, new.line
+ assert_same old.comments, new.comments
+ end
+
+ def test_map
+ file = "file.rb"
+
+ old = s(:lasgn, :var, s(:lit, 42).line(2)).line(1)
+ old.file = file
+ old.each_sexp do |x|
+ x.file = file
+ end
+ old.comments = "do the thing"
+
+ assert_same file, old.file
+ assert_equal 1, old.line
+ assert_same file, old.last.file
+ assert_equal 2, old.last.line
+
+ new = old.map { |x| x }
+
+ assert_same file, new.file
+ assert_equal 1, new.line
+ assert_same file, new.last.file
+ assert_equal 2, new.last.line
+ assert_same old.comments, new.comments
+ end
+
def test_mass
assert_equal 1, s(:a).mass
assert_equal 3, s(:a, s(:b), s(:c)).mass
@@ -259,11 +435,52 @@ class TestSexp < SexpTestCase # ZenTest FULL
end
def test_method_missing
- assert_nil @sexp.not_there
- assert_equal s(:lit, 42), @basic_sexp.lit
+ skip_if_strict 3
+
+ capture_io do
+ assert_nil @sexp.not_there
+ assert_equal s(:lit, 42), @basic_sexp.lit
+ end
+ end
+
+ def test_method_missing_missing
+ skip_if_strict 3
+ skip "debugging for now" if ENV["DEBUG"]
+
+ assert_silent do
+ assert_nil @basic_sexp.missing
+ end
+ end
+
+ def test_method_missing_missing_debug
+ skip_if_strict 3
+
+ exp = /#{Regexp.escape @basic_sexp.to_s}.method_missing\(:missing\) => nil from/
+
+ with_debug do
+ assert_output "", exp do
+ assert_nil @basic_sexp.missing
+ end
+ end
+ end
+
+ def test_method_missing_hit_debug_verbose
+ skip_if_strict 3
+
+ with_debug do
+ with_verbose do
+ exp = /#{Regexp.escape @basic_sexp.to_s}.method_missing\(:lit\) from/
+
+ assert_output "", exp do
+ assert_equal s(:lit, 42), @basic_sexp.lit
+ end
+ end
+ end
end
def test_method_missing_ambigious
+ skip_if_strict 3
+
assert_raises NoMethodError do
pirate = s(:says, s(:arrr!), s(:arrr!), s(:arrr!))
pirate.arrr!
@@ -271,26 +488,34 @@ class TestSexp < SexpTestCase # ZenTest FULL
end
def test_method_missing_deep
- sexp = s(:blah, s(:a, s(:b, s(:c, :yay!))))
- assert_equal(s(:c, :yay!), sexp.a.b.c)
+ skip_if_strict 3
+
+ capture_io do
+ sexp = s(:blah, s(:a, s(:b, s(:c, :yay!))))
+ assert_equal(s(:c, :yay!), sexp.a.b.c)
+ end
end
def test_method_missing_delete
+ skip_if_strict 3
+
sexp = s(:blah, s(:a, s(:b, s(:c, :yay!))))
- assert_equal(s(:c, :yay!), sexp.a.b.c(true))
- assert_equal(s(:blah, s(:a, s(:b))), sexp)
+ capture_io do
+ assert_equal(s(:c, :yay!), sexp.a.b.c(true))
+ assert_equal(s(:blah, s(:a, s(:b))), sexp)
+ end
end
def test_pretty_print
- util_pretty_print("s()",
- s())
- util_pretty_print("s(:a)",
- s(:a))
- util_pretty_print("s(:a, :b)",
- s(:a, :b))
- util_pretty_print("s(:a, s(:b))",
- s(:a, s(:b)))
+ assert_pretty_print("s()",
+ s())
+ assert_pretty_print("s(:a)",
+ s(:a))
+ assert_pretty_print("s(:a, :b)",
+ s(:a, :b))
+ assert_pretty_print("s(:a, s(:b))",
+ s(:a, s(:b)))
end
def test_sexp_body
@@ -298,6 +523,8 @@ class TestSexp < SexpTestCase # ZenTest FULL
end
def test_shift
+ skip_if_strict 5
+
skip "https://github.com/MagLev/maglev/issues/250" if maglev?
assert_equal(1, @sexp.shift)
@@ -327,6 +554,17 @@ class TestSexp < SexpTestCase # ZenTest FULL
assert_equal(backup, @sexp)
end
+ def test_structure_deprecated
+ exp_err = "NOTE: form s(s(:subsexp)).structure is deprecated. Removing in 5.0\n"
+
+ assert_output "", exp_err do
+ sexp = s(s(:subsexp))
+ act = sexp.structure
+
+ assert_equal s(:bogus, s(:subsexp)), act
+ end
+ end
+
def test_sub
assert_equal s(:c), s(:b). sub(s(:b), s(:c))
assert_equal s(:a, s(:c), s(:b)), s(:a, s(:b), s(:b)). sub(s(:b), s(:c))
@@ -342,8 +580,39 @@ class TestSexp < SexpTestCase # ZenTest FULL
assert_equal s(:c), s(). sub(s(), s(:c))
end
+ def assert_sub exp, sexp, from, to
+ assert_equal exp, sexp.sub(from, to)
+ end
+
+ def test_sub_matcher
+ assert_sub s(:c), s(:b), s{ s(:b) }, s(:c)
+ assert_sub s(:a, s(:c), s(:b)), s(:a, s(:b), s(:b)), s{ s(:b) }, s(:c)
+ assert_sub s(:a, s(:c), s(:a)), s(:a, s(:b), s(:a)), s{ s(:b) }, s(:c)
+
+ assert_sub s(:a, :b, :c), s(:a, s(:b, 42), :c), s{ s(:b, _) }, :b
+ assert_sub s(:a, s(:b), :c), s(:a, s(:b), :c), s{ s(:b, _) }, :b
+ assert_sub s(:a, s(:c, :b), :d), s(:a, s(:c, s(:b, 42)), :d), s{ s(:b, _) }, :b
+ assert_sub s(:a, s(:q), :c), s(:a, s(:q), :c), s{ s(:b, _) }, :b
+ end
+
def test_sub_structure
- assert_equal(s(:a, s(:c, s(:b))), s(:a, s(:b)). sub(s(:b), s(:c, s(:b))))
+ assert_sub s(:a, s(:c, s(:b))), s(:a, s(:b)), s(:b), s(:c, s(:b))
+ end
+
+ def test_sexp_type_eq
+ sexp = s(:bad_type, 42)
+
+ sexp.sexp_type = :good_type
+
+ assert_equal s(:good_type, 42), sexp
+ end
+
+ def test_sexp_body_eq
+ sexp = s(:type, 42)
+
+ sexp.sexp_body = [1, 2, 3]
+
+ assert_equal s(:type, 1, 2, 3), sexp
end
def test_to_a
@@ -374,7 +643,7 @@ class TestSexp < SexpTestCase # ZenTest FULL
def test_deep_each
result = []
- @complex_sexp.deep_each { |s| result << s if s.first == :if }
+ @complex_sexp.deep_each { |s| result << s if s.sexp_type == :if }
assert_equal [:if, :if], result.map { |k, _| k }
end
@@ -382,28 +651,896 @@ class TestSexp < SexpTestCase # ZenTest FULL
assert_kind_of Enumerator, @complex_sexp.deep_each
assert_equal [:if, :if], @complex_sexp.deep_each.select { |s, _| s == :if }.map { |k, _| k }
end
+
+ def test_unary_not
+ skip "TODO?"
+ assert_equal M::Not.new(M.q(:a)), s{ !s(:a) }
+ end
+
+ def test_unary_not_outside
+ skip "TODO?"
+ assert_equal M::Not.new(s(:a)), !s(:a)
+ end
+end # TestSexp
+
+class TestSexpMatcher < SexpTestCase
+ def test_cls_s
+ assert_equal M.s(:x), s{ s(:x) }
+ end
+
+ def test_cls_underscore
+ assert_equal M::Wild.new, s{ _ }
+ end
+
+ def test_cls_underscore3
+ assert_equal M::Remaining.new, s{ ___ }
+ end
+
+ def test_cls_include
+ assert_equal M::Include.new(:a), s{ include(:a) }
+ end
+
+ def test_cls_atom
+ assert_equal M::Atom.new, s{ atom }
+ end
+
+ def test_cls_any
+ assert_equal M::Any.new(M.s(:a), M.s(:b)), s{ any(s(:a), s(:b)) }
+ end
+
+ def test_cls_all
+ assert_equal M::All.new(M.s(:a), M.s(:b)), s{ all(s(:a), s(:b)) }
+ end
+
+ def test_cls_not_eh
+ assert_equal M::Not.new(M.s(:a)), s{ not?(s(:a)) }
+ end
+
+ def test_cls_child
+ assert_equal M::Child.new(M.s(:a)), s{ child(s(:a)) }
+ end
+
+ def test_cls_t
+ assert_equal M::Type.new(:a), s{ t(:a) }
+ end
+
+ def test_cls_m
+ assert_equal M::Pattern.new(/a/), s{ m(/a/) }
+ assert_equal M::Pattern.new(/\Aa\Z/), s{ m(:a) }
+ assert_equal M::Pattern.new(/test_\w/), s{ m(/test_\w/) }
+ re = Regexp.union [/\w/, /\d/]
+ assert_equal M::Pattern.new(re), s{ m(/\w/,/\d/) }
+ end
+
+ def test_amp
+ m = s{ s(:a) & s(:b) }
+ e = M::All.new(M.s(:a), M.s(:b))
+
+ assert_equal e, m
+ end
+
+ def test_pipe
+ m = s{ s(:a) | s(:b) }
+ e = M::Any.new(M.s(:a), M.s(:b))
+
+ assert_equal e, m
+ end
+
+ def test_unary_minus
+ assert_equal M::Not.new(M.s(:a)), s{ -s(:a) }
+ end
+
+ def test_rchevron
+ assert_equal M::Sibling.new(M.s(:a), M.s(:b)), s{ s(:a) >> s(:b) }
+ end
+
+ def test_greedy_eh
+ refute_operator s{ s(:a) }, :greedy?
+ end
+
+ def test_inspect
+ assert_inspect "q(:a)", s{ s(:a) }
+ end
+
+ def test_pretty_print
+ assert_pretty_print "q(:a)", s{ s(:a) }
+ end
+end # class TestSexpMatcher
+
+class TestWild < MatcherTestCase
+ def matcher
+ s{ _ }
+ end
+
+ def bad_sexp
+ nil
+ end
+
+ def inspect_str
+ "_"
+ end
+
+ def test_wild_satisfy_eh # TODO: possibly remove
+ w = Sexp::Wild.new
+
+ assert_satisfy w, :a
+ assert_satisfy w, 1
+ assert_satisfy w, nil
+ assert_satisfy w, []
+ assert_satisfy w, s()
+ end
+
+ def test_wild_search # TODO: possibly remove
+ sexp = CLASS_SEXP.dup
+
+ assert_search 1, s(:add, :a, :b), s{ s(:add, _, :b) }
+ assert_search 1, sexp, s{ s(:defn, :bar, _, _) }
+ assert_search 2, sexp, s{ s(:defn, _, _, s(_, :a, :b) ) }
+ assert_search 1, s(:a, s()), s{ s(:a, _) }
+ assert_search 1, s(:a, :b, :c), s{ s(_, _, _) }
+ assert_search 7, sexp, s{ _ }
+ end
+end
+
+class TestRemaining < MatcherTestCase
+ def matcher
+ s{ ___ }
+ end
+
+ def bad_sexp
+ nil
+ end
+
+ def inspect_str
+ "___"
+ end
+
+ def test_remaining_satisfy_eh # TODO: possibly remove
+ assert_satisfy s{ ___ }, s(:a)
+ assert_satisfy s{ ___ }, s(:a, :b, :c)
+ assert_satisfy s{ s(:x, ___ ) }, s(:x, :y)
+ refute_satisfy s{ s(:y, ___ ) }, s(:x, :y)
+ end
+
+ def test_greedy
+ assert_operator matcher, :greedy?
+ end
+end
+
+class TestAny < MatcherTestCase
+ def matcher
+ s{ s(:a) | s(:c) }
+ end
+
+ def inspect_str
+ "q(:a) | q(:c)"
+ end
+
+ def pretty_str
+ "any(q(:a), q(:c))"
+ end
+
+ def test_any_search # TODO: possibly remove
+ assert_search 2, s(:foo, s(:a), s(:b)), s{ s(any(:a, :b)) }
+ assert_search 1, s(:foo, s(:a), s(:b)), s{ any( s(:a), s(:c)) }
+ end
+
+ def test_or_satisfy_eh # TODO: possibly remove
+ assert_satisfy s{ s(:a) | s(:b) }, s(:a)
+ refute_satisfy s{ s(:a) | s(:b) }, s(:c)
+ end
+
+ def test_or_search # TODO: possibly remove
+ sexp = CLASS_SEXP.dup
+
+ assert_search 2, s(:a, s(:b, :c), s(:b, :d)), s{ s(:b, :c) | s(:b, :d) }
+ assert_search 2, sexp, s{ s(:add, :a, :b) | s(:defn, :bar, _, _) }
+ end
+end
+
+class TestAll < MatcherTestCase
+ def matcher
+ s{ s(:a) & s(:a) }
+ end
+
+ def inspect_str
+ "q(:a) & q(:a)"
+ end
+
+ def pretty_str
+ "all(q(:a), q(:a))"
+ end
+
+ def test_and_satisfy_eh # TODO: possibly remove
+ refute_satisfy s{ s(:a) & s(:b) }, s(:a)
+ assert_satisfy s{ s(:a) & s(atom) }, s(:a)
+ end
+end
+
+class TestNot < MatcherTestCase
+ def matcher
+ s{ not? s(:b) } # TODO: test unary minus
+ end
+
+ def inspect_str
+ "not?(q(:b))" # TODO: change?
+ end
+
+ def pretty_str
+ "not?(q(:b))" # TODO: change?
+ end
+
+ def test_not_satisfy_eh # TODO: possibly remove
+ refute_satisfy s{ -_ }, s(:a)
+ assert_satisfy s{ -s(:b) }, s(:a)
+ assert_satisfy s{ not?(s(:b)) }, s(:a)
+ refute_satisfy s{ -s(atom) }, s(:a)
+ assert_satisfy s{ s(not?(:b)) }, s(:a)
+ end
+end
+
+class TestChild < MatcherTestCase
+ def matcher
+ s{ child(s(:a)) }
+ end
+
+ def sexp
+ s(:x, s(:a))
+ end
+
+ def bad_sexp
+ s(:x, s(:b))
+ end
+
+ def inspect_str
+ "child(q(:a))"
+ end
+
+ def test_child_search # TODO: possibly remove
+ sexp = CLASS_SEXP.dup
+
+ assert_search 1, sexp, s{ s(:class, :cake, _, _, child( s(:sub, :a, :b) ) ) }
+ assert_search 1, sexp, s{ s(:class, :cake, _, _, child(include(:a))) }
+ end
+
+ def test_satisfy_eh_by_child
+ assert_satisfy matcher, s(:a)
+ end
+end
+
+class TestAtom < MatcherTestCase
+ def matcher
+ s{ atom }
+ end
+
+ def sexp
+ 42
+ end
+
+ def bad_sexp
+ s(:a)
+ end
+
+ def inspect_str
+ "atom"
+ end
+
+ def test_atom_satisfy_eh # TODO: possibly remove
+ a = s{ atom }
+
+ assert_satisfy a, :a
+ assert_satisfy a, 1
+ assert_satisfy a, nil
+ refute_satisfy a, s()
+ end
+
+ def test_atom_search # TODO: possibly remove
+ sexp = CLASS_SEXP.dup
+
+ assert_search 1, s(:add, :a, :b), s{ s(:add, atom, :b) }
+ assert_search 2, sexp, s{ s(:defn, atom, _, s(atom, :a, :b) ) }
+ assert_search 0, s(:a, s()), s{ s(:a, atom) }
+ end
+end
+
+class TestPattern < MatcherTestCase
+ def matcher
+ s{ s(:a, m(/a/)) }
+ end
+
+ def sexp
+ s(:a, :blah)
+ end
+
+ def inspect_str
+ "q(:a, m(/a/))"
+ end
+
+ def test_pattern_satisfy_eh # TODO: possibly remove
+ assert_satisfy s{ m(/a/) }, :a
+ assert_satisfy s{ m(/^test/) }, :test_case
+ assert_satisfy s{ m("test") }, :test
+ refute_satisfy s{ m("test") }, :test_case
+ refute_satisfy s{ m(/a/) }, s(:a)
+ end
+
+ def test_pattern_search # TODO: possibly remove
+ sexp = CLASS_SEXP.dup
+
+ assert_search 2, sexp, s{ s(m(/\w{3}/), :a, :b) }
+
+ assert_search 0, s(:a), s{ m(/\w/) }
+ assert_search 1, s(:a), s{ s(m(/\w/)) }
+ assert_search 0, s(:a), s{ m(/\w/,/\d/) }
+ assert_search 1, s(:a), s{ s(m(/\w/,/\d/)) }
+
+ assert_search 0, s(:tests, s(s(:test_a), s(:test_b))), s{ m(/test_\w/) }
+ assert_search 2, s(:tests, s(s(:test_a), s(:test_b))), s{ s(m(/test_\w/)) }
+ end
+end
+
+class TestType < MatcherTestCase
+ def matcher
+ s{ t(:a) }
+ end
+
+ def test_type_satisfy_eh # TODO: possibly remove
+ assert_satisfy s{ t(:a) }, s(:a)
+ assert_satisfy s{ t(:a) }, s(:a, :b, s(:oh_hai), :d)
+ end
+
+ def test_type_search
+ assert_search 2, CLASS_SEXP.dup, s{ t(:defn) }
+ end
+
+ def inspect_str
+ "t(:a)"
+ end
+end
+
+class TestInclude < MatcherTestCase
+ def sexp
+ s(:x, s(:a))
+ end
+
+ def matcher
+ s{ include(s(:a)) }
+ end
+
+ def inspect_str
+ "include(q(:a))"
+ end
+
+ def test_include_search # TODO: possibly remove
+ sexp = CLASS_SEXP.dup
+
+ assert_search 1, s(:add, :a, :b), s{ include(:a) }
+ assert_search 1, sexp, s{ include(:bar) }
+ assert_search 2, sexp, s{ s(:defn, atom, _, include(:a)) }
+ assert_search 2, sexp, s{ include(:a) }
+ assert_search 0, s(:a, s(:b, s(:c))), s{ s(:a, include(:c)) }
+ end
end
-class TestSexpAny < SexpTestCase
+class TestSibling < MatcherTestCase
+ def sexp
+ s(:x, s(:a), s(:x), s(:b))
+ end
+
+ def matcher
+ s{ s(:a) >> s(:b) }
+ end
+
+ def inspect_str
+ "q(:a) >> q(:b)"
+ end
+
+ def test_pretty_print_distance
+ m = s{ M::Sibling.new(s(:a), s(:b), 3) } # maybe s(:a) << s(:b) << 3 ?
+ assert_pretty_print "sibling(q(:a), q(:b), 3)", m
+ end
+
+ def test_sibling_satisfy_eh # TODO: possibly remove
+ a_a = s{ s(:a) >> s(:a) }
+ a_b = s{ s(:a) >> s(:b) }
+ a_c = s{ s(:a) >> s(:c) }
+ c_a = s{ s(:c) >> s(:a) }
+
+ assert_satisfy a_b, s(s(:a), s(:b))
+ assert_satisfy a_b, s(s(:a), s(:b), s(:c))
+ assert_satisfy a_c, s(s(:a), s(:b), s(:c))
+ refute_satisfy c_a, s(s(:a), s(:b), s(:c))
+ refute_satisfy a_a, s(s(:a))
+ assert_satisfy a_a, s(s(:a), s(:b), s(:a))
+ end
+
+ def test_sibling_search # TODO: possibly remove
+ sexp = CLASS_SEXP.dup
+
+ assert_search 1, sexp, s{ t(:defn) >> t(:defn) }
+ end
+end
+
+class TestMatchCollection < SexpTestCase
+ attr_accessor :sexp, :pat, :act
def setup
- super
+ self.sexp = s(:a, :b, :c)
+ self.pat = s{ _ }
+ self.act = sexp / pat
+ end
+
+ def test_slash
+ self.sexp =
+ s(:class, :cake, nil,
+ s(:defn, :foo, s(:args), s(:add, :a, :b)),
+ s(:defn, :bar, s(:args), s(:sub, :a, :b)))
+
+ res = sexp / s{ s(:class, atom, _, ___) } # sexp / pat => MC
+ act = res / s{ s(:defn, atom, ___) } # MC / pat => MC
+
+ _, _, _, defn1, defn2 = sexp
+
+ exp = MC.new
+ exp << defn1.deep_clone
+ exp << defn2.deep_clone
+
+ assert_equal exp, act
+ end
+
+ def test_sanity
+ act = sexp / pat
+ exp = MC.new << sexp
+
+ assert_equal exp, act
+ end
+
+ STR = "MatchCollection.new(s(:a, :b, :c))"
+
+ def test_to_s
+ assert_equal STR, act.to_s
+ end
+
+ def test_inspect
+ assert_inspect STR, act
+ end
+
+ def test_pretty_print
+ assert_pretty_print STR, act
+ end
+end
+
+class TestSexpSearch < SexpTestCase
+ attr_accessor :sexp
+
+ make_my_diffs_pretty!
+
+ def setup
+ self.sexp = CLASS_SEXP.dup
+ end
+
+ def coll *args
+ exp = MC.new
+
+ args.each_slice 2 do |sexp, hash|
+ exp << res(sexp, hash)
+ end
+
+ exp
+ end
+
+ def res sexp, hash
+ MR.new sexp.deep_clone, hash
+ end
+
+ def test_slash_simple
+ act = sexp / s{ s(:class, atom, _, ___) }
+
+ exp = MC.new
+ exp << sexp.deep_clone
+
+ assert_equal exp, act
+ end
+
+ def test_slash_subsexp
+ act = sexp / s{ s(:defn, atom, ___) }
+
+ exp = MC.new
+ exp << s(:defn, :foo, s(:args), s(:add, :a, :b))
+ exp << s(:defn, :bar, s(:args), s(:sub, :a, :b))
+
+ assert_equal exp, act
+ end
+
+ def test_slash_data
+ pat = s{ s(:defn, m(/^test_.+/), ___ ) }
+
+ _, _, (_klass, _, _, _setup, t1, t2, t3) = TestUseCase.sexp.deep_clone
+ exp = [t1, t2, t3]
+
+ assert_equal exp, TestUseCase.sexp.deep_clone / pat
+ end
+
+ def test_search_each_no_block
+ assert_kind_of Enumerator, sexp.search_each(s{_})
+ assert_equal 7, sexp.search_each(s{_}).count
+ assert_equal 2, sexp.search_each(s{t(:defn)}).count
+ assert_search 7, sexp, s{_}
+ assert_search 2, sexp, s{t(:defn)}
+
+ _, _, _, defn1, defn2 = sexp
+
+ mc = []
+ mc << defn1.deep_clone
+ mc << defn2.deep_clone
+
+ assert_equal mc, sexp.search_each(s{t(:defn)}).map { |x| x }
+ end
+
+ def test_searching_simple_examples # TODO: possibly remove
+ assert_raises ArgumentError do
+ assert_search 0, sexp, :class # non-pattern should raise
+ end
+
+ assert_search 0, sexp, s{ s(:class) }
+ assert_search 1, sexp, s{ s(:add, :a, :b) }
+ assert_search 1, s(:a, s(:b, s(:c))), s{ s(:b, s(:c)) }
+ assert_search 0, s(:a, s(:b, s(:c))), s{ s(:a, s(:c)) }
+ assert_search 1, sexp, s{ s(:defn, :bar, _, s(:sub, :a, :b)) }
end
- def test_equals
- util_equals @any, s()
- util_equals @any, s(:a)
- util_equals @any, s(:a, :b, s(:c))
+ def test_satisfy_eh_any_capture # TODO: remove
+ sexp = s(:add, :a, :b)
+ assert_satisfy s{ any(s(:add, :a, :b), s(:sub, :a, :b)) }, sexp
+
+ assert_satisfy s{ any(s(atom, :a, :b), s(:sub, :a, :b)) }, sexp
+ end
+
+ def test_satisfy_eh_all_capture # TODO: remove
+ sexp = s(:add, :a, :b)
+ assert_satisfy s{ all(s(_, :a, :b), s(atom, :a, :b)) }, sexp
+
+ assert_satisfy s{ all(s(_, :a, :b), s(atom, :a, :b)) }, sexp
+
+ assert_search 1, sexp, s{ all(s(_, :a, :b), s(atom, :a, :b)) }
+ end
+end
+
+class TestSexpPath < Minitest::Test
+ def test_global_s_block
+ sexp = s(:a, :b, :c) # s called outside block
+
+ assert_instance_of Sexp, s{ sexp.deep_clone }
+ assert_instance_of Sexp::Matcher, s{ s(:a, :b, :c) }
+ assert_instance_of Sexp::Matcher, s{ s(:a, atom, :c) }
+ end
+end
+
+class TestSexpReplaceSexp < SexpTestCase
+ def test_replace_sexp
+ sexp = s(:a, s(:b), :c)
+ actual = sexp.replace_sexp(s{ s(:b) }) { :b }
+
+ assert_equal s(:a, :b, :c), actual
+ end
+
+ def test_replace_sexp_root
+ sexp = s(:a, s(:b), :c)
+ actual = sexp.replace_sexp(s{ t(:a) }) { s(:new) }
+
+ assert_equal s(:new), actual
+ end
+
+ def test_replace_sexp_yields_match_result
+ sexp = s(:a, s(:b), :c)
+
+ exp = sexp.deep_clone
+
+ sexp.replace_sexp(s{ t(:a) }) { |x|
+ assert_equal exp, x
+ }
end
- def test_equals3
- util_equals3 @any, s()
- util_equals3 @any, s(:a)
- util_equals3 @any, s(:a, :b, s(:c))
+ def test_replace_sexp_non_matcher
+ e = assert_raises ArgumentError do
+ s(:a, s(:b), :c).replace_sexp(42) { :b }
+ end
+
+ assert_equal "Needs a pattern", e.message
end
+ def test_search_each_yields_match_result
+ sexp = s(:a, s(:b), :c)
+
+ exp = sexp.deep_clone
+
+ sexp.search_each(s{ t(:a) }) { |x|
+ assert_equal exp, x
+ }
+ end
+
+ def test_search_each_no_pattern
+ e = assert_raises ArgumentError do
+ s(:a, s(:b), :c).search_each(42) { :b }
+ end
+
+ assert_equal "Needs a pattern", e.message
+ end
end
+# Here's a crazy idea, these tests actually use sexp_path on some "real"
+# code to see if it can satisfy my requirements.
+#
+# These tests are two fold:
+# 1. Make sure it works
+# 2. Make sure it's not painful to use
+
+class TestUseCase < SexpTestCase
+ @@sexp = eval File.read(__FILE__).split(/^__END__/).last
+
+ def self.sexp
+ @@sexp
+ end
+
+ def setup
+ @sexp = @@sexp.deep_clone
+ end
+
+ def test_finding_methods
+ methods = @sexp / s{ t(:defn) }
+ assert_equal 5, methods.length
+ end
+
+ def test_finding_classes_and_methods
+ res = @sexp / s{ s(:class, atom, ___ ) }
+
+ _klass, name, * = res.first
+
+ assert_equal 1, res.length
+ assert_equal :ExampleTest, name
+
+ methods = res / s{ t(:defn) }
+ assert_equal 5, methods.length
+ end
+
+ def test_finding_empty_test_methods
+ empty_test = s{ s(:defn, m(/^test_.+/), s(:args), s(:nil)) }
+ res = @sexp / empty_test
+
+ _, _, (_klass, _, _, _setup, _t1, t2, _t3) = TestUseCase.sexp.deep_clone
+
+ assert_equal [t2], res
+ end
+
+ def test_search_each_finding_duplicate_test_names
+ pat = s{ s(:defn, m(/^test_.+/), ___ ) }
+ counts = Hash.new { |h, k| h[k] = 0 }
+
+ @sexp.search_each pat do |x|
+ _, name, * = x
+ counts[name] += 1
+ end
+
+ assert_equal 1, counts[:test_b], "Should have seen test_b once"
+ assert_equal 2, counts[:test_a], "Should have caught test_a being repeated"
+ end
+
+ def test_finding_duplicate_test_names_via_res
+ pat = s{ s(:defn, m(/^test_.+/), ___ ) }
+ res = @sexp / pat
+ counts = Hash.new { |h, k| h[k] = 0 }
+
+ _, _, (_klass, _, _, _setup, t1, t2, t3) = TestUseCase.sexp.deep_clone
+ exp = [t1, t2, t3]
+
+ assert_equal exp, res
+
+ res.each do |m|
+ _, name, * = m
+ counts[name] += 1
+ end
+
+ assert_equal 1, counts[:test_b], "Should have seen test_b once"
+ assert_equal 2, counts[:test_a], "Should have caught test_a being repeated"
+ end
+
+ def test_rewriting_colon2s
+ colon2 = s{ s(:colon2, s(:const, atom), atom) }
+ expected = s{ s(:const, "Minitest::Test") }
+
+ new_sexp = @sexp.replace_sexp(colon2) { |r|
+ (_, (_, a), b) = r
+ s(:const, "%s::%s" % [a, b])
+ }
+
+ assert_search 1, new_sexp, expected
+ assert_search 0, @sexp, expected
+ end
+end
+
+##
+# NOTE: this entire class is now redundant, but it illustrates usage
+# and edge cases well.
+
+class TestSexpMatchers < SexpTestCase
+ CLASS_LIT = s(:class, :X, nil,
+ s(:lasgn, :x, s(:lit, 42)),
+ s(:cdecl, :Y,
+ s(:hash, s(:lit, :a), s(:lit, 1), s(:lit, :b), s(:lit, 2))))
+
+ SEXP = s(:class, :X, nil, s(:defn, :x, s(:args)))
+
+ def test_match_subset
+ assert_match s{ child(s(:a)) }, s(:blah, s(:blah, s(:a)))
+ assert_match s{ child(s(:a)) }, s(:a)
+ end
+
+ def test_match_simple
+ assert_match s{ s(:lit, _) }, s(:lit, 42)
+ end
+
+ def test_match_mismatch_type
+ refute_match s{ s(:xxx, 42) }, s(:lit, 42)
+ end
+
+ def test_match_mismatch_data
+ refute_match s{ s(:lit, 24) }, s(:lit, 42)
+ end
+
+ def test_match_mismatch_length_shorter
+ refute_match s{ s(:a, :b) }, s(:a, :b, :c)
+ end
+
+ def test_match_mismatch_length_longer
+ refute_match s{ s(:a, :b, :c) }, s(:a, :b)
+ end
+
+ def test_match_wild
+ assert_match s{ s(:class, _, _, _) }, SEXP
+ end
+
+ def test_match_rest_same_length
+ assert_match s{ s(:class, _, _, ___) }, SEXP
+ end
+
+ def test_match_rest_diff_length
+ skip_if_strict
+
+ assert_match s{ s(:class, ___) }, SEXP
+ end
+
+ def test_match_reversed
+ assert_match SEXP, s{ s(:class, _, _, ___) }
+ end
+
+ def assert_match_case pat, data
+ case data
+ when pat then
+ assert true
+ else
+ flunk "Expected %p to match %p" % [pat, data]
+ end
+ end
+
+ def test_match_case
+ assert_match_case s{ s(:class, _, _, ___) }, SEXP
+ end
+
+ # NOTE: eqt is =~ (equal-tilde)
+
+ # cmt = create_match_test
+ def self.cmt e1, e2, e3, e4, lit, pat
+ Class.new SexpTestCase do
+ attr_accessor :lit, :pat
+
+ define_method :setup do
+ self.lit = lit
+ self.pat = pat
+ end
+
+ define_method :test_match_lit_eqt_pat do
+ skip_if_strict
+
+ if e1 then
+ assert_match lit, pat
+ else
+ refute_match lit, pat
+ end
+ end
+
+ define_method :test_match_pat_eqt_lit do
+ skip_if_strict
+
+ if e2 then
+ assert_match pat, lit
+ else
+ refute_match pat, lit
+ end
+ end
+
+ define_method :test_match_lit_eq3_pat do
+ if e3 then
+ assert_equal3 lit, pat
+ else
+ refute_equal3 lit, pat
+ end
+ end
+
+ define_method :test_match_pat_eq3_lit do
+ if e4 then
+ assert_equal3 pat, lit
+ else
+ refute_equal3 pat, lit
+ end
+ end
+ end
+ end
+
+ l_a = s(:a)
+ l_abc = s(:a, s(:b, s(:c)))
+ l_cls = s(:class, :X, nil,
+ s(:something_in_between),
+ s(:cdecl, :Y, s(:hash, s(:lit, :a), s(:lit, 1))))
+ p_cls1 = s{ s(:class, ___) & include(s(:cdecl, _, s(:hash, ___))) }
+ p_cls2 = s{ s(:class, _, _, s(:cdecl, _, s(:hash, ___))) }
+
+ x, o = true, false
+ TestMatcherDirectMatch = cmt x, x, o, x, l_a, s{ s(:a) }
+ TestMatcherSubtree = cmt x, x, o, x, l_abc, s{ s(:c) }
+ TestMatcherSubtreeType = cmt x, x, o, x, l_abc, s{ t(:c) }
+ TestMatcherDisparateSubtree = cmt x, x, o, x, l_cls, p_cls1
+ TestMatcherDisparateSubtree2 = cmt o, o, o, o, l_cls, p_cls2 # TODO: make pass
+end
+
+class TestSexpMatcherParser < Minitest::Test
+ def assert_parse exp, str
+ act = Sexp::Matcher.parse str
+
+ if exp.nil? then
+ assert_nil act
+ else
+ assert_equal exp, act
+ end
+ end
+
+ def self.test_parse name, exp_lambda, str
+ define_method "test_parse_#{name}" do
+ exp = exp_lambda && exp_lambda.call
+ assert_parse exp, str
+ end
+ end
+
+ def self.test_bad_parse name, str
+ define_method "test_parse_bad_#{name}" do
+ assert_raises SyntaxError do
+ assert_parse :whatever, str
+ end
+ end
+ end
+
+ def self.delay &b
+ lambda { s(&b) }
+ end
+
+ test_parse "nothing", nil, ""
+ test_parse "nil", delay{ nil }, "nil"
+ test_parse "empty", delay{ s() }, "()"
+ test_parse "simple", delay{ s(:a) }, "(a)"
+ test_parse "number", delay{ s(:a, 42) }, "(a 42)"
+ test_parse "string", delay{ s(:a, "s") }, "(a \"s\")"
+ test_parse "compound", delay{ s(:b) }, "(a) (b)"
+ test_parse "complex", delay{ s(:a, _, s(:b, :cde), ___) }, "(a _ (b cde) ___)"
+ test_parse "type", delay{ s(:a, t(:b)) }, "(a [t b])"
+ test_parse "match", delay{ s(:a, m(/b/)) }, "(a [m /b/])"
+ test_parse "not_atom", delay{ s(:atom) }, "(atom)"
+ test_parse "atom", delay{ atom }, "[atom]"
+
+ test_bad_parse "open_sexp", "(a"
+ test_bad_parse "closed_sexp", "a)"
+ test_bad_parse "open_cmd", "[a"
+ test_bad_parse "closed_cmd", "a]"
+end # class TestSexpMatcherParser
+
class BenchSexp < Minitest::Benchmark
def run
GC.disable
@@ -430,3 +1567,64 @@ class BenchSexp < Minitest::Benchmark
end
end
end if ENV["BENCH"]
+
+# class ExampleTest < Minitest::Test
+# def setup
+# 1 + 2
+# end
+#
+# def test_a
+# assert_equal 1+2, 4
+# end
+#
+# def test_b
+# # assert 1+1
+# end
+#
+# def test_a
+# assert_equal 1+2, 3
+# end
+#
+# private
+#
+# def helper_method apples, oranges, cakes = nil
+# [apples, oranges, cakes].compact.map { |food| food.to_s.upcase }
+# end
+# end
+
+__END__
+s(:block,
+ s(:call, nil, :require, s(:str, "minitest/autorun")),
+ s(:class,
+ :ExampleTest,
+ s(:colon2, s(:const, :Minitest), :Test),
+ s(:defn, :setup, s(:args), s(:call, s(:lit, 1), :+, s(:lit, 2))),
+ s(:defn,
+ :test_a,
+ s(:args),
+ s(:call,
+ nil,
+ :assert_equal,
+ s(:call, s(:lit, 1), :+, s(:lit, 2)),
+ s(:lit, 4))),
+ s(:defn, :test_b, s(:args), s(:nil)),
+ s(:defn,
+ :test_a,
+ s(:args),
+ s(:call,
+ nil,
+ :assert_equal,
+ s(:call, s(:lit, 1), :+, s(:lit, 2)),
+ s(:lit, 3))),
+ s(:call, nil, :private),
+ s(:defn,
+ :helper_method,
+ s(:args, :apples, :oranges, s(:lasgn, :cakes, s(:nil))),
+ s(:iter,
+ s(:call,
+ s(:call,
+ s(:array, s(:lvar, :apples), s(:lvar, :oranges), s(:lvar, :cakes)),
+ :compact),
+ :map),
+ s(:args, :food),
+ s(:call, s(:call, s(:lvar, :food), :to_s), :upcase)))))
diff --git a/sexp_processor-4.9.0/test/test_sexp_processor.rb b/sexp_processor-4.10.0/test/test_sexp_processor.rb
index 092a72a..b3055ca 100755
--- a/sexp_processor-4.9.0/test/test_sexp_processor.rb
+++ b/sexp_processor-4.10.0/test/test_sexp_processor.rb
@@ -1,78 +1,86 @@
$TESTING = true
-require 'sexp_processor'
-require 'stringio'
-require 'minitest/autorun'
-require 'pp'
+if ENV["COV"]
+ require "simplecov"
+ SimpleCov.start do
+ add_filter "lib/sexp.rb"
+ add_filter "test"
+ end
+ warn "Running simplecov"
+end
+
+require "sexp_processor"
+require "stringio"
+require "minitest/autorun"
+require "pp"
# Fake test classes:
class TestProcessor < SexpProcessor # ZenTest SKIP
attr_accessor :auto_shift_type
- def process_acc1(exp)
+ def initialize
+ super
+ self.require_empty = false
+ self.auto_shift_type = false
+ end
+
+ def process_acc1 exp
out = self.expected.new(:acc2, exp.thing_three, exp.thing_two, exp.thing_one)
exp.clear
- return out
+ out
end
- def process_acc2(exp)
- out = []
+ def process_acc2 exp
+ out = s()
out << exp.thing_one
end
- def process_specific(exp)
- _ = exp.shift
- result = s(:blah)
- until exp.empty?
- result.push process(exp.shift)
- end
- result
+ def process_specific exp
+ _, *data = exp
+ s(:blah, *data.map { |x| process x })
end
- def process_strip(exp)
- result = exp.deep_clone
- exp.clear
- result
+ def process_strip exp
+ exp.deep_clone
end
- def process_nonempty(exp)
+ def process_nonempty exp
s(*exp)
end
- def process_broken(exp)
+ def process_broken exp
result = [*exp]
exp.clear
result
end
- def process_expected(exp)
+ def process_expected exp
exp.clear
- return {}
+ {}
end
- def process_string(exp)
- return exp.shift
+ def process_string exp
+ exp.sexp_type
end
- def rewrite_rewritable(exp) # (a b c) => (a c b)
- return s(exp.shift, exp.pop, exp.shift)
+ def rewrite_rewritable exp # (a b c) => (a c b)
+ a, b, c = exp
+ s(a, c, b)
end
- def process_rewritable(exp)
+ def process_rewritable exp
+ _, *data = exp
+
@n ||= 0
- exp.shift # name
- result = s(:rewritten)
- until exp.empty?
- result.push process(exp.shift)
- end
- result.push @n
+ result = s(:rewritten, *data.map { |x| process x }, @n)
@n += 1
+
result
end
- def rewrite_major_rewrite(exp)
- exp[0] = :rewritable
+ def rewrite_major_rewrite exp
+ exp.sexp_type = :rewritable
exp
end
end
@@ -83,7 +91,7 @@ class TestProcessorDefault < SexpProcessor # ZenTest SKIP
self.default_method = :def_method
end
- def def_method(exp)
+ def def_method exp
exp.clear
self.expected.new(42)
end
@@ -98,13 +106,13 @@ class TestSexpProcessor < Minitest::Test
end
def test_process_specific
- a = [:specific, [:x, 1], [:y, 2], [:z, 3]]
- expected = [:blah, [:x, 1], [:y, 2], [:z, 3]]
+ a = s(:specific, s(:x, 1), s(:y, 2), s(:z, 3))
+ expected = s(:blah, s(:x, 1), s(:y, 2), s(:z, 3))
assert_equal(expected, @processor.process(a))
end
def test_process_generic
- a = [:blah, 1, 2, 3]
+ a = s(:blah, 1, 2, 3)
expected = a.deep_clone
assert_equal(expected, @processor.process(a))
end
@@ -122,7 +130,7 @@ class TestSexpProcessor < Minitest::Test
@processor.warn_on_default = false
assert_raises SexpTypeError do
- @processor.process([:broken, 1, 2, 3])
+ @processor.process(s(:broken, 1, 2, 3))
end
end
@@ -131,7 +139,7 @@ class TestSexpProcessor < Minitest::Test
@processor.unsupported << :strip
assert_raises UnsupportedNodeError do
- @processor.process([:whatever])
+ @processor.process(s(:whatever))
end
end
@@ -139,29 +147,32 @@ class TestSexpProcessor < Minitest::Test
@processor.strict = true
@processor.unsupported = [ :unsupported ]
assert_raises UnsupportedNodeError do
- @processor.process([:unsupported, 42])
+ @processor.process(s(:unsupported, 42))
end
end
def test_strict
@processor.strict = true
assert_raises UnknownNodeError do
- @processor.process([:blah, 1, 2, 3])
+ @processor.process(s(:blah, 1, 2, 3))
end
end
- def test_strict=; skip; end #Handled
+
+ def test_strict=; skip; end # handled
def test_require_empty_false
@processor.require_empty = false
- assert_equal s(:nonempty, 1, 2, 3), @processor.process([:nonempty, 1, 2, 3])
+ assert_equal s(:nonempty, 1, 2, 3), @processor.process(s(:nonempty, 1, 2, 3))
end
def test_require_empty_true
assert_raises NotEmptyError do
- @processor.process([:nonempty, 1, 2, 3])
+ @processor.require_empty = true
+ @processor.process(s(:nonempty, 1, 2, 3))
end
end
+
def test_require_empty=; skip; end # handled
def test_process_strip
@@ -189,7 +200,6 @@ class TestSexpProcessor < Minitest::Test
expect = s(:rewritable, 2, 1)
result = @processor.rewrite(insert)
assert_equal(expect, result)
- assert_equal(s(2), insert) # post-processing
end
def test_process_rewrite
@@ -256,6 +266,7 @@ class TestSexpProcessor < Minitest::Test
@processor.auto_shift_type = true
assert_equal(true, @processor.auto_shift_type)
end
+
def test_auto_shift_type_equal; skip; end # handled
def test_default_method
@@ -264,12 +275,13 @@ class TestSexpProcessor < Minitest::Test
@processor.default_method = :something
assert_equal :something, @processor.default_method
end
+
def test_default_method=; skip; end # handled
def test_expected
assert_equal Sexp, @processor.expected
assert_raises SexpTypeError do
- @processor.process([:expected]) # should raise
+ @processor.process(s(:expected)) # should raise
end
@processor.process(s(:str, "string")) # shouldn't raise
@@ -282,8 +294,9 @@ class TestSexpProcessor < Minitest::Test
@processor.process(s(:string, "string")) # should raise
end
- @processor.process([:expected]) # shouldn't raise
+ @processor.process(s(:expected)) # shouldn't raise
end
+
def test_expected=; skip; end # handled
# Not Testing:
@@ -323,7 +336,7 @@ class TestMethodBasedSexpProcessor < Minitest::Test
assert_empty processor.method_stack
- expected = {"main#xxx" => "file.rb:42"}
+ expected = { "main#xxx" => "file.rb:42" }
assert_equal expected, processor.method_locations
end
@@ -336,7 +349,7 @@ class TestMethodBasedSexpProcessor < Minitest::Test
assert_empty processor.method_stack
- expected = {"main#xxx" => "file.rb:42-44"}
+ expected = { "main#xxx" => "file.rb:42-44" }
assert_equal expected, processor.method_locations
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment