Project

General

Profile

Feature #14781 ยป enumerator-generate.patch

zverok (Victor Shepelev), 08/11/2019 10:29 AM

View differences:

enumerator.c
2659 2659
    return rb_attr_get(self, id_result);
2660 2660
}
2661 2661

  
2662

  
2663
static VALUE
2664
enumeratorgenerate_i(RB_BLOCK_CALL_FUNC_ARGLIST(yielder, enumerator))
2665
{
2666
    VALUE proc, cur;
2667
    cur = rb_ivar_get(enumerator, rb_intern("enumeratorgenerate_init"));
2668
    proc = rb_ivar_get(enumerator, rb_intern("enumeratorgenerate_proc"));
2669

  
2670
    // We need to pass initial value too -- unless there were none.
2671
    if (cur == rb_intern("__none__")) {
2672
        cur = Qnil;
2673
    } else {
2674
        rb_funcallv(yielder, idLTLT, 1, &cur);
2675
    }
2676

  
2677
    for (;;) {
2678
        cur = rb_proc_call(proc, rb_ary_new3(1, cur));
2679
        rb_funcallv(yielder, idLTLT, 1, &cur);
2680
    }
2681

  
2682
    return Qnil;
2683
}
2684

  
2685
/*
2686
 *  call-seq:
2687
 *     Enumerator.generate(initial) { |val| } -> enumerator
2688
 *     Enumerator.generate { |val| } -> enumerator
2689
 *
2690
 * Creates an infinite enumerator from any block, just called over and over.
2691
 * Result of the previous iteration is passed to the next one. If +initial+
2692
 * is provided, it is passed to the first iteration, and becomes the first
2693
 * element of the enumerator; if it is not provided, first iteration receives
2694
 * +nil+, and its result becomes first element of the iterator.
2695
 *
2696
 * One can think of this method as a Ruby's way of expressing +loop+/+while+,
2697
 * in the same manner as Array#each is Ruby's way of expressing +for+.
2698
 *
2699
 * Examples of usage:
2700
 *
2701
 *    # With initial value
2702
 *
2703
 *    # Simplest possible infinite sequence:
2704
 *    Enumerator.generate(1, &:succ) # => enumerator of 1, 2, 3, 4, ....
2705
 *
2706
 *    # Find next Tuesday
2707
 *    require 'date'
2708
 *    Enumerator.generate(Date.today, &:succ).detect(&:tuesday?)
2709
 *
2710
 *    # Some tree navigation:
2711
 *    Enumerator.generate(node) { |n| n.parent }.take_while { |n| n.respond_to?(:parent) }
2712
 *
2713
 *
2714
 *    # Without initial value
2715
 *
2716
 *    # Infinite random number sequence:
2717
 *    Enumerator.generate { rand(10) }
2718
 *
2719
 *    # Simple lexer:
2720
 *    require 'strscan'
2721
 *    scanner = StringScanner.new('7+38/6')
2722
 *    PATTERN = %r{\d+|[-+/*]}
2723
 *    p Enumerator.generate { scanner.scan(PATTERN) }.slice_after { scanner.eos? }.first
2724
 *    # => ["7", "+", "38", "/", "6"]
2725
 */
2726
static VALUE
2727
enumerator_generate(int argc, VALUE *argv, VALUE dummy)
2728
{
2729
    VALUE enumerator;
2730
    VALUE init, proc;
2731

  
2732
    if (rb_scan_args(argc, argv, "01", &init) == 0) {
2733
        // We need a way to pass the fact that there were no start value,
2734
        // An `nil` is not good enough: it still can be "`nil` as a start value"
2735
        init = rb_intern("__none__");
2736
    }
2737
    if (!rb_block_given_p()) {
2738
        rb_raise(rb_eArgError, "no block given");
2739
    }
2740

  
2741
    proc = rb_block_proc();
2742

  
2743
    enumerator = rb_obj_alloc(rb_cEnumerator);
2744
    rb_ivar_set(enumerator, rb_intern("enumeratorgenerate_init"), init);
2745
    rb_ivar_set(enumerator, rb_intern("enumeratorgenerate_proc"), proc);
2746

  
2747
    rb_block_call(enumerator, idInitialize, 0, 0, enumeratorgenerate_i, enumerator);
2748
    return enumerator;
2749

  
2750
}
2751

  
2662 2752
/*
2663 2753
 * Document-class: Enumerator::Chain
2664 2754
 *
......
3557 3647
    rb_include_module(rb_cEnumerator, rb_mEnumerable);
3558 3648

  
3559 3649
    rb_define_alloc_func(rb_cEnumerator, enumerator_allocate);
3650
    rb_define_singleton_method(rb_cEnumerator, "generate", enumerator_generate, -1);
3560 3651
    rb_define_method(rb_cEnumerator, "initialize", enumerator_initialize, -1);
3561 3652
    rb_define_method(rb_cEnumerator, "initialize_copy", enumerator_init_copy, 1);
3562 3653
    rb_define_method(rb_cEnumerator, "each", enumerator_each, -1);
test/ruby/test_enumerator.rb
811 811
      e5.inspect
812 812
    )
813 813
  end
814

  
815
  def test_generate
816
    # Without initial object
817
    passed_args = []
818
    enum = Enumerator.generate { |obj| passed_args << obj; (obj || 0).succ }
819
    assert_instance_of(Enumerator, enum)
820
    assert_equal [1, 2, 3], enum.take(3)
821
    assert_equal [nil, 1, 2], passed_args
822

  
823
    # With initial object
824
    passed_args = []
825
    enum = Enumerator.generate(1) { |obj| passed_args << obj; obj.succ }
826
    assert_instance_of(Enumerator, enum)
827
    assert_equal [1, 2, 3], enum.take(3)
828
    assert_equal [1, 2], passed_args
829

  
830
    # Without block
831
    assert_raise(ArgumentError) do
832
      Enumerator.generate
833
    end
834
  end
814 835
end