% svn diff --diff-cmd diff -x '-u -p'
Index: enum.c
===================================================================
--- enum.c (revision 45900)
+++ enum.c (working copy)
@@ -3083,6 +3083,136 @@ enum_slice_before(int argc, VALUE *argv,
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);
+ }
+
+ split_p = 1;
+ if (!NIL_P(memo->pat)) {
+ split_p = RTEST(rb_funcall(memo->pat, id_eqq, 1, i));
+ UPDATE_MEMO;
+ }
+ if (split_p && !NIL_P(memo->pred)) {
+ 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 _pattern_ === _elt_
returns true
or the block
+ * returns true
for the element, the element is end of a
+ * chunk.
+ *
+ * The ===
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(/(? [["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, pred;
+
+ rb_scan_args(argc, argv, "01", &pat);
+ if (rb_block_given_p())
+ pred = rb_block_proc();
+ else
+ pred = Qnil;
+
+ if (NIL_P(pat) && NIL_P(pred)) {
+ rb_raise(rb_eArgError, "no pattan/block given");
+ }
+
+ if (!NIL_P(pat) && !NIL_P(pred)) {
+ rb_raise(rb_eArgError, "both pattan and block are given");
+ }
+
+ enumerator = rb_obj_alloc(rb_cEnumerator);
+ rb_ivar_set(enumerator, rb_intern("sliceafter_pat"), pat);
+ rb_ivar_set(enumerator, rb_intern("sliceafter_pred"), pred);
+ rb_ivar_set(enumerator, rb_intern("sliceafter_enum"), enumerable);
+
+ rb_block_call(enumerator, idInitialize, 0, 0, sliceafter_i, enumerator);
+ return enumerator;
+}
+
/*
* The Enumerable
mixin provides collection classes with
* several traversal and searching methods, and with the ability to
@@ -3151,6 +3281,7 @@ Init_Enumerable(void)
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: enumerator.c
===================================================================
--- enumerator.c (revision 45900)
+++ enumerator.c (working copy)
@@ -2036,6 +2036,7 @@ InitVM_Enumerator(void)
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: test/ruby/test_enum.rb
===================================================================
--- test/ruby/test_enum.rb (revision 45900)
+++ test/ruby/test_enum.rb (working copy)
@@ -531,6 +531,49 @@ class TestEnumerable < Test::Unit::TestC
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 45900)
+++ test/ruby/test_lazy_enumerator.rb (working copy)
@@ -470,6 +470,7 @@ EOS
bug7507 = '[ruby-core:51510]'
{
slice_before: //,
+ slice_after: //,
with_index: nil,
cycle: nil,
each_with_object: 42,