diff options
| author | Hiroshi Mimaki <[email protected]> | 2019-10-18 14:46:03 +0900 |
|---|---|---|
| committer | Hiroshi Mimaki <[email protected]> | 2019-10-18 14:46:03 +0900 |
| commit | b6546835457d1935a9c77965686b2a1256874d96 (patch) | |
| tree | 724cfd71a7c956b0648e8c58f3717d797fff5f29 /mrbgems/mruby-string-ext | |
| parent | 8ee516436b8d174a50764939bee23a442aa00b3f (diff) | |
| parent | 20d01f118ddb7e7f2f36926a7a3db35573611857 (diff) | |
| download | mruby-b6546835457d1935a9c77965686b2a1256874d96.tar.gz mruby-b6546835457d1935a9c77965686b2a1256874d96.zip | |
Merge master.
Diffstat (limited to 'mrbgems/mruby-string-ext')
| -rw-r--r-- | mrbgems/mruby-string-ext/mrbgem.rake | 1 | ||||
| -rw-r--r-- | mrbgems/mruby-string-ext/mrblib/string.rb | 12 | ||||
| -rw-r--r-- | mrbgems/mruby-string-ext/src/string.c | 229 | ||||
| -rw-r--r-- | mrbgems/mruby-string-ext/test/numeric.rb | 29 | ||||
| -rw-r--r-- | mrbgems/mruby-string-ext/test/range.rb | 26 | ||||
| -rw-r--r-- | mrbgems/mruby-string-ext/test/string.rb | 82 |
6 files changed, 209 insertions, 170 deletions
diff --git a/mrbgems/mruby-string-ext/mrbgem.rake b/mrbgems/mruby-string-ext/mrbgem.rake index 9812f2cc9..f2df5a783 100644 --- a/mrbgems/mruby-string-ext/mrbgem.rake +++ b/mrbgems/mruby-string-ext/mrbgem.rake @@ -2,5 +2,4 @@ MRuby::Gem::Specification.new('mruby-string-ext') do |spec| spec.license = 'MIT' spec.author = 'mruby developers' spec.summary = 'String class extension' - spec.add_test_dependency 'mruby-enumerator', core: 'mruby-enumerator' end diff --git a/mrbgems/mruby-string-ext/mrblib/string.rb b/mrbgems/mruby-string-ext/mrblib/string.rb index 311803ea2..e57d75355 100644 --- a/mrbgems/mruby-string-ext/mrblib/string.rb +++ b/mrbgems/mruby-string-ext/mrblib/string.rb @@ -310,11 +310,15 @@ class String end end + ## + # Call the given block for each character of + # +self+. def each_char(&block) return to_enum :each_char unless block - - split('').each do |i| - block.call(i) + pos = 0 + while pos < self.size + block.call(self[pos]) + pos += 1 end self end @@ -410,7 +414,7 @@ class String e = max.ord while c <= e break if exclusive and c == e - yield c.chr + yield c.chr(__ENCODING__) c += 1 end return self diff --git a/mrbgems/mruby-string-ext/src/string.c b/mrbgems/mruby-string-ext/src/string.c index ba7e3c610..acf780005 100644 --- a/mrbgems/mruby-string-ext/src/string.c +++ b/mrbgems/mruby-string-ext/src/string.c @@ -5,82 +5,90 @@ #include <mruby/string.h> #include <mruby/range.h> -static mrb_value -mrb_str_getbyte(mrb_state *mrb, mrb_value str) -{ - mrb_int pos; - mrb_get_args(mrb, "i", &pos); +#define ENC_ASCII_8BIT "ASCII-8BIT" +#define ENC_BINARY "BINARY" +#define ENC_UTF8 "UTF-8" - if (pos < 0) - pos += RSTRING_LEN(str); - if (pos < 0 || RSTRING_LEN(str) <= pos) - return mrb_nil_value(); +#define ENC_COMP_P(enc, enc_lit) \ + str_casecmp_p(RSTRING_PTR(enc), RSTRING_LEN(enc), enc_lit, sizeof(enc_lit"")-1) + +#ifdef MRB_WITHOUT_FLOAT +# define mrb_float_p(o) FALSE +#endif - return mrb_fixnum_value((unsigned char)RSTRING_PTR(str)[pos]); +static mrb_bool +str_casecmp_p(const char *s1, mrb_int len1, const char *s2, mrb_int len2) +{ + const char *e1, *e2; + + if (len1 != len2) return FALSE; + e1 = s1 + len1; + e2 = s2 + len2; + while (s1 < e1 && s2 < e2) { + if (*s1 != *s2 && TOUPPER(*s1) != TOUPPER(*s2)) return FALSE; + ++s1; + ++s2; + } + return TRUE; } static mrb_value -mrb_str_setbyte(mrb_state *mrb, mrb_value str) +int_chr_binary(mrb_state *mrb, mrb_value num) { - mrb_int pos, byte; - mrb_int len; - - mrb_get_args(mrb, "ii", &pos, &byte); - - len = RSTRING_LEN(str); - if (pos < -len || len <= pos) - mrb_raisef(mrb, E_INDEX_ERROR, "index %S is out of array", mrb_fixnum_value(pos)); - if (pos < 0) - pos += len; + mrb_int cp = mrb_int(mrb, num); + char c; + mrb_value str; - mrb_str_modify(mrb, mrb_str_ptr(str)); - byte &= 0xff; - RSTRING_PTR(str)[pos] = (unsigned char)byte; - return mrb_fixnum_value((unsigned char)byte); + if (cp < 0 || 0xff < cp) { + mrb_raisef(mrb, E_RANGE_ERROR, "%v out of char range", num); + } + c = (char)cp; + str = mrb_str_new(mrb, &c, 1); + RSTR_SET_ASCII_FLAG(mrb_str_ptr(str)); + return str; } +#ifdef MRB_UTF8_STRING static mrb_value -mrb_str_byteslice(mrb_state *mrb, mrb_value str) +int_chr_utf8(mrb_state *mrb, mrb_value num) { - mrb_value a1; + mrb_int cp = mrb_int(mrb, num); + char utf8[4]; mrb_int len; + mrb_value str; + uint32_t ascii_flag = 0; - if (mrb_get_argc(mrb) == 2) { - mrb_int pos; - mrb_get_args(mrb, "ii", &pos, &len); - return mrb_str_substr(mrb, str, pos, len); + if (cp < 0 || 0x10FFFF < cp) { + mrb_raisef(mrb, E_RANGE_ERROR, "%v out of char range", num); } - mrb_get_args(mrb, "o|i", &a1, &len); - switch (mrb_type(a1)) { - case MRB_TT_RANGE: - { - mrb_int beg; - - len = RSTRING_LEN(str); - switch (mrb_range_beg_len(mrb, a1, &beg, &len, len, TRUE)) { - case 0: /* not range */ - break; - case 1: /* range */ - return mrb_str_substr(mrb, str, beg, len); - case 2: /* out of range */ - mrb_raisef(mrb, E_RANGE_ERROR, "%S out of range", a1); - break; - } - return mrb_nil_value(); - } -#ifndef MRB_WITHOUT_FLOAT - case MRB_TT_FLOAT: - a1 = mrb_fixnum_value((mrb_int)mrb_float(a1)); - /* fall through */ -#endif - case MRB_TT_FIXNUM: - return mrb_str_substr(mrb, str, mrb_fixnum(a1), 1); - default: - mrb_raise(mrb, E_TYPE_ERROR, "wrong type of argument"); + if (cp < 0x80) { + utf8[0] = (char)cp; + len = 1; + ascii_flag = MRB_STR_ASCII; } - /* not reached */ - return mrb_nil_value(); + else if (cp < 0x800) { + utf8[0] = (char)(0xC0 | (cp >> 6)); + utf8[1] = (char)(0x80 | (cp & 0x3F)); + len = 2; + } + else if (cp < 0x10000) { + utf8[0] = (char)(0xE0 | (cp >> 12)); + utf8[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); + utf8[2] = (char)(0x80 | ( cp & 0x3F)); + len = 3; + } + else { + utf8[0] = (char)(0xF0 | (cp >> 18)); + utf8[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); + utf8[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); + utf8[3] = (char)(0x80 | ( cp & 0x3F)); + len = 4; + } + str = mrb_str_new(mrb, utf8, len); + mrb_str_ptr(str)->flags |= ascii_flag; + return str; } +#endif /* * call-seq: @@ -137,8 +145,6 @@ mrb_str_swapcase(mrb_state *mrb, mrb_value self) return str; } -static mrb_value mrb_fixnum_chr(mrb_state *mrb, mrb_value num); - /* * call-seq: * str << integer -> str @@ -148,7 +154,8 @@ static mrb_value mrb_fixnum_chr(mrb_state *mrb, mrb_value num); * * Append---Concatenates the given object to <i>str</i>. If the object is a * <code>Integer</code>, it is considered as a codepoint, and is converted - * to a character before concatenation. + * to a character before concatenation + * (equivalent to <code>str.concat(integer.chr(__ENCODING__))</code>). * * a = "hello " * a << "world" #=> "hello world" @@ -160,8 +167,12 @@ mrb_str_concat_m(mrb_state *mrb, mrb_value self) mrb_value str; mrb_get_args(mrb, "o", &str); - if (mrb_fixnum_p(str)) - str = mrb_fixnum_chr(mrb, str); + if (mrb_fixnum_p(str) || mrb_float_p(str)) +#ifdef MRB_UTF8_STRING + str = int_chr_utf8(mrb, str); +#else + str = int_chr_binary(mrb, str); +#endif else str = mrb_ensure_string_type(mrb, str); mrb_str_concat(mrb, self, str); @@ -507,8 +518,7 @@ str_tr(mrb_state *mrb, mrb_value str, mrb_value p1, mrb_value p2, mrb_bool squee continue; } if (c > 0x80) { - mrb_raisef(mrb, E_ARGUMENT_ERROR, "character (%S) out of range", - mrb_fixnum_value((mrb_int)c)); + mrb_raisef(mrb, E_ARGUMENT_ERROR, "character (%i) out of range", c); } lastch = c; s[i] = (char)c; @@ -812,7 +822,7 @@ mrb_str_count(mrb_state *mrb, mrb_value str) tr_parse_pattern(mrb, &pat, v_pat, TRUE); tr_compile_pattern(&pat, v_pat, bitmap); tr_free_pattern(mrb, &pat); - + s = RSTRING_PTR(str); len = RSTRING_LEN(str); for (i = 0; i < len; i++) { @@ -848,49 +858,42 @@ mrb_str_chr(mrb_state *mrb, mrb_value self) return mrb_str_substr(mrb, self, 0, 1); } +/* + * call-seq: + * int.chr([encoding]) -> string + * + * Returns a string containing the character represented by the +int+'s value + * according to +encoding+. +"ASCII-8BIT"+ (+"BINARY"+) and +"UTF-8"+ (only + * with +MRB_UTF8_STRING+) can be specified as +encoding+ (default is + * +"ASCII-8BIT"+). + * + * 65.chr #=> "A" + * 230.chr #=> "\xE6" + * 230.chr("ASCII-8BIT") #=> "\xE6" + * 230.chr("UTF-8") #=> "\u00E6" + */ static mrb_value -mrb_fixnum_chr(mrb_state *mrb, mrb_value num) +mrb_int_chr(mrb_state *mrb, mrb_value num) { - mrb_int cp = mrb_fixnum(num); -#ifdef MRB_UTF8_STRING - char utf8[4]; - mrb_int len; - - if (cp < 0 || 0x10FFFF < cp) { - mrb_raisef(mrb, E_RANGE_ERROR, "%S out of char range", num); - } - if (cp < 0x80) { - utf8[0] = (char)cp; - len = 1; + mrb_value enc; + mrb_bool enc_given; + + mrb_get_args(mrb, "|S?", &enc, &enc_given); + if (!enc_given || + ENC_COMP_P(enc, ENC_ASCII_8BIT) || + ENC_COMP_P(enc, ENC_BINARY)) { + return int_chr_binary(mrb, num); } - else if (cp < 0x800) { - utf8[0] = (char)(0xC0 | (cp >> 6)); - utf8[1] = (char)(0x80 | (cp & 0x3F)); - len = 2; - } - else if (cp < 0x10000) { - utf8[0] = (char)(0xE0 | (cp >> 12)); - utf8[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); - utf8[2] = (char)(0x80 | ( cp & 0x3F)); - len = 3; +#ifdef MRB_UTF8_STRING + else if (ENC_COMP_P(enc, ENC_UTF8)) { + return int_chr_utf8(mrb, num); } +#endif else { - utf8[0] = (char)(0xF0 | (cp >> 18)); - utf8[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); - utf8[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); - utf8[3] = (char)(0x80 | ( cp & 0x3F)); - len = 4; - } - return mrb_str_new(mrb, utf8, len); -#else - char c; - - if (cp < 0 || 0xff < cp) { - mrb_raisef(mrb, E_RANGE_ERROR, "%S out of char range", num); + mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown encoding name - %v", enc); } - c = (char)cp; - return mrb_str_new(mrb, &c, 1); -#endif + /* not reached */ + return mrb_nil_value(); } /* @@ -1078,7 +1081,7 @@ mrb_str_del_prefix_bang(mrb_state *mrb, mrb_value self) if (plen > slen) return mrb_nil_value(); s = RSTR_PTR(str); if (memcmp(s, ptr, plen) != 0) return mrb_nil_value(); - if (!MRB_FROZEN_P(str) && (RSTR_SHARED_P(str) || RSTR_FSHARED_P(str))) { + if (!mrb_frozen_p(str) && (RSTR_SHARED_P(str) || RSTR_FSHARED_P(str))) { str->as.heap.ptr += plen; } else { @@ -1135,7 +1138,7 @@ mrb_str_del_suffix_bang(mrb_state *mrb, mrb_value self) if (plen > slen) return mrb_nil_value(); s = RSTR_PTR(str); if (memcmp(s+slen-plen, ptr, plen) != 0) return mrb_nil_value(); - if (!MRB_FROZEN_P(str) && (RSTR_SHARED_P(str) || RSTR_FSHARED_P(str))) { + if (!mrb_frozen_p(str) && (RSTR_SHARED_P(str) || RSTR_FSHARED_P(str))) { /* no need to modify string */ } else { @@ -1178,8 +1181,6 @@ mrb_str_lines(mrb_state *mrb, mrb_value self) char *p = b, *t; char *e = b + RSTRING_LEN(self); - mrb_get_args(mrb, ""); - result = mrb_ary_new(mrb); ai = mrb_gc_arena_save(mrb); while (p < e) { @@ -1199,9 +1200,6 @@ mrb_mruby_string_ext_gem_init(mrb_state* mrb) struct RClass * s = mrb->string_class; mrb_define_method(mrb, s, "dump", mrb_str_dump, MRB_ARGS_NONE()); - mrb_define_method(mrb, s, "getbyte", mrb_str_getbyte, MRB_ARGS_REQ(1)); - mrb_define_method(mrb, s, "setbyte", mrb_str_setbyte, MRB_ARGS_REQ(2)); - mrb_define_method(mrb, s, "byteslice", mrb_str_byteslice, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); mrb_define_method(mrb, s, "swapcase!", mrb_str_swapcase_bang, MRB_ARGS_NONE()); mrb_define_method(mrb, s, "swapcase", mrb_str_swapcase, MRB_ARGS_NONE()); mrb_define_method(mrb, s, "concat", mrb_str_concat_m, MRB_ARGS_REQ(1)); @@ -1222,8 +1220,8 @@ mrb_mruby_string_ext_gem_init(mrb_state* mrb) mrb_define_method(mrb, s, "chr", mrb_str_chr, MRB_ARGS_NONE()); mrb_define_method(mrb, s, "succ", mrb_str_succ, MRB_ARGS_NONE()); mrb_define_method(mrb, s, "succ!", mrb_str_succ_bang, MRB_ARGS_NONE()); - mrb_define_alias(mrb, s, "next", "succ"); - mrb_define_alias(mrb, s, "next!", "succ!"); + mrb_define_method(mrb, s, "next", mrb_str_succ, MRB_ARGS_NONE()); + mrb_define_method(mrb, s, "next!", mrb_str_succ_bang, MRB_ARGS_NONE()); mrb_define_method(mrb, s, "ord", mrb_str_ord, MRB_ARGS_NONE()); mrb_define_method(mrb, s, "delete_prefix!", mrb_str_del_prefix_bang, MRB_ARGS_REQ(1)); mrb_define_method(mrb, s, "delete_prefix", mrb_str_del_prefix, MRB_ARGS_REQ(1)); @@ -1231,7 +1229,8 @@ mrb_mruby_string_ext_gem_init(mrb_state* mrb) mrb_define_method(mrb, s, "delete_suffix", mrb_str_del_suffix, MRB_ARGS_REQ(1)); mrb_define_method(mrb, s, "__lines", mrb_str_lines, MRB_ARGS_NONE()); - mrb_define_method(mrb, mrb->fixnum_class, "chr", mrb_fixnum_chr, MRB_ARGS_NONE()); + + mrb_define_method(mrb, mrb_module_get(mrb, "Integral"), "chr", mrb_int_chr, MRB_ARGS_OPT(1)); } void diff --git a/mrbgems/mruby-string-ext/test/numeric.rb b/mrbgems/mruby-string-ext/test/numeric.rb new file mode 100644 index 000000000..dfcb9ebf4 --- /dev/null +++ b/mrbgems/mruby-string-ext/test/numeric.rb @@ -0,0 +1,29 @@ +# coding: utf-8 + +assert('Integer#chr') do + assert_equal("A", 65.chr) + assert_equal("B", 0x42.chr) + assert_equal("\xab", 171.chr) + assert_raise(RangeError) { -1.chr } + assert_raise(RangeError) { 256.chr } + + assert_equal("A", 65.chr("ASCII-8BIT")) + assert_equal("B", 0x42.chr("BINARY")) + assert_equal("\xab", 171.chr("ascii-8bit")) + assert_raise(RangeError) { -1.chr("binary") } + assert_raise(RangeError) { 256.chr("Ascii-8bit") } + assert_raise(ArgumentError) { 65.chr("ASCII") } + assert_raise(ArgumentError) { 65.chr("ASCII-8BIT", 2) } + assert_raise(TypeError) { 65.chr(:BINARY) } + + if __ENCODING__ == "ASCII-8BIT" + assert_raise(ArgumentError) { 65.chr("UTF-8") } + else + assert_equal("A", 65.chr("UTF-8")) + assert_equal("B", 0x42.chr("UTF-8")) + assert_equal("«", 171.chr("utf-8")) + assert_equal("あ", 12354.chr("Utf-8")) + assert_raise(RangeError) { -1.chr("utf-8") } + assert_raise(RangeError) { 0x110000.chr.chr("UTF-8") } + end +end diff --git a/mrbgems/mruby-string-ext/test/range.rb b/mrbgems/mruby-string-ext/test/range.rb new file mode 100644 index 000000000..80c286850 --- /dev/null +++ b/mrbgems/mruby-string-ext/test/range.rb @@ -0,0 +1,26 @@ +assert('Range#max') do + # returns the maximum value in the range when called with no arguments + assert_equal 'l', ('f'..'l').max + assert_equal 'e', ('a'...'f').max + + # returns nil when the endpoint is less than the start point + assert_equal nil, ('z'..'l').max +end + +assert('Range#max given a block') do + # returns nil when the endpoint is less than the start point + assert_equal nil, (('z'..'l').max { |x, y| x <=> y }) +end + +assert('Range#min') do + # returns the minimum value in the range when called with no arguments + assert_equal 'f', ('f'..'l').min + + # returns nil when the start point is greater than the endpoint + assert_equal nil, ('z'..'l').min +end + +assert('Range#min given a block') do + # returns nil when the start point is greater than the endpoint + assert_equal nil, (('z'..'l').min { |x, y| x <=> y }) +end diff --git a/mrbgems/mruby-string-ext/test/string.rb b/mrbgems/mruby-string-ext/test/string.rb index 44ca1fde2..3f11c00a0 100644 --- a/mrbgems/mruby-string-ext/test/string.rb +++ b/mrbgems/mruby-string-ext/test/string.rb @@ -2,39 +2,18 @@ ## # String(Ext) Test -UTF8STRING = ("\343\201\202".size == 1) +UTF8STRING = __ENCODING__ == "UTF-8" -assert('String#getbyte') do - str1 = "hello" - bytes1 = [104, 101, 108, 108, 111] - assert_equal bytes1[0], str1.getbyte(0) - assert_equal bytes1[-1], str1.getbyte(-1) - assert_equal bytes1[6], str1.getbyte(6) - - str2 = "\xFF" - bytes2 = [0xFF] - assert_equal bytes2[0], str2.getbyte(0) -end - -assert('String#setbyte') do - str1 = "hello" - h = "H".getbyte(0) - str1.setbyte(0, h) - assert_equal(h, str1.getbyte(0)) - assert_equal("Hello", str1) -end - -assert('String#byteslice') do - str1 = "hello" - assert_equal("e", str1.byteslice(1)) - assert_equal("o", str1.byteslice(-1)) - assert_equal("ell", str1.byteslice(1..3)) - assert_equal("el", str1.byteslice(1...3)) +def assert_upto(exp, receiver, *args) + act = [] + receiver.upto(*args) { |v| act << v } + assert_equal exp, act end assert('String#dump') do assert_equal("\"\\x00\"", "\0".dump) assert_equal("\"foo\"", "foo".dump) + assert_equal('"\xe3\x82\x8b"', "る".dump) assert_nothing_raised { ("\1" * 100).dump } # regress #1210 end @@ -116,8 +95,15 @@ end assert('String#concat') do assert_equal "Hello World!", "Hello " << "World" << 33 assert_equal "Hello World!", "Hello ".concat("World").concat(33) - assert_raise(TypeError) { "".concat(Object.new) } + + if UTF8STRING + assert_equal "H«", "H" << 0xab + assert_equal "Hは", "H" << 12399 + else + assert_equal "H\xab", "H" << 0xab + assert_raise(RangeError) { "H" << 12399 } + end end assert('String#casecmp') do @@ -247,12 +233,6 @@ assert('String#oct') do assert_equal (-8), "-10".oct end -assert('String#chr') do - assert_equal "a", "abcde".chr - # test Fixnum#chr as well - assert_equal "a", 97.chr -end - assert('String#lines') do assert_equal ["Hel\n", "lo\n", "World!"], "Hel\nlo\nWorld!".lines assert_equal ["Hel\n", "lo\n", "World!\n"], "Hel\nlo\nWorld!\n".lines @@ -539,16 +519,15 @@ assert('String#rjust should raise on zero width padding') do end assert('String#upto') do - assert_equal %w(a8 a9 b0 b1 b2 b3 b4 b5 b6), "a8".upto("b6").to_a - assert_equal ["9", "10", "11"], "9".upto("11").to_a - assert_equal [], "25".upto("5").to_a - assert_equal ["07", "08", "09", "10", "11"], "07".upto("11").to_a - -if UTF8STRING - assert_equal ["あ", "ぃ", "い", "ぅ", "う", "ぇ", "え", "ぉ", "お"], "あ".upto("お").to_a -end - - assert_equal ["9", ":", ";", "<", "=", ">", "?", "@", "A"], "9".upto("A").to_a + assert_upto %w(a8 a9 b0 b1 b2 b3 b4 b5 b6), "a8", "b6" + assert_upto ["9", "10", "11"], "9", "11" + assert_upto [], "25", "5" + assert_upto ["07", "08", "09", "10", "11"], "07", "11" + assert_upto ["9", ":", ";", "<", "=", ">", "?", "@", "A"], "9", "A" + + if UTF8STRING + assert_upto %w(あ ぃ い ぅ う ぇ え ぉ お), "あ", "お" + end a = "aa" start = "aa" @@ -630,8 +609,11 @@ assert('String#ord(UTF-8)') do end if UTF8STRING assert('String#chr') do + assert_equal "a", "abcde".chr assert_equal "h", "hello!".chr + assert_equal "", "".chr end + assert('String#chr(UTF-8)') do assert_equal "こ", "こんにちは世界!".chr end if UTF8STRING @@ -657,19 +639,19 @@ assert('String#chars(UTF-8)') do end if UTF8STRING assert('String#each_char') do - s = "" + chars = [] "hello!".each_char do |x| - s += x + chars << x end - assert_equal "hello!", s + assert_equal ["h", "e", "l", "l", "o", "!"], chars end assert('String#each_char(UTF-8)') do - s = "" + chars = [] "こんにちは世界!".each_char do |x| - s += x + chars << x end - assert_equal "こんにちは世界!", s + assert_equal ["こ", "ん", "に", "ち", "は", "世", "界", "!"], chars end if UTF8STRING assert('String#codepoints') do |
