Created
October 2, 2017 15:15
-
-
Save jrafanie/ebdbbec2802eec082d4f3e4aadef77d6 to your computer and use it in GitHub Desktop.
Diff of sexp_processor 4.9.0 vs 4.10.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', '[email protected]' | |
+ | |
+ 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