ruby-changes:63393
From: Kenta <ko1@a...>
Date: Wed, 21 Oct 2020 02:40:40 +0900 (JST)
Subject: [ruby-changes:63393] a6a8576e87 (master): Feature #16812: Allow slicing arrays with ArithmeticSequence (#3241)
https://git.ruby-lang.org/ruby.git/commit/?id=a6a8576e87 From a6a8576e877b02b83cabd0e712ecd377e7bc156b Mon Sep 17 00:00:00 2001 From: Kenta Murata <mrkn@u...> Date: Wed, 21 Oct 2020 02:40:18 +0900 Subject: Feature #16812: Allow slicing arrays with ArithmeticSequence (#3241) * 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] diff --git a/array.c b/array.c index 0373c1e..9183dfc 100644 --- a/array.c +++ b/array.c @@ -1141,6 +1141,52 @@ ary_make_partial(VALUE ary, VALUE klass, long offset, long len) https://github.com/ruby/ruby/blob/trunk/array.c#L1141 } 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) https://github.com/ruby/ruby/blob/trunk/array.c#L1617 } 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) https://github.com/ruby/ruby/blob/trunk/array.c#L1630 } 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); https://github.com/ruby/ruby/blob/trunk/array.c#L1651 * 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); https://github.com/ruby/ruby/blob/trunk/array.c#L1712 * 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) https://github.com/ruby/ruby/blob/trunk/array.c#L1753 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 3ea308a..6e88c5d 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3410,17 +3410,53 @@ rb_arithmetic_sequence_extract(VALUE obj, rb_arithmetic_sequence_components_t *c https://github.com/ruby/ruby/blob/trunk/enumerator.c#L3410 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 7698e24..c814851 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 *); https://github.com/ruby/ruby/blob/trunk/include/ruby/internal/intern/enumerator.h#L42 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 0b60f42..4fe6037 100644 --- a/internal/range.h +++ b/internal/range.h @@ -34,4 +34,8 @@ RANGE_EXCL(VALUE r) https://github.com/ruby/ruby/blob/trunk/internal/range.h#L34 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 c019fcf..a82763c 100644 --- a/range.c +++ b/range.c @@ -1329,48 +1329,81 @@ rb_range_values(VALUE range, VALUE *begp, VALUE *endp, int *exclp) https://github.com/ruby/ruby/blob/trunk/range.c#L1329 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; - V (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/