From 862e64104ad4a137e46ed2a262869a2700250ba5 Mon Sep 17 00:00:00 2001 From: Bouke van der Bijl Date: Fri, 9 Sep 2016 11:25:35 -0400 Subject: [PATCH] Add str.reverse_each_char and str.reverse_chars * string.c (rb_str_reverse_each_char, rb_str_reverse_chars): Add str.reverse_each_char and str.reverse_chars that allow for efficiently iterating over a string in reverse. * test/ruby/test_string.rb: add tests for above --- ChangeLog | 8 ++++ string.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++ test/ruby/test_string.rb | 31 ++++++++++++++ 3 files changed, 141 insertions(+) diff --git a/ChangeLog b/ChangeLog index 3fe44c3..df40a5d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +Thu Sep 10 00:22:18 2016 Bouke van der Bijl + + * string.c (rb_str_reverse_each_char, rb_str_reverse_chars): Add + str.reverse_each_char and str.reverse_chars that allow for efficiently + iterating over a string in reverse. + + * test/ruby/test_string.rb: add tests for above + Thu Sep 8 17:47:18 2016 Kazuki Tsujimoto * array.c (flatten): use rb_obj_class instead of rb_class_of diff --git a/string.c b/string.c index 59c44ca..c061492 100644 --- a/string.c +++ b/string.c @@ -7692,6 +7692,106 @@ rb_str_chars(VALUE str) return rb_str_enumerate_chars(str, 1); } +static VALUE +rb_str_reverse_enumerate_chars(VALUE str, int wantarray) +{ + VALUE orig = str; + VALUE substr; + long len, n; + const char *ptr, *end; + char *p; + rb_encoding *enc; + VALUE UNINITIALIZED_VAR(ary); + + str = rb_str_new_frozen(str); + ptr = RSTRING_PTR(str); + len = RSTRING_LEN(str); + end = ptr + len; + enc = rb_enc_get(str); + + if (rb_block_given_p()) { + if (wantarray) { +#if STRING_ENUMERATORS_WANTARRAY + rb_warn("given block not used"); + ary = rb_ary_new_capa(str_strlen(str, enc)); /* str's enc*/ +#else + rb_warning("passing a block to String#chars is deprecated"); + wantarray = 0; +#endif + } + } + else { + if (wantarray) + ary = rb_ary_new_capa(str_strlen(str, enc)); /* str's enc*/ + else + return SIZED_ENUMERATOR(str, 0, 0, rb_str_each_char_size); + } + + if (ENC_CODERANGE_CLEAN_P(ENC_CODERANGE(str))) { + for(p = rb_enc_left_char_head(ptr, end - 1, end, enc); p >= ptr; p = rb_enc_left_char_head(ptr, p - 1, end, enc)) { + n = rb_enc_fast_mbclen(p, end, enc); + substr = rb_str_subseq(str, p - ptr, n); + if (wantarray) + rb_ary_push(ary, substr); + else + rb_yield(substr); + } + } + else { + for(p = rb_enc_left_char_head(ptr, end - 1, end, enc); p >= ptr; p = rb_enc_left_char_head(ptr, p - 1, end, enc)) { + n = rb_enc_mbclen(p, end, enc); + substr = rb_str_subseq(str, p - ptr, n); + if (wantarray) + rb_ary_push(ary, substr); + else + rb_yield(substr); + } + } + + RB_GC_GUARD(str); + if (wantarray) + return ary; + else + return orig; +} + +/* + * call-seq: + * str.reverse_each_char {|cstr| block } -> str + * str.reverse_each_char -> an_enumerator + * + * Passes each character in str in reverse to the given block, or returns + * an enumerator if no block is given. + * + * "hello".reverse_each_char {|c| print c, ' ' } + * + * produces: + * + * o l l e h + */ + +static VALUE +rb_str_reverse_each_char(VALUE str) +{ + return rb_str_reverse_enumerate_chars(str, 0); +} + +/* + * call-seq: + * str.reverse_chars -> an_array + * + * Returns an array of characters in str in reverse. This is a shorthand + * for str.reverse_each_char.to_a. + * + * If a block is given, which is a deprecated form, works the same as + * reverse_each_char. + */ + +static VALUE +rb_str_reverse_chars(VALUE str) +{ + return rb_str_reverse_enumerate_chars(str, 1); +} static VALUE rb_str_enumerate_codepoints(VALUE str, int wantarray) @@ -9857,6 +9957,7 @@ Init_String(void) rb_define_method(rb_cString, "lines", rb_str_lines, -1); rb_define_method(rb_cString, "bytes", rb_str_bytes, 0); rb_define_method(rb_cString, "chars", rb_str_chars, 0); + rb_define_method(rb_cString, "reverse_chars", rb_str_reverse_chars, 0); rb_define_method(rb_cString, "codepoints", rb_str_codepoints, 0); rb_define_method(rb_cString, "reverse", rb_str_reverse, 0); rb_define_method(rb_cString, "reverse!", rb_str_reverse_bang, 0); @@ -9908,6 +10009,7 @@ Init_String(void) rb_define_method(rb_cString, "each_line", rb_str_each_line, -1); rb_define_method(rb_cString, "each_byte", rb_str_each_byte, 0); rb_define_method(rb_cString, "each_char", rb_str_each_char, 0); + rb_define_method(rb_cString, "reverse_each_char", rb_str_reverse_each_char, 0); rb_define_method(rb_cString, "each_codepoint", rb_str_each_codepoint, 0); rb_define_method(rb_cString, "sum", rb_str_sum, -1); diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index b1d795e..240f977 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -767,6 +767,37 @@ def test_chars end end + def test_reverse_each_char + s = S("ABC") + + res = [] + assert_equal s.object_id, s.reverse_each_char {|x| res << x }.object_id + assert_equal("C", res[0]) + assert_equal("B", res[1]) + assert_equal("A", res[2]) + + assert_equal "😀", S("ABC😀").reverse_each_char.next + end + + def test_reverse_chars + s = S("ABC") + assert_equal ["C", "B", "A"], s.reverse_chars + + if ENUMERATOR_WANTARRAY + assert_warn(/block not used/) { + assert_equal ["C", "B", "A"], s.reverse_chars {} + } + else + assert_warning(/deprecated/) { + res = [] + assert_equal s.object_id, s.reverse_chars {|x| res << x }.object_id + assert_equal("C", res[0]) + assert_equal("B", res[1]) + assert_equal("A", res[2]) + } + end + end + def test_each_line save = $/ $/ = "\n"