Skip to content

Instantly share code, notes, and snippets.

@kaishuu0123
Last active May 1, 2018 06:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaishuu0123/7838949 to your computer and use it in GitHub Desktop.
Save kaishuu0123/7838949 to your computer and use it in GitHub Desktop.
mruby-readline つくってみた

mruby advent calendar 2013 ネタです。

コードは github においてあります。

はじめまして、mruby にコミットしてたり(最近はできてない)、
mruby gem を作っていたりする kaishuu0123 です。
なにげに Advent calendar 初参加。ヽ|・ω・|ゞ

はて、advent calendar ネタですが、
Advent calendar は クリスマスまでカウントダウンするカレンダー(だったっけ?)ということで、
mruby 使ってる人たちに何かプレゼントできないかなー、と考えて、
mruby の gem を作って、皆様に使ってもらいたいなー、と思ってます。ヽ( ・∀・)ノ

何か使ってもらえるものないかなーと考えたところ、
mruby でミニマムなシェルが作れるといいなー、と思いました。
今の実装でも、while ループと IO の組み合わせとかで作れますが、
やはり、シェルはオートコンプリートができてなんぼ(`ω´*) というわけで、
libreadline をラッピングした gem を作ってみました。
Mac 上で作っています。Linux でも多分動きますが、BSD は(ry

今できることとか

  • readline() 関数で行指向の入力
  • 文字列のオートコンプリーション
  • エラー処理は無し。とりあえず正常系が動くまで。
  • append_character の設定はまだ未サポート(そのうちやるよ!)
  • あとは自分で書け!

使い方

build_config.rb に以下の行を追加すれば、使えます

  conf.gem :github => 'kaishuu0123/mruby-readline', :branch => 'master'

こんな感じのスクリプトが動いて、LIST 配列の中にあるワードを tab キーで補完してくれます。

LIST = [
  'search', 'download', 'open',
  'help', 'history', 'quit',
  'url', 'next', 'clear',
  'prev', 'past'
].sort

comp = proc { |s| LIST.grep( /^#{s}/ ) }

Readline.completion_append_character = " "
Readline.completion_proc = comp

while line = Readline.readline('> ', true)
  p line
end

これだけで終わったら少し寂しいので、実装までの簡単な流れと、困ったところをば。

実装までの道のり

実装するにあたって、何を調べて、何を考えて作ったのか、 というところをメモしておきます。

  1. libreadline について調べる
  2. auto completion をする小さなサンプルを C言語で書いてみる
  3. mruby の gem を作るためのテンプレートをつくる
  4. 後は小さいC言語のサンプルを見つつ、gem の作り込みをする

ちょっと困ったところ

コールバック関数を登録するような API の切り方をしているライブラリだと、mrb_state の引き継ぎ方が悩ましい件

  • 基本的に mruby の拡張では、実行がイベントドリブンというより は、きちんと逐次処理だったりするので、 mrb_state という mruby の状態を持つ変数は、 全ての関数上で辿ることができます。ですが、コールバック関数形式だと、 ライブラリ側から呼び出しされたときに、 どの mrb_state を見ればいいのか、という話があります。 なので、readline の場合には、グローバルに mrb_state 変数を持って、 初期化時に渡された mrb のポインタを持ちます。
mrb_state *readline_mrb_state; /* XXX: global variable */
(...)
void
mrb_mruby_readline_gem_init(mrb_state* mrb) { <- 初期化関数
  (...)
  readline_mrb_state = mrb;
  (...)
}

そして、コールバック関数内で再度取得するという ごまかし ワークアラウンドをしました。~(´ー`~)

/* completion をするためのコールバック関数の中 */
static char
**readline_attempted_completion_function(const char *text, int start, int end) {
  mrb_state *mrb = readline_mrb_state;
  mrb_value *self = mrb->c->stack;
  (...)
}

かなりインスタントではありますが、これで一応動作するため、これでお茶を濁しています。|´ー`)

最後に

  • mruby advent calendar という記事に参加させてもらえて嬉しいで>す。カレンダーを見ると、mruby というめちゃくちゃニッチ & マニアック 珍しい実装にこれだけの人が集まっているということは、私個人として、正直びっくりしました。
  • やっぱり、mruby を拡張したり、言語を安定させたり、ということは凄く楽しいことだと思います。
  • あとは、とにかく Lua 並(くらい?)に使えるようになっているか、続けて使ってみていくことが大切だと思います。

Happy mruby gem life! ヽ(*´∀`*)ノ.+゚

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment