summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--array.c91
-rw-r--r--enumerator.c44
-rw-r--r--include/ruby/internal/intern/enumerator.h1
-rw-r--r--internal/range.h4
-rw-r--r--range.c69
-rw-r--r--test/ruby/test_array.rb38
6 files changed, 216 insertions, 31 deletions
diff --git a/array.c b/array.c
index 0373c1e374..9183dfc3e6 100644
--- a/array.c
+++ b/array.c
@@ -1141,6 +1141,52 @@ ary_make_partial(VALUE ary, VALUE klass, long offset, long len)
}
static VALUE
+ary_make_partial_step(VALUE ary, VALUE klass, long offset, long len, long step)
+{
+ assert(offset >= 0);
+ assert(len >= 0);
+ assert(offset+len <= RARRAY_LEN(ary));
+ assert(step != 0);
+
+ const VALUE *values = RARRAY_CONST_PTR_TRANSIENT(ary);
+ const long orig_len = len;
+
+ if ((step > 0 && step >= len) || (step < 0 && (step < -len))) {
+ VALUE result = ary_new(klass, 1);
+ VALUE *ptr = (VALUE *)ARY_EMBED_PTR(result);
+ RB_OBJ_WRITE(result, ptr, values[offset]);
+ ARY_SET_EMBED_LEN(result, 1);
+ return result;
+ }
+
+ long ustep = (step < 0) ? -step : step;
+ len = (len + ustep - 1) / ustep;
+
+ long i;
+ long j = offset + ((step > 0) ? 0 : (orig_len - 1));
+ VALUE result = ary_new(klass, len);
+ if (len <= RARRAY_EMBED_LEN_MAX) {
+ VALUE *ptr = (VALUE *)ARY_EMBED_PTR(result);
+ for (i = 0; i < len; ++i) {
+ RB_OBJ_WRITE(result, ptr+i, values[j]);
+ j += step;
+ }
+ ARY_SET_EMBED_LEN(result, len);
+ }
+ else {
+ RARRAY_PTR_USE_TRANSIENT(result, ptr, {
+ for (i = 0; i < len; ++i) {
+ RB_OBJ_WRITE(result, ptr+i, values[j]);
+ j += step;
+ }
+ });
+ ARY_SET_LEN(result, len);
+ }
+
+ return result;
+}
+
+static VALUE
ary_make_shared_copy(VALUE ary)
{
return ary_make_partial(ary, rb_obj_class(ary), 0, RARRAY_LEN(ary));
@@ -1571,7 +1617,7 @@ rb_ary_entry(VALUE ary, long offset)
}
VALUE
-rb_ary_subseq(VALUE ary, long beg, long len)
+rb_ary_subseq_step(VALUE ary, long beg, long len, long step)
{
VALUE klass;
long alen = RARRAY_LEN(ary);
@@ -1584,8 +1630,18 @@ rb_ary_subseq(VALUE ary, long beg, long len)
}
klass = rb_obj_class(ary);
if (len == 0) return ary_new(klass, 0);
+ if (step == 0)
+ rb_raise(rb_eArgError, "slice step cannot be zero");
+ if (step == 1)
+ return ary_make_partial(ary, klass, beg, len);
+ else
+ return ary_make_partial_step(ary, klass, beg, len, step);
+}
- return ary_make_partial(ary, klass, beg, len);
+VALUE
+rb_ary_subseq(VALUE ary, long beg, long len)
+{
+ return rb_ary_subseq_step(ary, beg, len, 1);
}
static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e);
@@ -1595,6 +1651,11 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e);
* array[index] -> object or nil
* array[start, length] -> object or nil
* array[range] -> object or nil
+ * array[aseq] -> object or nil
+ * array.slice(index) -> object or nil
+ * array.slice(start, length) -> object or nil
+ * array.slice(range) -> object or nil
+ * array.slice(aseq) -> object or nil
*
* Returns elements from +self+; does not modify +self+.
*
@@ -1651,6 +1712,19 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e);
* a[-3..2] # => [:foo, "bar", 2]
*
* If <tt>range.start</tt> is larger than the array size, returns +nil+.
+ * a = [:foo, 'bar', 2]
+ * a[4..1] # => nil
+ * a[4..0] # => nil
+ * a[4..-1] # => nil
+ *
+ * When a single argument +aseq+ is given,
+ * ...(to be described)
+ *
+ * Raises an exception if given a single argument
+ * that is not an \Integer-convertible object or a \Range object:
+ * a = [:foo, 'bar', 2]
+ * # Raises TypeError (no implicit conversion of Symbol into Integer):
+ * a[:foo]
*
* Array#slice is an alias for Array#[].
*/
@@ -1679,21 +1753,22 @@ rb_ary_aref2(VALUE ary, VALUE b, VALUE e)
MJIT_FUNC_EXPORTED VALUE
rb_ary_aref1(VALUE ary, VALUE arg)
{
- long beg, len;
+ long beg, len, step;
/* special case - speeding up */
if (FIXNUM_P(arg)) {
return rb_ary_entry(ary, FIX2LONG(arg));
}
- /* check if idx is Range */
- switch (rb_range_beg_len(arg, &beg, &len, RARRAY_LEN(ary), 0)) {
+ /* check if idx is Range or ArithmeticSequence */
+ switch (rb_arithmetic_sequence_beg_len_step(arg, &beg, &len, &step, RARRAY_LEN(ary), 0)) {
case Qfalse:
- break;
+ break;
case Qnil:
- return Qnil;
+ return Qnil;
default:
- return rb_ary_subseq(ary, beg, len);
+ return rb_ary_subseq_step(ary, beg, len, step);
}
+
return rb_ary_entry(ary, NUM2LONG(arg));
}
diff --git a/enumerator.c b/enumerator.c
index 3ea308a7cd..6e88c5db4a 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -3410,17 +3410,53 @@ rb_arithmetic_sequence_extract(VALUE obj, rb_arithmetic_sequence_components_t *c
component->exclude_end = arith_seq_exclude_end_p(obj);
return 1;
}
- else if (rb_obj_is_kind_of(obj, rb_cRange)) {
- component->begin = RANGE_BEG(obj);
- component->end = RANGE_END(obj);
+ else if (rb_range_values(obj, &component->begin, &component->end, &component->exclude_end)) {
component->step = INT2FIX(1);
- component->exclude_end = RTEST(RANGE_EXCL(obj));
return 1;
}
return 0;
}
+VALUE
+rb_arithmetic_sequence_beg_len_step(VALUE obj, long *begp, long *lenp, long *stepp, long len, int err)
+{
+ RUBY_ASSERT(begp != NULL);
+ RUBY_ASSERT(lenp != NULL);
+ RUBY_ASSERT(stepp != NULL);
+
+ rb_arithmetic_sequence_components_t aseq;
+ if (!rb_arithmetic_sequence_extract(obj, &aseq)) {
+ return Qfalse;
+ }
+
+ long step = NIL_P(aseq.step) ? 1 : NUM2LONG(aseq.step);
+ *stepp = step;
+
+ if (step < 0) {
+ VALUE tmp = aseq.begin;
+ aseq.begin = aseq.end;
+ aseq.end = tmp;
+ }
+
+ if (err == 0 && (step < -1 || step > 1)) {
+ if (rb_range_component_beg_len(aseq.begin, aseq.end, aseq.exclude_end, begp, lenp, len, 1) == Qtrue) {
+ if (*begp > len)
+ goto out_of_range;
+ if (*lenp > len)
+ goto out_of_range;
+ return Qtrue;
+ }
+ }
+ else {
+ return rb_range_component_beg_len(aseq.begin, aseq.end, aseq.exclude_end, begp, lenp, len, err);
+ }
+
+ out_of_range:
+ rb_raise(rb_eRangeError, "%+"PRIsVALUE" out of range", obj);
+ return Qnil;
+}
+
/*
* call-seq:
* aseq.first -> num or nil
diff --git a/include/ruby/internal/intern/enumerator.h b/include/ruby/internal/intern/enumerator.h
index 7698e24538..c81485155c 100644
--- a/include/ruby/internal/intern/enumerator.h
+++ b/include/ruby/internal/intern/enumerator.h
@@ -42,6 +42,7 @@ VALUE rb_enumeratorize(VALUE, VALUE, int, const VALUE *);
VALUE rb_enumeratorize_with_size(VALUE, VALUE, int, const VALUE *, rb_enumerator_size_func *);
VALUE rb_enumeratorize_with_size_kw(VALUE, VALUE, int, const VALUE *, rb_enumerator_size_func *, int);
int rb_arithmetic_sequence_extract(VALUE, rb_arithmetic_sequence_components_t *);
+VALUE rb_arithmetic_sequence_beg_len_step(VALUE, long *begp, long *lenp, long *stepp, long len, int err);
RBIMPL_SYMBOL_EXPORT_END()
diff --git a/internal/range.h b/internal/range.h
index 0b60f42298..4fe6037c89 100644
--- a/internal/range.h
+++ b/internal/range.h
@@ -34,4 +34,8 @@ RANGE_EXCL(VALUE r)
return RSTRUCT(r)->as.ary[2];
}
+VALUE
+rb_range_component_beg_len(VALUE b, VALUE e, int excl,
+ long *begp, long *lenp, long len, int err);
+
#endif /* INTERNAL_RANGE_H */
diff --git a/range.c b/range.c
index c019fcf2e0..a82763ca76 100644
--- a/range.c
+++ b/range.c
@@ -1329,48 +1329,81 @@ rb_range_values(VALUE range, VALUE *begp, VALUE *endp, int *exclp)
return (int)Qtrue;
}
+/* Extract the components of a Range.
+ *
+ * You can use +err+ to control the behavior of out-of-range and exception.
+ *
+ * When +err+ is 0 or 2, if the begin offset is greater than +len+,
+ * it is out-of-range. The +RangeError+ is raised only if +err+ is 2,
+ * in this case. If +err+ is 0, +Qnil+ will be returned.
+ *
+ * When +err+ is 1, the begin and end offsets won't be adjusted even if they
+ * are greater than +len+. It allows +rb_ary_aset+ extends arrays.
+ *
+ * If the begin component of the given range is negative and is too-large
+ * abstract value, the +RangeError+ is raised only +err+ is 1 or 2.
+ *
+ * The case of <code>err = 0</code> is used in item accessing methods such as
+ * +rb_ary_aref+, +rb_ary_slice_bang+, and +rb_str_aref+.
+ *
+ * The case of <code>err = 1</code> is used in Array's methods such as
+ * +rb_ary_aset+ and +rb_ary_fill+.
+ *
+ * The case of <code>err = 2</code> is used in +rb_str_aset+.
+ */
VALUE
-rb_range_beg_len(VALUE range, long *begp, long *lenp, long len, int err)
+rb_range_component_beg_len(VALUE b, VALUE e, int excl,
+ long *begp, long *lenp, long len, int err)
{
long beg, end;
- VALUE b, e;
- int excl;
- if (!rb_range_values(range, &b, &e, &excl))
- return Qfalse;
beg = NIL_P(b) ? 0 : NUM2LONG(b);
end = NIL_P(e) ? -1 : NUM2LONG(e);
if (NIL_P(e)) excl = 0;
if (beg < 0) {
- beg += len;
- if (beg < 0)
- goto out_of_range;
+ beg += len;
+ if (beg < 0)
+ goto out_of_range;
}
if (end < 0)
- end += len;
+ end += len;
if (!excl)
- end++; /* include end point */
+ end++; /* include end point */
if (err == 0 || err == 2) {
- if (beg > len)
- goto out_of_range;
- if (end > len)
- end = len;
+ if (beg > len)
+ goto out_of_range;
+ if (end > len)
+ end = len;
}
len = end - beg;
if (len < 0)
- len = 0;
+ len = 0;
*begp = beg;
*lenp = len;
return Qtrue;
out_of_range:
- if (err) {
- rb_raise(rb_eRangeError, "%+"PRIsVALUE" out of range", range);
- }
return Qnil;
}
+VALUE
+rb_range_beg_len(VALUE range, long *begp, long *lenp, long len, int err)
+{
+ VALUE b, e;
+ int excl;
+
+ if (!rb_range_values(range, &b, &e, &excl))
+ return Qfalse;
+
+ VALUE res = rb_range_component_beg_len(b, e, excl, begp, lenp, len, err);
+ if (NIL_P(res) && err) {
+ rb_raise(rb_eRangeError, "%+"PRIsVALUE" out of range", range);
+ }
+
+ return res;
+}
+
/*
* call-seq:
* rng.to_s -> string
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index bc5d86354e..d6c15b8673 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -1496,9 +1496,46 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[10, 11, 12], a.slice(-91..-89))
assert_equal(@cls[10, 11, 12], a.slice(-91..-89))
+ assert_equal(@cls[5, 8, 11], a.slice((4..12)%3))
+ assert_equal(@cls[95, 97, 99], a.slice((94..)%2))
+
+ # [0] [1] [2] [3] [4] [5] [6] [7]
+ # ary = [ 1 2 3 4 5 6 7 8 ... ]
+ # (0) (1) (2) <- (..7) % 3
+ # (2) (1) (0) <- (7..) % -3
+ assert_equal(@cls[1, 4, 7], a.slice((..7)%3))
+ assert_equal(@cls[8, 5, 2], a.slice((7..)% -3))
+
+ # [-98] [-97] [-96] [-95] [-94] [-93] [-92] [-91] [-90]
+ # ary = [ ... 3 4 5 6 7 8 9 10 11 ... ]
+ # (0) (1) (2) <- (-98..-90) % 3
+ # (2) (1) (0) <- (-90..-98) % -3
+ assert_equal(@cls[3, 6, 9], a.slice((-98..-90)%3))
+ assert_equal(@cls[11, 8, 5], a.slice((-90..-98)% -3))
+
+ # [ 48] [ 49] [ 50] [ 51] [ 52] [ 53]
+ # [-52] [-51] [-50] [-49] [-48] [-47]
+ # ary = [ ... 49 50 51 52 53 54 ... ]
+ # (0) (1) (2) <- (48..-47) % 2
+ # (2) (1) (0) <- (-47..48) % -2
+ assert_equal(@cls[49, 51, 53], a.slice((48..-47)%2))
+ assert_equal(@cls[54, 52, 50], a.slice((-47..48)% -2))
+
+ idx = ((3..90) % 2).to_a
+ assert_equal(@cls[*a.values_at(*idx)], a.slice((3..90)%2))
+ idx = 90.step(3, -2).to_a
+ assert_equal(@cls[*a.values_at(*idx)], a.slice((90 .. 3)% -2))
+ end
+
+ def test_slice_out_of_range
+ a = @cls[*(1..100).to_a]
+
assert_nil(a.slice(-101..-1))
assert_nil(a.slice(-101..))
+ assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.slice((-101..-1)%2) }
+ assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.slice((-101..)%2) }
+
assert_nil(a.slice(10, -3))
assert_equal @cls[], a.slice(10..7)
end
@@ -2414,7 +2451,6 @@ class TestArray < Test::Unit::TestCase
def test_aref
assert_raise(ArgumentError) { [][0, 0, 0] }
- assert_raise(TypeError) { [][(1..10).step(2)] }
end
def test_fetch