summaryrefslogtreecommitdiff
path: root/range.c
diff options
context:
space:
mode:
Diffstat (limited to 'range.c')
-rw-r--r--range.c264
1 files changed, 186 insertions, 78 deletions
diff --git a/range.c b/range.c
index 7ddb9908f6..fd08a81de7 100644
--- a/range.c
+++ b/range.c
@@ -47,6 +47,7 @@ static VALUE r_cover_p(VALUE, VALUE, VALUE, VALUE);
static void
range_init(VALUE range, VALUE beg, VALUE end, VALUE exclude_end)
{
+ // Changing this condition has implications for JITs. If you do, please let maintainers know.
if ((!FIXNUM_P(beg) || !FIXNUM_P(end)) && !NIL_P(beg) && !NIL_P(end)) {
VALUE v;
@@ -153,14 +154,14 @@ recursive_equal(VALUE range, VALUE obj, int recur)
* call-seq:
* self == other -> true or false
*
- * Returns +true+ if and only if:
+ * Returns whether all of the following are true:
*
* - +other+ is a range.
* - <tt>other.begin == self.begin</tt>.
* - <tt>other.end == self.end</tt>.
* - <tt>other.exclude_end? == self.exclude_end?</tt>.
*
- * Otherwise returns +false+.
+ * Examples:
*
* r = (1..5)
* r == (1..5) # => true
@@ -488,6 +489,7 @@ range_step(int argc, VALUE *argv, VALUE range)
b = RANGE_BEG(range);
e = RANGE_END(range);
+ v = b;
const VALUE b_num_p = rb_obj_is_kind_of(b, rb_cNumeric);
const VALUE e_num_p = rb_obj_is_kind_of(e, rb_cNumeric);
@@ -559,7 +561,8 @@ range_step(int argc, VALUE *argv, VALUE range)
rb_yield(LONG2NUM(i));
i += unit;
}
- } else {
+ }
+ else {
if (!EXCL(range))
end += 1;
i = FIX2LONG(b);
@@ -571,7 +574,8 @@ range_step(int argc, VALUE *argv, VALUE range)
}
else if (b_num_p && step_num_p && ruby_float_step(b, e, step, EXCL(range), TRUE)) {
/* done */
- } else if (!NIL_P(str_b) && FIXNUM_P(step)) {
+ }
+ else if (!NIL_P(str_b) && FIXNUM_P(step)) {
// backwards compatibility behavior for String only, when no step/Integer step is passed
// See discussion in https://bugs.ruby-lang.org/issues/18368
@@ -583,7 +587,8 @@ range_step(int argc, VALUE *argv, VALUE range)
else {
rb_str_upto_each(str_b, e, EXCL(range), step_i, (VALUE)iter);
}
- } else if (!NIL_P(sym_b) && FIXNUM_P(step)) {
+ }
+ else if (!NIL_P(sym_b) && FIXNUM_P(step)) {
// same as above: backward compatibility for symbols
VALUE iter[2] = {INT2FIX(1), step};
@@ -594,47 +599,48 @@ range_step(int argc, VALUE *argv, VALUE range)
else {
rb_str_upto_each(sym_b, rb_sym2str(e), EXCL(range), sym_step_i, (VALUE)iter);
}
- } else {
- v = b;
- if (!NIL_P(e)) {
- if (b_num_p && step_num_p && r_less(step, INT2FIX(0)) < 0) {
- // iterate backwards, for consistency with ArithmeticSequence
- if (EXCL(range)) {
- for (; r_less(e, v) < 0; v = rb_funcall(v, id_plus, 1, step))
- rb_yield(v);
- }
- else {
- for (; (c = r_less(e, v)) <= 0; v = rb_funcall(v, id_plus, 1, step)) {
- rb_yield(v);
- if (!c) break;
- }
- }
-
- } else {
- // Direction of the comparison. We use it as a comparison operator in cycle:
- // if begin < end, the cycle performs while value < end (iterating forward)
- // if begin > end, the cycle performs while value > end (iterating backward with
- // a negative step)
- dir = r_less(b, e);
- // One preliminary addition to check the step moves iteration in the same direction as
- // from begin to end; otherwise, the iteration should be empty.
- if (r_less(b, rb_funcall(b, id_plus, 1, step)) == dir) {
- if (EXCL(range)) {
- for (; r_less(v, e) == dir; v = rb_funcall(v, id_plus, 1, step))
- rb_yield(v);
- }
- else {
- for (; (c = r_less(v, e)) == dir || c == 0; v = rb_funcall(v, id_plus, 1, step)) {
- rb_yield(v);
- if (!c) break;
- }
- }
- }
+ }
+ else if (NIL_P(e)) {
+ // endless range
+ for (;; v = rb_funcall(v, id_plus, 1, step))
+ rb_yield(v);
+ }
+ else if (b_num_p && step_num_p && r_less(step, INT2FIX(0)) < 0) {
+ // iterate backwards, for consistency with ArithmeticSequence
+ if (EXCL(range)) {
+ for (; r_less(e, v) < 0; v = rb_funcall(v, id_plus, 1, step))
+ rb_yield(v);
+ }
+ else {
+ for (; (c = r_less(e, v)) <= 0; v = rb_funcall(v, id_plus, 1, step)) {
+ rb_yield(v);
+ if (!c) break;
}
}
- else
- for (;; v = rb_funcall(v, id_plus, 1, step))
+
+ }
+ else if ((dir = r_less(b, e)) == 0) {
+ if (!EXCL(range)) {
+ rb_yield(v);
+ }
+ }
+ else if (dir == r_less(b, rb_funcall(b, id_plus, 1, step))) {
+ // Direction of the comparison. We use it as a comparison operator in cycle:
+ // if begin < end, the cycle performs while value < end (iterating forward)
+ // if begin > end, the cycle performs while value > end (iterating backward with
+ // a negative step)
+ // One preliminary addition to check the step moves iteration in the same direction as
+ // from begin to end; otherwise, the iteration should be empty.
+ if (EXCL(range)) {
+ for (; r_less(v, e) == dir; v = rb_funcall(v, id_plus, 1, step))
rb_yield(v);
+ }
+ else {
+ for (; (c = r_less(v, e)) == dir || c == 0; v = rb_funcall(v, id_plus, 1, step)) {
+ rb_yield(v);
+ if (!c) break;
+ }
+ }
}
return range;
}
@@ -778,7 +784,7 @@ bsearch_integer_range(VALUE beg, VALUE end, int excl)
*
* Returns an element from +self+ selected by a binary search.
*
- * See {Binary Searching}[rdoc-ref:bsearch.rdoc].
+ * See {Binary Searching}[rdoc-ref:language/bsearch.rdoc].
*
*/
@@ -908,6 +914,10 @@ sym_each_i(VALUE v, VALUE arg)
return each_i(rb_str_intern(v), arg);
}
+#define CANT_ITERATE_FROM(x) \
+ rb_raise(rb_eTypeError, "can't iterate from %s", \
+ rb_obj_classname(x))
+
/*
* call-seq:
* size -> non_negative_integer or Infinity or nil
@@ -944,13 +954,48 @@ range_size(VALUE range)
}
if (!discrete_object_p(b)) {
- rb_raise(rb_eTypeError, "can't iterate from %s",
- rb_obj_classname(b));
+ CANT_ITERATE_FROM(b);
+ }
+
+ return Qnil;
+}
+
+static VALUE
+range_reverse_size(VALUE range)
+{
+ VALUE b = RANGE_BEG(range), e = RANGE_END(range);
+
+ if (NIL_P(e)) {
+ CANT_ITERATE_FROM(e);
+ }
+
+ 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));
+ }
+ else {
+ CANT_ITERATE_FROM(e);
+ }
+ }
+
+ if (NIL_P(b)) {
+ if (RB_INTEGER_TYPE_P(e)) {
+ return DBL2NUM(HUGE_VAL);
+ }
+ else {
+ CANT_ITERATE_FROM(e);
+ }
+ }
+
+ if (!discrete_object_p(b)) {
+ CANT_ITERATE_FROM(e);
}
return Qnil;
}
+#undef CANT_ITERATE_FROM
+
/*
* call-seq:
* to_a -> array
@@ -973,12 +1018,41 @@ range_to_a(VALUE range)
return rb_call_super(0, 0);
}
+/*
+ * call-seq:
+ * to_set -> set
+ *
+ * Returns a set containing the elements in +self+, if a finite collection;
+ * raises an exception otherwise.
+ *
+ * (1..4).to_set # => Set[1, 2, 3, 4]
+ * (1...4).to_set # => Set[1, 2, 3]
+ *
+ * (1..).to_set
+ * # in 'Range#to_set': cannot convert endless range to a set (RangeError)
+ *
+ */
+static VALUE
+range_to_set(VALUE range)
+{
+ if (NIL_P(RANGE_END(range))) {
+ rb_raise(rb_eRangeError, "cannot convert endless range to a set");
+ }
+ return rb_call_super(0, NULL);
+}
+
static VALUE
range_enum_size(VALUE range, VALUE args, VALUE eobj)
{
return range_size(range);
}
+static VALUE
+range_enum_reverse_size(VALUE range, VALUE args, VALUE eobj)
+{
+ return range_reverse_size(range);
+}
+
RBIMPL_ATTR_NORETURN()
static void
range_each_bignum_endless(VALUE beg)
@@ -1225,7 +1299,7 @@ range_reverse_each_negative_bignum_section(VALUE beg, VALUE end)
static VALUE
range_reverse_each(VALUE range)
{
- RETURN_SIZED_ENUMERATOR(range, 0, 0, range_enum_size);
+ RETURN_SIZED_ENUMERATOR(range, 0, 0, range_enum_reverse_size);
VALUE beg = RANGE_BEG(range);
VALUE end = RANGE_END(range);
@@ -1355,12 +1429,29 @@ range_first(int argc, VALUE *argv, VALUE range)
return ary[1];
}
+static bool
+range_basic_each_p(VALUE range)
+{
+ return rb_method_basic_definition_p(CLASS_OF(range), idEach);
+}
+
+static bool
+integer_end_optimizable(VALUE range)
+{
+ VALUE b = RANGE_BEG(range);
+ if (!NIL_P(b) && !RB_INTEGER_TYPE_P(b)) return false;
+ VALUE e = RANGE_END(range);
+ if (!RB_INTEGER_TYPE_P(e)) return false;
+ if (RB_LIKELY(range_basic_each_p(range))) return true;
+ return false;
+}
+
static VALUE
rb_int_range_last(int argc, VALUE *argv, VALUE range)
{
static const VALUE ONE = INT2FIX(1);
- VALUE b, e, len_1, len, nv, ary;
+ VALUE b, e, len_1 = Qnil, len = Qnil, nv, ary;
int x;
long n;
@@ -1368,20 +1459,28 @@ rb_int_range_last(int argc, VALUE *argv, VALUE range)
b = RANGE_BEG(range);
e = RANGE_END(range);
- RUBY_ASSERT(RB_INTEGER_TYPE_P(b) && RB_INTEGER_TYPE_P(e));
+ RUBY_ASSERT(NIL_P(b) || RB_INTEGER_TYPE_P(b), "b=%"PRIsVALUE, rb_obj_class(b));
+ RUBY_ASSERT(RB_INTEGER_TYPE_P(e), "e=%"PRIsVALUE, rb_obj_class(e));
x = EXCL(range);
- len_1 = rb_int_minus(e, b);
- if (x) {
- e = rb_int_minus(e, ONE);
- len = len_1;
+ if (!NIL_P(b)) {
+ len_1 = rb_int_minus(e, b);
+ if (x) {
+ e = rb_int_minus(e, ONE);
+ len = len_1;
+ }
+ else {
+ len = rb_int_plus(len_1, ONE);
+ }
}
else {
- len = rb_int_plus(len_1, ONE);
+ if (x) {
+ e = rb_int_minus(e, ONE);
+ }
}
- if (FIXNUM_ZERO_P(len) || rb_num_negative_p(len)) {
+ if (!NIL_P(len) && (FIXNUM_ZERO_P(len) || rb_num_negative_p(len))) {
return rb_ary_new_capa(0);
}
@@ -1392,7 +1491,7 @@ rb_int_range_last(int argc, VALUE *argv, VALUE range)
}
nv = LONG2NUM(n);
- if (RTEST(rb_int_gt(nv, len))) {
+ if (!NIL_P(b) && RTEST(rb_int_gt(nv, len))) {
nv = len;
n = NUM2LONG(nv);
}
@@ -1446,17 +1545,11 @@ rb_int_range_last(int argc, VALUE *argv, VALUE range)
static VALUE
range_last(int argc, VALUE *argv, VALUE range)
{
- VALUE b, e;
-
if (NIL_P(RANGE_END(range))) {
rb_raise(rb_eRangeError, "cannot get the last element of endless range");
}
if (argc == 0) return RANGE_END(range);
-
- b = RANGE_BEG(range);
- e = RANGE_END(range);
- if (RB_INTEGER_TYPE_P(b) && RB_INTEGER_TYPE_P(e) &&
- RB_LIKELY(rb_method_basic_definition_p(rb_cRange, idEach))) {
+ if (integer_end_optimizable(range)) {
return rb_int_range_last(argc, argv, range);
}
return rb_ary_last(argc, argv, rb_Array(range));
@@ -1664,12 +1757,27 @@ range_max(int argc, VALUE *argv, VALUE range)
VALUE b = RANGE_BEG(range);
- if (rb_block_given_p() || (EXCL(range) && !nm) || argc) {
+ if (rb_block_given_p() || (EXCL(range) && !nm)) {
if (NIL_P(b)) {
rb_raise(rb_eRangeError, "cannot get the maximum of beginless range with custom comparison method");
}
return rb_call_super(argc, argv);
}
+ else if (argc) {
+ VALUE ary[2];
+ ID reverse_each;
+ CONST_ID(reverse_each, "reverse_each");
+ rb_scan_args(argc, argv, "1", &ary[0]);
+ ary[1] = rb_ary_new2(NUM2LONG(ary[0]));
+ rb_block_call(range, reverse_each, 0, 0, first_i, (VALUE)ary);
+ return ary[1];
+#if 0
+ if (integer_end_optimizable(range)) {
+ return rb_int_range_last(argc, argv, range, true);
+ }
+ return rb_ary_reverse(rb_ary_last(argc, argv, rb_Array(range)));
+#endif
+ }
else {
int c = NIL_P(b) ? -1 : OPTIMIZED_CMP(b, e);
@@ -1680,13 +1788,13 @@ range_max(int argc, VALUE *argv, VALUE range)
rb_raise(rb_eTypeError, "cannot exclude non Integer end value");
}
if (c == 0) return Qnil;
- if (!RB_INTEGER_TYPE_P(b)) {
+ if (!NIL_P(b) && !RB_INTEGER_TYPE_P(b)) {
rb_raise(rb_eTypeError, "cannot exclude end value with non Integer begin value");
}
if (FIXNUM_P(e)) {
return LONG2NUM(FIX2LONG(e) - 1);
}
- return rb_funcall(e, '-', 1, INT2FIX(1));
+ return rb_int_minus(e,INT2FIX(1));
}
return e;
}
@@ -1946,10 +2054,9 @@ VALUE rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive);
/*
* call-seq:
- * self === object -> true or false
+ * self === other -> true or false
*
- * Returns +true+ if +object+ is between <tt>self.begin</tt> and <tt>self.end</tt>.
- * +false+ otherwise:
+ * Returns whether +other+ is between <tt>self.begin</tt> and <tt>self.end</tt>:
*
* (1..4) === 2 # => true
* (1..4) === 5 # => false
@@ -2477,7 +2584,7 @@ range_overlap(VALUE range, VALUE other)
/* 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(self_end) && NIL_P(other_beg)) {
+ else if (NIL_P(self_beg) && !NIL_P(self_end) && NIL_P(other_beg) && !NIL_P(other_end)) {
VALUE cmp = rb_funcall(self_end, id_cmp, 1, other_end);
return RBOOL(!NIL_P(cmp));
}
@@ -2537,7 +2644,7 @@ range_overlap(VALUE range, VALUE other)
* r = (...2) # => nil...2
* a[r] # => [1, 2]
*
- * \Method +each+ for a beginless range raises an exception.
+ * Method +each+ for a beginless range raises an exception.
*
* == Endless Ranges
*
@@ -2567,7 +2674,7 @@ range_overlap(VALUE range, VALUE other)
* r = (2..) # => 2..
* a[r] # => [3, 4]
*
- * \Method +each+ for an endless range calls the given block indefinitely:
+ * Method +each+ for an endless range calls the given block indefinitely:
*
* a = []
* r = (1..)
@@ -2577,14 +2684,14 @@ range_overlap(VALUE range, VALUE other)
* end
* a # => [2, 4, 6, 8, 10]
*
- * A range can be both beginless and endless. For literal beginless, endless
+ * A range can be both beginless and endless. For literal beginless, endless
* ranges, at least the beginning or end of the range must be given as an
* explicit nil value. It is recommended to use an explicit nil beginning and
- * implicit nil end, since that is what Ruby uses for Range#inspect:
+ * end, since that is what Ruby uses for Range#inspect:
*
- * (nil..) # => (nil..)
- * (..nil) # => (nil..)
- * (nil..nil) # => (nil..)
+ * (nil..) # => (nil..nil)
+ * (..nil) # => (nil..nil)
+ * (nil..nil) # => (nil..nil)
*
* == Ranges and Other Classes
*
@@ -2659,7 +2766,7 @@ range_overlap(VALUE range, VALUE other)
*
* == What's Here
*
- * First, what's elsewhere. \Class \Range:
+ * First, what's elsewhere. Class \Range:
*
* - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
* - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here],
@@ -2760,6 +2867,7 @@ Init_Range(void)
rb_define_method(rb_cRange, "minmax", range_minmax, 0);
rb_define_method(rb_cRange, "size", range_size, 0);
rb_define_method(rb_cRange, "to_a", range_to_a, 0);
+ rb_define_method(rb_cRange, "to_set", range_to_set, 0);
rb_define_method(rb_cRange, "entries", range_to_a, 0);
rb_define_method(rb_cRange, "to_s", range_to_s, 0);
rb_define_method(rb_cRange, "inspect", range_inspect, 0);