From 74f4aed4bf22477deb3f71e0f2704d62e2f95460 Mon Sep 17 00:00:00 2001 From: Owen Stephens Date: Sun, 25 Mar 2018 13:37:12 +0100 Subject: [PATCH v4] range.c: allow cover? to accept Range argument --- range.c | 50 +++++++++++++++++++++++++++++++++++++---- test/ruby/test_range.rb | 30 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/range.c b/range.c index bcc8d1a8f4..08ba3eecfc 100644 --- a/range.c +++ b/range.c @@ -1326,10 +1326,12 @@ range_include_internal(VALUE range, VALUE val) return Qundef; } +static VALUE r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val); /* * call-seq: - * rng.cover?(obj) -> true or false + * rng.cover?(obj) -> true or false + * rng.cover?(range) -> true or false * * Returns true if +obj+ is between the begin and end of * the range. @@ -1337,11 +1339,20 @@ range_include_internal(VALUE range, VALUE val) * This tests begin <= obj <= end when #exclude_end? is +false+ * and begin <= obj < end when #exclude_end? is +true+. * - * ("a".."z").cover?("c") #=> true - * ("a".."z").cover?("5") #=> false - * ("a".."z").cover?("cc") #=> true + * Returns true for a Range when it is covered by the receiver, + * by comparing the begin and end values directly. This requires the end + * object of the receiver to implement succ if the end of +range+ + * is greater than the end of the receiver and +range+ excludes its end, but + * the receiver does not. + * + * ("a".."z").cover?("c") #=> true + * ("a".."z").cover?("5") #=> false + * ("a".."z").cover?("cc") #=> true + * ("a".."z").cover?("b".."c") #=> true + * ("a".."z").cover?("0".."z") #=> false */ + static VALUE range_cover(VALUE range, VALUE val) { @@ -1349,9 +1360,40 @@ range_cover(VALUE range, VALUE val) beg = RANGE_BEG(range); end = RANGE_END(range); + + if (rb_obj_is_kind_of(val, rb_cRange)) { + return RBOOL(r_cover_range_p(range, beg, end, val)); + } return r_cover_p(range, beg, end, val); } +static VALUE +r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val) +{ + VALUE val_beg, val_end; + int cmp_end; + + val_beg = RANGE_BEG(val); + val_end = RANGE_END(val); + + if (!r_cover_p(range, beg, end, val_beg)) return FALSE; + + cmp_end = r_less(end, val_end); + + if (EXCL(range) == EXCL(val)) { + return cmp_end >= 0; + } else if (EXCL(range)) { + return cmp_end > 0; + } else if (cmp_end >= 0) { + return TRUE; + } else if (!discrete_object_p(end)) { + rb_raise(rb_eTypeError, "can't iterate from %s", + rb_obj_classname(end)); + } + + return r_less(rb_funcallv(end, id_succ, 0, 0), val_end) == 0; +} + static VALUE r_cover_p(VALUE range, VALUE beg, VALUE end, VALUE val) { diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index eada19cfb6..d5689a4326 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -521,6 +521,36 @@ def test_cover assert_not_operator(5..., :cover?, 0) assert_not_operator(5..., :cover?, "a") assert_operator(5.., :cover?, 10) + + assert_operator(2..5, :cover?, 2..5) + assert_operator(2...6, :cover?, 2...6) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..5, :cover?, 2...6) + assert_operator(2..5, :cover?, 2..4) + assert_operator(2..5, :cover?, 2...4) + assert_operator(2..5, :cover?, 2...5) + assert_operator(2..5, :cover?, 3..5) + assert_operator(2..5, :cover?, 3..4) + assert_operator(2..5, :cover?, 3...6) + assert_operator(2...6, :cover?, 2...5) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..6, :cover?, 2...6) + assert_operator(2.., :cover?, 2..) + assert_operator(2.., :cover?, 3..) + assert_operator(1.0..4.0, :cover?, 2.0...3.0) + + assert_not_operator(2..5, :cover?, 1..5) + assert_not_operator(2...6, :cover?, 1..5) + assert_not_operator(2..5, :cover?, 1...6) + assert_not_operator(1..3, :cover?, 1...6) + assert_not_operator(2..5, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2...7) + assert_not_operator(2..3, :cover?, 1..4) + assert_not_operator(1..2, :cover?, 4..3) + assert_not_operator(2..1, :cover?, 1..2) + assert_not_operator(1...2, :cover?, 1...3) + assert_not_operator(2.., :cover?, 1..) end def test_beg_len -- 2.18.0