Files
mruby-mruby/mrbgems/mruby-array-ext/src/array.c
T
Yukihiro "Matz" Matsumoto 93619f06dd mruby-array-ext: validate start and length in fill operation; fix #6650
add validation to prevent out-of-bounds write when negative start or
length bypasses normalization

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-23 11:03:11 +09:00

1492 lines
40 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <mruby.h>
#include <string.h>
#include <mruby/value.h>
#include <mruby/array.h>
#include <mruby/range.h>
#include <mruby/hash.h>
#include <mruby/data.h>
#include <mruby/internal.h>
#include <mruby/presym.h>
#include <mruby/khash.h>
/* khash set for temporary array operations */
static inline khint_t
ary_set_hash_func(mrb_state *mrb, mrb_value key)
{
return (khint_t)mrb_obj_hash_code(mrb, key);
}
static inline mrb_bool
ary_set_equal_func(mrb_state *mrb, mrb_value a, mrb_value b)
{
return mrb_eql(mrb, a, b);
}
KHASH_DECLARE(ary_set, mrb_value, char, 0)
KHASH_DEFINE(ary_set, mrb_value, char, 0, ary_set_hash_func, ary_set_equal_func)
typedef khash_t(ary_set) ary_set_t;
/* Combination state structure for repeated_combination optimization */
struct mrb_combination_state {
mrb_int *indices;
mrb_int n;
mrb_int array_size;
mrb_bool permutation;
mrb_bool finished;
};
static void
mrb_combination_state_free(mrb_state *mrb, void *ptr)
{
struct mrb_combination_state *state = (struct mrb_combination_state*)ptr;
if (state) {
if (state->indices) {
mrb_free(mrb, state->indices);
}
mrb_free(mrb, state);
}
}
static struct mrb_data_type mrb_combination_state_type = {
"CombinationState", mrb_combination_state_free
};
/*
* call-seq:
* ary.assoc(obj) -> new_ary or nil
*
* Searches through an array whose elements are also arrays
* comparing _obj_ with the first element of each contained array
* using obj.==.
* Returns the first contained array that matches (that
* is, the first associated array),
* or `nil` if no match is found.
* See also `Array#rassoc`.
*
* s1 = [ "colors", "red", "blue", "green" ]
* s2 = [ "letters", "a", "b", "c" ]
* s3 = "foo"
* a = [ s1, s2, s3 ]
* a.assoc("letters") #=> [ "letters", "a", "b", "c" ]
* a.assoc("foo") #=> nil
*/
static mrb_value
ary_assoc(mrb_state *mrb, mrb_value ary)
{
mrb_int i;
mrb_value v;
mrb_value k = mrb_get_arg1(mrb);
/* Hoist pointer retrieval outside loop */
mrb_value *ptr = RARRAY_PTR(ary);
for (i = 0; i < RARRAY_LEN(ary); i++) {
v = mrb_check_array_type(mrb, ptr[i]);
if (!mrb_nil_p(v) && RARRAY_LEN(v) > 0 &&
mrb_equal(mrb, RARRAY_PTR(v)[0], k))
return v;
}
return mrb_nil_value();
}
/*
* call-seq:
* ary.rassoc(obj) -> new_ary or nil
*
* Searches through the array whose elements are also arrays. Compares
* _obj_ with the second element of each contained array using
* `==`. Returns the first contained array that matches. See
* also `Array#assoc`.
*
* a = [ [ 1, "one"], [2, "two"], [3, "three"], ["ii", "two"] ]
* a.rassoc("two") #=> [2, "two"]
* a.rassoc("four") #=> nil
*/
static mrb_value
ary_rassoc(mrb_state *mrb, mrb_value ary)
{
mrb_int i;
mrb_value v;
mrb_value value = mrb_get_arg1(mrb);
/* Hoist pointer retrieval outside loop */
mrb_value *ptr = RARRAY_PTR(ary);
for (i = 0; i < RARRAY_LEN(ary); i++) {
v = ptr[i];
if (mrb_array_p(v) &&
RARRAY_LEN(v) > 1 &&
mrb_equal(mrb, RARRAY_PTR(v)[1], value))
return v;
}
return mrb_nil_value();
}
/*
* call-seq:
* ary.at(index) -> obj or nil
*
* Returns the element at _index_. A
* negative index counts from the end of `self`. Returns `nil`
* if the index is out of range. See also `Array#[]`.
*
* a = [ "a", "b", "c", "d", "e" ]
* a.at(0) #=> "a"
* a.at(-1) #=> "e"
*/
static mrb_value
ary_at(mrb_state *mrb, mrb_value ary)
{
mrb_int pos = mrb_as_int(mrb, mrb_get_arg1(mrb));
return mrb_ary_entry(ary, pos);
}
/* Helper function for values_at - returns element at index n */
static mrb_value
ary_ref(mrb_state *mrb, mrb_value ary, mrb_int n)
{
return mrb_ary_entry(ary, n);
}
/*
* call-seq:
* ary.values_at(selector, ...) -> new_ary
*
* Returns an array containing the elements in `self` corresponding to the
* given `selector`(s). The selectors may be either integer indices or ranges.
*
* a = %w{ a b c d e f }
* a.values_at(1, 3, 5) # => ["b", "d", "f"]
* a.values_at(1, 3, 5, 7) # => ["b", "d", "f", nil]
* a.values_at(-1, -2, -2, -7) # => ["f", "e", "e", nil]
* a.values_at(4..6, 3...6) # => ["e", "f", nil, "d", "e", "f"]
*/
static mrb_value
ary_values_at(mrb_state *mrb, mrb_value self)
{
mrb_int argc = mrb_get_argc(mrb);
const mrb_value *argv = mrb_get_argv(mrb);
return mrb_get_values_at(mrb, self, RARRAY_LEN(self), argc, argv, ary_ref);
}
mrb_value mrb_ary_delete_at(mrb_state *mrb, mrb_value self);
/*
* call-seq:
* ary.slice!(index) -> obj or nil
* ary.slice!(start, length) -> new_ary or nil
* ary.slice!(range) -> new_ary or nil
*
* Deletes the element(s) given by an `index` (optionally up to `length`
* elements) or by a `range`.
*
* Returns the deleted object (or objects), or `nil` if the `index` is out of
* range.
*
* a = [ "a", "b", "c" ]
* a.slice!(1) #=> "b"
* a #=> ["a", "c"]
* a.slice!(-1) #=> "c"
* a #=> ["a"]
* a.slice!(100) #=> nil
* a #=> ["a"]
*/
static mrb_value
ary_slice_bang(mrb_state *mrb, mrb_value self)
{
struct RArray *a = mrb_ary_ptr(self);
mrb_int i, j, len, alen;
mrb_value ary;
mrb_ary_modify(mrb, a);
if (mrb_get_argc(mrb) == 1) {
mrb_value index = mrb_get_arg1(mrb);
if (mrb_type(index) == MRB_TT_RANGE) {
if (mrb_range_beg_len(mrb, index, &i, &len, ARY_LEN(a), TRUE) == MRB_RANGE_OK) {
goto delete_pos_len;
}
return mrb_nil_value();
}
return mrb_ary_delete_at(mrb, self);
}
mrb_get_args(mrb, "ii", &i, &len);
delete_pos_len:
alen = ARY_LEN(a);
if (i < 0) i += alen;
if (i < 0 || alen < i) return mrb_nil_value();
if (len < 0) return mrb_nil_value();
if (alen == i) return mrb_ary_new(mrb);
if (len > alen - i) len = alen - i;
ary = mrb_ary_new_from_values(mrb, len, ARY_PTR(a) + i);
/* refresh pointer after mrb_ary_new_from_values */
a = mrb_ary_ptr(self);
for (j = i; j < alen - len; j++) {
ARY_PTR(a)[j] = ARY_PTR(a)[j+len];
}
mrb_ary_resize(mrb, self, alen - len);
return ary;
}
/*
* call-seq:
* ary.compact! -> ary or nil
*
* Removes `nil` elements from the array.
* Returns `nil` if no changes were made, otherwise returns
* *ary*.
*
* [ "a", nil, "b", nil, "c" ].compact! #=> [ "a", "b", "c" ]
* [ "a", "b", "c" ].compact! #=> nil
*/
static mrb_value
ary_compact_bang(mrb_state *mrb, mrb_value self)
{
struct RArray *a = mrb_ary_ptr(self);
mrb_int i, j = 0;
mrb_int len = ARY_LEN(a);
mrb_ary_modify(mrb, a);
/* a is still valid here, as mrb_ary_modify only modifies the RArray struct, not reallocates it */
/* Hoist pointer retrieval outside loop to avoid repeated conditionals */
mrb_value *ptr = RARRAY_PTR(self);
for (i = 0; i < len; i++) {
if (!mrb_nil_p(ptr[i])) {
if (i != j) ptr[j] = ptr[i];
j++;
}
}
if (i == j) return mrb_nil_value();
ARY_SET_LEN(RARRAY(self), j);
return self;
}
/*
* call-seq:
* ary.compact -> new_ary
*
* Returns a copy of `self` with all `nil` elements removed.
*
* [ "a", nil, "b", nil, "c", nil ].compact
* #=> [ "a", "b", "c" ]
*/
static mrb_value
ary_compact(mrb_state *mrb, mrb_value self)
{
mrb_value ary = mrb_ary_dup(mrb, self);
ary_compact_bang(mrb, ary);
return ary;
}
/*
* call-seq:
* ary.rotate(count=1) -> new_ary
*
* Returns a new array by rotating `self` so that the element at `count` is
* the first element of the new array.
*
* If `count` is negative then it rotates in the opposite direction, starting
* from the end of `self` where +-1+ is the last element.
*
* a = [ "a", "b", "c", "d" ]
* a.rotate #=> ["b", "c", "d", "a"]
* a #=> ["a", "b", "c", "d"]
* a.rotate(2) #=> ["c", "d", "a", "b"]
* a.rotate(-3) #=> ["b", "c", "d", "a"]
*/
static mrb_value
ary_rotate(mrb_state *mrb, mrb_value self)
{
mrb_int count=1;
mrb_get_args(mrb, "|i", &count);
mrb_value ary = mrb_ary_new(mrb);
mrb_int len = RARRAY_LEN(self);
mrb_int idx;
if (len <= 0) return ary;
if (count < 0) {
idx = len - (~count % len) - 1;
}
else {
idx = count % len;
}
/* Hoist pointer retrieval outside loop */
mrb_value *ptr = RARRAY_PTR(self);
for (mrb_int i = 0; i<len; i++) {
mrb_ary_push(mrb, ary, ptr[idx++]);
if (idx == len) idx = 0;
}
return ary;
}
/* Helper function to reverse array elements in-place between beg and end indices */
static void
rev(mrb_value *p, mrb_int beg, mrb_int end)
{
for (mrb_int i=beg,j=end-1; i<j; i++,j--) {
mrb_value v = p[i];
p[i] = p[j];
p[j] = v;
}
}
/*
* call-seq:
* ary.rotate!(count=1) -> ary
*
* Rotates `self` in place so that the element at `count` comes first, and
* returns `self`.
*
* If `count` is negative then it rotates in the opposite direction, starting
* from the end of the array where `-1` is the last element.
*
* a = [ "a", "b", "c", "d" ]
* a.rotate! #=> ["b", "c", "d", "a"]
* a #=> ["b", "c", "d", "a"]
* a.rotate!(2) #=> ["d", "a", "b", "c"]
* a.rotate!(-3) #=> ["a", "b", "c", "d"]
*/
static mrb_value
ary_rotate_bang(mrb_state *mrb, mrb_value self)
{
mrb_int count=1;
mrb_get_args(mrb, "|i", &count);
struct RArray *a = mrb_ary_ptr(self);
mrb_int len = ARY_LEN(a);
mrb_int idx;
mrb_ary_modify(mrb, a);
mrb_value *p = ARY_PTR(a);
if (len == 0 || count == 0) return self;
if (count == 1) {
mrb_value v = p[0];
for (mrb_int i=1; i<len; i++) {
p[i-1] = p[i];
}
p[len-1] = v;
return self;
}
if (count < 0) {
idx = len - (~count % len) - 1;
}
else {
idx = count % len;
}
/* e.g. [1,2,3,4,5].rotate!(2) -> [3,4,5,1,2] */
/* first, reverse the whole array */
/* [1,2,3,4,5] -> [5,4,3,2,1] */
rev(p, 0, len);
/* then, re-reverse part before idx */
/* [5,4,3,2,1] -> [3,4,5,2,1] */
/* ^idx ~~~~~ */
rev(p, 0, len-idx);
/* finally, re-reverse part after idx */
/* [3,4,5,2,1] -> [3,4,5,1,2] */
/* ^idx ~~~ */
rev(p, len-idx, len);
return self;
}
#define SET_OP_HASH_THRESHOLD 32
/* Helper functions for temporary khash sets */
static void
ary_init_temp_set(mrb_state *mrb, ary_set_t *set, mrb_int capacity)
{
kh_init_data(ary_set, mrb, set, (khint_t)(capacity > 0 ? capacity : 8));
}
static void
ary_populate_temp_set(mrb_state *mrb, ary_set_t *set, mrb_value ary)
{
mrb_int len = RARRAY_LEN(ary);
for (mrb_int i = 0; i < len; i++) {
kh_put(ary_set, mrb, set, RARRAY_PTR(ary)[i]);
}
}
static void
ary_destroy_temp_set(mrb_state *mrb, ary_set_t *set)
{
if (set) {
kh_destroy_data(ary_set, mrb, set);
}
}
static mrb_int
ary_get_array_args(mrb_state *mrb, mrb_int argc, const mrb_value **argv_ptr)
{
mrb_int total_len = 0;
const mrb_value *argv = *argv_ptr;
mrb_value *converted_argv = (mrb_value *)mrb_alloca(mrb, sizeof(mrb_value) * argc);
for (mrb_int i = 0; i < argc; i++) {
mrb_value other = mrb_check_array_type(mrb, argv[i]);
if (mrb_nil_p(other)) {
mrb_raise(mrb, E_TYPE_ERROR, "can't convert passed argument to Array");
}
converted_argv[i] = other;
total_len += RARRAY_LEN(other);
}
*argv_ptr = converted_argv;
return total_len;
}
static mrb_value
ary_subtract_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_value *argv)
{
if (argc == 0) {
return mrb_ary_dup(mrb, self);
}
mrb_int total_len = ary_get_array_args(mrb, argc, &argv);
mrb_value result = mrb_ary_new(mrb);
if (total_len > SET_OP_HASH_THRESHOLD) {
ary_set_t set_struct;
ary_set_t *set = &set_struct;
ary_init_temp_set(mrb, set, total_len);
for (mrb_int i = 0; i < argc; i++) {
ary_populate_temp_set(mrb, set, argv[i]);
}
mrb_int self_len = RARRAY_LEN(self);
/* Hoist pointer retrieval outside loop */
mrb_value *self_ptr = RARRAY_PTR(self);
for (mrb_int i = 0; i < self_len; i++) {
mrb_value p = self_ptr[i];
khiter_t k = kh_get(ary_set, mrb, set, p);
if (k == kh_end(set)) { /* key doesn't exist in any ary */
mrb_ary_push(mrb, result, p);
}
}
ary_destroy_temp_set(mrb, set);
}
else {
mrb_int self_len = RARRAY_LEN(self);
/* Hoist pointer retrieval outside outer loop */
mrb_value *self_ptr = RARRAY_PTR(self);
for (mrb_int i = 0; i < self_len; i++) {
mrb_value p = self_ptr[i];
mrb_bool found = FALSE;
for (mrb_int j = 0; j < argc; j++) {
mrb_int len = RARRAY_LEN(argv[j]);
/* Hoist pointer retrieval outside inner loop */
mrb_value *argv_ptr = RARRAY_PTR(argv[j]);
for (mrb_int k = 0; k < len; k++) {
if (mrb_equal(mrb, p, argv_ptr[k])) {
found = TRUE;
break;
}
}
if (found) break;
}
if (!found) {
mrb_ary_push(mrb, result, p);
}
}
}
return result;
}
/*
* call-seq:
* ary - other_ary -> new_ary
*
* Returns a new array that is a copy of the original array, with any items
* that also appear in `other_ary` removed.
*
* [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
*/
static mrb_value
ary_sub(mrb_state *mrb, mrb_value self)
{
mrb_value other;
mrb_get_args(mrb, "A", &other);
return ary_subtract_internal(mrb, self, 1, &other);
}
/*
* call-seq:
* ary.difference(other_ary, ...) -> new_ary
*
* Returns a new array that is a copy of the original array, removing all
* occurrences of any item that also appear in any of the `other_ary`s.
* The order is preserved from the original array.
*
* [1, 2, 3, 4, 5].difference([2, 4], [1, 5]) #=> [3]
*/
static mrb_value
ary_difference(mrb_state *mrb, mrb_value self)
{
const mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
return ary_subtract_internal(mrb, self, argc, argv);
}
static void
add_uniq(mrb_state *mrb, mrb_value item, mrb_value result)
{
const mrb_int len = RARRAY_LEN(result);
for (mrb_int i = 0; i < len; i++) {
if (mrb_eql(mrb, item, RARRAY_PTR(result)[i])) {
return;
}
}
mrb_ary_push(mrb, result, item);
}
static mrb_value
ary_union_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_value *argv)
{
mrb_int total_len = ary_get_array_args(mrb, argc, &argv) + RARRAY_LEN(self);
mrb_value result = mrb_ary_new(mrb);
if (total_len > SET_OP_HASH_THRESHOLD) {
ary_set_t set_struct;
ary_set_t *set = &set_struct;
ary_init_temp_set(mrb, set, total_len);
/* Add unique elements from self */
mrb_int alen = RARRAY_LEN(self);
/* Hoist pointer retrieval outside loop */
mrb_value *self_ptr = RARRAY_PTR(self);
for (mrb_int i = 0; i < alen; i++) {
mrb_value elem = self_ptr[i];
khiter_t k = kh_get(ary_set, mrb, set, elem);
if (k == kh_end(set)) {
kh_put(ary_set, mrb, set, elem);
mrb_ary_push(mrb, result, elem);
}
}
/* Add unique elements from others */
for (mrb_int i = 0; i < argc; i++) {
mrb_value other = argv[i];
mrb_int olen = RARRAY_LEN(other);
/* Hoist pointer retrieval outside inner loop */
mrb_value *other_ptr = RARRAY_PTR(other);
for (mrb_int j = 0; j < olen; j++) {
mrb_value elem = other_ptr[j];
khiter_t k = kh_get(ary_set, mrb, set, elem);
if (k == kh_end(set)) {
kh_put(ary_set, mrb, set, elem);
mrb_ary_push(mrb, result, elem);
}
}
}
ary_destroy_temp_set(mrb, set);
}
else {
/* Use linear search for small arrays */
/* Add unique elements from self */
mrb_int alen = RARRAY_LEN(self);
/* Hoist pointer retrieval outside loop */
mrb_value *self_ptr = RARRAY_PTR(self);
for (mrb_int i = 0; i < alen; i++) {
add_uniq(mrb, self_ptr[i], result);
}
/* Add unique elements from others */
for (mrb_int i = 0; i < argc; i++) {
mrb_value other = argv[i];
mrb_int olen = RARRAY_LEN(other);
/* Hoist pointer retrieval outside inner loop */
mrb_value *other_ptr = RARRAY_PTR(other);
for (mrb_int j = 0; j < olen; j++) {
add_uniq(mrb, other_ptr[j], result);
}
}
}
return result;
}
/*
* call-seq:
* ary | other_ary -> new_ary
*
* Set Union---Returns a new array by joining this array with
* `other_ary`, removing duplicates.
*
* [ "a", "b", "c" ] | [ "c", "d", "a" ]
* #=> [ "a", "b", "c", "d" ]
*/
static mrb_value
ary_union(mrb_state *mrb, mrb_value self)
{
mrb_value other;
mrb_get_args(mrb, "A", &other);
return ary_union_internal(mrb, self, 1, &other);
}
/*
* call-seq:
* ary.union(other_ary,...) -> new_ary
*
* Set Union---Returns a new array by joining this array with
* `other_ary`s, removing duplicates.
*
* ["a", "b", "c"].union(["c", "d", "a"], ["a", "c", "e"])
* #=> ["a", "b", "c", "d", "e"]
*/
static mrb_value
ary_union_multi(mrb_state *mrb, mrb_value self)
{
const mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
return ary_union_internal(mrb, self, argc, argv);
}
static mrb_value
ary_intersection_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_value *argv)
{
if (argc == 0) {
return mrb_ary_new(mrb);
}
mrb_int total_len = ary_get_array_args(mrb, argc, &argv);
mrb_value result = mrb_ary_new(mrb);
if (total_len > SET_OP_HASH_THRESHOLD) {
ary_set_t set_struct;
ary_set_t *set = &set_struct;
ary_init_temp_set(mrb, set, total_len);
for (mrb_int i = 0; i < argc; i++) {
ary_populate_temp_set(mrb, set, argv[i]);
}
mrb_int self_len = RARRAY_LEN(self);
/* Hoist pointer retrieval outside loop */
mrb_value *self_ptr = RARRAY_PTR(self);
for (mrb_int i = 0; i < self_len; i++) {
mrb_value p = self_ptr[i];
khiter_t k = kh_get(ary_set, mrb, set, p);
if (k != kh_end(set)) {
mrb_ary_push(mrb, result, p);
kh_del(ary_set, mrb, set, k);
}
}
ary_destroy_temp_set(mrb, set);
}
else {
mrb_int self_len = RARRAY_LEN(self);
/* Hoist pointer retrieval outside outer loop */
mrb_value *self_ptr = RARRAY_PTR(self);
for (mrb_int i = 0; i < self_len; i++) {
mrb_value p = self_ptr[i];
mrb_bool found_in_all = TRUE;
for (mrb_int j = 0; j < argc; j++) {
mrb_bool found_in_current_other = FALSE;
mrb_int len = RARRAY_LEN(argv[j]);
/* Hoist pointer retrieval outside inner loop */
mrb_value *argv_ptr = RARRAY_PTR(argv[j]);
for (mrb_int k = 0; k < len; k++) {
if (mrb_equal(mrb, p, argv_ptr[k])) {
found_in_current_other = TRUE;
break;
}
}
if (!found_in_current_other) {
found_in_all = FALSE;
break;
}
}
if (found_in_all) {
mrb_bool already_added = FALSE;
mrb_int result_len = RARRAY_LEN(result);
/* Hoist pointer retrieval outside loop */
mrb_value *result_ptr = RARRAY_PTR(result);
for (mrb_int j = 0; j < result_len; j++) {
if (mrb_equal(mrb, p, result_ptr[j])) {
already_added = TRUE;
break;
}
}
if (!already_added) {
mrb_ary_push(mrb, result, p);
}
}
}
}
return result;
}
/*
* call-seq:
* ary & other_ary -> new_ary
*
* Set Intersection---Returns a new array
* containing elements common to the two arrays, with no duplicates.
*
* [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
*/
static mrb_value
ary_intersection(mrb_state *mrb, mrb_value self)
{
mrb_value other;
mrb_get_args(mrb, "A", &other);
return ary_intersection_internal(mrb, self, 1, &other);
}
/*
* call-seq:
* ary.intersection(other_ary,...) -> new_ary
*
* Set Intersection---Returns a new array containing elements common to
* this array and `other_ary`s, removing duplicates. The order is
* preserved from the original array.
*
* [1, 2, 3].intersection([3, 4, 1], [1, 3, 5]) #=> [1, 3]
*/
static mrb_value
ary_intersection_multi(mrb_state *mrb, mrb_value self)
{
const mrb_value *argv;
mrb_int argc;
mrb_get_args(mrb, "*", &argv, &argc);
return ary_intersection_internal(mrb, self, argc, argv);
}
/*
* call-seq:
* ary.intersect?(other_ary) -> true or false
*
* Returns `true` if the array and `other_ary` have at least one element in
* common, otherwise returns `false`.
*
* a = [ 1, 2, 3 ]
* b = [ 3, 4, 5 ]
* c = [ 5, 6, 7 ]
* a.intersect?(b) #=> true
* a.intersect?(c) #=> false
*/
static mrb_value
ary_intersect_p(mrb_state *mrb, mrb_value self)
{
mrb_value other;
mrb_get_args(mrb, "A", &other);
mrb_value shorter_ary, longer_ary;
if (RARRAY_LEN(self) > RARRAY_LEN(other)) {
shorter_ary = other;
longer_ary = self;
}
else {
shorter_ary = self;
longer_ary = other;
}
if (RARRAY_LEN(shorter_ary) == 0 || RARRAY_LEN(longer_ary) == 0) {
return mrb_false_value();
}
if (RARRAY_LEN(shorter_ary) > SET_OP_HASH_THRESHOLD) {
ary_set_t set_struct;
ary_set_t *set = &set_struct;
ary_init_temp_set(mrb, set, RARRAY_LEN(shorter_ary));
ary_populate_temp_set(mrb, set, shorter_ary);
mrb_int longer_len = RARRAY_LEN(longer_ary);
for (mrb_int i = 0; i < longer_len; i++) {
khiter_t k = kh_get(ary_set, mrb, set, RARRAY_PTR(longer_ary)[i]);
if (k != kh_end(set)) {
ary_destroy_temp_set(mrb, set);
return mrb_true_value();
}
}
ary_destroy_temp_set(mrb, set);
}
else {
mrb_int longer_len = RARRAY_LEN(longer_ary);
mrb_int shorter_len = RARRAY_LEN(shorter_ary);
/* Hoist pointer retrieval outside loops to avoid O(n×m) conditionals */
mrb_value *longer_ptr = RARRAY_PTR(longer_ary);
mrb_value *shorter_ptr = RARRAY_PTR(shorter_ary);
for (mrb_int i = 0; i < longer_len; i++) {
for (mrb_int j = 0; j < shorter_len; j++) {
if (mrb_equal(mrb, longer_ptr[i], shorter_ptr[j])) {
return mrb_true_value();
}
}
}
}
return mrb_false_value();
}
/*
* Internal helper for Array#fill that handles all the complex
* argument parsing logic including ranges, negative indices, etc.
* Returns normalized [start, length] array for use by ary_fill_exec.
*/
static mrb_value
ary_fill_parse_arg(mrb_state *mrb, mrb_value self)
{
mrb_value arg0 = mrb_nil_value(), arg1 = mrb_nil_value(), arg2 = mrb_nil_value();
mrb_value block = mrb_nil_value();
mrb_int argc;
argc = mrb_get_args(mrb, "|ooo&", &arg0, &arg1, &arg2, &block);
struct RArray *ary = mrb_ary_ptr(self);
mrb_int ary_len = ARY_LEN(ary);
mrb_int start = 0, length = 0;
if (!mrb_nil_p(block)) {
if (argc == 0 || (argc >= 1 && mrb_nil_p(arg0))) {
/* fill { |index| block } */
start = 0;
length = ary_len;
}
else if (argc >= 1 && mrb_range_p(arg0)) {
/* fill(range) { |index| block } */
mrb_int range_beg, range_end;
if (mrb_range_beg_len(mrb, arg0, &range_beg, &range_end, ary_len, 1)) {
start = range_beg;
length = range_end;
}
}
else if (argc >= 1 && !mrb_nil_p(arg0)) {
/* fill(start [, length]) { |index| block } */
start = mrb_int(mrb, arg0);
if (start < 0) start += ary_len;
if (start < 0) start = 0;
if (argc == 1 || mrb_nil_p(arg1)) {
length = ary_len - start;
}
else {
length = mrb_int(mrb, arg1);
if (length < 0) length = 0;
}
}
}
else {
if (argc >= 1 && !mrb_nil_p(arg0)) {
if (argc == 1 || (argc >= 2 && mrb_nil_p(arg1) && mrb_nil_p(arg2))) {
/* fill(obj) */
start = 0;
length = ary_len;
}
else if (argc >= 2 && mrb_range_p(arg1)) {
/* fill(obj, range) */
mrb_int range_beg, range_end;
if (mrb_range_beg_len(mrb, arg1, &range_beg, &range_end, ary_len, 1)) {
start = range_beg;
length = range_end;
}
}
else if (argc >= 2 && !mrb_nil_p(arg1)) {
/* fill(obj, start [, length]) */
start = mrb_int(mrb, arg1);
if (start < 0) start += ary_len;
if (start < 0) start = 0;
if (argc == 2 || mrb_nil_p(arg2)) {
length = ary_len - start;
}
else {
length = mrb_int(mrb, arg2);
if (length < 0) length = 0;
}
}
}
}
/* Return [start, length] array */
mrb_value result = mrb_ary_new_capa(mrb, 2);
mrb_ary_push(mrb, result, mrb_fixnum_value(start));
mrb_ary_push(mrb, result, mrb_fixnum_value(length));
return result;
}
/*
* Internal helper that fills a specific range of the array
* with the given object. Handles array extension if necessary.
* Used by Ruby-level Array#fill method.
*/
static mrb_value
ary_fill_exec(mrb_state *mrb, mrb_value self)
{
mrb_value obj;
mrb_int start, length;
mrb_get_args(mrb, "iio", &start, &length, &obj);
if (start < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "negative start index");
}
if (length < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "negative length");
}
struct RArray *ary = mrb_ary_ptr(self);
mrb_int ary_len = ARY_LEN(ary);
/* Extend array if necessary */
if (start + length > ary_len) {
mrb_ary_resize(mrb, self, start + length);
ary = mrb_ary_ptr(self); /* refresh pointer after resize */
}
/* Ensure we don't go beyond array bounds */
if (start >= ARY_LEN(ary) || length <= 0) return self;
if (start + length > ARY_LEN(ary)) {
length = ARY_LEN(ary) - start;
}
/* Fill the array */
mrb_value *ptr = ARY_PTR(ary) + start;
for (mrb_int i = 0; i < length; i++) {
ptr[i] = obj;
}
return self;
}
/*
* Internal helper for Array#uniq! without blocks.
* Modifies array in-place, returns nil if no changes.
*/
static mrb_value
ary_uniq_bang(mrb_state *mrb, mrb_value self)
{
mrb_int len = RARRAY_LEN(self);
if (len <= 1) {
return mrb_nil_value();
}
mrb_ary_modify(mrb, mrb_ary_ptr(self));
mrb_int write_pos = 0;
if (len > SET_OP_HASH_THRESHOLD) {
ary_set_t set_struct;
ary_set_t *set = &set_struct;
ary_init_temp_set(mrb, set, len);
ary_populate_temp_set(mrb, set, self);
/* Hoist pointer retrieval outside loop to avoid repeated conditionals */
mrb_value *ptr = RARRAY_PTR(self);
for (mrb_int read_pos = 0; read_pos < len; read_pos++) {
mrb_value elem = ptr[read_pos];
khiter_t k = kh_get(ary_set, mrb, set, elem);
if (k != kh_end(set)) {
if (write_pos != read_pos) {
ptr[write_pos] = elem;
}
write_pos++;
kh_del(ary_set, mrb, set, k);
}
}
ary_destroy_temp_set(mrb, set);
}
else {
/* Hoist pointer retrieval outside loop to avoid O(n²) conditionals */
mrb_value *ptr = RARRAY_PTR(self);
for (mrb_int read_pos = 0; read_pos < len; read_pos++) {
mrb_value elem = ptr[read_pos];
mrb_bool found = FALSE;
for (mrb_int j = 0; j < write_pos; j++) {
if (mrb_equal(mrb, elem, ptr[j])) {
found = TRUE;
break;
}
}
if (!found) {
if (write_pos != read_pos) {
ptr[write_pos] = elem;
}
write_pos++;
}
}
}
if (write_pos == len) {
return mrb_nil_value();
}
mrb_ary_resize(mrb, self, write_pos);
return self;
}
/*
* Internal helper for Array#uniq without blocks.
* Uses hash-based deduplication for large arrays,
* linear search for small arrays.
*/
static mrb_value
ary_uniq(mrb_state *mrb, mrb_value self)
{
mrb_value ary = mrb_ary_dup(mrb, self);
ary_uniq_bang(mrb, ary);
return ary;
}
/* Internal helper for flatten operations using iterative stack-based approach */
static mrb_value
flatten_internal(mrb_state *mrb, mrb_value self, mrb_int level, mrb_bool *modified)
{
*modified = FALSE;
mrb_value result = mrb_ary_new(mrb);
mrb_value stack = mrb_ary_new(mrb);
mrb_ary_push(mrb, stack, self);
mrb_ary_push(mrb, stack, mrb_fixnum_value(0)); // index
mrb_ary_push(mrb, stack, mrb_fixnum_value(1)); // depth
while (RARRAY_LEN(stack) > 0) {
mrb_int depth = mrb_fixnum(mrb_ary_pop(mrb, stack));
mrb_int idx = mrb_fixnum(mrb_ary_pop(mrb, stack));
mrb_value ary = mrb_ary_pop(mrb, stack);
while (idx < RARRAY_LEN(ary)) {
mrb_value e = mrb_ary_entry(ary, idx);
idx++;
if (mrb_array_p(e) && (level < 0 || depth <= level)) {
*modified = TRUE;
// Push current state back
mrb_ary_push(mrb, stack, ary);
mrb_ary_push(mrb, stack, mrb_fixnum_value(idx));
mrb_ary_push(mrb, stack, mrb_fixnum_value(depth));
// Push new array to process
ary = e;
idx = 0;
depth++;
}
else {
mrb_ary_push(mrb, result, e);
}
}
}
return result;
}
/*
* call-seq:
* ary.flatten -> new_ary
* ary.flatten(level) -> new_ary
*
* Returns a new array that is a one-dimensional flattening of this
* array (recursively). That is, for every element that is an array,
* extract its elements into the new array. If the optional
* `level` argument determines the level of recursion to flatten.
*
* s = [ 1, 2, 3 ] #=> [1, 2, 3]
* t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]]
* a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
* a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
* a = [ 1, 2, [3, [4, 5] ] ]
* a.flatten(1) #=> [1, 2, 3, [4, 5]]
*/
static mrb_value
ary_flatten(mrb_state *mrb, mrb_value self)
{
mrb_int level = -1;
mrb_get_args(mrb, "|i", &level);
mrb_bool modified; // dummy
return flatten_internal(mrb, self, level, &modified);
}
/*
* Internal helper for index normalization and bounds checking.
* Returns normalized index if in bounds, nil if out of bounds.
* Used by Ruby-level array methods.
*/
static mrb_value
ary_normalize_index(mrb_state *mrb, mrb_value self)
{
mrb_value index_val;
mrb_get_args(mrb, "o", &index_val);
mrb_int index = mrb_as_int(mrb, index_val);
struct RArray *ary = mrb_ary_ptr(self);
mrb_int len = ARY_LEN(ary);
// Handle negative indices
if (index < 0) {
index += len;
}
// Check bounds
if (index >= 0 && index < len) {
return mrb_fixnum_value(index);
}
else {
return mrb_nil_value();
}
}
/*
* Internal helper for Array#fetch without blocks.
* Returns the element at index, or default if out of bounds.
* Raises IndexError if out of bounds and default equals none.
*/
static mrb_value
ary_fetch(mrb_state *mrb, mrb_value self)
{
mrb_value index_val, default_val, none;
mrb_get_args(mrb, "ooo", &index_val, &default_val, &none);
// Convert index to integer
mrb_int index = mrb_as_int(mrb, index_val);
mrb_int original_index = index; // Keep original for error message
struct RArray *ary = mrb_ary_ptr(self);
mrb_int len = ARY_LEN(ary);
// Handle negative indices
if (index < 0) {
index += len;
}
// Check bounds
if (index < 0 || index >= len) {
// Check if default is the NONE sentinel (means no default provided)
if (mrb_obj_equal(mrb, default_val, none)) {
// No default provided - raise IndexError
mrb_raisef(mrb, E_INDEX_ERROR,
"index %i outside of array bounds: %i...%i",
original_index, -len, len);
}
return default_val;
}
// Return element at index
return ARY_PTR(ary)[index];
}
/*
* call-seq:
* ary.flatten! -> ary or nil
* ary.flatten!(level) -> array or nil
*
* Flattens `self` in place. Returns `nil` if no modifications were made
* (i.e., *ary* contains no subarrays.) If the optional `level` argument
* determines the level of recursion to flatten.
*
* a = [ 1, 2, [3, [4, 5] ] ]
* a.flatten! #=> [1, 2, 3, 4, 5]
* a.flatten! #=> nil
* a #=> [1, 2, 3, 4, 5]
* a = [ 1, 2, [3, [4, 5] ] ]
* a.flatten!(1) #=> [1, 2, 3, [4, 5]]
*/
static mrb_value
ary_flatten_bang(mrb_state *mrb, mrb_value self)
{
mrb_int level = -1;
mrb_get_args(mrb, "|i", &level);
mrb_ary_modify(mrb, mrb_ary_ptr(self));
mrb_bool modified;
mrb_value result = flatten_internal(mrb, self, level, &modified);
if (!modified) {
return mrb_nil_value();
}
mrb_ary_replace(mrb, self, result);
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;
}
/*
* Internal helper for Array#product to construct a group array.
* Takes the base array (self), the array of other arrays (arys),
* the current iteration index (current_i), and the desired length
* of the group array (group_len).
*/
static mrb_value
ary_product_group(mrb_state *mrb, mrb_value self_ary)
{
mrb_value arys_ary;
mrb_int current_i, group_len;
mrb_get_args(mrb, "Aii", &arys_ary, &current_i, &group_len);
mrb_value group = mrb_ary_new_capa(mrb, group_len);
mrb_int j = RARRAY_LEN(arys_ary); // Corresponds to 'size' in Ruby
mrb_int n = current_i;
while (j > 0) {
j -= 1;
mrb_value a = RARRAY_PTR(arys_ary)[j]; // arys[j]
mrb_int b = RARRAY_LEN(a); // a.size
mrb_ary_set(mrb, group, j + 1, RARRAY_PTR(a)[n % b]);
n /= b;
}
mrb_ary_set(mrb, group, 0, RARRAY_PTR(self_ary)[n]);
return group;
}
/*
* call-seq:
* ary.deconstruct -> ary
*
* Returns the array itself for pattern matching.
*
* This method is used by pattern matching to deconstruct arrays.
* It simply returns the array itself, allowing pattern matching
* to work with array elements.
*
* a = [1, 2, 3]
* a.deconstruct #=> [1, 2, 3]
*
* Pattern matching usage:
* case [1, 2, 3]
* in [x, y, z]
* # x=1, y=2, z=3
* end
*/
static mrb_value
ary_deconstruct(mrb_state *mrb, mrb_value ary)
{
return ary;
}
/*
* Internal method to initialize combination state.
* Returns opaque state object for use by __combination_next.
*/
static mrb_value
ary_combination_init(mrb_state *mrb, mrb_value self)
{
mrb_int n;
mrb_bool permutation;
mrb_get_args(mrb, "ib", &n, &permutation);
struct mrb_combination_state *state = (struct mrb_combination_state*)mrb_malloc(mrb, sizeof(*state));
state->n = n;
state->array_size = RARRAY_LEN(self);
state->permutation = permutation;
state->finished = (n <= 0 && n != 0);
if (n > 0) {
state->indices = (mrb_int*)mrb_malloc(mrb, sizeof(mrb_int) * n);
for (mrb_int i = 0; i < n; i++) {
state->indices[i] = 0;
}
}
else {
state->indices = NULL;
}
return mrb_obj_value(mrb_data_object_alloc(mrb, mrb->object_class, state, &mrb_combination_state_type));
}
/*
* Internal method to get next combination as index array.
* Returns array of indices or nil when iteration is complete.
*/
static mrb_value
ary_combination_next(mrb_state *mrb, mrb_value self)
{
mrb_value state_obj;
mrb_get_args(mrb, "o", &state_obj);
struct mrb_combination_state *state;
/* Validate state object type and get data */
state = (struct mrb_combination_state*)mrb_data_check_and_get(mrb, state_obj, &mrb_combination_state_type);
if (!state) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid combination state");
}
/* Check if iteration is complete */
if (state->finished) return mrb_nil_value();
/* Validate array hasn't been modified during iteration */
if (RARRAY_LEN(self) != state->array_size) {
mrb_raise(mrb, E_RUNTIME_ERROR, "array modified during iteration");
}
/* Edge case: empty array */
if (state->array_size == 0) {
state->finished = TRUE;
return mrb_nil_value();
}
/* Validate current indices are still in bounds */
for (mrb_int i = 0; i < state->n; i++) {
if (state->indices[i] >= state->array_size) {
state->finished = TRUE;
return mrb_nil_value();
}
}
/* Build current combination indices */
mrb_value result = mrb_ary_new_capa(mrb, state->n);
for (mrb_int i = 0; i < state->n; i++) {
mrb_ary_push(mrb, result, mrb_fixnum_value(state->indices[i]));
}
mrb_int pos = state->n - 1;
while (pos >= 0) {
state->indices[pos]++;
if (state->indices[pos] < state->array_size) break;
pos--;
}
if (pos < 0) {
state->finished = TRUE;
}
else {
/* Reset dependent indices */
for (mrb_int i = pos + 1; i < state->n; i++) {
if (state->permutation) {
state->indices[i] = 0;
}
else {
state->indices[i] = state->indices[i - 1];
}
}
}
return result;
}
void
mrb_mruby_array_ext_gem_init(mrb_state* mrb)
{
struct RClass * a = mrb->array_class;
mrb_define_method_id(mrb, a, MRB_SYM(assoc), ary_assoc, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(at), ary_at, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(rassoc), ary_rassoc, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(values_at), ary_values_at, MRB_ARGS_ANY());
mrb_define_method_id(mrb, a, MRB_SYM_B(slice), ary_slice_bang, MRB_ARGS_ARG(1,1));
mrb_define_method_id(mrb, a, MRB_SYM(compact), ary_compact, MRB_ARGS_NONE());
mrb_define_method_id(mrb, a, MRB_SYM_B(compact), ary_compact_bang, MRB_ARGS_NONE());
mrb_define_method_id(mrb, a, MRB_SYM(rotate), ary_rotate, MRB_ARGS_OPT(1));
mrb_define_method_id(mrb, a, MRB_SYM_B(rotate), ary_rotate_bang, MRB_ARGS_OPT(1));
mrb_define_method_id(mrb, a, MRB_OPSYM(sub), ary_sub, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(difference), ary_difference, MRB_ARGS_ANY());
mrb_define_method_id(mrb, a, MRB_OPSYM(or), ary_union, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(union), ary_union_multi, MRB_ARGS_ANY());
mrb_define_method_id(mrb, a, MRB_OPSYM(and), ary_intersection, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(intersection), ary_intersection_multi, MRB_ARGS_ANY());
mrb_define_method_id(mrb, a, MRB_SYM_Q(intersect), ary_intersect_p, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(__fill_parse_arg), ary_fill_parse_arg, MRB_ARGS_ARG(0,4));
mrb_define_method_id(mrb, a, MRB_SYM(__fill_exec), ary_fill_exec, MRB_ARGS_REQ(3));
mrb_define_method_id(mrb, a, MRB_SYM(__uniq), ary_uniq, MRB_ARGS_NONE());
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(__normalize_index), ary_normalize_index, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(__fetch), ary_fetch, MRB_ARGS_REQ(3));
mrb_define_method_id(mrb, a, MRB_SYM(insert), ary_insert, MRB_ARGS_ARG(1, -1));
mrb_define_method_id(mrb, a, MRB_SYM(deconstruct), ary_deconstruct, MRB_ARGS_NONE());
mrb_define_method_id(mrb, a, MRB_SYM(__product_group), ary_product_group, MRB_ARGS_REQ(4));
mrb_define_method_id(mrb, a, MRB_SYM(__combination_init), ary_combination_init, MRB_ARGS_REQ(2));
mrb_define_method_id(mrb, a, MRB_SYM(__combination_next), ary_combination_next, MRB_ARGS_REQ(1));
}
void
mrb_mruby_array_ext_gem_final(mrb_state* mrb)
{
}