summaryrefslogtreecommitdiff
path: root/range.c
diff options
context:
space:
mode:
Diffstat (limited to 'range.c')
-rw-r--r--range.c589
1 files changed, 448 insertions, 141 deletions
diff --git a/range.c b/range.c
index 39786ed97c..a6bf0fca51 100644
--- a/range.c
+++ b/range.c
@@ -78,7 +78,7 @@ range_modify(VALUE range)
rb_check_frozen(range);
/* Ranges are immutable, so that they should be initialized only once. */
if (RANGE_EXCL(range) != Qnil) {
- rb_name_err_raise("`initialize' called twice", range, ID2SYM(idInitialize));
+ rb_name_err_raise("'initialize' called twice", range, ID2SYM(idInitialize));
}
}
@@ -224,8 +224,8 @@ recursive_eql(VALUE range, VALUE obj, int recur)
* Returns +true+ if and only if:
*
* - +other+ is a range.
- * - <tt>other.begin eql? self.begin</tt>.
- * - <tt>other.end eql? self.end</tt>.
+ * - <tt>other.begin.eql?(self.begin)</tt>.
+ * - <tt>other.end.eql?(self.end)</tt>.
* - <tt>other.exclude_end? == self.exclude_end?</tt>.
*
* Otherwise returns +false+.
@@ -532,7 +532,11 @@ range_step(int argc, VALUE *argv, VALUE range)
rb_raise(rb_eTypeError, "can't iterate from %s",
rb_obj_classname(b));
}
- range_each_func(range, step_i, (VALUE)iter);
+ if (!NIL_P(e))
+ range_each_func(range, step_i, (VALUE)iter);
+ else
+ for (;; b = rb_funcallv(b, id_succ, 0, 0))
+ step_i(b, (VALUE)iter);
}
}
return range;
@@ -603,11 +607,15 @@ double_as_int64(double d)
static int
is_integer_p(VALUE v)
{
+ if (rb_integer_type_p(v)) {
+ return true;
+ }
+
ID id_integer_p;
VALUE is_int;
CONST_ID(id_integer_p, "integer?");
is_int = rb_check_funcall(v, id_integer_p, 0, 0);
- return RTEST(is_int) && is_int != Qundef;
+ return RTEST(is_int) && !UNDEF_P(is_int);
}
static VALUE
@@ -645,27 +653,30 @@ bsearch_integer_range(VALUE beg, VALUE end, int excl)
VALUE low = rb_to_int(beg);
VALUE high = rb_to_int(end);
- VALUE mid, org_high;
+ VALUE mid;
ID id_div;
CONST_ID(id_div, "div");
- if (excl) high = rb_funcall(high, '-', 1, INT2FIX(1));
- org_high = high;
+ if (!excl) high = rb_funcall(high, '+', 1, INT2FIX(1));
+ low = rb_funcall(low, '-', 1, INT2FIX(1));
- while (rb_cmpint(rb_funcall(low, id_cmp, 1, high), low, high) < 0) {
- mid = rb_funcall(rb_funcall(high, '+', 1, low), id_div, 1, INT2FIX(2));
+ /*
+ * This loop must continue while low + 1 < high.
+ * Instead of checking low + 1 < high, check low < mid, where mid = (low + high) / 2.
+ * This is to avoid the cost of calculating low + 1 on each iteration.
+ * Note that this condition replacement is valid because Integer#div always rounds
+ * towards negative infinity.
+ */
+ while (mid = rb_funcall(rb_funcall(high, '+', 1, low), id_div, 1, INT2FIX(2)),
+ rb_cmpint(rb_funcall(low, id_cmp, 1, mid), low, mid) < 0) {
BSEARCH_CHECK(mid);
if (smaller) {
high = mid;
}
else {
- low = rb_funcall(mid, '+', 1, INT2FIX(1));
+ low = mid;
}
}
- if (rb_equal(low, org_high)) {
- BSEARCH_CHECK(low);
- if (!smaller) return Qnil;
- }
return satisfied;
}
@@ -692,52 +703,58 @@ range_bsearch(VALUE range)
* by the mantissa. This is true with or without implicit bit.
*
* Finding the average of two ints needs to be careful about
- * potential overflow (since float to long can use 64 bits)
- * as well as the fact that -1/2 can be 0 or -1 in C89.
+ * potential overflow (since float to long can use 64 bits).
+ *
+ * The half-open interval (low, high] indicates where the target is located.
+ * The loop continues until low and high are adjacent.
+ *
+ * -1/2 can be either 0 or -1 in C89. However, when low and high are not adjacent,
+ * the rounding direction of mid = (low + high) / 2 does not affect the result of
+ * the binary search.
*
* Note that -0.0 is mapped to the same int as 0.0 as we don't want
* (-1...0.0).bsearch to yield -0.0.
*/
-#define BSEARCH(conv) \
+#define BSEARCH(conv, excl) \
do { \
RETURN_ENUMERATOR(range, 0, 0); \
- if (EXCL(range)) high--; \
- org_high = high; \
- while (low < high) { \
+ if (!(excl)) high++; \
+ low--; \
+ while (low + 1 < high) { \
mid = ((high < 0) == (low < 0)) ? low + ((high - low) / 2) \
- : (low < -high) ? -((-1 - low - high)/2 + 1) : (low + high) / 2; \
+ : (low + high) / 2; \
BSEARCH_CHECK(conv(mid)); \
if (smaller) { \
high = mid; \
} \
else { \
- low = mid + 1; \
+ low = mid; \
} \
} \
- if (low == org_high) { \
- BSEARCH_CHECK(conv(low)); \
- if (!smaller) return Qnil; \
- } \
return satisfied; \
} while (0)
+#define BSEARCH_FIXNUM(beg, end, excl) \
+ do { \
+ long low = FIX2LONG(beg); \
+ long high = FIX2LONG(end); \
+ long mid; \
+ BSEARCH(INT2FIX, (excl)); \
+ } while (0)
beg = RANGE_BEG(range);
end = RANGE_END(range);
if (FIXNUM_P(beg) && FIXNUM_P(end)) {
- long low = FIX2LONG(beg);
- long high = FIX2LONG(end);
- long mid, org_high;
- BSEARCH(INT2FIX);
+ BSEARCH_FIXNUM(beg, end, EXCL(range));
}
#if SIZEOF_DOUBLE == 8 && defined(HAVE_INT64_T)
else if (RB_FLOAT_TYPE_P(beg) || RB_FLOAT_TYPE_P(end)) {
int64_t low = double_as_int64(NIL_P(beg) ? -HUGE_VAL : RFLOAT_VALUE(rb_Float(beg)));
int64_t high = double_as_int64(NIL_P(end) ? HUGE_VAL : RFLOAT_VALUE(rb_Float(end)));
- int64_t mid, org_high;
- BSEARCH(int64_as_double_to_num);
+ int64_t mid;
+ BSEARCH(int64_as_double_to_num, EXCL(range));
}
#endif
else if (is_integer_p(beg) && is_integer_p(end)) {
@@ -751,9 +768,15 @@ range_bsearch(VALUE range)
VALUE mid = rb_funcall(beg, '+', 1, diff);
BSEARCH_CHECK(mid);
if (smaller) {
- return bsearch_integer_range(beg, mid, 0);
+ if (FIXNUM_P(beg) && FIXNUM_P(mid)) {
+ BSEARCH_FIXNUM(beg, mid, false);
+ }
+ else {
+ return bsearch_integer_range(beg, mid, false);
+ }
}
diff = rb_funcall(diff, '*', 1, LONG2FIX(2));
+ beg = mid;
}
}
else if (NIL_P(beg) && is_integer_p(end)) {
@@ -763,9 +786,15 @@ range_bsearch(VALUE range)
VALUE mid = rb_funcall(end, '+', 1, diff);
BSEARCH_CHECK(mid);
if (!smaller) {
- return bsearch_integer_range(mid, end, 0);
+ if (FIXNUM_P(mid) && FIXNUM_P(end)) {
+ BSEARCH_FIXNUM(mid, end, false);
+ }
+ else {
+ return bsearch_integer_range(mid, end, false);
+ }
}
diff = rb_funcall(diff, '*', 1, LONG2FIX(2));
+ end = mid;
}
}
else {
@@ -798,7 +827,12 @@ sym_each_i(VALUE v, VALUE arg)
* (1..4).size # => 4
* (1...4).size # => 3
* (1..).size # => Infinity
- * ('a'..'z').size #=> nil
+ * ('a'..'z').size # => nil
+ *
+ * If +self+ is not iterable, raises an exception:
+ *
+ * (0.5..2.5).size # TypeError
+ * (..1).size # TypeError
*
* Related: Range#count.
*/
@@ -807,7 +841,8 @@ static VALUE
range_size(VALUE range)
{
VALUE b = RANGE_BEG(range), e = RANGE_END(range);
- if (rb_obj_is_kind_of(b, rb_cNumeric)) {
+
+ if (RB_INTEGER_TYPE_P(b)) {
if (rb_obj_is_kind_of(e, rb_cNumeric)) {
return ruby_num_interval_step_size(b, e, INT2FIX(1), EXCL(range));
}
@@ -815,8 +850,10 @@ range_size(VALUE range)
return DBL2NUM(HUGE_VAL);
}
}
- else if (NIL_P(b)) {
- return DBL2NUM(HUGE_VAL);
+
+ if (!discrete_object_p(b)) {
+ rb_raise(rb_eTypeError, "can't iterate from %s",
+ rb_obj_classname(b));
}
return Qnil;
@@ -833,7 +870,6 @@ range_size(VALUE range)
* (1...4).to_a # => [1, 2, 3]
* ('a'..'d').to_a # => ["a", "b", "c", "d"]
*
- * Range#entries is an alias for Range#to_a.
*/
static VALUE
@@ -994,6 +1030,144 @@ range_each(VALUE range)
return range;
}
+RBIMPL_ATTR_NORETURN()
+static void
+range_reverse_each_bignum_beginless(VALUE end)
+{
+ RUBY_ASSERT(RBIGNUM_NEGATIVE_P(end));
+
+ for (;; end = rb_big_minus(end, INT2FIX(1))) {
+ rb_yield(end);
+ }
+ UNREACHABLE;
+}
+
+static void
+range_reverse_each_bignum(VALUE beg, VALUE end)
+{
+ RUBY_ASSERT(RBIGNUM_POSITIVE_P(beg) == RBIGNUM_POSITIVE_P(end));
+
+ VALUE c;
+ while ((c = rb_big_cmp(beg, end)) != INT2FIX(1)) {
+ rb_yield(end);
+ if (c == INT2FIX(0)) break;
+ end = rb_big_minus(end, INT2FIX(1));
+ }
+}
+
+static void
+range_reverse_each_positive_bignum_section(VALUE beg, VALUE end)
+{
+ RUBY_ASSERT(!NIL_P(end));
+
+ if (FIXNUM_P(end) || RBIGNUM_NEGATIVE_P(end)) return;
+
+ if (NIL_P(beg) || FIXNUM_P(beg) || RBIGNUM_NEGATIVE_P(beg)) {
+ beg = LONG2NUM(FIXNUM_MAX + 1);
+ }
+
+ range_reverse_each_bignum(beg, end);
+}
+
+static void
+range_reverse_each_fixnum_section(VALUE beg, VALUE end)
+{
+ RUBY_ASSERT(!NIL_P(end));
+
+ if (!FIXNUM_P(beg)) {
+ if (!NIL_P(beg) && RBIGNUM_POSITIVE_P(beg)) return;
+
+ beg = LONG2FIX(FIXNUM_MIN);
+ }
+
+ if (!FIXNUM_P(end)) {
+ if (RBIGNUM_NEGATIVE_P(end)) return;
+
+ end = LONG2FIX(FIXNUM_MAX);
+ }
+
+ long b = FIX2LONG(beg);
+ long e = FIX2LONG(end);
+ for (long i = e; i >= b; --i) {
+ rb_yield(LONG2FIX(i));
+ }
+}
+
+static void
+range_reverse_each_negative_bignum_section(VALUE beg, VALUE end)
+{
+ RUBY_ASSERT(!NIL_P(end));
+
+ if (FIXNUM_P(end) || RBIGNUM_POSITIVE_P(end)) {
+ end = LONG2NUM(FIXNUM_MIN - 1);
+ }
+
+ if (NIL_P(beg)) {
+ range_reverse_each_bignum_beginless(end);
+ }
+
+ if (FIXNUM_P(beg) || RBIGNUM_POSITIVE_P(beg)) return;
+
+ range_reverse_each_bignum(beg, end);
+}
+
+/*
+ * call-seq:
+ * reverse_each {|element| ... } -> self
+ * reverse_each -> an_enumerator
+ *
+ * With a block given, passes each element of +self+ to the block in reverse order:
+ *
+ * a = []
+ * (1..4).reverse_each {|element| a.push(element) } # => 1..4
+ * a # => [4, 3, 2, 1]
+ *
+ * a = []
+ * (1...4).reverse_each {|element| a.push(element) } # => 1...4
+ * a # => [3, 2, 1]
+ *
+ * With no block given, returns an enumerator.
+ *
+ */
+
+static VALUE
+range_reverse_each(VALUE range)
+{
+ RETURN_SIZED_ENUMERATOR(range, 0, 0, range_enum_size);
+
+ VALUE beg = RANGE_BEG(range);
+ VALUE end = RANGE_END(range);
+ int excl = EXCL(range);
+
+ if (NIL_P(end)) {
+ rb_raise(rb_eTypeError, "can't iterate from %s",
+ rb_obj_classname(end));
+ }
+
+ if (FIXNUM_P(beg) && FIXNUM_P(end)) {
+ if (excl) {
+ if (end == LONG2FIX(FIXNUM_MIN)) return range;
+
+ end = rb_int_minus(end, INT2FIX(1));
+ }
+
+ range_reverse_each_fixnum_section(beg, end);
+ }
+ else if ((NIL_P(beg) || RB_INTEGER_TYPE_P(beg)) && RB_INTEGER_TYPE_P(end)) {
+ if (excl) {
+ end = rb_int_minus(end, INT2FIX(1));
+ }
+ range_reverse_each_positive_bignum_section(beg, end);
+ range_reverse_each_fixnum_section(beg, end);
+ range_reverse_each_negative_bignum_section(beg, end);
+ }
+ else {
+ return rb_call_super(0, NULL);
+ }
+
+ return range;
+}
+
/*
* call-seq:
* self.begin -> object
@@ -1098,19 +1272,15 @@ rb_int_range_last(int argc, VALUE *argv, VALUE range)
int x;
long n;
- assert(argc > 0);
+ RUBY_ASSERT(argc > 0);
b = RANGE_BEG(range);
e = RANGE_END(range);
- assert(RB_INTEGER_TYPE_P(b) && RB_INTEGER_TYPE_P(e));
+ RUBY_ASSERT(RB_INTEGER_TYPE_P(b) && RB_INTEGER_TYPE_P(e));
x = EXCL(range);
len_1 = rb_int_minus(e, b);
- if (FIXNUM_ZERO_P(len_1) || rb_num_negative_p(len_1)) {
- return rb_ary_new_capa(0);
- }
-
if (x) {
e = rb_int_minus(e, ONE);
len = len_1;
@@ -1119,6 +1289,10 @@ rb_int_range_last(int argc, VALUE *argv, VALUE range)
len = rb_int_plus(len_1, ONE);
}
+ if (FIXNUM_ZERO_P(len) || rb_num_negative_p(len)) {
+ return rb_ary_new_capa(0);
+ }
+
rb_scan_args(argc, argv, "1", &nv);
n = NUM2LONG(nv);
if (n < 0) {
@@ -1295,10 +1469,9 @@ range_min(int argc, VALUE *argv, VALUE range)
return range_first(argc, argv, range);
}
else {
- struct cmp_opt_data cmp_opt = { 0, 0 };
VALUE b = RANGE_BEG(range);
VALUE e = RANGE_END(range);
- int c = NIL_P(e) ? -1 : OPTIMIZED_CMP(b, e, cmp_opt);
+ int c = NIL_P(e) ? -1 : OPTIMIZED_CMP(b, e);
if (c > 0 || (c == 0 && EXCL(range)))
return Qnil;
@@ -1406,8 +1579,7 @@ range_max(int argc, VALUE *argv, VALUE range)
return rb_call_super(argc, argv);
}
else {
- struct cmp_opt_data cmp_opt = { 0, 0 };
- int c = NIL_P(b) ? -1 : OPTIMIZED_CMP(b, e, cmp_opt);
+ int c = NIL_P(b) ? -1 : OPTIMIZED_CMP(b, e);
if (c > 0)
return Qnil;
@@ -1503,11 +1675,11 @@ rb_range_values(VALUE range, VALUE *begp, VALUE *endp, int *exclp)
else {
VALUE x;
b = rb_check_funcall(range, id_beg, 0, 0);
- if (b == Qundef) return (int)Qfalse;
+ if (UNDEF_P(b)) return (int)Qfalse;
e = rb_check_funcall(range, id_end, 0, 0);
- if (e == Qundef) return (int)Qfalse;
+ if (UNDEF_P(e)) return (int)Qfalse;
x = rb_check_funcall(range, rb_intern("exclude_end?"), 0, 0);
- if (x == Qundef) return (int)Qfalse;
+ if (UNDEF_P(x)) return (int)Qfalse;
excl = RTEST(x);
}
*begp = b;
@@ -1644,7 +1816,7 @@ inspect_range(VALUE range, VALUE dummy, int recur)
if (NIL_P(RANGE_BEG(range)) || !NIL_P(RANGE_END(range))) {
str2 = rb_inspect(RANGE_END(range));
}
- if (str2 != Qundef) rb_str_append(str, str2);
+ if (!UNDEF_P(str2)) rb_str_append(str, str2);
return str;
}
@@ -1677,7 +1849,8 @@ range_inspect(VALUE range)
return rb_exec_recursive(inspect_range, range, 0);
}
-static VALUE range_include_internal(VALUE range, VALUE val, int string_use_cover);
+static VALUE range_include_internal(VALUE range, VALUE val);
+VALUE rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive);
/*
* call-seq:
@@ -1721,8 +1894,6 @@ static VALUE range_include_internal(VALUE range, VALUE val, int string_use_cover
static VALUE
range_eqq(VALUE range, VALUE val)
{
- VALUE ret = range_include_internal(range, val, 1);
- if (ret != Qundef) return ret;
return r_cover_p(range, RANGE_BEG(range), RANGE_END(range), val);
}
@@ -1754,56 +1925,59 @@ range_eqq(VALUE range, VALUE val)
* ('a'..'d').cover?('cc') # => true
*
* Related: Range#cover?.
- *
- * Range#member? is an alias for Range#include?.
*/
static VALUE
range_include(VALUE range, VALUE val)
{
- VALUE ret = range_include_internal(range, val, 0);
- if (ret != Qundef) return ret;
+ VALUE ret = range_include_internal(range, val);
+ if (!UNDEF_P(ret)) return ret;
return rb_call_super(1, &val);
}
+static inline bool
+range_integer_edge_p(VALUE beg, VALUE end)
+{
+ return (!NIL_P(rb_check_to_integer(beg, "to_int")) ||
+ !NIL_P(rb_check_to_integer(end, "to_int")));
+}
+
+static inline bool
+range_string_range_p(VALUE beg, VALUE end)
+{
+ return RB_TYPE_P(beg, T_STRING) && RB_TYPE_P(end, T_STRING);
+}
+
+static inline VALUE
+range_include_fallback(VALUE beg, VALUE end, VALUE val)
+{
+ if (NIL_P(beg) && NIL_P(end)) {
+ if (linear_object_p(val)) return Qtrue;
+ }
+
+ if (NIL_P(beg) || NIL_P(end)) {
+ rb_raise(rb_eTypeError, "cannot determine inclusion in beginless/endless ranges");
+ }
+
+ return Qundef;
+}
+
static VALUE
-range_include_internal(VALUE range, VALUE val, int string_use_cover)
+range_include_internal(VALUE range, VALUE val)
{
VALUE beg = RANGE_BEG(range);
VALUE end = RANGE_END(range);
int nv = FIXNUM_P(beg) || FIXNUM_P(end) ||
linear_object_p(beg) || linear_object_p(end);
- if (nv ||
- !NIL_P(rb_check_to_integer(beg, "to_int")) ||
- !NIL_P(rb_check_to_integer(end, "to_int"))) {
+ if (nv || range_integer_edge_p(beg, end)) {
return r_cover_p(range, beg, end, val);
}
- else if (RB_TYPE_P(beg, T_STRING) || RB_TYPE_P(end, T_STRING)) {
- if (RB_TYPE_P(beg, T_STRING) && RB_TYPE_P(end, T_STRING)) {
- if (string_use_cover) {
- return r_cover_p(range, beg, end, val);
- }
- else {
- VALUE rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive);
- return rb_str_include_range_p(beg, end, val, RANGE_EXCL(range));
- }
- }
- else if (NIL_P(beg)) {
- VALUE r = rb_funcall(val, id_cmp, 1, end);
- if (NIL_P(r)) return Qfalse;
- if (RANGE_EXCL(range)) {
- return RBOOL(rb_cmpint(r, val, end) < 0);
- }
- return RBOOL(rb_cmpint(r, val, end) <= 0);
- }
- else if (NIL_P(end)) {
- VALUE r = rb_funcall(beg, id_cmp, 1, val);
- if (NIL_P(r)) return Qfalse;
- return RBOOL(rb_cmpint(r, beg, val) <= 0);
- }
+ else if (range_string_range_p(beg, end)) {
+ return rb_str_include_range_p(beg, end, val, RANGE_EXCL(range));
}
- return Qundef;
+
+ return range_include_fallback(beg, end, val);
}
static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val);
@@ -1830,7 +2004,7 @@ static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val);
* r.cover?(0) # => false
* r.cover?(5) # => false
* r.cover?('foo') # => false
-
+ *
* r = ('a'..'d')
* r.cover?('a') # => true
* r.cover?('d') # => true
@@ -1851,7 +2025,7 @@ static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val);
* r.cover?(0) # => false
* r.cover?(4) # => false
* r.cover?('foo') # => false
-
+ *
* r = ('a'...'d')
* r.cover?('a') # => true
* r.cover?('c') # => true
@@ -1867,7 +2041,7 @@ static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val);
* r.cover?(0..4) # => false
* r.cover?(1..5) # => false
* r.cover?('a'..'d') # => false
-
+ *
* r = (1...4)
* r.cover?(1..3) # => true
* r.cover?(1..4) # => false
@@ -1888,48 +2062,48 @@ static int r_cover_range_p(VALUE range, VALUE beg, VALUE end, VALUE val);
* - An internal call to <tt><=></tt> returns +nil+;
* that is, the operands are not comparable.
*
- * Beginless ranges cover all values of the same type before the end,
- * excluding the end for exclusive ranges. Beginless ranges cover
- * ranges that end before the end of the beginless range, or at the
- * end of the beginless range for inclusive ranges.
- *
- * (..2).cover?(1) # => true
- * (..2).cover?(2) # => true
- * (..2).cover?(3) # => false
- * (...2).cover?(2) # => false
- * (..2).cover?("2") # => false
- * (..2).cover?(..2) # => true
- * (..2).cover?(...2) # => true
- * (..2).cover?(.."2") # => false
- * (...2).cover?(..2) # => false
- *
- * Endless ranges cover all values of the same type after the
- * beginning. Endless exclusive ranges do not cover endless
- * inclusive ranges.
- *
- * (2..).cover?(1) # => false
- * (2..).cover?(3) # => true
- * (2...).cover?(3) # => true
- * (2..).cover?(2) # => true
- * (2..).cover?("2") # => false
- * (2..).cover?(2..) # => true
- * (2..).cover?(2...) # => true
- * (2..).cover?("2"..) # => false
- * (2...).cover?(2..) # => false
- * (2...).cover?(3...) # => true
- * (2...).cover?(3..) # => false
- * (3..).cover?(2..) # => false
- *
- * Ranges that are both beginless and endless cover all values and
- * ranges, and return true for all arguments, with the exception that
- * beginless and endless exclusive ranges do not cover endless
- * inclusive ranges.
- *
- * (nil...).cover?(Object.new) # => true
- * (nil...).cover?(nil...) # => true
- * (nil..).cover?(nil...) # => true
- * (nil...).cover?(nil..) # => false
- * (nil...).cover?(1..) # => false
+ * Beginless ranges cover all values of the same type before the end,
+ * excluding the end for exclusive ranges. Beginless ranges cover
+ * ranges that end before the end of the beginless range, or at the
+ * end of the beginless range for inclusive ranges.
+ *
+ * (..2).cover?(1) # => true
+ * (..2).cover?(2) # => true
+ * (..2).cover?(3) # => false
+ * (...2).cover?(2) # => false
+ * (..2).cover?("2") # => false
+ * (..2).cover?(..2) # => true
+ * (..2).cover?(...2) # => true
+ * (..2).cover?(.."2") # => false
+ * (...2).cover?(..2) # => false
+ *
+ * Endless ranges cover all values of the same type after the
+ * beginning. Endless exclusive ranges do not cover endless
+ * inclusive ranges.
+ *
+ * (2..).cover?(1) # => false
+ * (2..).cover?(3) # => true
+ * (2...).cover?(3) # => true
+ * (2..).cover?(2) # => true
+ * (2..).cover?("2") # => false
+ * (2..).cover?(2..) # => true
+ * (2..).cover?(2...) # => true
+ * (2..).cover?("2"..) # => false
+ * (2...).cover?(2..) # => false
+ * (2...).cover?(3...) # => true
+ * (2...).cover?(3..) # => false
+ * (3..).cover?(2..) # => false
+ *
+ * Ranges that are both beginless and endless cover all values and
+ * ranges, and return true for all arguments, with the exception that
+ * beginless and endless exclusive ranges do not cover endless
+ * inclusive ranges.
+ *
+ * (nil...).cover?(Object.new) # => true
+ * (nil...).cover?(nil...) # => true
+ * (nil..).cover?(nil...) # => true
+ * (nil...).cover?(nil..) # => false
+ * (nil...).cover?(1..) # => false
*
* Related: Range#include?.
*
@@ -2089,17 +2263,137 @@ range_count(int argc, VALUE *argv, VALUE range)
* Infinity. Just let it loop. */
return rb_call_super(argc, argv);
}
- else if (NIL_P(RANGE_END(range))) {
+
+ VALUE beg = RANGE_BEG(range), end = RANGE_END(range);
+
+ if (NIL_P(beg) || NIL_P(end)) {
/* We are confident that the answer is Infinity. */
return DBL2NUM(HUGE_VAL);
}
- else if (NIL_P(RANGE_BEG(range))) {
- /* We are confident that the answer is Infinity. */
- return DBL2NUM(HUGE_VAL);
+
+ if (is_integer_p(beg)) {
+ VALUE size = range_size(range);
+ if (!NIL_P(size)) {
+ return size;
+ }
}
- else {
- return rb_call_super(argc, argv);
+
+ return rb_call_super(argc, argv);
+}
+
+static bool
+empty_region_p(VALUE beg, VALUE end, int excl)
+{
+ if (NIL_P(beg)) return false;
+ if (NIL_P(end)) return false;
+ int less = r_less(beg, end);
+ /* empty range */
+ if (less > 0) return true;
+ if (excl && less == 0) return true;
+ return false;
+}
+
+/*
+ * call-seq:
+ * overlap?(range) -> true or false
+ *
+ * Returns +true+ if +range+ overlaps with +self+, +false+ otherwise:
+ *
+ * (0..2).overlap?(1..3) #=> true
+ * (0..2).overlap?(3..4) #=> false
+ * (0..).overlap?(..0) #=> true
+ *
+ * With non-range argument, raises TypeError.
+ *
+ * (1..3).overlap?(1) # TypeError
+ *
+ * Returns +false+ if an internal call to <tt><=></tt> returns +nil+;
+ * that is, the operands are not comparable.
+ *
+ * (1..3).overlap?('a'..'d') # => false
+ *
+ * Returns +false+ if +self+ or +range+ is empty. "Empty range" means
+ * that its begin value is larger than, or equal for an exclusive
+ * range, its end value.
+ *
+ * (4..1).overlap?(2..3) # => false
+ * (4..1).overlap?(..3) # => false
+ * (4..1).overlap?(2..) # => false
+ * (2...2).overlap?(1..2) # => false
+ *
+ * (1..4).overlap?(3..2) # => false
+ * (..4).overlap?(3..2) # => false
+ * (1..).overlap?(3..2) # => false
+ * (1..2).overlap?(2...2) # => false
+ *
+ * Returns +false+ if the begin value one of +self+ and +range+ is
+ * larger than, or equal if the other is an exclusive range, the end
+ * value of the other:
+ *
+ * (4..5).overlap?(2..3) # => false
+ * (4..5).overlap?(2...4) # => false
+ *
+ * (1..2).overlap?(3..4) # => false
+ * (1...3).overlap?(3..4) # => false
+ *
+ * Returns +false+ if the end value one of +self+ and +range+ is
+ * larger than, or equal for an exclusive range, the end value of the
+ * other:
+ *
+ * (4..5).overlap?(2..3) # => false
+ * (4..5).overlap?(2...4) # => false
+ *
+ * (1..2).overlap?(3..4) # => false
+ * (1...3).overlap?(3..4) # => false
+ *
+ * Note that the method wouldn't make any assumptions about the beginless
+ * range being actually empty, even if its upper bound is the minimum
+ * possible value of its type, so all this would return +true+:
+ *
+ * (...-Float::INFINITY).overlap?(...-Float::INFINITY) # => true
+ * (..."").overlap?(..."") # => true
+ * (...[]).overlap?(...[]) # => true
+ *
+ * Even if those ranges are effectively empty (no number can be smaller than
+ * <tt>-Float::INFINITY</tt>), they are still considered overlapping
+ * with themselves.
+ *
+ * Related: Range#cover?.
+ */
+
+static VALUE
+range_overlap(VALUE range, VALUE other)
+{
+ if (!rb_obj_is_kind_of(other, rb_cRange)) {
+ rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (expected Range)",
+ rb_class_name(rb_obj_class(other)));
}
+
+ VALUE self_beg = RANGE_BEG(range);
+ VALUE self_end = RANGE_END(range);
+ int self_excl = EXCL(range);
+ VALUE other_beg = RANGE_BEG(other);
+ VALUE other_end = RANGE_END(other);
+ int other_excl = EXCL(other);
+
+ if (empty_region_p(self_beg, other_end, other_excl)) return Qfalse;
+ if (empty_region_p(other_beg, self_end, self_excl)) return Qfalse;
+
+ if (!NIL_P(self_beg) && !NIL_P(other_beg)) {
+ VALUE cmp = rb_funcall(self_beg, id_cmp, 1, other_beg);
+ if (NIL_P(cmp)) return Qfalse;
+ /* if both begin values are equal, no more comparisons needed */
+ if (rb_cmpint(cmp, self_beg, other_beg) == 0) return Qtrue;
+ }
+ else if (NIL_P(self_beg) && NIL_P(other_beg)) {
+ VALUE cmp = rb_funcall(self_end, id_cmp, 1, other_end);
+ return RBOOL(!NIL_P(cmp));
+ }
+
+ if (empty_region_p(self_beg, self_end, self_excl)) return Qfalse;
+ if (empty_region_p(other_beg, other_end, other_excl)) return Qfalse;
+
+ return Qtrue;
}
/* A \Range object represents a collection of values
@@ -2282,6 +2576,7 @@ range_count(int argc, VALUE *argv, VALUE range)
* - {Comparing}[rdoc-ref:Range@Methods+for+Comparing]
* - {Iterating}[rdoc-ref:Range@Methods+for+Iterating]
* - {Converting}[rdoc-ref:Range@Methods+for+Converting]
+ * - {Methods for Working with JSON}[rdoc-ref:Range@Methods+for+Working+with+JSON]
*
* === Methods for Creating a \Range
*
@@ -2316,7 +2611,7 @@ range_count(int argc, VALUE *argv, VALUE range)
* - #%: Requires argument +n+; calls the block with each +n+-th element of +self+.
* - #each: Calls the block with each element of +self+.
* - #step: Takes optional argument +n+ (defaults to 1);
- calls the block with each +n+-th element of +self+.
+ * calls the block with each +n+-th element of +self+.
*
* === Methods for Converting
*
@@ -2324,6 +2619,16 @@ range_count(int argc, VALUE *argv, VALUE range)
* - #to_a (aliased as #entries): Returns elements of +self+ in an array.
* - #to_s: Returns a string representation of +self+ (uses #to_s).
*
+ * === Methods for Working with \JSON
+ *
+ * - ::json_create: Returns a new \Range object constructed from the given object.
+ * - #as_json: Returns a 2-element hash representing +self+.
+ * - #to_json: Returns a \JSON string representing +self+.
+ *
+ * To make these methods available:
+ *
+ * require 'json/add/range'
+ *
*/
void
@@ -2348,6 +2653,7 @@ Init_Range(void)
rb_define_method(rb_cRange, "each", range_each, 0);
rb_define_method(rb_cRange, "step", range_step, -1);
rb_define_method(rb_cRange, "%", range_percent_step, 1);
+ rb_define_method(rb_cRange, "reverse_each", range_reverse_each, 0);
rb_define_method(rb_cRange, "bsearch", range_bsearch, 0);
rb_define_method(rb_cRange, "begin", range_begin, 0);
rb_define_method(rb_cRange, "end", range_end, 0);
@@ -2368,4 +2674,5 @@ Init_Range(void)
rb_define_method(rb_cRange, "include?", range_include, 1);
rb_define_method(rb_cRange, "cover?", range_cover, 1);
rb_define_method(rb_cRange, "count", range_count, -1);
+ rb_define_method(rb_cRange, "overlap?", range_overlap, 1);
}