From 43da661882e9194c8979f4e4e74ee1bb44cc394f Mon Sep 17 00:00:00 2001 From: Owen Stephens Date: Sun, 25 Mar 2018 13:37:12 +0100 Subject: [PATCH v3] range.c: add subrange/superrange methods Add the following methods: Range#subrange? Range#strict_subrange? Range#superrange? Range#strict_superrange? which compare two Range objects, checking that the min/max elements are covered by the other Range (the strict_ methods additionally check that the two ranges have different min or max values). --- range.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++- test/ruby/test_range.rb | 80 +++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) diff --git a/range.c b/range.c index 828231f33d..b47ababfe1 100644 --- a/range.c +++ b/range.c @@ -18,7 +18,7 @@ #include VALUE rb_cRange; -static ID id_beg, id_end, id_excl, id_integer_p, id_div; +static ID id_beg, id_end, id_excl, id_integer_p, id_div, id_min, id_max; #define id_cmp idCmp #define id_succ idSucc @@ -1202,6 +1202,119 @@ r_cover_p(VALUE range, VALUE beg, VALUE end, VALUE val) } static VALUE +r_cover_min_and_max_p(VALUE range, VALUE obj, VALUE (*func)(VALUE, VALUE, VALUE)) +{ + if (!rb_obj_is_kind_of(obj, rb_cRange)) + rb_raise(rb_eTypeError, "not a range object"); + + VALUE range_min = rb_funcall(range, id_min, 0); + + if (range_min == Qnil) + return Qfalse; + + VALUE range_max = rb_funcall(range, id_max, 0); + + if (range_cover(obj, range_min) && + range_cover(obj, range_max) && + (!func || (*func)(obj, range_min, range_max))) { + return Qtrue; + } + return Qfalse; +} + +/* + * call-seq: + * rng.subrange?(obj) -> true or false + * + * Returns true if +obj+ is a Range and it covers the min and max + * elements of the range, false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."d").subrange?("b".."e") #=> true + * ("b".."e").subrange?("b".."e") #=> true + * ("a".."e").subrange?("b".."e") #=> false + */ + +static VALUE +range_subrange(VALUE range, VALUE obj) +{ + return r_cover_min_and_max_p(range, obj, NULL); +} + +#define DIFFER_AT(recv, mid, val) (!rb_equal(rb_funcall(recv, mid, 0), val)) + +static VALUE +r_min_or_max_differ(VALUE obj, VALUE min, VALUE max) +{ + return DIFFER_AT(obj, id_min, min) || DIFFER_AT(obj, id_max, max); +} + +/* + * call-seq: + * rng.strict_subrange?(obj) -> true or false + * + * Returns true if +obj+ is a Range and it covers the min and max + * elements of the range and the ranges differ in their min or max elements, + * false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."d").strict_subrange?("b".."e") #=> true + * ("b".."e").strict_subrange?("b".."e") #=> false + * ("a".."e").strict_subrange?("b".."e") #=> false + */ + +static VALUE +range_strict_subrange(VALUE range, VALUE obj) +{ + return r_cover_min_and_max_p(range, obj, &r_min_or_max_differ); +} + +/* + * call-seq: + * rng.superrange?(obj) -> true or false + * + * Returns true if +obj+ is a Range and its min and max elements + * are covered by the range, false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."e").superrange?("b".."d") #=> true + * ("b".."e").superrange?("b".."e") #=> true + * ("b".."e").superrange?("a".."e") #=> false + */ + +static VALUE +range_superrange(VALUE range, VALUE obj) +{ + if (!rb_obj_is_kind_of(obj, rb_cRange)) + rb_raise(rb_eTypeError, "not a range object"); + + return range_subrange(obj, range); +} + +/* + * call-seq: + * rng.strict_superrange?(obj) -> true or false + * + * Returns true if +obj+ is a Range and its min and max elements + * are covered by the range and the ranges differ in their min or max + * elements, false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."e").strict_superrange?("b".."d") #=> true + * ("b".."e").strict_superrange?("b".."e") #=> false + * ("b".."e").strict_superrange?("a".."e") #=> false + */ + +static VALUE +range_strict_superrange(VALUE range, VALUE obj) +{ + if (!rb_obj_is_kind_of(obj, rb_cRange)) + rb_raise(rb_eTypeError, "not a range object"); + + return range_strict_subrange(obj, range); +} + +static VALUE range_dumper(VALUE range) { VALUE v; @@ -1310,6 +1423,8 @@ Init_Range(void) id_excl = rb_intern("excl"); id_integer_p = rb_intern("integer?"); id_div = rb_intern("div"); + id_min = rb_intern("min"); + id_max = rb_intern("max"); rb_cRange = rb_struct_define_without_accessor( "Range", rb_cObject, range_alloc, @@ -1341,4 +1456,9 @@ Init_Range(void) rb_define_method(rb_cRange, "member?", range_include, 1); rb_define_method(rb_cRange, "include?", range_include, 1); rb_define_method(rb_cRange, "cover?", range_cover, 1); + + rb_define_method(rb_cRange, "subrange?", range_subrange, 1); + rb_define_method(rb_cRange, "strict_subrange?", range_strict_subrange, 1); + rb_define_method(rb_cRange, "superrange?", range_superrange, 1); + rb_define_method(rb_cRange, "strict_superrange?", range_strict_superrange, 1); } diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index fef7acc062..02c9741776 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -379,6 +379,86 @@ def test_cover assert_operator("a".."z", :cover?, "cc") end + def test_subrange + assert_operator("b".."e", :subrange?, "b".."e") + assert_operator("b".."e", :subrange?, "b"..."f") + assert_operator("b"..."f", :subrange?, "b".."e") + assert_operator("b".."d", :subrange?, "b".."e") + assert_operator("c".."e", :subrange?, "b".."e") + assert_operator("c".."d", :subrange?, "b".."e") + + assert_not_operator("a".."e", :subrange?, "b".."e") + assert_not_operator("a".."e", :subrange?, "b"..."f") + assert_not_operator("a"..."f", :subrange?, "b".."e") + assert_not_operator("b".."f", :subrange?, "b".."e") + assert_not_operator("a".."f", :subrange?, "b".."e") + assert_not_operator("g".."f", :subrange?, "b".."e") + assert_not_operator("b".."c", :subrange?, "d".."a") + assert_not_operator("c".."b", :subrange?, "d".."a") + + assert_raise(TypeError) { ("a".."c").subrange?("x") } + end + + def test_strict_subrange + assert_operator("b".."d", :strict_subrange?, "b".."e") + assert_operator("b".."d", :strict_subrange?, "b"..."f") + assert_operator("b"..."e", :strict_subrange?, "b".."e") + + assert_not_operator("b".."e", :strict_subrange?, "b".."e") + assert_not_operator("b".."e", :strict_subrange?, "b"..."f") + assert_not_operator("b"..."f", :strict_subrange?, "b".."e") + assert_not_operator("a".."e", :strict_subrange?, "b".."e") + assert_not_operator("b".."f", :strict_subrange?, "b".."e") + assert_not_operator("a".."f", :strict_subrange?, "b".."e") + assert_not_operator("g".."f", :strict_subrange?, "b".."e") + assert_not_operator("b".."c", :strict_subrange?, "d".."a") + assert_not_operator("c".."b", :strict_subrange?, "d".."a") + + assert_raise(TypeError) { ("a".."c").strict_subrange?("x") } + end + + def test_superrange + assert_operator("b".."e", :superrange?, "b".."e") + assert_operator("b".."e", :superrange?, "b"..."f") + assert_operator("b"..."f", :superrange?, "b".."e") + assert_operator("b".."e", :superrange?, "b".."d") + assert_operator("b".."e", :superrange?, "c".."e") + assert_operator("b".."e", :superrange?, "c".."d") + + assert_not_operator("b".."e", :superrange?, "a".."e") + assert_not_operator("b".."e", :superrange?, "a"..."f") + assert_not_operator("b"..."f", :superrange?, "a".."e") + assert_not_operator("b".."e", :superrange?, "b".."f") + assert_not_operator("b".."e", :superrange?, "a".."f") + assert_not_operator("b".."e", :superrange?, "b".."f") + assert_not_operator("b".."e", :superrange?, "a".."f") + assert_not_operator("b".."e", :subrange?, "g".."f") + assert_not_operator("d".."a", :subrange?, "b".."c") + assert_not_operator("d".."a", :subrange?, "c".."b") + + assert_raise(TypeError) { ("a".."c").superrange?("x") } + end + + def test_strict_superrange + assert_operator("b".."e", :strict_superrange?, "b".."d") + assert_operator("b".."e", :strict_superrange?, "b"..."e") + assert_operator("b"..."f", :strict_superrange?, "b".."d") + assert_operator("b".."e", :strict_superrange?, "c".."e") + assert_operator("b".."e", :strict_superrange?, "c".."d") + + assert_not_operator("b".."e", :strict_superrange?, "b".."e") + assert_not_operator("b".."e", :strict_superrange?, "b"..."f") + assert_not_operator("b"..."f", :strict_superrange?, "b".."e") + assert_not_operator("b".."e", :strict_superrange?, "a".."e") + assert_not_operator("b".."e", :strict_superrange?, "b".."f") + assert_not_operator("b".."e", :strict_superrange?, "a".."f") + assert_not_operator("b".."e", :strict_subrange?, "g".."f") + assert_not_operator("d".."a", :strict_subrange?, "b".."c") + assert_not_operator("d".."a", :strict_subrange?, "c".."b") + + assert_raise(TypeError) { ("a".."c").strict_superrange?("x") } + end + def test_beg_len o = Object.new assert_raise(TypeError) { [][o] } -- 2.15.0