Emacs
mruby
mrubyDay 7

Emacsに mrubyを組み込んでみた

More than 3 years have passed since last update.

Emacsに mrubyを組み込んでみました. 以下のようなことができるようになります.

(let ((mrb (mruby-init)))

(mruby-eval mrb
"
def fizzbuzz(num)
(1..num).map do |n|
case
when n % 15 == 0 then \"fizzbuzz\"
when n % 5 == 0 then \"buzz\"
when n % 3 == 0 then \"fizz\"
else n
end
end
end

fizzbuzz(ARGV[0].to_i)
" 20))

このS式の結果は以下のようになります.

[1 2 "fizz" 4 "buzz" "fizz" 7 8 "fizz" "buzz" 11 "fizz" 13 14 "fizzbuzz" 16 17 "fizz" 19 "buzz"]

Emacs Lispのオブジェクトに対して mrubyのメソッドを呼ぶこともできます(注: mrubyの Arrayの機能を使う場合はリストでなくベクタを使用する必要があります)

(let ((mrb (mruby-init)))

(mruby-send mrb -10 'abs)) ;; => 10

(let ((mrb (mruby-init)))
(mruby-send mrb "hello WORLD" 'swapcase)) ;; => "HELLO world"

(let ((mrb (mruby-init)))
(mruby-send mrb [1 [2 3] [4 [5 6]]] 'flatten)) ;; => [1 2 3 4 5 6]


仕組み

Emacs 25で実装予定の Dynamic module機能を使って実現しています(なので実際のところ, 組み込んだというほどではないです). 要は dlopen的なもので, 共有ライブラリを実行時に読み込んでその機能を使うというものです. これにより Cライブラリの機能を再ビルドすることなく使えることになります. 高速化や機能の拡張が期待されます. 他のエディタの人からするとまだ実装していなかったのかというような機能かもしれないですが, それが Emacsでもついに使えるときが来たわけですね.


リポジトリ

https://github.com/syohex/emacs-mruby-test


セットアップ


Emacsの構築

現状 Dynamic module機能は emacs-25ブランチにしか実装されていないので, emacs-25ブランチのソースコードからビルドする必要があります.

% git clone git://git.savannah.gnu.org/emacs.git

% cd emacs
% git checkout -b emacs-25 origin/emacs-25
% ./configure --with-modules --prefix=installed_path # --with-modulesが必須
% make
% make install


mruby.elのインストール

現状規約も何もない感じですが, とりあえず Dynamic moduleのテストと同様に modules/以下に置いておきます. 現状 Linux環境(Ubuntu 15.10 x86_64)でしかテストしていませんが, Dynamic module機能は Windowsでも MacOSXでも使えるように開発が行われています.

% cd modules

% git clone https://github.com/syohex/emacs-mruby-test.git
% make


mruby.elのロード

makeしたディレクトリで下記のように起動してください(同ディレクトリに load-pathを通して, (require 'mruby)だけでもよいです). emacsは上記で構築した emacsを使ってください.

% emacs -Q -L . -l mruby.el


API

実装は @mattn さんの go-mrubyを参考にしました.


(mruby-init)

mrb_stateオブジェクトを作成. GCされるとき, mrb_close関数が呼ばれるので明示的に終了関数を呼ぶ必要がありません.


(mruby-eval mrb code &rest args)

第一引数は mruby-initの戻り値です. 第二引数が mrubyのコード, 第三引数以降は ARGVに設定されます.


コード例

(let ((mrb (mruby-init)))

(mruby-eval mrb "ARGV.map{|x| x + 1}" 1 2 3)) ;; => [2 3 4]


(mruby-send mrb code method &rest args)

第一引数は mruby-initの戻り値です. 第二引数が mrubyのコード, 第三引数がメソッド名(シンボル), 第四引数以降は ARGVに設定されます.


コード例

(let ((mrb (mruby-init)))

(mruby-send mrb ["vim" "emacs" "atom"] 'join "+")) ;; => "vim+emacs+atom"


おわりに

Emacs 25で導入予定の Dynamic module機能を使って mrubyを使えるようにしてみたことを示しました. 現状はお遊びというレベルですが, lambda式の扱いとか mruby側から Emacs側を操作する方法等について検討していきたいと思います. また mruby力が超絶に低いのでそこを鍛えて面白い発想ができるようにもなっていければと思います. アイデア, 問題点等ありましたら, github issuestwitterまでお知らせいただければと思います.