[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]