From badb86556777cc16d11beb47b38b25997d7aea75 Mon Sep 17 00:00:00 2001 From: knu Date: Thu, 10 Apr 2008 10:52:50 +0000 Subject: * enumerator.c (rb_eStopIteration), eval.c (rb_f_loop), ruby.h: Add a new exception class StopIteration, which breaks Kernel#loop iteration when raised; backported from 1.9. * enumerator.c (enumerator_next, enumerator_rewind): Implement #next and #rewind using the "generator" library. * lib/generator.rb: Implement Enumerable::Enumerator#next and #rewind. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_8@15954 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 12 ++++++++++++ NEWS | 10 ++++++++-- enumerator.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ eval.c | 17 +++++++++++++---- lib/generator.rb | 38 ++++++++++++++++++++++++++++++++++++++ ruby.h | 1 + 6 files changed, 117 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 78e4b4e3bb..5a1cee9d81 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +Thu Apr 10 19:49:10 2008 Akinori MUSHA + + * enumerator.c (rb_eStopIteration), eval.c (rb_f_loop), ruby.h: + Add a new exception class StopIteration, which breaks Kernel#loop + iteration when raised; backported from 1.9. + + * enumerator.c (enumerator_next, enumerator_rewind): Implement + #next and #rewind using the "generator" library. + + * lib/generator.rb: Implement Enumerable::Enumerator#next and + #rewind. + Thu Apr 10 19:29:48 2008 Akinori MUSHA * array.c (rb_ary_first, rb_ary_last): Return a shared array when diff --git a/NEWS b/NEWS index f91c4b1efb..bd3e1f6e61 100644 --- a/NEWS +++ b/NEWS @@ -61,10 +61,16 @@ with all sufficient information, see the ChangeLog file. * Regexp.union accepts an array of patterns. + * StopIteration + + New exception class that causes Kernel#loop to stop iteration when + raised. + * enumerator - * Enumerator is now a built-in module. Almost everything has been - backported from 1.9, except for the #next and #rewind methods. + * Enumerator is now a built-in module. The #next and #rewind + methods are implemented using the "generator" library. Use with + care and be aware of the performance loss. * ipaddr diff --git a/enumerator.c b/enumerator.c index e5bf538f0c..fefbc28b34 100644 --- a/enumerator.c +++ b/enumerator.c @@ -23,6 +23,8 @@ VALUE rb_cEnumerator; static VALUE sym_each, sym_call; +VALUE rb_eStopIteration; + static VALUE proc_call(proc, args) VALUE proc; @@ -387,6 +389,45 @@ enumerator_with_index(obj) enumerator_with_index_i, (VALUE)&memo); } +/* + * call-seq: + * e.next => object + * + * Returns the next object in the enumerator, and move the internal + * position forward. When the position reached at the end, internal + * position is rewinded then 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. + * + * Caution: Calling this method causes the "generator" library to be + * loaded. + */ + +static VALUE +enumerator_next(obj) + VALUE obj; +{ + rb_require("generator"); + return rb_funcall(obj, rb_intern("next"), 0, 0); +} + +/* + * call-seq: + * e.rewind => e + * + * Rewinds the enumeration sequence by the next method. + */ + +static VALUE +enumerator_rewind(obj) + VALUE obj; +{ + rb_require("generator"); + return rb_funcall(obj, rb_intern("rewind"), 0, 0); +} + void Init_Enumerator() { @@ -406,6 +447,10 @@ Init_Enumerator() rb_define_method(rb_cEnumerator, "initialize_copy", enumerator_init_copy, 1); rb_define_method(rb_cEnumerator, "each", enumerator_each, 0); rb_define_method(rb_cEnumerator, "with_index", enumerator_with_index, 0); + rb_define_method(rb_cEnumerator, "next", enumerator_next, 0); + rb_define_method(rb_cEnumerator, "rewind", enumerator_rewind, 0); + + rb_eStopIteration = rb_define_class("StopIteration", rb_eIndexError); sym_each = ID2SYM(rb_intern("each")); sym_call = ID2SYM(rb_intern("call")); diff --git a/eval.c b/eval.c index 8972f3bd72..5a03eccc91 100644 --- a/eval.c +++ b/eval.c @@ -5157,6 +5157,16 @@ rb_yield_splat(values) return rb_yield_0(values, 0, 0, 0, avalue); } +static VALUE +loop_i() +{ + for (;;) { + rb_yield_0(Qundef, 0, 0, 0, Qfalse); + CHECK_INTS; + } + return Qnil; +} + /* * call-seq: * loop {|| block } @@ -5169,15 +5179,14 @@ rb_yield_splat(values) * break if !line or line =~ /^qQ/ * # ... * end + * + * StopIteration raised in the block breaks the loop. */ static VALUE rb_f_loop() { - for (;;) { - rb_yield_0(Qundef, 0, 0, 0, Qfalse); - CHECK_INTS; - } + rb_rescue2(loop_i, (VALUE)0, 0, 0, rb_eStopIteration, (VALUE)0); return Qnil; /* dummy */ } diff --git a/lib/generator.rb b/lib/generator.rb index a010559b60..dbdd0f40a9 100644 --- a/lib/generator.rb +++ b/lib/generator.rb @@ -165,6 +165,44 @@ class Generator end end +class Enumerable::Enumerator + def __generator + @generator ||= Generator.new(self) + end + private :__generator + + # call-seq: + # e.next => object + # + # Returns the next object in the enumerator, and move the internal + # position forward. When the position reached at the end, internal + # position is rewinded then 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. + # + # Caution: This feature internally uses Generator, which uses callcc + # to stop and resume enumeration to fetch each value. Use with care + # and be aware of the performance loss. + def next + g = __generator + return g.next unless g.end? + + g.rewind + raise StopIteration, 'iteration reached at end' + end + + # call-seq: + # e.rewind => e + # + # Rewinds the enumeration sequence by the next method. + def rewind + __generator.rewind + self + end +end + # # SyncEnumerator creates an Enumerable object from multiple Enumerable # objects and enumerates them synchronously. diff --git a/ruby.h b/ruby.h index ac10e9d04d..95b4c4ca78 100644 --- a/ruby.h +++ b/ruby.h @@ -659,6 +659,7 @@ RUBY_EXTERN VALUE rb_eFatal; RUBY_EXTERN VALUE rb_eArgError; RUBY_EXTERN VALUE rb_eEOFError; RUBY_EXTERN VALUE rb_eIndexError; +RUBY_EXTERN VALUE rb_eStopIteration; RUBY_EXTERN VALUE rb_eRangeError; RUBY_EXTERN VALUE rb_eIOError; RUBY_EXTERN VALUE rb_eRuntimeError; -- cgit v1.2.3