diff options
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | NEWS | 10 | ||||
-rw-r--r-- | enumerator.c | 45 | ||||
-rw-r--r-- | eval.c | 17 | ||||
-rw-r--r-- | lib/generator.rb | 38 | ||||
-rw-r--r-- | ruby.h | 1 |
6 files changed, 117 insertions, 6 deletions
@@ -1,3 +1,15 @@ +Thu Apr 10 19:49:10 2008 Akinori MUSHA <knu@iDaemons.org> + + * 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 <knu@iDaemons.org> * array.c (rb_ary_first, rb_ary_last): Return a shared array when @@ -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")); @@ -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. @@ -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; |