Macのrubyのreadlineに不具合がある!?(2)
Macのrubyのreadlineに不具合がある!?の続き。
ruby_1_8ブランチのext/readline/readline.cを読む。
上から順番に読んでいく。
TOLOWER
#define TOLOWER(c) (isupper(c) ? tolower(c) : c)
これは、なんのへんてつもない大文字に変換するマクロですが、file.cにも定義してあるようです。
#if !defined(TOLOWER) #define TOLOWER(c) (ISUPPER(c) ? tolower(c) : (c)) #endif
file.cより
また、TOLOWERでは、isupperではなく、ISUPPERを使った方が良さそうです。
#define ISUPPER(c) (ISASCII(c) && isupper((int)(unsigned char)(c)))
ruby.hより
せっかくなので、リファクタリングしてみます。ISUPPERがruby.hに記述してあることから、ruby.hに追記してみます。
Index: ext/readline/readline.c =================================================================== --- ext/readline/readline.c (revision 18037) +++ ext/readline/readline.c (working copy) @@ -26,8 +26,6 @@ static VALUE mReadline; -#define TOLOWER(c) (isupper(c) ? tolower(c) : c) - #define COMPLETION_PROC "completion_proc" #define COMPLETION_CASE_FOLD "completion_case_fold" static ID completion_proc, completion_case_fold; Index: ruby.h =================================================================== --- ruby.h (revision 18037) +++ ruby.h (working copy) @@ -66,6 +66,9 @@ #define ISDIGIT(c) (ISASCII(c) && isdigit((int)(unsigned char)(c))) #define ISXDIGIT(c) (ISASCII(c) && isxdigit((int)(unsigned char)(c))) #endif +#if !defined(TOLOWER) +#define TOLOWER(c) (ISUPPER(c) ? tolower(c) : (c)) +#endif #if defined(HAVE_ALLOCA_H) #include <alloca.h> Index: file.c =================================================================== --- file.c (revision 18037) +++ file.c (working copy) @@ -2515,10 +2515,6 @@ buflen = RSTRING(result)->len,\ pend = p + buflen) -#if !defined(TOLOWER) -#define TOLOWER(c) (ISUPPER(c) ? tolower(c) : (c)) -#endif - static int is_absolute_path _((const char*)); static VALUE
脱線しました。続けます。
readline_event
static int readline_event(void); ... rl_event_hook = readline_event;
Readline ライブラリのグローバル変数 rl_instream に対して select(2) により入力を待つ。
Readline ライブラリのグローバル変数 rl_event_hook にこの関数を設定する。
readline_readline
static VALUE readline_readline(argc, argv, self) ... rb_define_module_function(mReadline, "readline", readline_readline, -1); ...
ユーザからのキー入力を待つ。ユーザが文字列を入力し終えると、入力した文字列を返す。
次の2つの省略可能な引数を取る。
- prompt: カーソルの前に表示する文字列を指定する。
- add_hist: 真ならば入力した文字列をヒストリに記録する。
何も入力していない状態で EOF を入力するなどにより、ユーザからの入力がない場合は nil を返す。
標準入力が tty でなく、すでにクローズされている(isatty(2) の errno が EBADF である)場合、例外 IOError が発生する。
以下が読んでも理解できなかったので調査した。GNU Readline の readline 関数を呼び出しているだけだった。rb_protect が受け取れるように readline 関数を「(VALUE(*)_((VALUE)))」という型にキャストしているのが読めなかった。なお、rb_protect は C の関数を呼び出す関数でした。
buff = (char*)rb_protect((VALUE(*)_((VALUE)))readline, (VALUE)prompt,
&status);
本メソッドのソースに[ruby-dev:29116]とある。
Subject: [ruby-dev:29116] Re: irb completion From: Tanaka Akira <akr fsij.org> Date: Mon, 24 Jul 2006 01:18:03 +0900 References: 29064 29070 29114 In-reply-to: 29114 In article <87hd18vgah.fsf / fsij.org>, Tanaka Akira <akr / fsij.org> writes: > (gdb) p (void *)rl_deprep_term_function > $2 = (void *) 0x0 It is libedit problem. It is possible on other than darwin.
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/29116より
darwin の libedit だけ、 rl_deprep_term_function に NULL(0x0) が設定されている、と読める。 darwin だけというのがやばい。このような報告があったため、前田さんは Mac の Ruby は、 Readline が...というような感想を持つようになられたのだろうか。
readline_s_set_completion_proc
static VALUE readline_s_set_completion_proc(self, proc) ... rb_define_singleton_method(mReadline, "completion_proc=", readline_s_set_completion_proc, 1);
Proc オブジェクトということではなく、 call メソッドを持つオブジェクトであればなんでも良いようだ。
readline_attempted_completion_function
static char ** readline_attempted_completion_function(text, start, end)
入力した文字列に対する候補を取得する。このとき completion_proc= で設定した候補を取得する処理を実行する(@completion_proc.call(入力した文字列))。
戻り値は(result)はchar *の配列へのポインタで、例えば次のようになる。
completion_proc.call は、["/dev", "/doc", "/disk"]を返すとする。
Readline.completion_proc = proc {|s| ["/dev", "/doc", "/disk"]}
「/」を入力後、tab キーを押す。すると、 以下のようになる。
- result[0]: "/d"
- result[1]: "/dev"
- result[2]: "/doc"
- result[3]: "/disk"
- result[4]: NULL
最初の要素は候補の共通部分。上記の場合は、 "/d" までは全ての候補で共通している。
2番目から最後の1つ前までは候補。最後は NULL。
readline_s_set_completion_append_character
static VALUE readline_s_set_completion_append_character(self, str) ... rb_define_singleton_method(mReadline, "completion_append_character=", readline_s_set_completion_append_character, 1);
nil を指定すると、 Readline ライブラリの変数 rl_completion_append_character に '\0' を代入する。
文字列を指定すると、最初の1文字だけを Readline ライブラリの変数 rl_completion_append_character に代入する。
最終的な補完結果の末尾に指定した文字を追加する。
rl_completion_append_character はなんのためにあるのだろうか。きっと目的があるはず。あとで調べる。
まとめ
ext/readline/readline.cの全てに目を通した。
とてもシンプルだと思う。Readline ライブラリの readline 関数を呼び出しているだけという感じ。
こんなにシンプルなのに、 Mac の editline で動作が想定と違うということは、 Mac の editline がまずい気がする。まぁ、なんか回避策があるのだろう。
次は以下に挙げる GNU Readline の API を読む。