Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 334 additions & 1 deletion array.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@

VALUE rb_cArray;

static ID id_cmp, id_div, id_power;
static ID id_cmp, id_div, id_power, id_call;

#define id_eqq idEqq

#define ARY_DEFAULT_SIZE 16
#define ARY_MAX_SIZE (LONG_MAX / (int)sizeof(VALUE))
Expand Down Expand Up @@ -3833,6 +3835,7 @@ rb_ary_hash(VALUE ary)
/*
* call-seq:
* ary.include?(object) -> true or false
* ary.member?(object) -> true or false
*
* Returns +true+ if the given +object+ is present in +self+ (that is, if any
* element <code>==</code> +object+), otherwise returns +false+.
Expand Down Expand Up @@ -5407,6 +5410,318 @@ rb_ary_drop_while(VALUE ary)
return rb_ary_drop(ary, LONG2FIX(i));
}

/*
* call-seq:
* ary.each_with_index(*args) { |obj, i| block } -> ary
* ary.each_with_index(*args) -> an_enumerator
*
* See also Enumerable#each_with_index
*/
VALUE
rb_ary_each_with_index(int argc, VALUE *argv, VALUE array)
{
long i;
volatile VALUE ary = array;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be volatile?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently because clang: be953b4

It's an odd thing to do, and I don't know if there's any science behind it or if it was just some ✨magic✨ at that point in time.


RETURN_SIZED_ENUMERATOR(ary, argc, argv, ary_enum_length);
for (i=0; i<RARRAY_LEN(ary); i++) {
rb_yield_values(2, RARRAY_AREF(ary, i), INT2NUM(i));
}
return ary;
}

/*
* call-seq:
* ary.each_with_object(obj) { |(*args), memo_obj| ... } -> obj
* ary.each_with_object(obj) -> an_enumerator
*
* See also Enumerable#each_with_object
*/
VALUE
rb_ary_each_with_object(VALUE array, VALUE memo)
{
long i;
volatile VALUE ary = array;

RETURN_SIZED_ENUMERATOR(ary, 1, &memo, ary_enum_length);
for (i=0; i<RARRAY_LEN(ary); i++) {
rb_yield_values(2, RARRAY_AREF(ary, i), memo);
}
return memo;
}

/*
* call-seq:
* ary.inject(initial, sym) -> obj
* ary.inject(sym) -> obj
* ary.inject(initial) { |memo, obj| block } -> obj
* ary.inject { |memo, obj| block } -> obj
* ary.reduce(initial, sym) -> obj
* ary.reduce(sym) -> obj
* ary.reduce(initial) { |memo, obj| block } -> obj
* ary.reduce { |memo, obj| block } -> obj
*
* See also Enumerable#inject
*/
static VALUE
rb_ary_inject(int argc, VALUE *argv, VALUE array)
{
ID id;
VALUE op, init = Qnil;
volatile VALUE ary = array;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this local copy of the receiver turned out to be important. Thanks to @grollest for suggesting this fix. There's still a bug lurking in here, but it shows up in the tests rather than some weird failure to build an extension:

#191 test_flow.rb:19:in `<top (required)>':
     ["a"].inject("ng"){|x,y|
       break :ok
     }
  #=> "ng" (expected "ok")
#193 test_flow.rb:37:in `<top (required)>':
     ["a"].inject("ng"){|x,y|; $a << 2
       break :ok; $a << 3
     }; $a << 4
   ; $a << 5
   ; rescue Exception; $a << 99; end; $a
  #=> "[1, 4, 5]" (expected "[1, 2, 4, 5]")
test_flow.rb            FAIL 2/61

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed now.

long i = 0;
long len = RARRAY_LEN(ary);
int n = rb_scan_args(argc, argv, "02", &init, &op);

if (n == 0 || n == 1 && rb_block_given_p()) {
if (!len) return init;
if (!argc) {
init = RARRAY_AREF(ary, 0);
++i;
}
for (; i < RARRAY_LEN(ary); ++i) {
init = rb_yield_values(2, init, RARRAY_AREF(ary, i));
}
return init;
}
switch (n) {
case 1:
id = rb_check_id(&init);
op = id ? ID2SYM(id) : init;
argc = 0;
init = Qnil;
break;
case 2:
if (rb_block_given_p()) {
rb_warning("given block not used");
}
id = rb_check_id(&op);
if (id) op = ID2SYM(id);
break;
}
if (!len) return init;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you move this to right after the call to rb_scan_args?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't do that because it would change the behaviour of the method. If either init or op isn't a string or a symbol, then rb_check_id will raise.

if (!argc) {
init = RARRAY_AREF(ary, 0);
++i;
}
for (; i < RARRAY_LEN(ary); ++i) {
if (SYMBOL_P(op)) {
init = rb_funcall(init, SYM2ID(op), 1, RARRAY_AREF(ary, i));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else should be on the same line.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nevermind, I see it defined on new lines in other source files. :S

else {
VALUE args[2];
args[0] = op;
args[1] = RARRAY_AREF(ary, i);
init = rb_f_send(numberof(args), args, init);
}
}
return init;
}

/*
* call-seq:
* ary.flat_map { |obj| block } -> array
* ary.collect_concat { |obj| block } -> array
* ary.flat_map -> an_enumerator
* ary.collect_concat -> an_enumerator
*
* See also Enumerable#flat_map
*/
static VALUE
rb_ary_flat_map(VALUE ary)
{
long i;
VALUE obj, tmp, result;

RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length);

result = rb_ary_new();
for (i = 0; i < RARRAY_LEN(ary); ++i) {
obj = rb_yield(RARRAY_AREF(ary, i));
tmp = rb_check_array_type(obj);
if (NIL_P(obj)) {
rb_ary_push(result, obj);
}
else {
rb_ary_concat(result, tmp);
}
}

return result;
}

/*
* call-seq:
* ary.grep(pattern) -> array
* ary.grep(pattern) { |obj| block } -> array
*
* See also Enumerable#grep
*/
static VALUE
rb_ary_grep(VALUE ary, VALUE pat)
{
long i, len = RARRAY_LEN(ary);
const VALUE *ptr = RARRAY_CONST_PTR(ary);
VALUE obj, result = rb_ary_new();

if (!len) return result;
if (!rb_block_given_p()) {
for (i = 0; i < len; ++i) {
if (RTEST(rb_funcall(pat, id_eqq, 1, ptr[i]))) {
rb_ary_push(result, ptr[i]);
}
}
}
else {
for (i = 0; i < RARRAY_LEN(ary); ++i) {
obj = RARRAY_AREF(ary, i);
if (RTEST(rb_funcall(pat, id_eqq, 1, obj))) {
rb_ary_push(result, rb_yield(obj));
}
}
}

return result;
}

/*
* call-seq:
* ary.partition { |obj| block } -> [ true_array, false_array ]
* ary.partition -> an_enumerator
*
* See also Enumerable#partition
*/
static VALUE
rb_ary_partition(VALUE ary)
{
long i;
VALUE false_array, true_array, obj;
RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length);

false_array = rb_ary_new();
true_array = rb_ary_new();
for (i = 0; i < RARRAY_LEN(ary); ++i) {
obj = RARRAY_AREF(ary, i);
if (RTEST(rb_yield(obj))) {
rb_ary_push(true_array, obj);
}
else {
rb_ary_push(false_array, obj);
}
}

return rb_assoc_new(true_array, false_array);
}

/*
* call-seq:
* ary.detect(ifnone = nil) { |obj| block } -> obj or nil
* ary.find(ifnone = nil) { |obj| block } -> obj or nil
* ary.detect(ifnone = nil) -> an_enumerator
* ary.find(ifnone = nil) -> an_enumerator
*
* See also Enumerable#find
*/
static VALUE
rb_ary_find(int argc, VALUE *argv, VALUE ary)
{
long i;
VALUE obj, if_none;

rb_scan_args(argc, argv, "01", &if_none);
RETURN_ENUMERATOR(ary, argc, argv);
for (i = 0; i < RARRAY_LEN(ary); ++i) {
obj = RARRAY_AREF(ary, i);
if (RTEST(rb_yield(obj))) return obj;
}
if (!NIL_P(if_none)) {
return rb_funcall(if_none, id_call, 0, 0);
}
return Qnil;
}

/*
* call-seq:
* ary.one? [{ |obj| block }] -> true or false
*
* See also Enumerable#one?
*/
static VALUE
rb_ary_one_p(VALUE ary)
{
long i, len = RARRAY_LEN(ary);
const VALUE *ptr = RARRAY_CONST_PTR(ary);
VALUE result = Qundef;

if (!len) return Qfalse;
if (!rb_block_given_p()) {
for (i = 0; i < len; ++i) {
if (RTEST(ptr[i])) {
if (result == Qundef) result = Qtrue;
else if (result == Qtrue) return Qfalse;
}
}
}
else {
for (i = 0; i < RARRAY_LEN(ary); ++i) {
if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) {
if (result == Qundef) result = Qtrue;
else if (result == Qtrue) return Qfalse;
}
}
}
if (result == Qundef) return Qfalse;
return result;
}

/*
* call-seq:
* ary.none? [{ |obj| block }] -> true or false
*
* See also Enumerable#none?
*/
static VALUE
rb_ary_none_p(VALUE ary)
{
long i, len = RARRAY_LEN(ary);
const VALUE *ptr = RARRAY_CONST_PTR(ary);

if (!len) return Qtrue;
if (!rb_block_given_p()) {
for (i = 0; i < len; ++i) if (RTEST(ptr[i])) return Qfalse;
}
else {
for (i = 0; i < RARRAY_LEN(ary); ++i) {
if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) return Qfalse;
}
}
return Qtrue;
}

/*
* call-seq:
* ary.all? [{ |obj| block } ] -> true or false
*
* See also Enumerable#all?
*/
static VALUE
rb_ary_all_p(VALUE ary)
{
long i, len = RARRAY_LEN(ary);
const VALUE *ptr = RARRAY_CONST_PTR(ary);

if (!len) return Qtrue;
if (!rb_block_given_p()) {
for (i = 0; i < len; ++i) if (!RTEST(ptr[i])) return Qfalse;
}
else {
for (i = 0; i < RARRAY_LEN(ary); ++i) {
if (!RTEST(rb_yield(RARRAY_AREF(ary, i)))) return Qfalse;
}
}
return Qtrue;
}

/*
* call-seq:
* ary.any? [{ |obj| block }] -> true or false
Expand Down Expand Up @@ -5687,6 +6002,7 @@ Init_Array(void)
rb_define_method(rb_cArray, "inspect", rb_ary_inspect, 0);
rb_define_alias(rb_cArray, "to_s", "inspect");
rb_define_method(rb_cArray, "to_a", rb_ary_to_a, 0);
rb_define_method(rb_cArray, "entries", rb_ary_to_a, 0);
rb_define_method(rb_cArray, "to_h", rb_ary_to_h, 0);
rb_define_method(rb_cArray, "to_ary", rb_ary_to_ary_m, 0);
rb_define_method(rb_cArray, "frozen?", rb_ary_frozen_p, 0);
Expand Down Expand Up @@ -5729,6 +6045,7 @@ Init_Array(void)
rb_define_method(rb_cArray, "collect!", rb_ary_collect_bang, 0);
rb_define_method(rb_cArray, "map", rb_ary_collect, 0);
rb_define_method(rb_cArray, "map!", rb_ary_collect_bang, 0);
rb_define_method(rb_cArray, "find_all", rb_ary_select, 0);
rb_define_method(rb_cArray, "select", rb_ary_select, 0);
rb_define_method(rb_cArray, "select!", rb_ary_select_bang, 0);
rb_define_method(rb_cArray, "keep_if", rb_ary_keep_if, 0);
Expand All @@ -5744,6 +6061,7 @@ Init_Array(void)
rb_define_method(rb_cArray, "clear", rb_ary_clear, 0);
rb_define_method(rb_cArray, "fill", rb_ary_fill, -1);
rb_define_method(rb_cArray, "include?", rb_ary_includes, 1);
rb_define_method(rb_cArray, "member?", rb_ary_includes, 1);
rb_define_method(rb_cArray, "<=>", rb_ary_cmp, 1);

rb_define_method(rb_cArray, "slice", rb_ary_aref, -1);
Expand Down Expand Up @@ -5783,8 +6101,23 @@ Init_Array(void)
rb_define_method(rb_cArray, "bsearch", rb_ary_bsearch, 0);
rb_define_method(rb_cArray, "any?", rb_ary_any_p, 0);

rb_define_method(rb_cArray, "all?", rb_ary_all_p, 0);
rb_define_method(rb_cArray, "one?", rb_ary_one_p, 0);
rb_define_method(rb_cArray, "none?", rb_ary_none_p, 0);
rb_define_method(rb_cArray, "find", rb_ary_find, -1);
rb_define_method(rb_cArray, "detect", rb_ary_find, -1);
rb_define_method(rb_cArray, "inject", rb_ary_inject, -1);
rb_define_method(rb_cArray, "reduce", rb_ary_inject, -1);
rb_define_method(rb_cArray, "each_with_index", rb_ary_each_with_index, -1);
rb_define_method(rb_cArray, "each_with_object", rb_ary_each_with_object, 1);
rb_define_method(rb_cArray, "partition", rb_ary_partition, 0);
rb_define_method(rb_cArray, "grep", rb_ary_grep, 1);
rb_define_method(rb_cArray, "flat_map", rb_ary_flat_map, 0);
rb_define_method(rb_cArray, "collect_concat", rb_ary_flat_map, 0);

id_cmp = rb_intern("<=>");
id_random = rb_intern("random");
id_div = rb_intern("div");
id_power = rb_intern("**");
id_call = rb_intern("call");
}
2 changes: 0 additions & 2 deletions enum.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
#include "id.h"
#include "internal.h"

VALUE rb_f_send(int argc, VALUE *argv, VALUE recv);

VALUE rb_mEnumerable;

static ID id_next;
Expand Down
Loading