summaryrefslogtreecommitdiff
path: root/enumerator.c
diff options
context:
space:
mode:
Diffstat (limited to 'enumerator.c')
-rw-r--r--enumerator.c397
1 files changed, 228 insertions, 169 deletions
diff --git a/enumerator.c b/enumerator.c
index 2c9858cda6..193a865dbc 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -20,6 +20,7 @@
#include "id.h"
#include "internal.h"
+#include "internal/class.h"
#include "internal/enumerator.h"
#include "internal/error.h"
#include "internal/hash.h"
@@ -72,6 +73,8 @@
* puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
* # => ["0:foo", "1:bar", "2:baz"]
*
+ * == External Iteration
+ *
* An Enumerator can also be used as an external iterator.
* For example, Enumerator#next returns the next value of the iterator
* or raises StopIteration if the Enumerator is at the end.
@@ -82,15 +85,49 @@
* puts e.next # => 3
* puts e.next # raises StopIteration
*
- * Note that enumeration sequence by +next+, +next_values+, +peek+ and
- * +peek_values+ do not affect other non-external
- * enumeration methods, unless the underlying iteration method itself has
- * side-effect, e.g. IO#each_line.
+ * +next+, +next_values+, +peek+, and +peek_values+ are the only methods
+ * which use external iteration (and Array#zip(Enumerable-not-Array) which uses +next+ internally).
+ *
+ * These methods do not affect other internal enumeration methods,
+ * unless the underlying iteration method itself has side-effect, e.g. IO#each_line.
+ *
+ * FrozenError will be raised if these methods are called against a frozen enumerator.
+ * Since +rewind+ and +feed+ also change state for external iteration,
+ * these methods may raise FrozenError too.
+ *
+ * External iteration differs *significantly* from internal iteration
+ * due to using a Fiber:
+ * - The Fiber adds some overhead compared to internal enumeration.
+ * - The stacktrace will only include the stack from the Enumerator, not above.
+ * - Fiber-local variables are *not* inherited inside the Enumerator Fiber,
+ * which instead starts with no Fiber-local variables.
+ * - Fiber storage variables *are* inherited and are designed
+ * to handle Enumerator Fibers. Assigning to a Fiber storage variable
+ * only affects the current Fiber, so if you want to change state
+ * in the caller Fiber of the Enumerator Fiber, you need to use an
+ * extra indirection (e.g., use some object in the Fiber storage
+ * variable and mutate some ivar of it).
+ *
+ * Concretely:
+ *
+ * Thread.current[:fiber_local] = 1
+ * Fiber[:storage_var] = 1
+ * e = Enumerator.new do |y|
+ * p Thread.current[:fiber_local] # for external iteration: nil, for internal iteration: 1
+ * p Fiber[:storage_var] # => 1, inherited
+ * Fiber[:storage_var] += 1
+ * y << 42
+ * end
+ *
+ * p e.next # => 42
+ * p Fiber[:storage_var] # => 1 (it ran in a different Fiber)
+ *
+ * e.each { p _1 }
+ * p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber)
*
- * Moreover, implementation typically uses fibers so performance could be
- * slower and exception stacktraces different than expected.
+ * == Convert External Iteration to Internal Iteration
*
- * You can use this to implement an internal iterator as follows:
+ * You can use an external iterator to implement an internal iterator as follows:
*
* def ext_each(e)
* while true
@@ -132,7 +169,10 @@ static VALUE sym_each, sym_cycle, sym_yield;
static VALUE lazy_use_super_method;
+extern ID ruby_static_id_cause;
+
#define id_call idCall
+#define id_cause ruby_static_id_cause
#define id_each idEach
#define id_eqq idEqq
#define id_initialize idInitialize
@@ -155,6 +195,19 @@ struct enumerator {
int kw_splat;
};
+RUBY_REFERENCES(enumerator_refs) = {
+ RUBY_REF_EDGE(struct enumerator, obj),
+ RUBY_REF_EDGE(struct enumerator, args),
+ RUBY_REF_EDGE(struct enumerator, fib),
+ RUBY_REF_EDGE(struct enumerator, dst),
+ RUBY_REF_EDGE(struct enumerator, lookahead),
+ RUBY_REF_EDGE(struct enumerator, feedvalue),
+ RUBY_REF_EDGE(struct enumerator, stop_exc),
+ RUBY_REF_EDGE(struct enumerator, size),
+ RUBY_REF_EDGE(struct enumerator, procs),
+ RUBY_REF_END
+};
+
static VALUE rb_cGenerator, rb_cYielder, rb_cEnumProducer;
struct generator {
@@ -173,9 +226,11 @@ struct producer {
typedef struct MEMO *lazyenum_proc_func(VALUE, struct MEMO *, VALUE, long);
typedef VALUE lazyenum_size_func(VALUE, VALUE);
+typedef int lazyenum_precheck_func(VALUE proc_entry);
typedef struct {
lazyenum_proc_func *proc;
lazyenum_size_func *size;
+ lazyenum_precheck_func *precheck;
} lazyenum_funcs;
struct proc_entry {
@@ -202,56 +257,15 @@ struct enum_product {
VALUE rb_cArithSeq;
-/*
- * Enumerator
- */
-static void
-enumerator_mark(void *p)
-{
- struct enumerator *ptr = p;
- rb_gc_mark_movable(ptr->obj);
- rb_gc_mark_movable(ptr->args);
- rb_gc_mark_movable(ptr->fib);
- rb_gc_mark_movable(ptr->dst);
- rb_gc_mark_movable(ptr->lookahead);
- rb_gc_mark_movable(ptr->feedvalue);
- rb_gc_mark_movable(ptr->stop_exc);
- rb_gc_mark_movable(ptr->size);
- rb_gc_mark_movable(ptr->procs);
-}
-
-static void
-enumerator_compact(void *p)
-{
- struct enumerator *ptr = p;
- ptr->obj = rb_gc_location(ptr->obj);
- ptr->args = rb_gc_location(ptr->args);
- ptr->fib = rb_gc_location(ptr->fib);
- ptr->dst = rb_gc_location(ptr->dst);
- ptr->lookahead = rb_gc_location(ptr->lookahead);
- ptr->feedvalue = rb_gc_location(ptr->feedvalue);
- ptr->stop_exc = rb_gc_location(ptr->stop_exc);
- ptr->size = rb_gc_location(ptr->size);
- ptr->procs = rb_gc_location(ptr->procs);
-}
-
-#define enumerator_free RUBY_TYPED_DEFAULT_FREE
-
-static size_t
-enumerator_memsize(const void *p)
-{
- return sizeof(struct enumerator);
-}
-
static const rb_data_type_t enumerator_data_type = {
"enumerator",
{
- enumerator_mark,
- enumerator_free,
- enumerator_memsize,
- enumerator_compact,
+ RUBY_REFS_LIST_PTR(enumerator_refs),
+ RUBY_TYPED_DEFAULT_FREE,
+ NULL, // Nothing allocated externally, so don't need a memsize function
+ NULL,
},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, NULL, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_DECL_MARKING | RUBY_TYPED_EMBEDDABLE
};
static struct enumerator *
@@ -260,7 +274,7 @@ enumerator_ptr(VALUE obj)
struct enumerator *ptr;
TypedData_Get_Struct(obj, struct enumerator, &enumerator_data_type, ptr);
- if (!ptr || ptr->obj == Qundef) {
+ if (!ptr || UNDEF_P(ptr->obj)) {
rb_raise(rb_eArgError, "uninitialized enumerator");
}
return ptr;
@@ -282,22 +296,15 @@ proc_entry_compact(void *p)
ptr->memo = rb_gc_location(ptr->memo);
}
-#define proc_entry_free RUBY_TYPED_DEFAULT_FREE
-
-static size_t
-proc_entry_memsize(const void *p)
-{
- return p ? sizeof(struct proc_entry) : 0;
-}
-
static const rb_data_type_t proc_entry_data_type = {
"proc_entry",
{
proc_entry_mark,
- proc_entry_free,
- proc_entry_memsize,
+ RUBY_TYPED_DEFAULT_FREE,
+ NULL, // Nothing allocated externally, so don't need a memsize function
proc_entry_compact,
},
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
};
static struct proc_entry *
@@ -382,7 +389,7 @@ obj_to_enum(int argc, VALUE *argv, VALUE obj)
}
enumerator = rb_enumeratorize_with_size(obj, meth, argc, argv, 0);
if (rb_block_given_p()) {
- enumerator_ptr(enumerator)->size = rb_block_proc();
+ RB_OBJ_WRITE(enumerator, &enumerator_ptr(enumerator)->size, rb_block_proc());
}
return enumerator;
}
@@ -411,15 +418,15 @@ enumerator_init(VALUE enum_obj, VALUE obj, VALUE meth, int argc, const VALUE *ar
rb_raise(rb_eArgError, "unallocated enumerator");
}
- ptr->obj = obj;
+ RB_OBJ_WRITE(enum_obj, &ptr->obj, obj);
ptr->meth = rb_to_id(meth);
- if (argc) ptr->args = rb_ary_new4(argc, argv);
+ if (argc) RB_OBJ_WRITE(enum_obj, &ptr->args, rb_ary_new4(argc, argv));
ptr->fib = 0;
ptr->dst = Qnil;
ptr->lookahead = Qundef;
ptr->feedvalue = Qundef;
ptr->stop_exc = Qfalse;
- ptr->size = size;
+ RB_OBJ_WRITE(enum_obj, &ptr->size, size);
ptr->size_fn = size_fn;
ptr->kw_splat = kw_splat;
@@ -498,13 +505,13 @@ enumerator_init_copy(VALUE obj, VALUE orig)
rb_raise(rb_eArgError, "unallocated enumerator");
}
- ptr1->obj = ptr0->obj;
- ptr1->meth = ptr0->meth;
- ptr1->args = ptr0->args;
+ RB_OBJ_WRITE(obj, &ptr1->obj, ptr0->obj);
+ RB_OBJ_WRITE(obj, &ptr1->meth, ptr0->meth);
+ RB_OBJ_WRITE(obj, &ptr1->args, ptr0->args);
ptr1->fib = 0;
ptr1->lookahead = Qundef;
ptr1->feedvalue = Qundef;
- ptr1->size = ptr0->size;
+ RB_OBJ_WRITE(obj, &ptr1->size, ptr0->size);
ptr1->size_fn = ptr0->size_fn;
return obj;
@@ -519,8 +526,8 @@ rb_enumeratorize(VALUE obj, VALUE meth, int argc, const VALUE *argv)
return rb_enumeratorize_with_size(obj, meth, argc, argv, 0);
}
-static VALUE
-lazy_to_enum_i(VALUE self, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat);
+static VALUE lazy_to_enum_i(VALUE self, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat);
+static int lazy_precheck(VALUE procs);
VALUE
rb_enumeratorize_with_size_kw(VALUE obj, VALUE meth, int argc, const VALUE *argv, rb_enumerator_size_func *size_fn, int kw_splat)
@@ -552,11 +559,17 @@ enumerator_block_call(VALUE obj, rb_block_call_func *func, VALUE arg)
const struct enumerator *e = enumerator_ptr(obj);
ID meth = e->meth;
- if (e->args) {
- argc = RARRAY_LENINT(e->args);
- argv = RARRAY_CONST_PTR(e->args);
+ VALUE args = e->args;
+ if (args) {
+ argc = RARRAY_LENINT(args);
+ argv = RARRAY_CONST_PTR(args);
}
- return rb_block_call_kw(e->obj, meth, argc, argv, func, arg, e->kw_splat);
+
+ VALUE ret = rb_block_call_kw(e->obj, meth, argc, argv, func, arg, e->kw_splat);
+
+ RB_GC_GUARD(args);
+
+ return ret;
}
/*
@@ -598,9 +611,10 @@ enumerator_block_call(VALUE obj, rb_block_call_func *func, VALUE arg)
static VALUE
enumerator_each(int argc, VALUE *argv, VALUE obj)
{
+ struct enumerator *e = enumerator_ptr(obj);
+
if (argc > 0) {
- struct enumerator *e = enumerator_ptr(obj = rb_obj_dup(obj));
- VALUE args = e->args;
+ VALUE args = (e = enumerator_ptr(obj = rb_obj_dup(obj)))->args;
if (args) {
#if SIZEOF_INT < SIZEOF_LONG
/* check int range overflow */
@@ -612,11 +626,14 @@ enumerator_each(int argc, VALUE *argv, VALUE obj)
else {
args = rb_ary_new4(argc, argv);
}
- e->args = args;
+ RB_OBJ_WRITE(obj, &e->args, args);
e->size = Qnil;
e->size_fn = 0;
}
if (!rb_block_given_p()) return obj;
+
+ if (!lazy_precheck(e->procs)) return Qnil;
+
return enumerator_block_call(obj, 0, obj);
}
@@ -735,7 +752,7 @@ next_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, obj))
VALUE feedvalue = Qnil;
VALUE args = rb_ary_new4(argc, argv);
rb_fiber_yield(1, &args);
- if (e->feedvalue != Qundef) {
+ if (!UNDEF_P(e->feedvalue)) {
feedvalue = e->feedvalue;
e->feedvalue = Qundef;
}
@@ -750,7 +767,7 @@ next_i(RB_BLOCK_CALL_FUNC_ARGLIST(_, obj))
VALUE result;
result = rb_block_call(obj, id_each, 0, 0, next_ii, obj);
- e->stop_exc = rb_exc_new2(rb_eStopIteration, "iteration reached an end");
+ RB_OBJ_WRITE(obj, &e->stop_exc, rb_exc_new2(rb_eStopIteration, "iteration reached an end"));
rb_ivar_set(e->stop_exc, id_result, result);
return rb_fiber_yield(1, &nil);
}
@@ -759,8 +776,8 @@ static void
next_init(VALUE obj, struct enumerator *e)
{
VALUE curr = rb_fiber_current();
- e->dst = curr;
- e->fib = rb_fiber_new(next_i, obj);
+ RB_OBJ_WRITE(obj, &e->dst, curr);
+ RB_OBJ_WRITE(obj, &e->fib, rb_fiber_new(next_i, obj));
e->lookahead = Qundef;
}
@@ -769,8 +786,16 @@ get_next_values(VALUE obj, struct enumerator *e)
{
VALUE curr, vs;
- if (e->stop_exc)
- rb_exc_raise(e->stop_exc);
+ if (e->stop_exc) {
+ VALUE exc = e->stop_exc;
+ VALUE result = rb_attr_get(exc, id_result);
+ VALUE mesg = rb_attr_get(exc, idMesg);
+ if (!NIL_P(mesg)) mesg = rb_str_dup(mesg);
+ VALUE stop_exc = rb_exc_new_str(rb_eStopIteration, mesg);
+ rb_ivar_set(stop_exc, id_cause, exc);
+ rb_ivar_set(stop_exc, id_result, result);
+ rb_exc_raise(stop_exc);
+ }
curr = rb_fiber_current();
@@ -840,7 +865,9 @@ enumerator_next_values(VALUE obj)
struct enumerator *e = enumerator_ptr(obj);
VALUE vs;
- if (e->lookahead != Qundef) {
+ rb_check_frozen(obj);
+
+ if (!UNDEF_P(e->lookahead)) {
vs = e->lookahead;
e->lookahead = Qundef;
return vs;
@@ -901,9 +928,12 @@ enumerator_peek_values(VALUE obj)
{
struct enumerator *e = enumerator_ptr(obj);
- if (e->lookahead == Qundef) {
- e->lookahead = get_next_values(obj, e);
+ rb_check_frozen(obj);
+
+ if (UNDEF_P(e->lookahead)) {
+ RB_OBJ_WRITE(obj, &e->lookahead, get_next_values(obj, e));
}
+
return e->lookahead;
}
@@ -1025,10 +1055,12 @@ enumerator_feed(VALUE obj, VALUE v)
{
struct enumerator *e = enumerator_ptr(obj);
- if (e->feedvalue != Qundef) {
+ rb_check_frozen(obj);
+
+ if (!UNDEF_P(e->feedvalue)) {
rb_raise(rb_eTypeError, "feed value already set");
}
- e->feedvalue = v;
+ RB_OBJ_WRITE(obj, &e->feedvalue, v);
return Qnil;
}
@@ -1047,6 +1079,8 @@ enumerator_rewind(VALUE obj)
{
struct enumerator *e = enumerator_ptr(obj);
+ rb_check_frozen(obj);
+
rb_check_funcall(e->obj, id_rewind, 0, 0);
e->fib = 0;
@@ -1070,7 +1104,7 @@ inspect_enumerator(VALUE obj, VALUE dummy, int recur)
cname = rb_obj_class(obj);
- if (!e || e->obj == Qundef) {
+ if (!e || UNDEF_P(e->obj)) {
return rb_sprintf("#<%"PRIsVALUE": uninitialized>", rb_class_path(cname));
}
@@ -1239,7 +1273,7 @@ enumerator_size(VALUE obj)
argv = RARRAY_CONST_PTR(e->args);
}
size = rb_check_funcall_kw(e->size, id_call, argc, argv, e->kw_splat);
- if (size != Qundef) return size;
+ if (!UNDEF_P(size)) return size;
return e->size;
}
@@ -1260,23 +1294,15 @@ yielder_compact(void *p)
ptr->proc = rb_gc_location(ptr->proc);
}
-#define yielder_free RUBY_TYPED_DEFAULT_FREE
-
-static size_t
-yielder_memsize(const void *p)
-{
- return sizeof(struct yielder);
-}
-
static const rb_data_type_t yielder_data_type = {
"yielder",
{
yielder_mark,
- yielder_free,
- yielder_memsize,
+ RUBY_TYPED_DEFAULT_FREE,
+ NULL,
yielder_compact,
},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
};
static struct yielder *
@@ -1285,7 +1311,7 @@ yielder_ptr(VALUE obj)
struct yielder *ptr;
TypedData_Get_Struct(obj, struct yielder, &yielder_data_type, ptr);
- if (!ptr || ptr->proc == Qundef) {
+ if (!ptr || UNDEF_P(ptr->proc)) {
rb_raise(rb_eArgError, "uninitialized yielder");
}
return ptr;
@@ -1315,7 +1341,7 @@ yielder_init(VALUE obj, VALUE proc)
rb_raise(rb_eArgError, "unallocated yielder");
}
- ptr->proc = proc;
+ RB_OBJ_WRITE(obj, &ptr->proc, proc);
return obj;
}
@@ -1400,23 +1426,15 @@ generator_compact(void *p)
ptr->obj = rb_gc_location(ptr->obj);
}
-#define generator_free RUBY_TYPED_DEFAULT_FREE
-
-static size_t
-generator_memsize(const void *p)
-{
- return sizeof(struct generator);
-}
-
static const rb_data_type_t generator_data_type = {
"generator",
{
generator_mark,
- generator_free,
- generator_memsize,
+ RUBY_TYPED_DEFAULT_FREE,
+ NULL,
generator_compact,
},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
};
static struct generator *
@@ -1425,7 +1443,7 @@ generator_ptr(VALUE obj)
struct generator *ptr;
TypedData_Get_Struct(obj, struct generator, &generator_data_type, ptr);
- if (!ptr || ptr->proc == Qundef) {
+ if (!ptr || UNDEF_P(ptr->proc)) {
rb_raise(rb_eArgError, "uninitialized generator");
}
return ptr;
@@ -1456,7 +1474,7 @@ generator_init(VALUE obj, VALUE proc)
rb_raise(rb_eArgError, "unallocated generator");
}
- ptr->proc = proc;
+ RB_OBJ_WRITE(obj, &ptr->proc, proc);
return obj;
}
@@ -1504,7 +1522,7 @@ generator_init_copy(VALUE obj, VALUE orig)
rb_raise(rb_eArgError, "unallocated generator");
}
- ptr1->proc = ptr0->proc;
+ RB_OBJ_WRITE(obj, &ptr1->proc, ptr0->proc);
return obj;
}
@@ -1529,7 +1547,7 @@ static VALUE
enum_size(VALUE self)
{
VALUE r = rb_check_funcall(self, id_size, 0, 0);
- return (r == Qundef) ? Qnil : r;
+ return UNDEF_P(r) ? Qnil : r;
}
static VALUE
@@ -1562,7 +1580,7 @@ lazy_init_iterator(RB_BLOCK_CALL_FUNC_ARGLIST(val, m))
result = rb_yield_values2(len, nargv);
ALLOCV_END(args);
}
- if (result == Qundef) rb_iter_break();
+ if (UNDEF_P(result)) rb_iter_break();
return Qnil;
}
@@ -1671,11 +1689,27 @@ lazy_generator_init(VALUE enumerator, VALUE procs)
lazy_init_block, rb_ary_new3(2, obj, procs));
gen_ptr = generator_ptr(generator);
- gen_ptr->obj = obj;
+ RB_OBJ_WRITE(generator, &gen_ptr->obj, obj);
return generator;
}
+static int
+lazy_precheck(VALUE procs)
+{
+ if (RTEST(procs)) {
+ long num_procs = RARRAY_LEN(procs), i = num_procs;
+ while (i-- > 0) {
+ VALUE proc = RARRAY_AREF(procs, i);
+ struct proc_entry *entry = proc_entry_ptr(proc);
+ lazyenum_precheck_func *precheck = entry->fn->precheck;
+ if (precheck && !precheck(proc)) return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
/*
* Document-class: Enumerator::Lazy
*
@@ -1840,10 +1874,10 @@ lazy_add_method(VALUE obj, int argc, VALUE *argv, VALUE args, VALUE memo,
VALUE entry_obj = TypedData_Make_Struct(rb_cObject, struct proc_entry,
&proc_entry_data_type, entry);
if (rb_block_given_p()) {
- entry->proc = rb_block_proc();
+ RB_OBJ_WRITE(entry_obj, &entry->proc, rb_block_proc());
}
entry->fn = fn;
- entry->memo = args;
+ RB_OBJ_WRITE(entry_obj, &entry->memo, args);
lazy_set_args(entry_obj, memo);
@@ -1852,9 +1886,9 @@ lazy_add_method(VALUE obj, int argc, VALUE *argv, VALUE args, VALUE memo,
rb_ary_push(new_procs, entry_obj);
new_obj = enumerator_init_copy(enumerator_allocate(rb_cLazy), obj);
- new_e = DATA_PTR(new_obj);
- new_e->obj = new_generator;
- new_e->procs = new_procs;
+ new_e = RTYPEDDATA_GET_DATA(new_obj);
+ RB_OBJ_WRITE(new_obj, &new_e->obj, new_generator);
+ RB_OBJ_WRITE(new_obj, &new_e->procs, new_procs);
if (argc > 0) {
new_e->meth = rb_to_id(*argv++);
@@ -1863,7 +1897,9 @@ lazy_add_method(VALUE obj, int argc, VALUE *argv, VALUE args, VALUE memo,
else {
new_e->meth = id_each;
}
- new_e->args = rb_ary_new4(argc, argv);
+
+ RB_OBJ_WRITE(new_obj, &new_e->args, rb_ary_new4(argc, argv));
+
return new_obj;
}
@@ -1949,7 +1985,7 @@ lazy_to_enum(int argc, VALUE *argv, VALUE self)
}
lazy = lazy_to_enum_i(self, meth, argc, argv, 0, rb_keyword_given_p());
if (rb_block_given_p()) {
- enumerator_ptr(lazy)->size = rb_block_proc();
+ RB_OBJ_WRITE(lazy, &enumerator_ptr(lazy)->size, rb_block_proc());
}
return lazy;
}
@@ -2335,7 +2371,6 @@ lazy_zip_arrays_func(VALUE proc_entry, struct MEMO *result, VALUE memos, long me
rb_ary_push(ary, rb_ary_entry(RARRAY_AREF(arrays, i), count));
}
LAZY_MEMO_SET_VALUE(result, ary);
- LAZY_MEMO_SET_PACKED(result);
rb_ary_store(memos, memo_index, LONG2NUM(++count));
return result;
}
@@ -2425,13 +2460,8 @@ lazy_take_proc(VALUE proc_entry, struct MEMO *result, VALUE memos, long memo_ind
}
remain = NUM2LONG(memo);
- if (remain == 0) {
- LAZY_MEMO_SET_BREAK(result);
- }
- else {
- if (--remain == 0) LAZY_MEMO_SET_BREAK(result);
- rb_ary_store(memos, memo_index, LONG2NUM(remain));
- }
+ if (--remain == 0) LAZY_MEMO_SET_BREAK(result);
+ rb_ary_store(memos, memo_index, LONG2NUM(remain));
return result;
}
@@ -2444,8 +2474,15 @@ lazy_take_size(VALUE entry, VALUE receiver)
return LONG2NUM(len);
}
+static int
+lazy_take_precheck(VALUE proc_entry)
+{
+ struct proc_entry *entry = proc_entry_ptr(proc_entry);
+ return entry->memo != INT2FIX(0);
+}
+
static const lazyenum_funcs lazy_take_funcs = {
- lazy_take_proc, lazy_take_size,
+ lazy_take_proc, lazy_take_size, lazy_take_precheck,
};
/*
@@ -2459,20 +2496,14 @@ static VALUE
lazy_take(VALUE obj, VALUE n)
{
long len = NUM2LONG(n);
- int argc = 0;
- VALUE argv[2];
if (len < 0) {
rb_raise(rb_eArgError, "attempt to take negative size");
}
- if (len == 0) {
- argv[0] = sym_cycle;
- argv[1] = INT2NUM(0);
- argc = 2;
- }
+ n = LONG2NUM(len); /* no more conversion */
- return lazy_add_method(obj, argc, argv, n, rb_ary_new3(1, n), &lazy_take_funcs);
+ return lazy_add_method(obj, 0, 0, n, rb_ary_new3(1, n), &lazy_take_funcs);
}
static struct MEMO *
@@ -2914,7 +2945,7 @@ static const rb_data_type_t producer_data_type = {
producer_memsize,
producer_compact,
},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
};
static struct producer *
@@ -2923,7 +2954,7 @@ producer_ptr(VALUE obj)
struct producer *ptr;
TypedData_Get_Struct(obj, struct producer, &producer_data_type, ptr);
- if (!ptr || ptr->proc == Qundef) {
+ if (!ptr || UNDEF_P(ptr->proc)) {
rb_raise(rb_eArgError, "uninitialized producer");
}
return ptr;
@@ -2954,8 +2985,8 @@ producer_init(VALUE obj, VALUE init, VALUE proc)
rb_raise(rb_eArgError, "unallocated producer");
}
- ptr->init = init;
- ptr->proc = proc;
+ RB_OBJ_WRITE(obj, &ptr->init, init);
+ RB_OBJ_WRITE(obj, &ptr->proc, proc);
return obj;
}
@@ -2978,7 +3009,7 @@ producer_each_i(VALUE obj)
init = ptr->init;
proc = ptr->proc;
- if (init == Qundef) {
+ if (UNDEF_P(init)) {
curr = Qnil;
}
else {
@@ -3109,7 +3140,7 @@ enum_chain_ptr(VALUE obj)
struct enum_chain *ptr;
TypedData_Get_Struct(obj, struct enum_chain, &enum_chain_data_type, ptr);
- if (!ptr || ptr->enums == Qundef) {
+ if (!ptr || UNDEF_P(ptr->enums)) {
rb_raise(rb_eArgError, "uninitialized chain");
}
return ptr;
@@ -3302,7 +3333,7 @@ inspect_enum_chain(VALUE obj, VALUE dummy, int recur)
TypedData_Get_Struct(obj, struct enum_chain, &enum_chain_data_type, ptr);
- if (!ptr || ptr->enums == Qundef) {
+ if (!ptr || UNDEF_P(ptr->enums)) {
return rb_sprintf("#<%"PRIsVALUE": uninitialized>", rb_class_path(klass));
}
@@ -3384,7 +3415,7 @@ enumerator_plus(VALUE obj, VALUE eobj)
*
* The method used against each enumerable object is `each_entry`
* instead of `each` so that the product of N enumerable objects
- * yields exactly N arguments in each iteration.
+ * yields an array of exactly N elements in each iteration.
*
* When no enumerator is given, it calls a given block once yielding
* an empty argument list.
@@ -3431,7 +3462,7 @@ enum_product_ptr(VALUE obj)
struct enum_product *ptr;
TypedData_Get_Struct(obj, struct enum_product, &enum_product_data_type, ptr);
- if (!ptr || ptr->enums == Qundef) {
+ if (!ptr || UNDEF_P(ptr->enums)) {
rb_raise(rb_eArgError, "uninitialized product");
}
return ptr;
@@ -3462,9 +3493,16 @@ enum_product_allocate(VALUE klass)
* e.size #=> 6
*/
static VALUE
-enum_product_initialize(VALUE obj, VALUE enums)
+enum_product_initialize(int argc, VALUE *argv, VALUE obj)
{
struct enum_product *ptr;
+ VALUE enums = Qnil, options = Qnil;
+
+ rb_scan_args(argc, argv, "*:", &enums, &options);
+
+ if (!NIL_P(options) && !RHASH_EMPTY_P(options)) {
+ rb_exc_raise(rb_keyword_error_new("unknown", rb_hash_keys(options)));
+ }
rb_check_frozen(obj);
TypedData_Get_Struct(obj, struct enum_product, &enum_product_data_type, ptr);
@@ -3498,10 +3536,19 @@ static VALUE
enum_product_total_size(VALUE enums)
{
VALUE total = INT2FIX(1);
+ VALUE sizes = rb_ary_hidden_new(RARRAY_LEN(enums));
long i;
for (i = 0; i < RARRAY_LEN(enums); i++) {
VALUE size = enum_size(RARRAY_AREF(enums, i));
+ if (size == INT2FIX(0)) {
+ rb_ary_resize(sizes, 0);
+ return size;
+ }
+ rb_ary_push(sizes, size);
+ }
+ for (i = 0; i < RARRAY_LEN(sizes); i++) {
+ VALUE size = RARRAY_AREF(sizes, i);
if (NIL_P(size) || (RB_TYPE_P(size, T_FLOAT) && isinf(NUM2DBL(size)))) {
return size;
@@ -3570,7 +3617,7 @@ product_each(VALUE obj, struct product_state *pstate)
rb_block_call(eobj, id_each_entry, 0, NULL, product_each_i, (VALUE)pstate);
}
else {
- rb_funcallv(pstate->block, id_call, pstate->argc, pstate->argv);
+ rb_funcall(pstate->block, id_call, 1, rb_ary_new_from_values(pstate->argc, pstate->argv));
}
return obj;
@@ -3642,7 +3689,7 @@ inspect_enum_product(VALUE obj, VALUE dummy, int recur)
TypedData_Get_Struct(obj, struct enum_product, &enum_product_data_type, ptr);
- if (!ptr || ptr->enums == Qundef) {
+ if (!ptr || UNDEF_P(ptr->enums)) {
return rb_sprintf("#<%"PRIsVALUE": uninitialized>", rb_class_path(klass));
}
@@ -3668,6 +3715,7 @@ enum_product_inspect(VALUE obj)
/*
* call-seq:
* Enumerator.product(*enums) -> enumerator
+ * Enumerator.product(*enums) { |elts| ... } -> enumerator
*
* Generates a new enumerator object that generates a Cartesian
* product of given enumerable objects. This is equivalent to
@@ -3676,18 +3724,29 @@ enum_product_inspect(VALUE obj)
* e = Enumerator.product(1..3, [4, 5])
* e.to_a #=> [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
* e.size #=> 6
+ *
+ * When a block is given, calls the block with each N-element array
+ * generated and returns +nil+.
*/
static VALUE
-enumerator_s_product(VALUE klass, VALUE enums)
+enumerator_s_product(int argc, VALUE *argv, VALUE klass)
{
- VALUE obj = enum_product_initialize(enum_product_allocate(rb_cEnumProduct), enums);
+ VALUE enums = Qnil, options = Qnil, block = Qnil;
- if (rb_block_given_p()) {
- return enum_product_run(obj, rb_block_proc());
+ rb_scan_args(argc, argv, "*:&", &enums, &options, &block);
+
+ if (!NIL_P(options) && !RHASH_EMPTY_P(options)) {
+ rb_exc_raise(rb_keyword_error_new("unknown", rb_hash_keys(options)));
}
- else {
- return obj;
+
+ VALUE obj = enum_product_initialize(argc, argv, enum_product_allocate(rb_cEnumProduct));
+
+ if (!NIL_P(block)) {
+ enum_product_run(obj, block);
+ return Qnil;
}
+
+ return obj;
}
/*
@@ -4512,7 +4571,7 @@ InitVM_Enumerator(void)
rb_hash_aset(lazy_use_super_method, sym("uniq"), sym("_enumerable_uniq"));
rb_hash_aset(lazy_use_super_method, sym("with_index"), sym("_enumerable_with_index"));
rb_obj_freeze(lazy_use_super_method);
- rb_gc_register_mark_object(lazy_use_super_method);
+ rb_vm_register_global_object(lazy_use_super_method);
#if 0 /* for RDoc */
rb_define_method(rb_cLazy, "to_a", lazy_to_a, 0);
@@ -4567,7 +4626,7 @@ InitVM_Enumerator(void)
/* Product */
rb_cEnumProduct = rb_define_class_under(rb_cEnumerator, "Product", rb_cEnumerator);
rb_define_alloc_func(rb_cEnumProduct, enum_product_allocate);
- rb_define_method(rb_cEnumProduct, "initialize", enum_product_initialize, -2);
+ rb_define_method(rb_cEnumProduct, "initialize", enum_product_initialize, -1);
rb_define_method(rb_cEnumProduct, "initialize_copy", enum_product_init_copy, 1);
rb_define_method(rb_cEnumProduct, "each", enum_product_each, 0);
rb_define_method(rb_cEnumProduct, "size", enum_product_size, 0);
@@ -4578,7 +4637,7 @@ InitVM_Enumerator(void)
rb_undef_method(rb_cEnumProduct, "next_values");
rb_undef_method(rb_cEnumProduct, "peek");
rb_undef_method(rb_cEnumProduct, "peek_values");
- rb_define_singleton_method(rb_cEnumerator, "product", enumerator_s_product, -2);
+ rb_define_singleton_method(rb_cEnumerator, "product", enumerator_s_product, -1);
/* ArithmeticSequence */
rb_cArithSeq = rb_define_class_under(rb_cEnumerator, "ArithmeticSequence", rb_cEnumerator);