Feature #20664 ยป 0001-Add-before-and-until-options-to-Enumerator.produce.patch
| enumerator.c | ||
|---|---|---|
|
static ID id_rewind, id_new, id_to_enum, id_each_entry;
|
||
|
static ID id_next, id_result, id_receiver, id_arguments, id_memo, id_method, id_force;
|
||
|
static ID id_begin, id_end, id_step, id_exclude_end;
|
||
|
static ID id_before, id_until;
|
||
|
static VALUE sym_each, sym_cycle, sym_yield;
|
||
|
static VALUE lazy_use_super_method;
|
||
| ... | ... | |
|
struct producer {
|
||
|
VALUE init;
|
||
|
VALUE proc;
|
||
|
VALUE before_proc;
|
||
|
VALUE until_proc;
|
||
|
};
|
||
|
typedef struct MEMO *lazyenum_proc_func(VALUE, struct MEMO *, VALUE, long);
|
||
| ... | ... | |
|
struct producer *ptr = p;
|
||
|
rb_gc_mark_movable(ptr->init);
|
||
|
rb_gc_mark_movable(ptr->proc);
|
||
|
rb_gc_mark_movable(ptr->before_proc);
|
||
|
rb_gc_mark_movable(ptr->until_proc);
|
||
|
}
|
||
|
static void
|
||
| ... | ... | |
|
struct producer *ptr = p;
|
||
|
ptr->init = rb_gc_location(ptr->init);
|
||
|
ptr->proc = rb_gc_location(ptr->proc);
|
||
|
ptr->before_proc = rb_gc_location(ptr->before_proc);
|
||
|
ptr->until_proc = rb_gc_location(ptr->until_proc);
|
||
|
}
|
||
|
#define producer_free RUBY_TYPED_DEFAULT_FREE
|
||
| ... | ... | |
|
obj = TypedData_Make_Struct(klass, struct producer, &producer_data_type, ptr);
|
||
|
ptr->init = Qundef;
|
||
|
ptr->proc = Qundef;
|
||
|
ptr->before_proc = Qundef;
|
||
|
ptr->until_proc = Qundef;
|
||
|
return obj;
|
||
|
}
|
||
|
static VALUE
|
||
|
producer_init(VALUE obj, VALUE init, VALUE proc)
|
||
|
producer_init(VALUE obj, VALUE init, VALUE proc, VALUE before, VALUE until)
|
||
|
{
|
||
|
struct producer *ptr;
|
||
| ... | ... | |
|
RB_OBJ_WRITE(obj, &ptr->init, init);
|
||
|
RB_OBJ_WRITE(obj, &ptr->proc, proc);
|
||
|
if (!UNDEF_P(before) && !NIL_P(before)) {
|
||
|
VALUE pred = before;
|
||
|
if (!rb_obj_is_proc(pred)) {
|
||
|
pred = rb_funcall(before, idTo_proc, 0);
|
||
|
if (!rb_obj_is_proc(pred)) {
|
||
|
rb_raise(rb_eTypeError, "wrong argument type %s (expected Proc)", rb_obj_classname(before));
|
||
|
}
|
||
|
}
|
||
|
RB_OBJ_WRITE(obj, &ptr->before_proc, pred);
|
||
|
}
|
||
|
if (!UNDEF_P(until) && !NIL_P(until)) {
|
||
|
VALUE pred = until;
|
||
|
if (!rb_obj_is_proc(pred)) {
|
||
|
pred = rb_funcall(until, idTo_proc, 0);
|
||
|
if (!rb_obj_is_proc(pred)) {
|
||
|
rb_raise(rb_eTypeError, "wrong argument type %s (expected Proc)", rb_obj_classname(until));
|
||
|
}
|
||
|
}
|
||
|
RB_OBJ_WRITE(obj, &ptr->until_proc, pred);
|
||
|
}
|
||
|
return obj;
|
||
|
}
|
||
| ... | ... | |
|
producer_each_i(VALUE obj)
|
||
|
{
|
||
|
struct producer *ptr;
|
||
|
VALUE init, proc, curr;
|
||
|
VALUE init, proc, before_proc, until_proc, curr;
|
||
|
ptr = producer_ptr(obj);
|
||
|
init = ptr->init;
|
||
|
proc = ptr->proc;
|
||
|
before_proc = ptr->before_proc;
|
||
|
until_proc = ptr->until_proc;
|
||
|
if (UNDEF_P(init)) {
|
||
|
curr = Qnil;
|
||
|
}
|
||
|
else {
|
||
|
rb_yield(init);
|
||
|
} else {
|
||
|
curr = init;
|
||
|
goto yield;
|
||
|
}
|
||
|
for (;;) {
|
||
|
curr = rb_funcall(proc, id_call, 1, curr);
|
||
|
yield:
|
||
|
if (!UNDEF_P(before_proc) && RTEST(rb_funcall(before_proc, id_call, 1, curr))) {
|
||
|
rb_raise(rb_eStopIteration, "before condition is met");
|
||
|
}
|
||
|
rb_yield(curr);
|
||
|
if (!UNDEF_P(until_proc) && RTEST(rb_funcall(until_proc, id_call, 1, curr))) {
|
||
|
rb_raise(rb_eStopIteration, "until condition is met");
|
||
|
}
|
||
|
}
|
||
|
UNREACHABLE_RETURN(Qnil);
|
||
| ... | ... | |
|
return DBL2NUM(HUGE_VAL);
|
||
|
}
|
||
|
static VALUE
|
||
|
producer_size_unknown(VALUE obj, VALUE args, VALUE eobj)
|
||
|
{
|
||
|
return Qnil;
|
||
|
}
|
||
|
/*
|
||
|
* call-seq:
|
||
|
* Enumerator.produce(initial = nil) { |prev| block } -> enumerator
|
||
|
* Enumerator.produce(initial = nil, before: nil, until: nil) { |prev| block } -> enumerator
|
||
|
*
|
||
|
* Creates an infinite enumerator from any block, just called over and
|
||
|
* over. The result of the previous iteration is passed to the next one.
|
||
|
* If +initial+ is provided, it is passed to the first iteration, and
|
||
|
* becomes the first element of the enumerator; if it is not provided,
|
||
|
* the first iteration receives +nil+, and its result becomes the first
|
||
|
* element of the iterator.
|
||
|
* Creates an enumerator that generates a sequence of values from a
|
||
|
* block that is called over and over. The result of the previous
|
||
|
* iteration is passed to the next one. If +initial+ is provided, it
|
||
|
* is passed to the first iteration, and becomes the first element of
|
||
|
* the enumerator; if it is not provided, the first iteration receives
|
||
|
* +nil+, and its result becomes the first element of the iterator.
|
||
|
*
|
||
|
* Raising StopIteration from the block stops an iteration.
|
||
|
* If +before+ is provided, it is used as a predicate to determine if
|
||
|
* an iteration should end before a generated value gets yielded.
|
||
|
*
|
||
|
* If +until+ is provided, it is used as a predicate to determine if
|
||
|
* an iteration should end after a generated value gets yielded.
|
||
|
*
|
||
|
* Any value that responds to +to_proc+ and returns a +Proc+ object is
|
||
|
* accepted in these options.
|
||
|
*
|
||
|
* Raising StopIteration from the block also stops an iteration.
|
||
|
*
|
||
|
* Enumerator.produce(1, &:succ) # => enumerator of 1, 2, 3, 4, ....
|
||
|
*
|
||
|
* Enumerator.produce(File, before: :nil?, &:superclass) # => enumerator of File, IO, Object, BasicObject
|
||
|
*
|
||
|
* Enumerator.produce(3, until: :zero?, &:pred) # => enumerator of 3, 2, 1, 0
|
||
|
*
|
||
|
* Enumerator.produce { rand(10) } # => infinite random number sequence
|
||
|
*
|
||
|
* ancestors = Enumerator.produce(node) { |prev| node = prev.parent or raise StopIteration }
|
||
| ... | ... | |
|
static VALUE
|
||
|
enumerator_s_produce(int argc, VALUE *argv, VALUE klass)
|
||
|
{
|
||
|
VALUE init, producer;
|
||
|
VALUE init, producer, options, proc, before = Qundef, until = Qundef;
|
||
|
if (!rb_block_given_p()) rb_raise(rb_eArgError, "no block given");
|
||
|
if (rb_scan_args(argc, argv, "01", &init) == 0) {
|
||
|
if (rb_scan_args(argc, argv, "01:&", &init, &options, &proc) == 0) {
|
||
|
init = Qundef;
|
||
|
}
|
||
|
producer = producer_init(producer_allocate(rb_cEnumProducer), init, rb_block_proc());
|
||
|
if (!NIL_P(options)) {
|
||
|
ID keys[2];
|
||
|
VALUE values[2];
|
||
|
keys[0] = id_before;
|
||
|
keys[1] = id_until;
|
||
|
rb_get_kwargs(options, keys, 0, 2, values);
|
||
|
before = values[0];
|
||
|
until = values[1];
|
||
|
}
|
||
|
return rb_enumeratorize_with_size_kw(producer, sym_each, 0, 0, producer_size, RB_NO_KEYWORDS);
|
||
|
producer = producer_init(producer_allocate(rb_cEnumProducer), init, proc, before, until);
|
||
|
return rb_enumeratorize_with_size_kw(
|
||
|
producer, sym_each, 0, 0,
|
||
|
before == Qundef && until == Qundef ? producer_size : producer_size_unknown,
|
||
|
RB_NO_KEYWORDS
|
||
|
);
|
||
|
}
|
||
|
/*
|
||
| ... | ... | |
|
id_end = rb_intern_const("end");
|
||
|
id_step = rb_intern_const("step");
|
||
|
id_exclude_end = rb_intern_const("exclude_end");
|
||
|
id_before = rb_intern_const("before");
|
||
|
id_until = rb_intern_const("until");
|
||
|
sym_each = ID2SYM(id_each);
|
||
|
sym_cycle = ID2SYM(rb_intern_const("cycle"));
|
||
|
sym_yield = ID2SYM(rb_intern_const("yield"));
|
||
| test/ruby/test_enumerator.rb | ||
|---|---|---|
|
def test_produce
|
||
|
assert_raise(ArgumentError) { Enumerator.produce }
|
||
|
# Reject unknown keyword arguments
|
||
|
assert_raise(ArgumentError) {
|
||
|
Enumerator.produce(a: 1, b: 1) {}
|
||
|
}
|
||
|
# Without initial object
|
||
|
passed_args = []
|
||
|
enum = Enumerator.produce { |obj| passed_args << obj; (obj || 0).succ }
|
||
| ... | ... | |
|
assert_equal [1, 2, 3], enum.take(3)
|
||
|
assert_equal [1, 2], passed_args
|
||
|
# With initial keyword arguments
|
||
|
passed_args = []
|
||
|
enum = Enumerator.produce(a: 1, b: 1) { |obj| passed_args << obj; obj.shift if obj.respond_to?(:shift)}
|
||
|
assert_instance_of(Enumerator, enum)
|
||
|
assert_equal Float::INFINITY, enum.size
|
||
|
assert_equal [{b: 1}, [1], :a, nil], enum.take(4)
|
||
|
assert_equal [{b: 1}, [1], :a], passed_args
|
||
|
# Raising StopIteration
|
||
|
words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/)
|
||
|
enum = Enumerator.produce { words.shift or raise StopIteration }
|
||
| ... | ... | |
|
"abc",
|
||
|
], enum.to_a
|
||
|
}
|
||
|
# before
|
||
|
enum = Enumerator.produce(File, before: :nil?, &:superclass)
|
||
|
assert_instance_of(Enumerator, enum)
|
||
|
assert_equal nil, enum.size
|
||
|
assert_equal [File, IO, Object, BasicObject], enum.to_a
|
||
|
# until
|
||
|
enum = Enumerator.produce(3, until: :zero?, &:pred)
|
||
|
assert_instance_of(Enumerator, enum)
|
||
|
assert_equal nil, enum.size
|
||
|
assert_equal [3, 2, 1, 0], enum.to_a
|
||
|
# before & until
|
||
|
calls = []
|
||
|
enum = Enumerator.produce(
|
||
|
3,
|
||
|
before: ->(x) {
|
||
|
calls << [:before, x]
|
||
|
false
|
||
|
},
|
||
|
until: ->(x) {
|
||
|
calls << [:until, x]
|
||
|
x.zero?
|
||
|
}
|
||
|
) { |x|
|
||
|
calls << [:proc, x]
|
||
|
x.pred
|
||
|
}
|
||
|
assert_instance_of(Enumerator, enum)
|
||
|
assert_equal nil, enum.size
|
||
|
enum.each do |x|
|
||
|
calls << [:yield, x]
|
||
|
end
|
||
|
assert_equal [
|
||
|
[:before, 3],
|
||
|
[:yield, 3],
|
||
|
[:until, 3],
|
||
|
[:proc, 3],
|
||
|
[:before, 2],
|
||
|
[:yield, 2],
|
||
|
[:until, 2],
|
||
|
[:proc, 2],
|
||
|
[:before, 1],
|
||
|
[:yield, 1],
|
||
|
[:until, 1],
|
||
|
[:proc, 1],
|
||
|
[:before, 0],
|
||
|
[:yield, 0],
|
||
|
[:until, 0],
|
||
|
], calls
|
||
|
end
|
||
|
def test_chain_each_lambda
|
||