Ruby1.9/エンコーディング処理を読む
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
#contents
*はじめに [#p1ed9a23]
次のスクリプトがあり、文字コードはEUC-JPとします。
p "日本語の文字列".length
p "日本語の文字列"[0]
p "日本語の文字列"[4..-1]
p "日本語の文字列".index("文字")
普通にスクリプトを実行させると以下の結果が得られます。
$ ruby m17n_string.rb
14
"\xC6"
"\xB8\xEC\xA4\xCE\xCA\xB8\xBB\xFA\xCE\xF3"
8
"-E EUC-JP"付きでスクリプトを実行させると以下の結果が得ら...
$ ruby -E EUC-JP /home/junjis/tmp/m17n.rb
7
"日"
"文字列"
4
これはRuby1.9でm17n対応が行われたためです。今回の読解対象...
*エンコーディングの初期化 [#s95437e5]
[[初期化>Ruby1.9/初期化を読む]]のときも触れましたがエンコ...
encoding.c
#code(C){{
rb_enc_init(void)
{
enc_table_count = enc_table_expand(ENCINDEX_BUILTIN_M...
#define ENC_REGISTER(enc) enc_register_at(ENCINDEX_##enc,...
ENC_REGISTER(ASCII);
ENC_REGISTER(EUC_JP);
ENC_REGISTER(SJIS);
ENC_REGISTER(UTF8);
}}
というわけでエンコーディング処理の実体は鬼車が行っている...
oniguruma.h
#code(C){{
#define ONIG_ENCODING_EUC_JP (&OnigEncodingEUC_JP)
ONIG_EXTERN OnigEncodingType OnigEncodingEUC_JP;
}}
encoding.h
#code(C){{
typedef OnigEncodingType rb_encoding;
}}
enc/euc_jp.c
#code(C){{
OnigEncodingDefine(euc_jp, EUC_JP) = {
mbc_enc_len,
"EUC-JP", /* name */
3, /* max enc length */
1, /* min enc length */
onigenc_is_mbc_newline_0x0a,
mbc_to_code,
code_to_mbclen,
code_to_mbc,
mbc_case_fold,
onigenc_ascii_apply_all_case_fold,
onigenc_ascii_get_case_fold_codes_by_str,
property_name_to_ctype,
is_code_ctype,
get_ctype_code_range,
left_adjust_char_head,
is_allowed_reverse_match,
0
};
}}
regenc.h
#code(C){{
#ifdef ONIG_ENC_REGISTER
extern int ONIG_ENC_REGISTER(const char *, OnigEncodingTy...
#define OnigEncodingName(n) encoding_##n
#define OnigEncodingDeclare(n) static OnigEncodingType On...
#define OnigEncodingDefine(f,n) \
OnigEncodingDeclare(n); \
void Init_##f(void) { \
ONIG_ENC_REGISTER(OnigEncodingName(n).name, \
&OnigEncodingName(n)); \
} \
OnigEncodingDeclare(n)
#else
#define OnigEncodingName(n) OnigEncoding##n
#define OnigEncodingDeclare(n) OnigEncodingType OnigEncod...
#define OnigEncodingDefine(f,n) OnigEncodingDeclare(n)
#endif
}}
見るとわかると思いますがEUC-JPみたいな組み込みのエンコー...
OnigEncodingEUC_JPの各関数は時が来たら見るとして次にスク...
*スクリプト読み込み時のエンコーディング設定 [#b22c3205]
まず、-Kオプションや-Eオプションが指定された場合の動きで...
ruby.c
#code(C){{
proc_options(int argc, char **argv, struct cmdline_option...
{
...
for (argc--, argv++; argc > 0; argc--, argv++) {
...
s = argv[0] + 1;
switch (*s) {
...
case 'K':
if (*++s) {
rb_encoding *enc = 0;
switch (*s) {
case 'E': case 'e':
enc = ONIG_ENCODING_EUC_JP;
break;
...
}
if (enc) {
opt->enc_index = rb_enc_find_index(rb...
}
case 'E':
...
goto encoding;
...
case '-':
...
s++;
...
else if (strcmp("encoding", s) == 0) {
int idx;
...
encoding:
if ((idx = rb_enc_find_index(s)) < 0) {
rb_raise(rb_eRuntimeError, "unknown e...
}
opt->enc_index = idx;
...
load_file(VALUE parser, const char *fname, int script, st...
{
...
if (opt->enc_index >= 0) rb_enc_associate_index(f, op...
}}
次にスクリプト解析時の動きです。
parse.y
#code(C){{
yycompile0(VALUE arg, int tracing)
{
parser->enc = rb_enc_get(lex_input);
...
}}
これでスクリプトを読み込むときにどの文字コードを使うかが...
続いてスクリプトに埋め込まれているエンコーディング指定を...
#code(C){{
parser_yylex(struct parser_params *parser)
{
...
switch (c = nextc()) {
case '#': /* it's a comment */
if (!parser->has_shebang || parser->line_count !=...
/* no magic_comment in shebang line */
if (!parser_magic_comment(parser, lex_p, lex_...
if (parser->line_count == (parser->has_sh...
set_file_encoding(parser, lex_p, lex_...
}
}
}
}}
parser_magic_comment関数ではまずmagic_comment_marker関数...
↓lex_p ↓lex_pend
# -*- encoding: EUC-JP -*-\n
↑beg ↑end
次にヘッダ部分と値部分にポインタがセットされます。
↓end ↓vend
# -*- encoding: EUC-JP -*-\n
↑beg ↑vbeg
その後、ヘッダ部とmagic_comments構造体のnameを比較し、一...
*Stringへのエンコーディング設定 [#dd71f28b]
文字列の読み込みはparser_parse_string関数で行われます。注...
#code(C){{
parser_parse_string(struct parser_params *parser, NODE *q...
{
...
rb_encoding *enc = parser->enc;
...
if (tokadd_string(func, term, paren, "e->nd_nest,
&enc) == -1) {
...
set_yylval_str(STR_NEW3(tok(), toklen(), enc, func));
}}
parser_tokadd_string関数はいろいろやっていますがとりあえ...
#code(C){{
parser_tokadd_string(struct parser_params *parser,
int func, int term, int paren, long *nest,
rb_encoding **encp)
{
...
while ((c = nextc()) != -1) {
...
else if (c == '\\') {
...
c = nextc();
switch (c) {
...
case 'u':
...
parser_tokadd_utf8(parser, &enc, 1,
func & STR_FUNC_SYMBOL,
func & STR_FUNC_REGEXP);
if (has_nonascii && enc != *encp) {
mixed_escape(beg, enc, *encp);
}
continue;
...
else if (!parser_isascii()) {
has_nonascii = 1;
if (enc != *encp) {
mixed_error(enc, *encp);
continue;
}
if (tokadd_mbchar(c) == -1) return -1;
continue;
}
...
tokadd(c);
}
*encp = enc;
}}
どういう動きかをしているかというと以下のような動きをして...
-\uXXXX形式のバックスラッシュ記法があると文字列のエンコー...
-複数のエンコーディングが混ざっている、例えば、EUC-JPとし...
次の注目対象はparser_tokadd_mbchar関数です。
#code(C){{
parser_tokadd_mbchar(struct parser_params *parser, int c)
{
int len = parser_precise_mbclen();
if (!MBCLEN_CHARFOUND(len)) {
compile_error(PARSER_ARG "invalid multibyte char");
return -1;
}
tokadd(c);
lex_p += --len;
if (len > 0) tokcopy(len);
return c;
}
#define parser_precise_mbclen() rb_enc_precise_mbclen((l...
}}
エラー処理はとりあえず無視するとして、以下のことが行われ...
+マルチバイト文字のバイト長を取得
+マルチバイト文字の1バイト目をトークンに格納
+現在の読み込み位置をマルチバイト文字の後ろに移動
+マルチバイト文字の2バイト目以降をトークンに格納
rb_enc_precise_mbclen関数に進みましょう。
encoding.c
#code(C){{
rb_enc_precise_mbclen(const char *p, const char *e, rb_en...
{
int n;
if (e <= p)
return ONIGENC_CONSTRUCT_MBCLEN_NEEDMORE(1);
n = ONIGENC_PRECISE_MBC_ENC_LEN(enc, (UChar*)p, (UCha...
if (e-p < n)
return ONIGENC_CONSTRUCT_MBCLEN_NEEDMORE(n-(e-p));
return n;
}
}}
include/ruby/oniguruma.h
#code(C){{
#define ONIGENC_PRECISE_MBC_ENC_LEN(enc,p,e) (enc)->pre...
}}
というわけで各エンコーディングに対する処理関数が呼び出さ...
最後にSTR_NEW3マクロを見ていきましょう。
#code(C){{
#define STR_NEW3(p,n,e,func) parser_str_new((p),(n),(e),(...
parser_str_new(const char *p, long n, rb_encoding *enc, i...
{
VALUE str;
str = rb_enc_str_new(p, n, enc);
if (!(func & STR_FUNC_REGEXP) &&
rb_enc_asciicompat(enc) &&
rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT) {
rb_enc_associate(str, rb_ascii8bit_encoding());
}
return str;
}
}}
rb_enc_str_new関数に続く。
string.c
#code(C){{
rb_enc_str_new(const char *ptr, long len, rb_encoding *enc)
{
VALUE str = str_new(rb_cString, ptr, len);
rb_enc_associate(str, enc);
return str;
}
}}
encoding.c
#code(C){{
rb_enc_associate(VALUE obj, rb_encoding *enc)
{
rb_enc_associate_index(obj, rb_enc_to_index(enc));
}
rb_enc_associate_index(VALUE obj, int idx)
{
enc_check_capable(obj);
if (!ENC_CODERANGE_ASCIIONLY(obj) ||
!rb_enc_asciicompat(rb_enc_from_index(idx))) {
ENC_CODERANGE_CLEAR(obj);
}
if (idx < ENCODING_INLINE_MAX) {
ENCODING_SET(obj, idx);
return;
}
ENCODING_SET(obj, ENCODING_INLINE_MAX);
rb_ivar_set(obj, rb_id_encoding(), INT2NUM(idx));
return;
}
}}
include/ruby/encoding.h
#code(C){{
#define ENCODING_INLINE_MAX 1023
#define ENCODING_SHIFT (FL_USHIFT+10)
#define ENCODING_MASK (ENCODING_INLINE_MAX<<ENCODING_SHIFT)
#define ENCODING_SET(obj,i) do {\
RBASIC(obj)->flags &= ~ENCODING_MASK;\
RBASIC(obj)->flags |= i << ENCODING_SHIFT;\
} while (0)
}}
というわけでオブジェクトにエンコーディングのインデックス...
*設定されたエンコーディングの利用 [#b3f2eb79]
それでは最後にStringの各メソッドでエンコーディングがどう...
#code(C){{
rb_str_aref(VALUE str, VALUE indx)
{
...
switch (TYPE(indx)) {
...
default:
/* check if indx is Range */
{
long beg, len;
VALUE tmp;
len = str_strlen(str, rb_enc_get(str));
switch (rb_range_beg_len(indx, &beg, &len, le...
case Qfalse:
break;
case Qnil:
return Qnil;
default:
tmp = rb_str_substr(str, beg, len);
return tmp;
}
}
}}
str_strlen関数は以下のようになっています。ASCIIのみの文字...
#code(C){{
str_strlen(VALUE str, rb_encoding *enc)
{
long len;
if (is_ascii_string(str)) return RSTRING_LEN(str);
if (!enc) enc = rb_enc_get(str);
len = rb_enc_strlen(RSTRING_PTR(str), RSTRING_END(str...
if (len < 0) {
rb_raise(rb_eArgError, "invalid mbstring sequence");
}
return len;
}
#define is_ascii_string(str) (rb_enc_str_coderange(str) =...
}}
rb_enc_str_coderange関数です。少し長めですがカットすると...
#code(C){{
rb_enc_str_coderange(VALUE str)
{
int cr = ENC_CODERANGE(str);
if (cr == ENC_CODERANGE_UNKNOWN) {
rb_encoding *enc = rb_enc_get(str);
const char *p = RSTRING_PTR(str);
const char *e = p + RSTRING_LEN(str);
cr = rb_enc_asciicompat(enc) ? ENC_CODERANGE_7BIT...
while (p < e) {
int ret = rb_enc_precise_mbclen(p, e, enc);
int len = MBCLEN_CHARFOUND(ret);
if (len) {
if (len != 1 || !rb_enc_isascii((unsigned...
cr = ENC_CODERANGE_VALID;
}
p += len;
}
else {
cr = ENC_CODERANGE_BROKEN;
break;
}
}
ENC_CODERANGE_SET(str, cr);
}
return cr;
}
}}
include/ruby/encoding.h
#code(C){{
#define ENC_CODERANGE_MASK (FL_USER8|FL_USER9)
#define ENC_CODERANGE_UNKNOWN 0
#define ENC_CODERANGE_7BIT FL_USER8
#define ENC_CODERANGE_VALID FL_USER9
#define ENC_CODERANGE_BROKEN (FL_USER8|FL_USER9)
#define ENC_CODERANGE(obj) (RBASIC(obj)->flags & ENC_CODE...
}}
というわけでFL_USER8とFL_USER9が使われています。最初はfla...
続いてrb_enc_strlen関数。同じく文字を表現する最大バイト数...
encoding.c
#code(C){{
rb_enc_strlen(const char *p, const char *e, rb_encoding *...
{
long c;
if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) {
return (e - p) / rb_enc_mbminlen(enc);
}
for (c=0; p<e; c++) {
int n = rb_enc_mbclen(p, e, enc);
p += n;
}
return c;
}
}}
rb_range_beg_len関数をかけることで"日本語の文字列"[4..-1]...
rb_str_substr関数は引数に応じていろいろ処理が分岐していま...
#code(C){{
rb_str_substr(VALUE str, long beg, long len)
{
rb_encoding *enc = rb_enc_get(str);
VALUE str2;
char *p, *s = RSTRING_PTR(str), *e = s + RSTRING_LEN(...
int asc = IS_7BIT(str);
...
else if ((p = str_nth(s, e, beg, enc, asc)) == e) {
...
else {
len = str_offset(p, e, len, enc, asc);
}
sub:
str2 = rb_str_new5(str, p, len);
rb_enc_copy(str2, str);
OBJ_INFECT(str2, str);
return str2;
}
}}
str_nth関数およびstr_nth関数が呼び出すrb_enc_nth関数では...
#code(C){{
str_nth(char *p, char *e, int nth, rb_encoding *enc, int ...
{
if (asc)
p += nth;
else
p = rb_enc_nth(p, e, nth, enc);
if (!p) return 0;
if (p > e) return e;
return p;
}
}}
encoding.c
#code(C){{
rb_enc_nth(const char *p, const char *e, int nth, rb_enco...
{
int c;
if (rb_enc_mbmaxlen(enc) == 1) {
p += nth;
}
else if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)...
p += nth * rb_enc_mbmaxlen(enc);
}
else {
for (c = 0; p < e && nth--; c++) {
int n = rb_enc_mbclen(p, e, enc);
p += n;
}
}
return (char*)p;
}
}}
str_offset関数をかけることで文字列長からバイト長に変換さ...
#code(C){{
str_offset(char *p, char *e, int nth, rb_encoding *enc, i...
{
const char *pp = str_nth(p, e, nth, enc, asc);
if (!pp) return e - p;
return pp - p;
}
}}
最後に設定したポインタとバイト長を引数にrb_str_new5関数を...
*おわりに [#rc33c0d2]
今回はRuby1.9でのm17n対応を見てきました。わかったこととし...
-エンコーディングの処理は構造体に処理関数を設定し呼び出す...
-オブジェクトにはエンコーディング構造体のインデックスが設...
-必要な場合のみエンコーディングの処理関数を呼び出すように...
それではみなさんもよいコードリーディングを。
終了行:
#contents
*はじめに [#p1ed9a23]
次のスクリプトがあり、文字コードはEUC-JPとします。
p "日本語の文字列".length
p "日本語の文字列"[0]
p "日本語の文字列"[4..-1]
p "日本語の文字列".index("文字")
普通にスクリプトを実行させると以下の結果が得られます。
$ ruby m17n_string.rb
14
"\xC6"
"\xB8\xEC\xA4\xCE\xCA\xB8\xBB\xFA\xCE\xF3"
8
"-E EUC-JP"付きでスクリプトを実行させると以下の結果が得ら...
$ ruby -E EUC-JP /home/junjis/tmp/m17n.rb
7
"日"
"文字列"
4
これはRuby1.9でm17n対応が行われたためです。今回の読解対象...
*エンコーディングの初期化 [#s95437e5]
[[初期化>Ruby1.9/初期化を読む]]のときも触れましたがエンコ...
encoding.c
#code(C){{
rb_enc_init(void)
{
enc_table_count = enc_table_expand(ENCINDEX_BUILTIN_M...
#define ENC_REGISTER(enc) enc_register_at(ENCINDEX_##enc,...
ENC_REGISTER(ASCII);
ENC_REGISTER(EUC_JP);
ENC_REGISTER(SJIS);
ENC_REGISTER(UTF8);
}}
というわけでエンコーディング処理の実体は鬼車が行っている...
oniguruma.h
#code(C){{
#define ONIG_ENCODING_EUC_JP (&OnigEncodingEUC_JP)
ONIG_EXTERN OnigEncodingType OnigEncodingEUC_JP;
}}
encoding.h
#code(C){{
typedef OnigEncodingType rb_encoding;
}}
enc/euc_jp.c
#code(C){{
OnigEncodingDefine(euc_jp, EUC_JP) = {
mbc_enc_len,
"EUC-JP", /* name */
3, /* max enc length */
1, /* min enc length */
onigenc_is_mbc_newline_0x0a,
mbc_to_code,
code_to_mbclen,
code_to_mbc,
mbc_case_fold,
onigenc_ascii_apply_all_case_fold,
onigenc_ascii_get_case_fold_codes_by_str,
property_name_to_ctype,
is_code_ctype,
get_ctype_code_range,
left_adjust_char_head,
is_allowed_reverse_match,
0
};
}}
regenc.h
#code(C){{
#ifdef ONIG_ENC_REGISTER
extern int ONIG_ENC_REGISTER(const char *, OnigEncodingTy...
#define OnigEncodingName(n) encoding_##n
#define OnigEncodingDeclare(n) static OnigEncodingType On...
#define OnigEncodingDefine(f,n) \
OnigEncodingDeclare(n); \
void Init_##f(void) { \
ONIG_ENC_REGISTER(OnigEncodingName(n).name, \
&OnigEncodingName(n)); \
} \
OnigEncodingDeclare(n)
#else
#define OnigEncodingName(n) OnigEncoding##n
#define OnigEncodingDeclare(n) OnigEncodingType OnigEncod...
#define OnigEncodingDefine(f,n) OnigEncodingDeclare(n)
#endif
}}
見るとわかると思いますがEUC-JPみたいな組み込みのエンコー...
OnigEncodingEUC_JPの各関数は時が来たら見るとして次にスク...
*スクリプト読み込み時のエンコーディング設定 [#b22c3205]
まず、-Kオプションや-Eオプションが指定された場合の動きで...
ruby.c
#code(C){{
proc_options(int argc, char **argv, struct cmdline_option...
{
...
for (argc--, argv++; argc > 0; argc--, argv++) {
...
s = argv[0] + 1;
switch (*s) {
...
case 'K':
if (*++s) {
rb_encoding *enc = 0;
switch (*s) {
case 'E': case 'e':
enc = ONIG_ENCODING_EUC_JP;
break;
...
}
if (enc) {
opt->enc_index = rb_enc_find_index(rb...
}
case 'E':
...
goto encoding;
...
case '-':
...
s++;
...
else if (strcmp("encoding", s) == 0) {
int idx;
...
encoding:
if ((idx = rb_enc_find_index(s)) < 0) {
rb_raise(rb_eRuntimeError, "unknown e...
}
opt->enc_index = idx;
...
load_file(VALUE parser, const char *fname, int script, st...
{
...
if (opt->enc_index >= 0) rb_enc_associate_index(f, op...
}}
次にスクリプト解析時の動きです。
parse.y
#code(C){{
yycompile0(VALUE arg, int tracing)
{
parser->enc = rb_enc_get(lex_input);
...
}}
これでスクリプトを読み込むときにどの文字コードを使うかが...
続いてスクリプトに埋め込まれているエンコーディング指定を...
#code(C){{
parser_yylex(struct parser_params *parser)
{
...
switch (c = nextc()) {
case '#': /* it's a comment */
if (!parser->has_shebang || parser->line_count !=...
/* no magic_comment in shebang line */
if (!parser_magic_comment(parser, lex_p, lex_...
if (parser->line_count == (parser->has_sh...
set_file_encoding(parser, lex_p, lex_...
}
}
}
}}
parser_magic_comment関数ではまずmagic_comment_marker関数...
↓lex_p ↓lex_pend
# -*- encoding: EUC-JP -*-\n
↑beg ↑end
次にヘッダ部分と値部分にポインタがセットされます。
↓end ↓vend
# -*- encoding: EUC-JP -*-\n
↑beg ↑vbeg
その後、ヘッダ部とmagic_comments構造体のnameを比較し、一...
*Stringへのエンコーディング設定 [#dd71f28b]
文字列の読み込みはparser_parse_string関数で行われます。注...
#code(C){{
parser_parse_string(struct parser_params *parser, NODE *q...
{
...
rb_encoding *enc = parser->enc;
...
if (tokadd_string(func, term, paren, "e->nd_nest,
&enc) == -1) {
...
set_yylval_str(STR_NEW3(tok(), toklen(), enc, func));
}}
parser_tokadd_string関数はいろいろやっていますがとりあえ...
#code(C){{
parser_tokadd_string(struct parser_params *parser,
int func, int term, int paren, long *nest,
rb_encoding **encp)
{
...
while ((c = nextc()) != -1) {
...
else if (c == '\\') {
...
c = nextc();
switch (c) {
...
case 'u':
...
parser_tokadd_utf8(parser, &enc, 1,
func & STR_FUNC_SYMBOL,
func & STR_FUNC_REGEXP);
if (has_nonascii && enc != *encp) {
mixed_escape(beg, enc, *encp);
}
continue;
...
else if (!parser_isascii()) {
has_nonascii = 1;
if (enc != *encp) {
mixed_error(enc, *encp);
continue;
}
if (tokadd_mbchar(c) == -1) return -1;
continue;
}
...
tokadd(c);
}
*encp = enc;
}}
どういう動きかをしているかというと以下のような動きをして...
-\uXXXX形式のバックスラッシュ記法があると文字列のエンコー...
-複数のエンコーディングが混ざっている、例えば、EUC-JPとし...
次の注目対象はparser_tokadd_mbchar関数です。
#code(C){{
parser_tokadd_mbchar(struct parser_params *parser, int c)
{
int len = parser_precise_mbclen();
if (!MBCLEN_CHARFOUND(len)) {
compile_error(PARSER_ARG "invalid multibyte char");
return -1;
}
tokadd(c);
lex_p += --len;
if (len > 0) tokcopy(len);
return c;
}
#define parser_precise_mbclen() rb_enc_precise_mbclen((l...
}}
エラー処理はとりあえず無視するとして、以下のことが行われ...
+マルチバイト文字のバイト長を取得
+マルチバイト文字の1バイト目をトークンに格納
+現在の読み込み位置をマルチバイト文字の後ろに移動
+マルチバイト文字の2バイト目以降をトークンに格納
rb_enc_precise_mbclen関数に進みましょう。
encoding.c
#code(C){{
rb_enc_precise_mbclen(const char *p, const char *e, rb_en...
{
int n;
if (e <= p)
return ONIGENC_CONSTRUCT_MBCLEN_NEEDMORE(1);
n = ONIGENC_PRECISE_MBC_ENC_LEN(enc, (UChar*)p, (UCha...
if (e-p < n)
return ONIGENC_CONSTRUCT_MBCLEN_NEEDMORE(n-(e-p));
return n;
}
}}
include/ruby/oniguruma.h
#code(C){{
#define ONIGENC_PRECISE_MBC_ENC_LEN(enc,p,e) (enc)->pre...
}}
というわけで各エンコーディングに対する処理関数が呼び出さ...
最後にSTR_NEW3マクロを見ていきましょう。
#code(C){{
#define STR_NEW3(p,n,e,func) parser_str_new((p),(n),(e),(...
parser_str_new(const char *p, long n, rb_encoding *enc, i...
{
VALUE str;
str = rb_enc_str_new(p, n, enc);
if (!(func & STR_FUNC_REGEXP) &&
rb_enc_asciicompat(enc) &&
rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT) {
rb_enc_associate(str, rb_ascii8bit_encoding());
}
return str;
}
}}
rb_enc_str_new関数に続く。
string.c
#code(C){{
rb_enc_str_new(const char *ptr, long len, rb_encoding *enc)
{
VALUE str = str_new(rb_cString, ptr, len);
rb_enc_associate(str, enc);
return str;
}
}}
encoding.c
#code(C){{
rb_enc_associate(VALUE obj, rb_encoding *enc)
{
rb_enc_associate_index(obj, rb_enc_to_index(enc));
}
rb_enc_associate_index(VALUE obj, int idx)
{
enc_check_capable(obj);
if (!ENC_CODERANGE_ASCIIONLY(obj) ||
!rb_enc_asciicompat(rb_enc_from_index(idx))) {
ENC_CODERANGE_CLEAR(obj);
}
if (idx < ENCODING_INLINE_MAX) {
ENCODING_SET(obj, idx);
return;
}
ENCODING_SET(obj, ENCODING_INLINE_MAX);
rb_ivar_set(obj, rb_id_encoding(), INT2NUM(idx));
return;
}
}}
include/ruby/encoding.h
#code(C){{
#define ENCODING_INLINE_MAX 1023
#define ENCODING_SHIFT (FL_USHIFT+10)
#define ENCODING_MASK (ENCODING_INLINE_MAX<<ENCODING_SHIFT)
#define ENCODING_SET(obj,i) do {\
RBASIC(obj)->flags &= ~ENCODING_MASK;\
RBASIC(obj)->flags |= i << ENCODING_SHIFT;\
} while (0)
}}
というわけでオブジェクトにエンコーディングのインデックス...
*設定されたエンコーディングの利用 [#b3f2eb79]
それでは最後にStringの各メソッドでエンコーディングがどう...
#code(C){{
rb_str_aref(VALUE str, VALUE indx)
{
...
switch (TYPE(indx)) {
...
default:
/* check if indx is Range */
{
long beg, len;
VALUE tmp;
len = str_strlen(str, rb_enc_get(str));
switch (rb_range_beg_len(indx, &beg, &len, le...
case Qfalse:
break;
case Qnil:
return Qnil;
default:
tmp = rb_str_substr(str, beg, len);
return tmp;
}
}
}}
str_strlen関数は以下のようになっています。ASCIIのみの文字...
#code(C){{
str_strlen(VALUE str, rb_encoding *enc)
{
long len;
if (is_ascii_string(str)) return RSTRING_LEN(str);
if (!enc) enc = rb_enc_get(str);
len = rb_enc_strlen(RSTRING_PTR(str), RSTRING_END(str...
if (len < 0) {
rb_raise(rb_eArgError, "invalid mbstring sequence");
}
return len;
}
#define is_ascii_string(str) (rb_enc_str_coderange(str) =...
}}
rb_enc_str_coderange関数です。少し長めですがカットすると...
#code(C){{
rb_enc_str_coderange(VALUE str)
{
int cr = ENC_CODERANGE(str);
if (cr == ENC_CODERANGE_UNKNOWN) {
rb_encoding *enc = rb_enc_get(str);
const char *p = RSTRING_PTR(str);
const char *e = p + RSTRING_LEN(str);
cr = rb_enc_asciicompat(enc) ? ENC_CODERANGE_7BIT...
while (p < e) {
int ret = rb_enc_precise_mbclen(p, e, enc);
int len = MBCLEN_CHARFOUND(ret);
if (len) {
if (len != 1 || !rb_enc_isascii((unsigned...
cr = ENC_CODERANGE_VALID;
}
p += len;
}
else {
cr = ENC_CODERANGE_BROKEN;
break;
}
}
ENC_CODERANGE_SET(str, cr);
}
return cr;
}
}}
include/ruby/encoding.h
#code(C){{
#define ENC_CODERANGE_MASK (FL_USER8|FL_USER9)
#define ENC_CODERANGE_UNKNOWN 0
#define ENC_CODERANGE_7BIT FL_USER8
#define ENC_CODERANGE_VALID FL_USER9
#define ENC_CODERANGE_BROKEN (FL_USER8|FL_USER9)
#define ENC_CODERANGE(obj) (RBASIC(obj)->flags & ENC_CODE...
}}
というわけでFL_USER8とFL_USER9が使われています。最初はfla...
続いてrb_enc_strlen関数。同じく文字を表現する最大バイト数...
encoding.c
#code(C){{
rb_enc_strlen(const char *p, const char *e, rb_encoding *...
{
long c;
if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) {
return (e - p) / rb_enc_mbminlen(enc);
}
for (c=0; p<e; c++) {
int n = rb_enc_mbclen(p, e, enc);
p += n;
}
return c;
}
}}
rb_range_beg_len関数をかけることで"日本語の文字列"[4..-1]...
rb_str_substr関数は引数に応じていろいろ処理が分岐していま...
#code(C){{
rb_str_substr(VALUE str, long beg, long len)
{
rb_encoding *enc = rb_enc_get(str);
VALUE str2;
char *p, *s = RSTRING_PTR(str), *e = s + RSTRING_LEN(...
int asc = IS_7BIT(str);
...
else if ((p = str_nth(s, e, beg, enc, asc)) == e) {
...
else {
len = str_offset(p, e, len, enc, asc);
}
sub:
str2 = rb_str_new5(str, p, len);
rb_enc_copy(str2, str);
OBJ_INFECT(str2, str);
return str2;
}
}}
str_nth関数およびstr_nth関数が呼び出すrb_enc_nth関数では...
#code(C){{
str_nth(char *p, char *e, int nth, rb_encoding *enc, int ...
{
if (asc)
p += nth;
else
p = rb_enc_nth(p, e, nth, enc);
if (!p) return 0;
if (p > e) return e;
return p;
}
}}
encoding.c
#code(C){{
rb_enc_nth(const char *p, const char *e, int nth, rb_enco...
{
int c;
if (rb_enc_mbmaxlen(enc) == 1) {
p += nth;
}
else if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)...
p += nth * rb_enc_mbmaxlen(enc);
}
else {
for (c = 0; p < e && nth--; c++) {
int n = rb_enc_mbclen(p, e, enc);
p += n;
}
}
return (char*)p;
}
}}
str_offset関数をかけることで文字列長からバイト長に変換さ...
#code(C){{
str_offset(char *p, char *e, int nth, rb_encoding *enc, i...
{
const char *pp = str_nth(p, e, nth, enc, asc);
if (!pp) return e - p;
return pp - p;
}
}}
最後に設定したポインタとバイト長を引数にrb_str_new5関数を...
*おわりに [#rc33c0d2]
今回はRuby1.9でのm17n対応を見てきました。わかったこととし...
-エンコーディングの処理は構造体に処理関数を設定し呼び出す...
-オブジェクトにはエンコーディング構造体のインデックスが設...
-必要な場合のみエンコーディングの処理関数を呼び出すように...
それではみなさんもよいコードリーディングを。
ページ名: