diff options
author | akr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2014-05-18 00:06:05 +0000 |
---|---|---|
committer | akr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2014-05-18 00:06:05 +0000 |
commit | ddd155842fb4fd96a028836d926bae7501cbd985 (patch) | |
tree | b51c8ae4bed4325a62301eda1319a6fbf21185ec | |
parent | 944994b4cceb3ce094090237d04b94d867dd22db (diff) | |
download | ruby-ddd155842fb4fd96a028836d926bae7501cbd985.tar.gz |
* enum.c: Enumerable#slice_after implemented.
* enumerator.c: Enumerator::Lazy#slice_after implemented.
Requested by Tsuyoshi Sawada. [ruby-core:58123] [Feature #9071]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@45981 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r-- | ChangeLog | 8 | ||||
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | enum.c | 125 | ||||
-rw-r--r-- | enumerator.c | 1 | ||||
-rw-r--r-- | test/ruby/test_enum.rb | 43 | ||||
-rw-r--r-- | test/ruby/test_lazy_enumerator.rb | 1 |
6 files changed, 180 insertions, 0 deletions
@@ -1,3 +1,11 @@ +Sun May 18 09:02:17 2014 Tanaka Akira <akr@fsij.org> + + * 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@ruby-lang.org> * io.c (io_setstrbuf): always check if the buffer is modifiable. @@ -16,6 +16,8 @@ with all sufficient information, see the ChangeLog file. === 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. @@ -3083,6 +3083,130 @@ enum_slice_before(int argc, VALUE *argv, VALUE enumerable) 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) 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"); diff --git a/enumerator.c b/enumerator.c index 9df01032af..4192c2f551 100644 --- a/enumerator.c +++ b/enumerator.c @@ -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"); diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index 7c8f37c8ce..d6795e49dd 100644 --- a/test/ruby/test_enum.rb +++ b/test/ruby/test_enum.rb @@ -531,6 +531,49 @@ class TestEnumerable < Test::Unit::TestCase 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' }) diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 8ac3340173..549d0f104e 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -470,6 +470,7 @@ EOS bug7507 = '[ruby-core:51510]' { slice_before: //, + slice_after: //, with_index: nil, cycle: nil, each_with_object: 42, |