ruby-changes:12855
From: akr <ko1@a...>
Date: Thu, 20 Aug 2009 01:36:18 +0900 (JST)
Subject: [ruby-changes:12855] Ruby:r24587 (trunk): * enumerator.c: implement Enumerator#{next_values,peek_values,feed}
akr 2009-08-20 01:36:00 +0900 (Thu, 20 Aug 2009) New Revision: 24587 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=24587 Log: * enumerator.c: implement Enumerator#{next_values,peek_values,feed} and StopIteration#result. [ruby-dev:39109] (struct enumerator): replace no_next by stop_exc. new field feedvalue. (enumerator_mark): mark feedvalue and stop_exc. (enumerator_init): initialize feedvalue and stop_exc. (enumerator_init_copy): initialize feedvalue. (next_ii): send yield arguments as an array. return feedvalue. (next_i): generate StopIteration exception here. set result. (next_init): initialize feedvalue. (enumerator_next_values): new method Enumerator#next_values. (ary2sv): new function. (enumerator_peek_values): new method Enumerator#peek_values. (enumerator_feed): new method Enumerator#feed. (yielder_yield): return the yield value. (generator_each): return the iterator value. (stop_result): new method StopIteration#result. Modified files: trunk/ChangeLog trunk/NEWS trunk/enumerator.c trunk/test/ruby/test_enumerator.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 24586) +++ ChangeLog (revision 24587) @@ -1,3 +1,23 @@ +Thu Aug 20 01:28:42 2009 Tanaka Akira <akr@f...> + + * enumerator.c: implement Enumerator#{next_values,peek_values,feed} + and StopIteration#result. [ruby-dev:39109] + (struct enumerator): replace no_next by stop_exc. + new field feedvalue. + (enumerator_mark): mark feedvalue and stop_exc. + (enumerator_init): initialize feedvalue and stop_exc. + (enumerator_init_copy): initialize feedvalue. + (next_ii): send yield arguments as an array. return feedvalue. + (next_i): generate StopIteration exception here. set result. + (next_init): initialize feedvalue. + (enumerator_next_values): new method Enumerator#next_values. + (ary2sv): new function. + (enumerator_peek_values): new method Enumerator#peek_values. + (enumerator_feed): new method Enumerator#feed. + (yielder_yield): return the yield value. + (generator_each): return the iterator value. + (stop_result): new method StopIteration#result. + Thu Aug 20 01:06:48 2009 Yukihiro Matsumoto <matz@r...> * dir.c (DEFINE_STRUCT_DIRENT): use union to allocate sufficient Index: enumerator.c =================================================================== --- enumerator.c (revision 24586) +++ enumerator.c (revision 24587) @@ -19,6 +19,60 @@ * * A class which provides a method `each' to be used as an Enumerable * object. + * + * An enumerator can be created by following methods. + * - Kernel#to_enum + * - Kernel#enum_for + * - Enumerator.new + * + * Also, most iteration methods without a block returns an enumerator. + * For example, Array#map returns an enumerator if no block given. + * The enumerator has with_index. + * So ary.map.with_index works as follows. + * + * p [1,2,3].map.with_index {|o, i| o+i } #=> [1, 3, 5] + * + * An enumerator object can be used as an external iterator. + * I.e. Enumerator#next returns the next value of the iterator. + * Enumerator#next raises StopIteration at end. + * + * e = [1,2,3].each # enumerator object. + * p e.next #=> 1 + * p e.next #=> 2 + * p e.next #=> 3 + * p e.next #raises StopIteration + * + * An external iterator can be used to implement an internal iterator as follows. + * + * def ext_each(e) + * while true + * begin + * vs = e.next_values + * rescue StopIteration + * return $!.result + * end + * y = yield *vs + * e.feed y + * end + * end + * + * o = Object.new + * def o.each + * p yield + * p yield 1 + * p yield(1, 2) + * 3 + * end + * + * # use o.each as an internal iterator directly. + * p o.each {|*x| p x; [:b, *x] } + * #=> [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3 + * + * # convert o.each to an external external iterator for + * # implementing an internal iterator. + * p ext_each(o.to_enum) {|*x| p x; [:b, *x] } + * #=> [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3 + * */ VALUE rb_cEnumerator; static ID id_rewind, id_each; @@ -33,7 +87,8 @@ VALUE fib; VALUE dst; VALUE lookahead; - VALUE no_next; + VALUE feedvalue; + VALUE stop_exc; }; static VALUE rb_cGenerator, rb_cYielder; @@ -61,6 +116,8 @@ rb_gc_mark(ptr->fib); rb_gc_mark(ptr->dst); rb_gc_mark(ptr->lookahead); + rb_gc_mark(ptr->feedvalue); + rb_gc_mark(ptr->stop_exc); } static struct enumerator * @@ -284,7 +341,8 @@ ptr->fib = 0; ptr->dst = Qnil; ptr->lookahead = Qundef; - ptr->no_next = Qfalse; + ptr->feedvalue = Qundef; + ptr->stop_exc = Qfalse; return enum_obj; } @@ -365,6 +423,7 @@ ptr1->args = ptr0->args; ptr1->fib = 0; ptr1->lookahead = Qundef; + ptr1->feedvalue = Qundef; return obj; } @@ -502,8 +561,15 @@ static VALUE next_ii(VALUE i, VALUE obj, int argc, VALUE *argv) { - rb_fiber_yield(argc, argv); - return Qnil; + struct enumerator *e = enumerator_ptr(obj); + VALUE feedvalue = Qnil; + VALUE args = rb_ary_new4(argc, argv); + rb_fiber_yield(1, &args); + if (e->feedvalue != Qundef) { + feedvalue = e->feedvalue; + e->feedvalue = Qundef; + } + return feedvalue; } static VALUE @@ -511,9 +577,11 @@ { struct enumerator *e = enumerator_ptr(obj); VALUE nil = Qnil; + VALUE result; - rb_block_call(obj, id_each, 0, 0, next_ii, obj); - e->no_next = Qtrue; + result = rb_block_call(obj, id_each, 0, 0, next_ii, obj); + e->stop_exc = rb_exc_new2(rb_eStopIteration, "iteration reached at end"); + rb_ivar_set(e->stop_exc, rb_intern("result"), result); return rb_fiber_yield(1, &nil); } @@ -524,24 +592,56 @@ e->dst = curr; e->fib = rb_fiber_new(next_i, obj); e->lookahead = Qundef; + e->feedvalue = Qundef; } /* * call-seq: - * e.next => object + * e.next_values => array * - * Returns the next object in the enumerator, and move the internal - * position forward. When the position reached at the end, StopIteration - * is raised. + * Returns the next object as an array in the enumerator, + * and move the internal position forward. + * When the position reached at the end, StopIteration is raised. * - * Note that enumeration sequence by next method does not affect other + * This method can be used to distinguish <code>yield</code> and <code>yield nil</code>. + * + * o = Object.new + * def o.each + * yield + * yield 1 + * yield 1, 2 + * yield nil + * yield [1, 2] + * end + * e = o.to_enum + * p e.next_values + * p e.next_values + * p e.next_values + * p e.next_values + * p e.next_values + * e = o.to_enum + * p e.next + * p e.next + * p e.next + * p e.next + * p e.next + * + * # result + * # next_values next + * # [] nil + * # [1] 1 + * # [1, 2] [1, 2] + * # [nil] nil + * # [[1, 2]] [1, 2] + * + * Note that enumeration sequence by next_values method does not affect other * non-external enumeration methods, unless underlying iteration * methods itself has side-effect, e.g. IO#each_line. * */ static VALUE -enumerator_next(VALUE obj) +enumerator_next_values(VALUE obj) { struct enumerator *e = enumerator_ptr(obj); VALUE curr, v; @@ -552,8 +652,8 @@ return v; } - if (e->no_next) - rb_raise(rb_eStopIteration, "iteration reached at end"); + if (e->stop_exc) + rb_exc_raise(e->stop_exc); curr = rb_fiber_current(); @@ -562,28 +662,83 @@ } v = rb_fiber_resume(e->fib, 1, &curr); - if (e->no_next) { + if (e->stop_exc) { e->fib = 0; e->dst = Qnil; e->lookahead = Qundef; - rb_raise(rb_eStopIteration, "iteration reached at end"); + e->feedvalue = Qundef; + rb_exc_raise(e->stop_exc); } return v; } +static VALUE +ary2sv(VALUE args) +{ + if (TYPE(args) != T_ARRAY) + return args; + + switch (RARRAY_LEN(args)) { + case 0: + return Qnil; + + case 1: + return RARRAY_PTR(args)[0]; + + default: + return args; + } +} + /* * call-seq: - * e.peek => object + * e.next => object * - * Returns the next object in the enumerator, but don't move the internal + * Returns the next object in the enumerator, and move the internal * position forward. When the position reached at the end, StopIteration * is raised. * + * Note that enumeration sequence by next method does not affect other + * non-external enumeration methods, unless underlying iteration + * methods itself has side-effect, e.g. IO#each_line. + * */ static VALUE -enumerator_peek(VALUE obj) +enumerator_next(VALUE obj) { + VALUE vs = enumerator_next_values(obj); + return ary2sv(vs); +} + +/* + * call-seq: + * e.peek_values => array + * + * Returns the next object as an array in the enumerator, + * but don't move the internal position forward. + * When the position reached at the end, StopIteration is raised. + * + * o = Object.new + * def o.each + * yield + * yield 1 + * yield 1, 2 + * end + * e = o.to_enum + * p e.peek_values #=> [] + * e.next + * p e.peek_values #=> [1] + * e.next + * p e.peek_values #=> [1, 2] + * e.next + * p e.peek_values # raises StopIteration + * + */ + +static VALUE +enumerator_peek_values(VALUE obj) +{ struct enumerator *e = enumerator_ptr(obj); VALUE v; @@ -592,13 +747,67 @@ return v; } - v = enumerator_next(obj); + v = enumerator_next_values(obj); e->lookahead = v; return v; } /* * call-seq: + * e.peek => object + * + * Returns the next object in the enumerator, but don't move the internal + * position forward. When the position reached at the end, StopIteration + * is raised. + * + */ + +static VALUE +enumerator_peek(VALUE obj) +{ + VALUE vs = enumerator_peek_values(obj); + return ary2sv(vs); +} + +/* + * call-seq: + * e.feed obj => nil + * + * Set the value for the next yield in the enumerator returns. + * + * If the value is not set, yield returns nil. + * + * This value is cleared after used. + * + * o = Object.new + * def o.each + * p yield #=> 1 + * p yield #=> nil + * p yield + * end + * e = o.to_enum + * e.next + * e.feed 1 + * e.next + * e.next + * + */ + +static VALUE +enumerator_feed(VALUE obj, VALUE v) +{ + struct enumerator *e = enumerator_ptr(obj); + + if (e->feedvalue != Qundef) { + rb_raise(rb_eTypeError, "feed value already set"); + } + e->feedvalue = v; + + return Qnil; +} + +/* + * call-seq: * e.rewind => e * * Rewinds the enumeration sequence by the next method. @@ -617,7 +826,8 @@ e->fib = 0; e->dst = Qnil; e->lookahead = Qundef; - e->no_next = Qfalse; + e->feedvalue = Qundef; + e->stop_exc = Qfalse; return obj; } @@ -754,9 +964,7 @@ { struct yielder *ptr = yielder_ptr(obj); - rb_proc_call(ptr->proc, args); - - return obj; + return rb_proc_call(ptr->proc, args); } static VALUE @@ -883,9 +1091,42 @@ yielder = yielder_new(); - rb_proc_call(ptr->proc, rb_ary_new3(1, yielder)); + return rb_proc_call(ptr->proc, rb_ary_new3(1, yielder)); +} - return obj; +/* + * StopIteration + */ + +/* + * call-seq: + * stopiteration.result => value + * + * Returns the return value of the iterator. + * + * + * o = Object.new + * def o.each + * yield 1 + * yield 2 + * yield 3 + * 100 + * end + * e = o.to_enum + * p e.next #=> 1 + * p e.next #=> 2 + * p e.next #=> 3 + * begin + * e.next + * rescue StopIteration + * p $!.result #=> 100 + * end + * + */ +static VALUE +stop_result(VALUE self) +{ + return rb_attr_get(self, rb_intern("result")); } void @@ -909,12 +1150,16 @@ rb_define_method(rb_cEnumerator, "each_with_object", enumerator_with_object, 1); rb_define_method(rb_cEnumerator, "with_index", enumerator_with_index, -1); rb_define_method(rb_cEnumerator, "with_object", enumerator_with_object, 1); + rb_define_method(rb_cEnumerator, "next_values", enumerator_next_values, 0); + rb_define_method(rb_cEnumerator, "peek_values", enumerator_peek_values, 0); rb_define_method(rb_cEnumerator, "next", enumerator_next, 0); rb_define_method(rb_cEnumerator, "peek", enumerator_peek, 0); + rb_define_method(rb_cEnumerator, "feed", enumerator_feed, 1); rb_define_method(rb_cEnumerator, "rewind", enumerator_rewind, 0); rb_define_method(rb_cEnumerator, "inspect", enumerator_inspect, 0); rb_eStopIteration = rb_define_class("StopIteration", rb_eIndexError); + rb_define_method(rb_eStopIteration, "result", stop_result, 0); /* Generator */ rb_cGenerator = rb_define_class_under(rb_cEnumerator, "Generator", rb_cObject); Index: NEWS =================================================================== --- NEWS (revision 24586) +++ NEWS (revision 24587) @@ -8,7 +8,6 @@ with all sufficient information, see the ChangeLog file. == Changes since the 1.9.1 release - === Library updates (outstanding ones only) * builtin classes @@ -22,6 +21,13 @@ * Dir.home * Enumerator + * new methods: + * Enumerator#peek + * Enumerator#next_values + * Enumerator#peek_values + * Enumerator#feed + * StopIteration#result + * extended methods: * #with_index accepts an optional argument that specifies the index number to start with, defaulted to 0. @@ -29,6 +35,7 @@ * incompatible changes: * #rewind now calls the "rewind" method of the enclosed object if defined. + * #next doesn't clear the position at end. * IO * new method: Index: test/ruby/test_enumerator.rb =================================================================== --- test/ruby/test_enumerator.rb (revision 24586) +++ test/ruby/test_enumerator.rb (revision 24587) @@ -152,5 +152,130 @@ assert_raise(StopIteration) { e.next } assert_raise(StopIteration) { e.next } end + + def test_stop_result + a = [1] + res = a.each {} + e = a.each + assert_equal(1, e.next) + exc = assert_raise(StopIteration) { e.next } + assert_equal(res, exc.result) + end + + def test_next_values + o = Object.new + def o.each + yield + yield 1 + yield 1, 2 + end + e = o.to_enum + assert_equal(nil, e.next) + assert_equal(1, e.next) + assert_equal([1,2], e.next) + e = o.to_enum + assert_equal([], e.next_values) + assert_equal([1], e.next_values) + assert_equal([1,2], e.next_values) + end + + def test_peek_values + o = Object.new + def o.each + yield + yield 1 + yield 1, 2 + end + e = o.to_enum + assert_equal(nil, e.peek) + assert_equal(nil, e.next) + assert_equal(1, e.peek) + assert_equal(1, e.next) + assert_equal([1,2], e.peek) + assert_equal([1,2], e.next) + e = o.to_enum + assert_equal([], e.peek_values) + assert_equal([], e.next_values) + assert_equal([1], e.peek_values) + assert_equal([1], e.next_values) + assert_equal([1,2], e.peek_values) + assert_equal([1,2], e.next_values) + e = o.to_enum + assert_equal([], e.peek_values) + assert_equal(nil, e.next) + assert_equal([1], e.peek_values) + assert_equal(1, e.next) + assert_equal([1,2], e.peek_values) + assert_equal([1,2], e.next) + e = o.to_enum + assert_equal(nil, e.peek) + assert_equal([], e.next_values) + assert_equal(1, e.peek) + assert_equal([1], e.next_values) + assert_equal([1,2], e.peek) + assert_equal([1,2], e.next_values) + end + + def test_feed + o = Object.new + def o.each(ary) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.next + e.feed 1 + e.next + e.feed 2 + e.next + e.feed 3 + assert_raise(StopIteration) { e.next } + assert_equal([1,2,3], ary) + end + + def test_feed_mixed + o = Object.new + def o.each(ary) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.next + e.feed 1 + e.next + e.next + e.feed 3 + assert_raise(StopIteration) { e.next } + assert_equal([1,nil,3], ary) + end + + def test_feed_twice + o = Object.new + def o.each(ary) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.feed 1 + assert_raise(TypeError) { e.feed 2 } + end + + def test_feed_yielder + x = nil + e = Enumerator.new {|y| x = y.yield; 10 } + e.next + e.feed 100 + exc = assert_raise(StopIteration) { e.next } + assert_equal(100, x) + assert_equal(10, exc.result) + end + + end -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/