週末の遊びとして、Vimにmrubyインターフェースを組込む実験をしてみた。
ほとんどCで書いた経験が無いので、出来るかわからんなーと思っていたが、構文を実行するだけなら何とか実現できたので、とりあえずまとめておく。
ほとんどmrubyというよりVimの話なんだけど。
mrubyについて調べる
流石にもう試してる人は結構居るみたいなので、mrubyをCのプログラムから実行して結果を取得するのはすぐに分かった。
しかし、なんかいくつかパターンがあるっぽいので繰り返し実行するのに良さそうなのをチョイス。
実行方法の違いが良く分かってない。
#include <mruby.h> #include <mruby/compile.h> #include <stdio.h> int main() { mrb_state *mrb; mrbc_context *cxt; mrb = mrb_open(); if (mrb == NULL) { printf("Error\n"); } cxt = mrbc_context_new(mrb); mrb_load_string_cxt(mrb, "puts \"Hello, world\"", cxt); mrbc_context_free(mrb, cxt); mrb_close(mrb); return 0; }
Vimのif_ruby.cを読む
RubyのコードをCに組込む方法はある程度知っているので、Vimがどういう作法でそれを読んでいるのかを調べる。
マルチプラットフォームな分岐が混じっていて非常に読みづらかったが、要はensure_ruby_initializedという関数で、ruby_init_stack()とruby_init()を読んで、load_pathを設定し、vimとやりとりするオブジェクトを仕込んでいるらしい。
そして、呼び出しのキック元はex_ruby()とかex_rubydo()とかの関数で、それぞれがVimの:rubyコマンド等に対応している。
その対応関係は、ex_docmd.cとかex_cmds.hあたりで定義されている。
ensure_ruby_initializedはこんな感じの関数。
static int ensure_ruby_initialized(void) { if (!ruby_initialized) { #ifdef DYNAMIC_RUBY if (ruby_enabled(TRUE)) { #endif #ifdef _WIN32 /* suggested by Ariya Mizutani */ int argc = 1; char *argv[] = {"gvim.exe"}; NtInitialize(&argc, &argv); #endif { #if defined(RUBY19_OR_LATER) || defined(RUBY_INIT_STACK) ruby_init_stack(ruby_stack_start); #endif ruby_init(); } #ifdef RUBY19_OR_LATER { int dummy_argc = 2; char *dummy_argv[] = {"vim-ruby", "-e0"}; ruby_process_options(dummy_argc, dummy_argv); } ruby_script("vim-ruby"); #else ruby_init_loadpath(); #endif ruby_io_init(); ruby_vim_init(); ruby_initialized = 1; #ifdef DYNAMIC_RUBY } else { EMSG(_("E266: Sorry, this command is disabled, the Ruby library could not be loaded.")); return 0; } #endif } return ruby_initialized; }
:mrubyコマンドを動かせるようにする
マルチプラットフォームとかはとりあえずスルー。
Vimとのデータのやり取りも置いといて、:mrubyコマンドで構文を実行する所までを目指す。
#ifdef HAVE_CONFIG_H # include "auto/config.h" #endif #include <stdio.h> #include <string.h> #include <mruby.h> #include <mruby/proc.h> #include <mruby/compile.h> #include "vim.h" #include "version.h" static int mruby_initialized = 0; static int ensure_mruby_initialized(void); static mrb_state* vimMrb; void ex_mruby(exarg_T *eap) { char *script = NULL; mrbc_context *cxt; script = (char *)script_get(eap, eap->arg); if (!eap->skip && ensure_mruby_initialized()) { cxt = mrbc_context_new(vimMrb); if (script == NULL) { puts((char *)eap->arg); mrb_load_string_cxt(vimMrb, (char *)eap->arg, cxt); mrbc_context_free(vimMrb, cxt); } else { EMSG(_("no mruby script")); } } vim_free(script); } static int ensure_mruby_initialized(void) { if (!mruby_initialized) { vimMrb = mrb_open(); mruby_initialized = 1; return mruby_initialized; } else { return mruby_initialized; } } void mruby_end() { mrb_close(vimMrb); }
非常に適当だが、とりあえずif_ruby.cからコードの構造をパクってきてでっち上げた。
続いて、ex_cmds.hとex_docmd.cを弄る。
diff -r f6cacdc34495 src/ex_cmds.h --- a/src/ex_cmds.h Wed Aug 07 21:13:23 2013 +0200 +++ b/src/ex_cmds.h Sun Aug 11 16:00:41 2013 +0900 @@ -785,6 +785,8 @@ NEEDARG|EXTRA|NOTRLCOM), EX(CMD_runtime, "runtime", ex_runtime, BANG|NEEDARG|FILES|TRLBAR|SBOXOK|CMDWIN), +EX(CMD_mruby, "mruby", ex_mruby, + RANGE|EXTRA|NEEDARG|CMDWIN), EX(CMD_ruby, "ruby", ex_ruby, RANGE|EXTRA|NEEDARG|CMDWIN), EX(CMD_rubydo, "rubydo", ex_rubydo,
diff -r f6cacdc34495 src/ex_docmd.c --- a/src/ex_docmd.c Wed Aug 07 21:13:23 2013 +0200 +++ b/src/ex_docmd.c Sun Aug 11 16:00:41 2013 +0900 @@ -289,6 +289,9 @@ # define ex_rubydo ex_ni # define ex_rubyfile ex_ni #endif +#ifndef FEAT_MRUBY +# define ex_mruby ex_script_ni +#endif #ifndef FEAT_SNIFF # define ex_sniff ex_ni #endif
mrubyをちゃんと片付けられるように、mruby_endの呼び出しをmain.cに加える。
diff -r f6cacdc34495 src/main.c --- a/src/main.c Wed Aug 07 21:13:23 2013 +0200 +++ b/src/main.c Sun Aug 11 16:00:41 2013 +0900 @@ -1478,6 +1478,9 @@ #ifdef FEAT_RUBY ruby_end(); #endif +#ifdef FEAT_MRUBY + mruby_end(); +#endif #ifdef FEAT_PYTHON python_end(); #endif
関数のプロトタイプ宣言を追加するために、proto/if_mruby.proを作成。
diff -r f6cacdc34495 src/proto/if_mruby.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/proto/if_mruby.pro Sun Aug 11 16:00:41 2013 +0900 @@ -0,0 +1,4 @@ +/* if_mruby.c */ +void mruby_end __ARGS((void)); +void ex_mruby __ARGS((exarg_T *eap)); +/* vim: set ft=c : */
ヘッダの読み込みを追加する。
diff -r f6cacdc34495 src/proto.h --- a/src/proto.h Wed Aug 07 21:13:23 2013 +0200 +++ b/src/proto.h Sun Aug 11 16:00:41 2013 +0900 @@ -196,6 +196,9 @@ # ifdef FEAT_RUBY # include "if_ruby.pro" # endif +# ifdef FEAT_MRUBY +# include "if_mruby.pro" +# endif /* Ugly solution for "BalloonEval" not being defined while it's used in some * .pro files. */
後はせっせとautoconfのスクリプトを弄って、--enable-mrubyinterpみたいなオプション足したり、FEAT_MRUBYを定義したり、って感じでコンパイルをでっち上げる。
実際には、autoconfを弄る前に手動でMakefileを直で弄ってコンパイル通らねー、と頭を悩ませたりしてた。
一通り通って動かせるようになってからautoconfを弄っている。
結果、こんな感じでconfigureしてコンパイルすれば動くようになった。
% ./configure --with-features=huge --enable-multibyte --enable-luainterp=yes --with-luajit --with-lua-prefix=/usr/local --enable-rubyinterp=yes --enable-mrubyinterp=yes --with-libmruby=/Users/joker/mruby/build/host/lib/libmruby.a --with-mruby-include=/Users/joker/mruby/include % make
なんか表示が狂ってておかしいけど、とりあえず実行出来てるっぽい。
パっと見:rubyコマンドと何も変わらないので、少し寂しいが結構楽しかった。
出力はさておき、Vim側とのインターフェースをちゃんと書けば、mrubyでvimプラグインとか書けるかもしれないし、mrbgemsの資産使えたりするんじゃね?とちょっと思っている。
が、そこまでやる気が続くかは分からないw
正直、C書いたりautoconf弄ったりするの辛い…。LLerには厳しい世界であった。
ぬるま湯最高!
編集差分のgistは → https://gist.github.com/joker1007/6203801