diff --git a/ChangeLog b/ChangeLog index 88e9e4236b..38264c435f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Thu Dec 11 19:33:10 2008 Akinori MUSHA + + * enumerator.c (enumerator_initialize): Add an ability to generate + an enumerator from a block. [ruby-dev:35903] + Thu Dec 11 19:08:38 2008 Akinori MUSHA * enumerator.c (enum_each_with_object): Add diff --git a/NEWS b/NEWS index 64623b3f4d..bac18349ea 100644 --- a/NEWS +++ b/NEWS @@ -27,12 +27,14 @@ with all sufficient information, see the ChangeLog file. New method with which replaces #choice. * Enumerable#each_with_object - - New method. - * Enumerator#with_object - New method. + New methods. + + * Enumerator.new { |y| ... } + + Now can generate an enumerator from a block that defines + enumeration instead of an enumerable object. * Hash#default_proc= diff --git a/enumerator.c b/enumerator.c index 71362c168c..541083890f 100644 --- a/enumerator.c +++ b/enumerator.c @@ -33,7 +33,25 @@ struct enumerator { rb_block_call_func *iter; }; +static VALUE rb_cGenerator, rb_cYielder; + +struct generator { + VALUE proc; +}; + +struct yielder { + VALUE proc; +}; + +static VALUE generator_allocate _((VALUE klass)); +static VALUE generator_init _((VALUE obj, VALUE proc)); + +/* + * Enumerator + */ + static void enumerator_mark _((void *)); + static void enumerator_mark(p) void *p; @@ -292,12 +310,34 @@ enumerator_init(enum_obj, obj, meth, argc, argv) /* * call-seq: * Enumerator.new(obj, method = :each, *args) + * Enumerator.new { |y| ... } * * Creates a new Enumerator object, which is to be used as an - * Enumerable object using the given object's given method with the - * given arguments. + * Enumerable object iterating in a given way. * - * Use of this method is discouraged. Use Kernel#enum_for() instead. + * In the first form, a generated Enumerator iterates over the given + * object using the given method with the given arguments passed. + * Use of this form is discouraged. Use Kernel#enum_for(), alias + * to_enum, instead. + * + * e = Enumerator.new(ObjectSpace, :each_object) + * #-> ObjectSpace.enum_for(:each_object) + * + * e.select { |obj| obj.is_a?(Class) } #=> array of all classes + * + * In the second form, iteration is defined by the given block, in + * which a "yielder" object given as block parameter can be used to + * yield a value by calling the +yield+ method, alias +<<+. + * + * fib = Enumerator.new { |y| + * a = b = 1 + * loop { + * y << a + * a, b = b, a + b + * } + * } + * + * p fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] */ static VALUE enumerator_initialize(argc, argv, obj) @@ -307,13 +347,19 @@ enumerator_initialize(argc, argv, obj) { VALUE recv, meth = sym_each; - if (argc == 0) - rb_raise(rb_eArgError, "wrong number of argument (0 for 1)"); - recv = *argv++; - if (--argc) { - meth = *argv++; - --argc; + if (argc == 0) { + if (!rb_block_given_p()) + rb_raise(rb_eArgError, "wrong number of argument (0 for 1+)"); + + recv = generator_init(generator_allocate(rb_cGenerator), rb_block_proc()); + } else { + recv = *argv++; + if (--argc) { + meth = *argv++; + --argc; + } } + return enumerator_init(obj, recv, meth, argc, argv); } @@ -490,6 +536,242 @@ enumerator_rewind(obj) return rb_funcall(obj, rb_intern("rewind"), 0, 0); } +/* + * Yielder + */ + +static void yielder_mark _((void *)); + +static void +yielder_mark(void *p) +{ + struct yielder *ptr = p; + rb_gc_mark(ptr->proc); +} + +static struct yielder * +yielder_ptr(VALUE obj) +{ + struct yielder *ptr; + + Data_Get_Struct(obj, struct yielder, ptr); + if (RDATA(obj)->dmark != yielder_mark) { + rb_raise(rb_eTypeError, + "wrong argument type %s (expected %s)", + rb_obj_classname(obj), rb_class2name(rb_cYielder)); + } + if (!ptr || ptr->proc == Qundef) { + rb_raise(rb_eArgError, "uninitialized yielder"); + } + return ptr; +} + +static VALUE yielder_allocate _((VALUE)); + +/* :nodoc: */ +static VALUE +yielder_allocate(klass) + VALUE klass; +{ + struct yielder *ptr; + VALUE obj; + + obj = Data_Make_Struct(klass, struct yielder, yielder_mark, -1, ptr); + ptr->proc = Qundef; + + return obj; +} + +static VALUE +yielder_init(obj, proc) + VALUE obj, proc; +{ + struct yielder *ptr; + + Data_Get_Struct(obj, struct yielder, ptr); + + if (!ptr) { + rb_raise(rb_eArgError, "unallocated yielder"); + } + + ptr->proc = proc; + + return obj; +} + +/* :nodoc: */ +static VALUE +yielder_initialize(obj) + VALUE obj; +{ + rb_need_block(); + + return yielder_init(obj, rb_block_proc()); +} + +/* :nodoc: */ +static VALUE +yielder_yield(obj, args) + VALUE obj, args; +{ + struct yielder *ptr = yielder_ptr(obj); + + rb_proc_call(ptr->proc, args); + + return obj; +} + +static VALUE +yielder_new_i(dummy) + VALUE dummy; +{ + return yielder_init(yielder_allocate(rb_cYielder), rb_block_proc()); +} + +static VALUE +yielder_yield_i(obj, memo) + VALUE obj, memo; +{ + return rb_yield(obj); +} + +static VALUE +yielder_new() +{ + return rb_iterate(yielder_new_i, (VALUE)0, yielder_yield_i, (VALUE)0); +} + +/* + * Generator + */ + +static void generator_mark _((void *)); + +static void +generator_mark(p) + void *p; +{ + struct generator *ptr = p; + rb_gc_mark(ptr->proc); +} + +static struct generator * +generator_ptr(obj) + VALUE obj; +{ + struct generator *ptr; + + Data_Get_Struct(obj, struct generator, ptr); + if (RDATA(obj)->dmark != generator_mark) { + rb_raise(rb_eTypeError, + "wrong argument type %s (expected %s)", + rb_obj_classname(obj), rb_class2name(rb_cGenerator)); + } + if (!ptr || ptr->proc == Qundef) { + rb_raise(rb_eArgError, "uninitialized generator"); + } + return ptr; +} + +static VALUE generator_allocate _((VALUE)); + +/* :nodoc: */ +static VALUE +generator_allocate(klass) + VALUE klass; +{ + struct generator *ptr; + VALUE obj; + + obj = Data_Make_Struct(klass, struct generator, generator_mark, -1, ptr); + ptr->proc = Qundef; + + return obj; +} + +static VALUE +generator_init(obj, proc) + VALUE obj, proc; +{ + struct generator *ptr; + + Data_Get_Struct(obj, struct generator, ptr); + + if (!ptr) { + rb_raise(rb_eArgError, "unallocated generator"); + } + + ptr->proc = proc; + + return obj; +} + +VALUE rb_obj_is_proc _((VALUE)); + +/* :nodoc: */ +static VALUE +generator_initialize(argc, argv, obj) + int argc; + VALUE *argv; + VALUE obj; +{ + VALUE proc; + + if (argc == 0) { + rb_need_block(); + + proc = rb_block_proc(); + } else { + rb_scan_args(argc, argv, "1", &proc); + + if (!rb_obj_is_proc(proc)) + rb_raise(rb_eTypeError, + "wrong argument type %s (expected Proc)", + rb_obj_classname(proc)); + + if (rb_block_given_p()) { + rb_warn("given block not used"); + } + } + + return generator_init(obj, proc); +} + +/* :nodoc: */ +static VALUE +generator_init_copy(obj, orig) + VALUE obj, orig; +{ + struct generator *ptr0, *ptr1; + + ptr0 = generator_ptr(orig); + + Data_Get_Struct(obj, struct generator, ptr1); + + if (!ptr1) { + rb_raise(rb_eArgError, "unallocated generator"); + } + + ptr1->proc = ptr0->proc; + + return obj; +} + +/* :nodoc: */ +static VALUE +generator_each(obj) + VALUE obj; +{ + struct generator *ptr = generator_ptr(obj); + VALUE yielder; + + yielder = yielder_new(); + + rb_proc_call(ptr->proc, rb_ary_new3(1, yielder)); + + return obj; +} + void Init_Enumerator() { @@ -518,6 +800,21 @@ Init_Enumerator() rb_eStopIteration = rb_define_class("StopIteration", rb_eIndexError); + /* Generator */ + rb_cGenerator = rb_define_class_under(rb_cEnumerator, "Generator", rb_cObject); + rb_include_module(rb_cGenerator, rb_mEnumerable); + rb_define_alloc_func(rb_cGenerator, generator_allocate); + rb_define_method(rb_cGenerator, "initialize", generator_initialize, -1); + rb_define_method(rb_cGenerator, "initialize_copy", generator_init_copy, 1); + rb_define_method(rb_cGenerator, "each", generator_each, 0); + + /* Yielder */ + rb_cYielder = rb_define_class_under(rb_cEnumerator, "Yielder", rb_cObject); + rb_define_alloc_func(rb_cYielder, yielder_allocate); + rb_define_method(rb_cYielder, "initialize", yielder_initialize, 0); + rb_define_method(rb_cYielder, "yield", yielder_yield, -2); + rb_define_method(rb_cYielder, "<<", yielder_yield, -2); + sym_each = ID2SYM(rb_intern("each")); /* backward compatibility */