diff options
Diffstat (limited to 'enumerator.c')
-rw-r--r-- | enumerator.c | 397 |
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); |