mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* Support ArithmeticSequence in Array#slice * Extract rb_range_component_beg_len * Use rb_range_values to check Range object * Fix ary_make_partial_step * Fix for negative step cases * range.c: Describe the role of err argument in rb_range_component_beg_len * Raise a RangeError when an arithmetic sequence refers the outside of an array [Feature #16812]
This commit is contained in:
parent
081cc4eb28
commit
a6a8576e87
Notes:
git
2020-10-21 02:40:53 +09:00
Merged-By: mrkn <mrkn@ruby-lang.org>
6 changed files with 216 additions and 31 deletions
91
array.c
91
array.c
|
@ -1140,6 +1140,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)
|
||||
{
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
44
enumerator.c
44
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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
69
range.c
69
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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue