From 87c39d3c0bf26ea0afb6a5ffecc3ae6b2f1bd5fb Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Mon, 30 Jun 2025 08:19:55 +0900 Subject: [PATCH] mruby-array-ext: implement Array#insert in C This commit also corrects the behavior of `Array#insert` when a negative index is out of bounds. It now raises an `IndexError`, which is consistent with CRuby. Co-authored-by: Gemini --- mrbgems/mruby-array-ext/mrblib/array.rb | 18 -------- mrbgems/mruby-array-ext/src/array.c | 57 +++++++++++++++++++++++ mrbgems/mruby-array-ext/test/array.rb | 61 +++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 23 deletions(-) diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index 9e98e4f06..335f1e35b 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -233,25 +233,7 @@ class Array self.replace(result) end - ## - # call-seq: - # ary.insert(index, obj...) -> ary - # - # Inserts the given values before the element with the given +index+. - # - # Negative indices count backwards from the end of the array, where +-1+ is - # the last element. - # - # a = %w{ a b c d } - # a.insert(2, 99) #=> ["a", "b", 99, "c", "d"] - # a.insert(-2, 1, 2, 3) #=> ["a", "b", 99, "c", 1, 2, 3, "d"] - def insert(idx, *args) - idx = idx.__to_int - idx += self.size + 1 if idx < 0 - self[idx, 0] = args - self - end ## # call-seq: diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c index 974fb2ec3..a05c3268e 100644 --- a/mrbgems/mruby-array-ext/src/array.c +++ b/mrbgems/mruby-array-ext/src/array.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -1136,6 +1137,61 @@ ary_flatten_bang(mrb_state *mrb, mrb_value self) return self; } +/* + * call-seq: + * ary.insert(index, obj...) -> ary + * + * Inserts the given values before the element with the given index. + * + * Negative indices count backwards from the end of the array, where -1 + * is the last element. If a negative index is used, the elements are + * inserted after that element. + * + * If the index is greater than the length of the array, the array is + * extended with nil elements. + * + * a = %w{ a b c d } + * a.insert(2, 99) #=> ["a", "b", 99, "c", "d"] + * a.insert(-2, 1, 2, 3) #=> ["a", "b", 99, "c", 1, 2, 3, "d"] + */ +static mrb_value +ary_insert(mrb_state *mrb, mrb_value self) +{ + mrb_int idx; + const mrb_value *argv; + mrb_int argc; + + mrb_get_args(mrb, "i*", &idx, &argv, &argc); + + if (argc == 0) { + return self; + } + + mrb_int len = RARRAY_LEN(self); + + if (idx < 0) { + idx += len + 1; + if (idx < 0) { + mrb_raisef(mrb, E_INDEX_ERROR, "index %i outside of array bounds", idx - (len + 1)); + } + } + + mrb_ary_modify(mrb, mrb_ary_ptr(self)); + + mrb_int new_len = (idx > len ? idx : len) + argc; + mrb_ary_resize(mrb, self, new_len); + + if (idx < len) { + memmove(RARRAY_PTR(self) + idx + argc, RARRAY_PTR(self) + idx, (len - idx) * sizeof(mrb_value)); + } + + for (mrb_int i = 0; i < argc; i++) { + mrb_ary_set(mrb, self, idx + i, argv[i]); + } + + return self; +} + void mrb_mruby_array_ext_gem_init(mrb_state* mrb) { @@ -1163,6 +1219,7 @@ mrb_mruby_array_ext_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, a, MRB_SYM_B(__uniq), ary_uniq_bang, MRB_ARGS_NONE()); mrb_define_method_id(mrb, a, MRB_SYM(flatten), ary_flatten, MRB_ARGS_OPT(1)); mrb_define_method_id(mrb, a, MRB_SYM_B(flatten), ary_flatten_bang, MRB_ARGS_OPT(1)); + mrb_define_method_id(mrb, a, MRB_SYM(insert), ary_insert, MRB_ARGS_ARG(1, -1)); } void diff --git a/mrbgems/mruby-array-ext/test/array.rb b/mrbgems/mruby-array-ext/test/array.rb index 45038377f..a2185a1d5 100644 --- a/mrbgems/mruby-array-ext/test/array.rb +++ b/mrbgems/mruby-array-ext/test/array.rb @@ -461,12 +461,63 @@ assert("Array#reject!") do end assert("Array#insert") do - a = ["a", "b", "c", "d"] - assert_equal ["a", "b", 99, "c", "d"], a.insert(2, 99) - assert_equal ["a", "b", 99, "c", 1, 2, 3, "d"], a.insert(-2, 1, 2, 3) + # Basic insertion + a = [1, 2, 3] + assert_same a, a.insert(1, 99) + assert_equal [1, 99, 2, 3], a - b = ["a", "b", "c", "d"] - assert_equal ["a", "b", "c", "d", nil, nil, 99], b.insert(6, 99) + # Multiple elements + a = [1, 2, 3] + a.insert(2, 'a', 'b') + assert_equal [1, 2, 'a', 'b', 3], a + + # Negative index + a = [1, 2, 3, 4] + a.insert(-2, 99) + assert_equal [1, 2, 3, 99, 4], a + + # Negative index out of bounds + a = [1, 2, 3] + assert_raise(IndexError) { a.insert(-5, 99) } + assert_equal [1, 2, 3], a + + # Insertion beyond bounds (creates nils) + a = [1, 2] + a.insert(5, 99) + assert_equal [1, 2, nil, nil, nil, 99], a + + # Insertion at the end + a = [1, 2, 3] + a.insert(3, 99) + assert_equal [1, 2, 3, 99], a + + # Insertion into an empty array + a = [] + a.insert(0, 1, 2) + assert_equal [1, 2], a + + # Insertion into an empty array at a non-zero index + a = [] + a.insert(2, 99) + assert_equal [nil, nil, 99], a + + # No-op (inserting zero elements) + a = [1, 2, 3] + a.insert(1) + assert_equal [1, 2, 3], a + + # Return value is self + a = [1, 2, 3] + b = a.insert(1, 99) + assert_same a, b + + # Large array insertion + a = (0...1000).to_a + a.insert(500, "x") + assert_equal 1001, a.size + assert_equal "x", a[500] + assert_equal 499, a[499] + assert_equal 500, a[501] end assert("Array#bsearch") do