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

ruby-changes:33900

From: akr <ko1@a...>
Date: Sun, 18 May 2014 09:06:10 +0900 (JST)
Subject: [ruby-changes:33900] akr:r45981 (trunk): * enum.c: Enumerable#slice_after implemented.

akr	2014-05-18 09:06:05 +0900 (Sun, 18 May 2014)

  New Revision: 45981

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=45981

  Log:
    * enum.c: Enumerable#slice_after implemented.
    
    * enumerator.c: Enumerator::Lazy#slice_after implemented.
    
      Requested by Tsuyoshi Sawada.  [ruby-core:58123] [Feature #9071]

  Modified files:
    trunk/ChangeLog
    trunk/NEWS
    trunk/enum.c
    trunk/enumerator.c
    trunk/test/ruby/test_enum.rb
    trunk/test/ruby/test_lazy_enumerator.rb
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 45980)
+++ ChangeLog	(revision 45981)
@@ -1,3 +1,11 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Sun May 18 09:02:17 2014  Tanaka Akira  <akr@f...>
+
+	* enum.c: Enumerable#slice_after implemented.
+
+	* enumerator.c: Enumerator::Lazy#slice_after implemented.
+
+	  Requested by Tsuyoshi Sawada.  [ruby-core:58123] [Feature #9071]
+
 Sun May 18 08:22:25 2014  Nobuyoshi Nakada  <nobu@r...>
 
 	* io.c (io_setstrbuf): always check if the buffer is modifiable.
Index: enumerator.c
===================================================================
--- enumerator.c	(revision 45980)
+++ enumerator.c	(revision 45981)
@@ -2036,6 +2036,7 @@ InitVM_Enumerator(void) https://github.com/ruby/ruby/blob/trunk/enumerator.c#L2036
     rb_define_method(rb_cLazy, "lazy", lazy_lazy, 0);
     rb_define_method(rb_cLazy, "chunk", lazy_super, -1);
     rb_define_method(rb_cLazy, "slice_before", lazy_super, -1);
+    rb_define_method(rb_cLazy, "slice_after", lazy_super, -1);
 
     rb_define_alias(rb_cLazy, "force", "to_a");
 
Index: enum.c
===================================================================
--- enum.c	(revision 45980)
+++ enum.c	(revision 45981)
@@ -3083,6 +3083,130 @@ enum_slice_before(int argc, VALUE *argv, https://github.com/ruby/ruby/blob/trunk/enum.c#L3083
     return enumerator;
 }
 
+
+struct sliceafter_arg {
+    VALUE pat;
+    VALUE pred;
+    VALUE prev_elts;
+    VALUE yielder;
+};
+
+static VALUE
+sliceafter_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, _memo))
+{
+#define UPDATE_MEMO ((memo = MEMO_FOR(struct sliceafter_arg, _memo)), 1)
+    struct sliceafter_arg *memo;
+    int split_p;
+    UPDATE_MEMO;
+
+    ENUM_WANT_SVALUE();
+
+    if (NIL_P(memo->prev_elts)) {
+        memo->prev_elts = rb_ary_new3(1, i);
+    }
+    else {
+        rb_ary_push(memo->prev_elts, i);
+    }
+
+    if (NIL_P(memo->pred)) {
+        split_p = RTEST(rb_funcall(memo->pat, id_eqq, 1, i));
+        UPDATE_MEMO;
+    }
+    else {
+        split_p = RTEST(rb_funcall(memo->pred, id_call, 1, i));
+        UPDATE_MEMO;
+    }
+
+    if (split_p) {
+        rb_funcall(memo->yielder, id_lshift, 1, memo->prev_elts);
+        UPDATE_MEMO;
+        memo->prev_elts = Qnil;
+    }
+
+    return Qnil;
+#undef UPDATE_MEMO
+}
+
+static VALUE
+sliceafter_i(RB_BLOCK_CALL_FUNC_ARGLIST(yielder, enumerator))
+{
+    VALUE enumerable;
+    VALUE arg;
+    struct sliceafter_arg *memo = NEW_MEMO_FOR(struct sliceafter_arg, arg);
+
+    enumerable = rb_ivar_get(enumerator, rb_intern("sliceafter_enum"));
+    memo->pat = rb_ivar_get(enumerator, rb_intern("sliceafter_pat"));
+    memo->pred = rb_attr_get(enumerator, rb_intern("sliceafter_pred"));
+    memo->prev_elts = Qnil;
+    memo->yielder = yielder;
+
+    rb_block_call(enumerable, id_each, 0, 0, sliceafter_ii, arg);
+    memo = MEMO_FOR(struct sliceafter_arg, arg);
+    if (!NIL_P(memo->prev_elts))
+        rb_funcall(memo->yielder, id_lshift, 1, memo->prev_elts);
+    return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     enum.slice_after(pattern)       -> an_enumerator
+ *     enum.slice_after { |elt| bool } -> an_enumerator
+ *
+ *  Creates an enumerator for each chunked elements.
+ *  The ends of chunks are defined by _pattern_ and the block.
+ *
+ *  If <code>_pattern_ === _elt_</code> returns <code>true</code> or the block
+ *  returns <code>true</code> for the element, the element is end of a
+ *  chunk.
+ *
+ *  The <code>===</code> and _block_ is called from the first element to the last
+ *  element of _enum_.
+ *
+ *  The result enumerator yields the chunked elements as an array.
+ *  So +each+ method can be called as follows:
+ *
+ *    enum.slice_after(pattern).each { |ary| ... }
+ *    enum.slice_after { |elt| bool }.each { |ary| ... }
+ *
+ *  Other methods of the Enumerator class and Enumerable module,
+ *  such as +map+, etc., are also usable.
+ *
+ *  For example, continuation lines (lines end with backslash) can be
+ *  concatenated as follows:
+ *
+ *    lines = ["foo\n", "bar\\\n", "baz\n", "\n", "qux\n"]
+ *    e = lines.slice_after(/(?<!\\)\n\z/)
+ *    p e.to_a
+ *    #=> [["foo\n"], ["bar\\\n", "baz\n"], ["\n"], ["qux\n"]]
+ *    p e.map {|ll| ll[0...-1].map {|l| l.sub(/\\\n\z/, "") }.join + ll.last }
+ *    #=>["foo\n", "barbaz\n", "\n", "qux\n"]
+ *
+ */
+
+static VALUE
+enum_slice_after(int argc, VALUE *argv, VALUE enumerable)
+{
+    VALUE enumerator;
+    VALUE pat = Qnil, pred = Qnil;
+
+    if (rb_block_given_p()) {
+        if (0 < argc)
+            rb_raise(rb_eArgError, "both pattan and block are given");
+        pred = rb_block_proc();
+    }
+    else {
+        rb_scan_args(argc, argv, "1", &pat);
+    }
+
+    enumerator = rb_obj_alloc(rb_cEnumerator);
+    rb_ivar_set(enumerator, rb_intern("sliceafter_enum"), enumerable);
+    rb_ivar_set(enumerator, rb_intern("sliceafter_pat"), pat);
+    rb_ivar_set(enumerator, rb_intern("sliceafter_pred"), pred);
+
+    rb_block_call(enumerator, idInitialize, 0, 0, sliceafter_i, enumerator);
+    return enumerator;
+}
+
 /*
  *  The <code>Enumerable</code> mixin provides collection classes with
  *  several traversal and searching methods, and with the ability to
@@ -3151,6 +3275,7 @@ Init_Enumerable(void) https://github.com/ruby/ruby/blob/trunk/enum.c#L3275
     rb_define_method(rb_mEnumerable, "cycle", enum_cycle, -1);
     rb_define_method(rb_mEnumerable, "chunk", enum_chunk, -1);
     rb_define_method(rb_mEnumerable, "slice_before", enum_slice_before, -1);
+    rb_define_method(rb_mEnumerable, "slice_after", enum_slice_after, -1);
 
     id_next = rb_intern("next");
     id_call = rb_intern("call");
Index: NEWS
===================================================================
--- NEWS	(revision 45980)
+++ NEWS	(revision 45981)
@@ -16,6 +16,8 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L16
 === Core classes updates (outstanding ones only)
 
 * Enumerable
+  * New methods:
+    * Enumerable#slice_after
   * Extended methods:
     * min, min_by, max and max_by supports optional argument to return
       multiple elements.
Index: test/ruby/test_enum.rb
===================================================================
--- test/ruby/test_enum.rb	(revision 45980)
+++ test/ruby/test_enum.rb	(revision 45981)
@@ -531,6 +531,49 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L531
     assert_not_warn{ss.slice_before(/\A...\z/).to_a}
   end
 
+  def test_slice_after0
+    assert_raise(ArgumentError) { [].slice_after }
+  end
+
+  def test_slice_after1
+    e = [].slice_after {|a| flunk "should not be called" }
+    assert_equal([], e.to_a)
+
+    e = [1,2].slice_after(1)
+    assert_equal([[1], [2]], e.to_a)
+
+    e = [1,2].slice_after(3)
+    assert_equal([[1, 2]], e.to_a)
+
+    [true, false].each {|b|
+      block_results = [true, b]
+      e = [1,2].slice_after {|a| block_results.shift }
+      assert_equal([[1], [2]], e.to_a)
+      assert_equal([], block_results)
+
+      block_results = [false, b]
+      e = [1,2].slice_after {|a| block_results.shift }
+      assert_equal([[1, 2]], e.to_a)
+      assert_equal([], block_results)
+    }
+  end
+
+  def test_slice_after_both_pattern_and_block
+    assert_raise(ArgumentError) { [].slice_after(1) {|a| true } }
+  end
+
+  def test_slice_after_continuation_lines
+    lines = ["foo\n", "bar\\\n", "baz\n", "\n", "qux\n"]
+    e = lines.slice_after(/[^\\]\n\z/)
+    assert_equal([["foo\n"], ["bar\\\n", "baz\n"], ["\n", "qux\n"]], e.to_a)
+  end
+
+  def test_slice_before_empty_line
+    lines = ["foo", "", "bar"]
+    e = lines.slice_after(/\A\s*\z/)
+    assert_equal([["foo", ""], ["bar"]], e.to_a)
+  end
+
   def test_detect
     @obj = ('a'..'z')
     assert_equal('c', @obj.detect {|x| x == 'c' })
Index: test/ruby/test_lazy_enumerator.rb
===================================================================
--- test/ruby/test_lazy_enumerator.rb	(revision 45980)
+++ test/ruby/test_lazy_enumerator.rb	(revision 45981)
@@ -470,6 +470,7 @@ EOS https://github.com/ruby/ruby/blob/trunk/test/ruby/test_lazy_enumerator.rb#L470
     bug7507 = '[ruby-core:51510]'
     {
       slice_before: //,
+      slice_after: //,
       with_index: nil,
       cycle: nil,
       each_with_object: 42,

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

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