summaryrefslogtreecommitdiff
path: root/enumerator.c
diff options
context:
space:
mode:
authorakr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-08-19 16:36:00 +0000
committerakr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-08-19 16:36:00 +0000
commit3b4949ffa8602bf5b8fb4be880b9c73c9375d8fa (patch)
treeace672ab48d817ca1927ee8660859cdb2308da2c /enumerator.c
parentb307bd9847b548f0f80621b2c4097a7be28de411 (diff)
* 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. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@24587 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'enumerator.c')
-rw-r--r--enumerator.c297
1 files changed, 271 insertions, 26 deletions
diff --git a/enumerator.c b/enumerator.c
index 63c05d155f..a8120aa79b 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -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 @@ struct enumerator {
VALUE fib;
VALUE dst;
VALUE lookahead;
- VALUE no_next;
+ VALUE feedvalue;
+ VALUE stop_exc;
};
static VALUE rb_cGenerator, rb_cYielder;
@@ -61,6 +116,8 @@ enumerator_mark(void *p)
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 @@ enumerator_init(VALUE enum_obj, VALUE obj, VALUE meth, int argc, VALUE *argv)
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 @@ enumerator_init_copy(VALUE obj, VALUE orig)
ptr1->args = ptr0->args;
ptr1->fib = 0;
ptr1->lookahead = Qundef;
+ ptr1->feedvalue = Qundef;
return obj;
}
@@ -502,8 +561,15 @@ enumerator_with_object(VALUE obj, VALUE memo)
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 @@ next_i(VALUE curr, VALUE obj)
{
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 @@ next_init(VALUE obj, struct enumerator *e)
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 @@ enumerator_next(VALUE obj)
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,27 +662,82 @@ enumerator_next(VALUE obj)
}
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 @@ enumerator_peek(VALUE obj)
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 @@ enumerator_rewind(VALUE obj)
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 @@ yielder_yield(VALUE obj, VALUE args)
{
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 @@ generator_each(VALUE obj)
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 @@ Init_Enumerator(void)
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);