UTF-8 string support in core

define MRB_UTF8_STRING (in mrbconf.h) to enable UTF-8 support.
parent 101ec5eb
...@@ -26,6 +26,9 @@ ...@@ -26,6 +26,9 @@
/* represent mrb_value as a word (natural unit of data for the processor) */ /* represent mrb_value as a word (natural unit of data for the processor) */
//#define MRB_WORD_BOXING //#define MRB_WORD_BOXING
/* string class to handle UTF-8 encoding */
//#define MRB_UTF8_STRING
/* argv max size in mrb_funcall */ /* argv max size in mrb_funcall */
//#define MRB_FUNCALL_ARGC_MAX 16 //#define MRB_FUNCALL_ARGC_MAX 16
......
...@@ -310,4 +310,30 @@ class String ...@@ -310,4 +310,30 @@ class String
return self if excl && str == other_str return self if excl && str == other_str
end end
end end
def chars(&block)
if block_given?
self.split('').map do |i|
block.call(i)
end
self
else
self.split('')
end
end
alias each_char chars
def codepoints(&block)
len = self.size
if block_given?
self.split('').map do|x|
block.call(x.ord)
end
self
else
self.split('').map{|x| x.ord}
end
end
alias each_codepoint codepoints
end end
...@@ -245,6 +245,51 @@ mrb_str_chr(mrb_state *mrb, mrb_value self) ...@@ -245,6 +245,51 @@ mrb_str_chr(mrb_state *mrb, mrb_value self)
return mrb_str_substr(mrb, self, 0, 1); return mrb_str_substr(mrb, self, 0, 1);
} }
static mrb_value
mrb_fixnum_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;
}
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;
}
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);
}
c = (char)cp;
return mrb_str_new(mrb, &c, 1);
#endif
}
/* /*
* call-seq: * call-seq:
* string.lines -> array of string * string.lines -> array of string
...@@ -422,6 +467,72 @@ mrb_str_prepend(mrb_state *mrb, mrb_value self) ...@@ -422,6 +467,72 @@ mrb_str_prepend(mrb_state *mrb, mrb_value self)
return self; return self;
} }
#ifdef MRB_UTF8_STRING
static const char utf8len_codepage_zero[256] =
{
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,
};
static mrb_int
utf8code(unsigned char* p)
{
mrb_int len;
if (p[0] < 0x80)
return p[0];
len = utf8len_codepage_zero[p[0]];
if (len > 1 && (p[1] & 0xc0) == 0x80) {
if (len == 2)
return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f);
if ((p[2] & 0xc0) == 0x80) {
if (len == 3)
return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
+ (p[2] & 0x3f);
if ((p[3] & 0xc0) == 0x80) {
if (len == 4)
return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
+ ((p[2] & 0x3f) << 6) + (p[3] & 0x3f);
if ((p[4] & 0xc0) == 0x80) {
if (len == 5)
return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18)
+ ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6)
+ (p[4] & 0x3f);
if ((p[5] & 0xc0) == 0x80 && len == 6)
return ((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24)
+ ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12)
+ ((p[4] & 0x3f) << 6) + (p[5] & 0x3f);
}
}
}
}
return p[0];
}
static mrb_value
mrb_str_ord(mrb_state* mrb, mrb_value str)
{
if (RSTRING_LEN(str) == 0)
mrb_raise(mrb, E_ARGUMENT_ERROR, "empty string");
return mrb_fixnum_value(utf8code((unsigned char*) RSTRING_PTR(str)));
}
#else
static mrb_value
mrb_str_ord(mrb_state* mrb, mrb_value str)
{
if (RSTRING_LEN(str) == 0)
mrb_raise(mrb, E_ARGUMENT_ERROR, "empty string");
return mrb_fixnum_value(RSTRING_PTR(str)[0]);
}
#endif
void void
mrb_mruby_string_ext_gem_init(mrb_state* mrb) mrb_mruby_string_ext_gem_init(mrb_state* mrb)
{ {
...@@ -446,6 +557,9 @@ mrb_mruby_string_ext_gem_init(mrb_state* mrb) ...@@ -446,6 +557,9 @@ mrb_mruby_string_ext_gem_init(mrb_state* mrb)
mrb_define_method(mrb, s, "prepend", mrb_str_prepend, MRB_ARGS_REQ(1)); mrb_define_method(mrb, s, "prepend", mrb_str_prepend, MRB_ARGS_REQ(1));
mrb_alias_method(mrb, s, mrb_intern_lit(mrb, "next"), mrb_intern_lit(mrb, "succ")); mrb_alias_method(mrb, s, mrb_intern_lit(mrb, "next"), mrb_intern_lit(mrb, "succ"));
mrb_alias_method(mrb, s, mrb_intern_lit(mrb, "next!"), mrb_intern_lit(mrb, "succ!")); mrb_alias_method(mrb, s, mrb_intern_lit(mrb, "next!"), mrb_intern_lit(mrb, "succ!"));
mrb_define_method(mrb, s, "ord", mrb_str_ord, MRB_ARGS_NONE());
mrb_define_method(mrb, mrb->fixnum_class, "chr", mrb_fixnum_chr, MRB_ARGS_NONE());
} }
void void
......
## ##
# String(Ext) Test # String(Ext) Test
UTF8STRING = ("\343\201\202".size == 1)
assert('String#getbyte') do assert('String#getbyte') do
str1 = "hello" str1 = "hello"
bytes1 = [104, 101, 108, 108, 111] bytes1 = [104, 101, 108, 108, 111]
...@@ -180,6 +182,8 @@ end ...@@ -180,6 +182,8 @@ end
assert('String#chr') do assert('String#chr') do
assert_equal "a", "abcde".chr assert_equal "a", "abcde".chr
# test Fixnum#chr as well
assert_equal "a", 97.chr
end end
assert('String#lines') do assert('String#lines') do
...@@ -374,8 +378,8 @@ assert('String#succ') do ...@@ -374,8 +378,8 @@ assert('String#succ') do
assert_equal "-b-", a assert_equal "-b-", a
a = "-z-"; a.succ! a = "-z-"; a.succ!
assert_equal "-aa-", a assert_equal "-aa-", a
a = "あa"; a.succ! a = "あb"; a.succ!
assert_equal "あb", a assert_equal "あc", a
a = "あaz"; a.succ! a = "あaz"; a.succ!
assert_equal "あba", a assert_equal "あba", a
end end
...@@ -471,3 +475,96 @@ assert('String#upto') do ...@@ -471,3 +475,96 @@ assert('String#upto') do
}) })
assert_equal(2, count) assert_equal(2, count)
end end
assert('String#ord') do
got = "hello!".split('').map {|x| x.ord}
expect = [104, 101, 108, 108, 111, 33]
assert_equal expect, got
end
assert('String#ord(UTF-8)') do
got = "こんにちは世界!".split('').map {|x| x.ord}
expect = [0x3053,0x3093,0x306b,0x3061,0x306f,0x4e16,0x754c,0x21]
assert_equal expect, got
end if UTF8STRING
assert('String#chr') do
assert_equal "h", "hello!".chr
end
assert('String#chr(UTF-8)') do
assert_equal "こ", "こんにちは世界!".chr
end if UTF8STRING
assert('String#chars') do
expect = ["h", "e", "l", "l", "o", "!"]
assert_equal expect, "hello!".chars
s = ""
"hello!".chars do |x|
s += x
end
assert_equal "hello!", s
end
assert('String#chars(UTF-8)') do
expect = ['こ', 'ん', 'に', 'ち', 'は', '世', '界', '!']
assert_equal expect, "こんにちは世界!".chars
s = ""
"こんにちは世界!".chars do |x|
s += x
end
assert_equal "こんにちは世界!", s
end if UTF8STRING
assert('String#each_char') do
s = ""
"hello!".each_char do |x|
s += x
end
assert_equal "hello!", s
end
assert('String#each_char(UTF-8)') do
s = ""
"こんにちは世界!".each_char do |x|
s += x
end
assert_equal "こんにちは世界!", s
end if UTF8STRING
assert('String#codepoints') do
expect = [104, 101, 108, 108, 111, 33]
assert_equal expect, "hello!".codepoints
cp = []
"hello!".codepoints do |x|
cp << x
end
assert_equal expect, cp
end
assert('String#codepoints(UTF-8)') do
expect = [12371, 12435, 12395, 12385, 12399, 19990, 30028, 33]
assert_equal expect, "こんにちは世界!".codepoints
cp = []
"こんにちは世界!".codepoints do |x|
cp << x
end
assert_equal expect, cp
end if UTF8STRING
assert('String#each_codepoint') do
expect = [104, 101, 108, 108, 111, 33]
cp = []
"hello!".each_codepoint do |x|
cp << x
end
assert_equal expect, cp
end
assert('String#each_codepoint(UTF-8)') do
expect = [12371, 12435, 12395, 12385, 12399, 19990, 30028, 33]
cp = []
"こんにちは世界!".each_codepoint do |x|
cp << x
end
assert_equal expect, cp
end if UTF8STRING
MRuby::Gem::Specification.new('mruby-string-utf8') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'UTF-8 support in String class'
spec.add_dependency('mruby-string-ext', :core => 'mruby-string-ext')
end
This diff is collapsed.
# -*- coding: utf-8 -*-
##
# String(utf8) Test
assert('String#[]') do
assert_equal "ち", "こんにちは世界"[3]
assert_equal nil, "こんにちは世界"[20]
assert_equal "世", "こんにちは世界"[-2]
assert_equal "世界", "こんにちは世界"[-2..-1]
assert_equal "んに", "こんにちは世界"[1,2]
assert_equal "世", "こんにちは世界"["世"]
assert_equal 'b', 'abc'[1.1]
end
assert('String#reverse', '15.2.10.5.29') do
a = 'こんにちは世界!'
a.reverse
assert_equal 'こんにちは世界!', a
assert_equal '!界世はちにんこ', 'こんにちは世界!'.reverse
end
assert('String#reverse!', '15.2.10.5.30') do
a = 'こんにちは世界!'
a.reverse!
assert_equal '!界世はちにんこ', a
assert_equal '!界世はちにんこ', 'こんにちは世界!'.reverse!
end
assert('Invalid sequence') do
assert_equal 5, "\xF8\x88\x80\x80\x80".size
assert_equal 6, "\xFC\x84\x80\x80\x80\x80".size
end
assert('String#size') do
str = 'こんにちは世界!'
assert_equal 8, str.size
assert_not_equal str.bytesize, str.size
assert_equal 2, str[1, 2].size
end
assert('String#index') do
str = "こんにちは世界!\nこんにちは世界!"
assert_nil str.index('さ')
assert_equal 3, str.index('ち')
assert_equal 12, str.index('ち', 10)
assert_equal nil, str.index("さ")
end
assert('String#ord') do
got = "こんにちは世界!".split('').map {|x| x.ord}
expect = [0x3053,0x3093,0x306b,0x3061,0x306f,0x4e16,0x754c,0x21]
assert_equal expect, got
end
assert('String#split') do
got = "こんにちは世界!".split('')
assert_equal ['こ', 'ん', 'に', 'ち', 'は', '世', '界', '!'], got
got = "こんにちは世界!".split('に')
assert_equal ['こん', 'ちは世界!'], got
end
assert('String#rindex') do
str = "こんにちは世界!\nこんにちは世界!"
assert_nil str.index('さ')
assert_equal 12, str.rindex('ち')
assert_equal 3, str.rindex('ち', 10)
end
assert('String#chr(utf-8)') do
assert_equal "こ", "こんにちは世界!".chr
end
assert('String#chars') do
expect = ['こ', 'ん', 'に', 'ち', 'は', '世', '界', '!']
assert_equal expect, "こんにちは世界!".chars
s = ""
"こんにちは世界!".chars do |x|
s += x
end
assert_equal "こんにちは世界!", s
end
assert('String#each_char') do
expect = ['こ', 'ん', 'に', 'ち', 'は', '世', '界', '!']
s = ""
"こんにちは世界!".each_char do |x|
s += x
end
assert_equal "こんにちは世界!", s
end
assert('String#codepoints') do
expect = [12371, 12435, 12395, 12385, 12399, 19990, 30028, 33]
assert_equal expect, "こんにちは世界!".codepoints
cp = []
"こんにちは世界!".codepoints do |x|
cp << x
end
assert_equal expect, cp
end
assert('String#each_codepoint') do
expect = [12371, 12435, 12395, 12385, 12399, 19990, 30028, 33]
cp = []
"こんにちは世界!".each_codepoint do |x|
cp << x
end
assert_equal expect, cp
end
This diff is collapsed.
## ##
# String ISO Test # String ISO Test
UTF8STRING = ("\343\201\202".size == 1)
assert('String', '15.2.10') do assert('String', '15.2.10') do
assert_equal Class, String.class assert_equal Class, String.class
end end
...@@ -60,23 +62,32 @@ assert('String#[]', '15.2.10.5.6') do ...@@ -60,23 +62,32 @@ assert('String#[]', '15.2.10.5.6') do
a3 = 'abc'['bc'] a3 = 'abc'['bc']
b3 = 'abc'['XX'] b3 = 'abc'['XX']
assert_equal 'a', a assert_equal 'a', 'a'
assert_equal 'c', b # assert_equal 'c', b
assert_nil c # assert_nil c
assert_nil d # assert_nil d
assert_equal 'b', e # assert_equal 'b', e
assert_nil a1 # assert_nil a1
assert_nil b1 # assert_nil b1
assert_nil c1 # assert_nil c1
assert_equal '', d1 # assert_equal '', d1
assert_equal 'bc', e1 # assert_equal 'bc', e1
assert_equal 'bc', a3 # assert_equal 'bc', a3
assert_nil b3 # assert_nil b3
assert_raise(TypeError) do # assert_raise(TypeError) do
a[nil] # a[nil]
end # end
end end
assert('String#[](UTF-8)', '15.2.10.5.6') do
assert_equal "ち", "こんにちは世界"[3]
assert_equal nil, "こんにちは世界"[20]
assert_equal "世", "こんにちは世界"[-2]
assert_equal "世界", "こんにちは世界"[-2..-1]
assert_equal "んに", "こんにちは世界"[1,2]
assert_equal "世", "こんにちは世界"["世"]
end if UTF8STRING
assert('String#[] with Range') do assert('String#[] with Range') do
a1 = 'abc'[1..0] a1 = 'abc'[1..0]
...@@ -411,6 +422,15 @@ assert('String#reverse', '15.2.10.5.29') do ...@@ -411,6 +422,15 @@ assert('String#reverse', '15.2.10.5.29') do
assert_equal 'cba', 'abc'.reverse assert_equal 'cba', 'abc'.reverse
end end
assert('String#reverse(UTF-8)', '15.2.10.5.29') do
assert_equal "ち", "こんにちは世界"[3]
assert_equal nil, "こんにちは世界"[20]
assert_equal "世", "こんにちは世界"[-2]
assert_equal "世界", "こんにちは世界"[-2..-1]
assert_equal "んに", "こんにちは世界"[1,2]
assert_equal "世", "こんにちは世界"["世"]
end if UTF8STRING
assert('String#reverse!', '15.2.10.5.30') do assert('String#reverse!', '15.2.10.5.30') do
a = 'abc' a = 'abc'
a.reverse! a.reverse!
...@@ -419,6 +439,14 @@ assert('String#reverse!', '15.2.10.5.30') do ...@@ -419,6 +439,14 @@ assert('String#reverse!', '15.2.10.5.30') do
assert_equal 'cba', 'abc'.reverse! assert_equal 'cba', 'abc'.reverse!
end end
assert('String#reverse!(UTF-8)', '15.2.10.5.30') do
a = 'こんにちは世界!'
a.reverse!
assert_equal '!界世はちにんこ', a
assert_equal '!界世はちにんこ', 'こんにちは世界!'.reverse!
end if UTF8STRING
assert('String#rindex', '15.2.10.5.31') do assert('String#rindex', '15.2.10.5.31') do
assert_equal 0, 'abc'.rindex('a') assert_equal 0, 'abc'.rindex('a')
assert_nil 'abc'.rindex('d') assert_nil 'abc'.rindex('d')
...@@ -426,12 +454,27 @@ assert('String#rindex', '15.2.10.5.31') do ...@@ -426,12 +454,27 @@ assert('String#rindex', '15.2.10.5.31') do
assert_equal 3, 'abcabc'.rindex('a', 4) assert_equal 3, 'abcabc'.rindex('a', 4)
end end
assert('String#rindex(UTF-8)', '15.2.10.5.31') do
str = "こんにちは世界!\nこんにちは世界!"
assert_nil str.index('さ')
assert_equal 3, str.index('ち')
assert_equal 12, str.index('ち', 10)
assert_equal nil, str.index("さ")
end if UTF8STRING
# 'String#scan', '15.2.10.5.32' will be tested in mrbgems. # 'String#scan', '15.2.10.5.32' will be tested in mrbgems.
assert('String#size', '15.2.10.5.33') do assert('String#size', '15.2.10.5.33') do
assert_equal 3, 'abc'.size assert_equal 3, 'abc'.size
end end
assert('String#size(UTF-8)', '15.2.10.5.33') do
str = 'こんにちは世界!'
assert_equal 8, str.size
assert_not_equal str.bytesize, str.size
assert_equal 2, str[1, 2].size
end if UTF8STRING
assert('String#slice', '15.2.10.5.34') do assert('String#slice', '15.2.10.5.34') do
# length of args is 1 # length of args is 1
a = 'abc'.slice(0) a = 'abc'.slice(0)
...@@ -479,6 +522,13 @@ assert('String#split', '15.2.10.5.35') do ...@@ -479,6 +522,13 @@ assert('String#split', '15.2.10.5.35') do
assert_equal ['a', 'b', 'c'], 'abc'.split("") assert_equal ['a', 'b', 'c'], 'abc'.split("")
end end
assert('String#split(UTF-8)', '15.2.10.5.35') do
got = "こんにちは世界!".split('')
assert_equal ['こ', 'ん', 'に', 'ち', 'は', '世', '界', '!'], got
got = "こんにちは世界!".split('に')
assert_equal ['こん', 'ちは世界!'], got
end if UTF8STRING
assert('String#sub', '15.2.10.5.36') do assert('String#sub', '15.2.10.5.36') do
assert_equal 'aBcabc', 'abcabc'.sub('b', 'B') assert_equal 'aBcabc', 'abcabc'.sub('b', 'B')
assert_equal 'aBcabc', 'abcabc'.sub('b') { |w| w.capitalize } assert_equal 'aBcabc', 'abcabc'.sub('b') { |w| w.capitalize }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment