色々な言葉を理解する。
Babel: active code in Org-mode
これ、本当に凄いですね。
以前のエントリで、gauche を評価する scratch バッファもどきを作ったなんてつまらない話を書きましたが、本当は、こんなものを求めていたんだ、と思いました。
どんなものかと言うと、例えば、プログラムを書くべくアイデアを練っているとします。アイデアをただのテキストにメモしているバッファ上で、ちょっとコードを書いて確認してみたいなんて思ったら、おもむろに、
M-x org-babel-demarcate-block (C-c C-v d)
とすると、ミニバッファで、
Lang:
と訊かれるので、書きたいプログラム言語を指示してやります。すると、
#+begin_src C #+end_src
というコード断片を記述するためのメタブロックが、現在のバッファ上に作成されます。ここに、該当するプログラミング言語のコード断片を記述するのですが、そのやり方も、このブロックにポイントしている状態で、
M-x org-edit-special (C-c ')
とすると、新たなバッファをプログラミング言語に対応するモードで開いてくれるという親切設計。
更に、
M-x org-babel-execute-src-block (C-c C-c)
とすると、ポイントされているブロックのコードを実行して結果を出力してくれます。
いや、便利ですし、色んな可能性が感じられてワクワクします。チュートリアルなどを見ると、shell でコマンドを実行した結果を R に喰わせてグラフ化するとか、まあ素晴らしい。
この org-babel のインストールや使い方などは、
Emacs上のマルチな実行環境、Org-babel - sheephead Emacs org-modeを使ってみる: (35) org-babel-perlを使う1/4 - 屯遁のパズルとプログラミングの日記
などを見て貰えば詳しく解説されています。
但し、現在は org-babel が contrib から org-mode 本体に統合されたので、インストールの手順などは変わってます。まあ、楽になったので問題ないと思いますが。特別な環境でなければ make && sudo make install 的な普通の手順で org-babel までインストールされます。
私はホームディレクトリに作ってある elisp 置場におきたいのと、その際にディレクトリにまとめておきたかったので、以下の様な感じでインストールしました。
$ make prefix=~/.emacs-23.d/ lispdir=~/.emacs-23.d/share/emacs/site-lisp/orgmode $ make prefix=~/.emacs-23.d/ lispdir=~/.emacs-23.d/share/emacs/site-lisp/orgmode install
そして、.emacs とかに、
(require 'org-babel-init) (eval-after-load "org" '(progn (require 'ob-sh) (require 'ob-R) (require 'ob-ruby) (require 'ob-python)))
と書けば良い様です。
さて、本来はこれで動作する筈なんですが、私の環境では上手く動いてくれませんでした。調べてみると問題は、利用する各プログラム言語に対応するモードの hook でした。
前述の様に、コードを編集するときには、各言語の編集用モードを適用したバッファを開いてくれるのですが、そのときに、当然ながらそれぞれのモードフックが実行されます。本来ならば普通にフック関数が実行されるのですが、このバッファには visit しているファイルがありません。元の org-mode で記述されているバッファからメタブロック内のコードを抽出して別のバッファを作っていますので対応するファイルが存在しないのです。実際のところ、新たに開かれたバッファには、最終的には `buffer-file-name' が設定されるのですが、言語ごとのモードが適用されるときにはまだ設定されていないという訳です。
そのため、通常であれば対応するファイルが存在しているバッファ上で使われる各モードフックから実行される関数のうち、buffer-file-name が nil でないことを仮定しているコードがあると、それが失敗してしまいます。
自分が独自に展開しているコードは直せば良いので別に良いのですが、ちょっと困ったのは flymake でした。flymake を有効にしたいプログラミングモードでは、モードフックで `flymake-mode' を有効にしているのですが、flymake が `buffer-file-name' があることを仮定していました。syntax-check を行なうために、言語ごとに異なる設定や準備が必要だからですね。
取り敢えず、`buffer-file-name' が存在しないときには flymake-mode にはしない様に、
(add-hook 'c-mode-common-hook '(lambda () ;; ob-C のために buffer-file-name が設定されていないことを考慮。 (when buffer-file-name (flymake-mode t) (define-key (current-local-map) "\C-cc" 'flymake-start-syntax-check) (define-key (current-local-map) "\C-ce" 'flymake-show-and-sit) (define-key (current-local-map) "\C-cn" 'flymake-goto-next-error) (define-key (current-local-map) "\C-cp" 'flymake-goto-prev-error))))
などとして逃げました。ruby-mode の場合は、そもそも rhtml のために似たようなことをしてモードフックから除外していたので問題ありませんでした。
さて、これでは `org-edit-special' して編集するときに、flymake の恩恵を受けることができません。これは余りにも残念です。
なので少しばかり考えました。
(push '(".*Org Src.*\\[ ruby \\]" flymake-ruby-init flymake-simple-cleanup-extra) flymake-allowed-file-name-masks) (push '(".*Org Src.*\\[ C \\]" flymake-c-init flymake-simple-cleanup-extra) flymake-allowed-file-name-masks) (defun enable-flymake-setup () "org-babel (ob-C) のバッファに flymake-mode を適用する。" (flymake-mode t) (define-key (current-local-map) "\C-cc" 'flymake-start-syntax-check) (define-key (current-local-map) "\C-ce" 'flymake-show-and-sit) (define-key (current-local-map) "\C-cn" 'flymake-goto-next-error) (define-key (current-local-map) "\C-cp" 'flymake-goto-prev-error)) (defadvice org-src-mode-configure-edit-buffer (after org-src-mode-configure-edit-buffer-for-c activate) "c-mode-common-hook が走るタイミングでは buffer-file-name が設定されていない ため、少し遅延して setup を実施する。" (enable-flymake-setup))
それぞれコード編集用に開かれるバッファに設定される `buffer-file-name' のパターンを flymake に知らせてやった上で、`org-src-mode-configure-edit-buffer' という org-mode の「コード編集バッファを設定する関数」に無理矢理アドバイスして、flymake-mode を有効にする様にしてみました。
これで取り敢えずは使えてます。が、また更に工夫しないとならないところがあったり。
C言語なんてみんな使わなくなっちゃった??
もう随分前から、C の評判はどんどん落ちてきてますかね。ちょっと前にも、
卜部昌平のあまりreblogしないtumblr - どうも周知徹底が不足しているようなので再度のお願いとなりますが、C死ね。
なんてコンテンツが話題になったり。まあ、このコンテンツのコメントには色々な視点があって安心したりもしましたが。
私は未だに C で書かれたものを扱わないとならないので、必要に応じて C のコードを触っています。なので、org-babel でも ob-C があって嬉しかったりします。
ob-C では、実は少し問題がありました。ob-C の中に、
`org-babel-expand-body:c' `org-babel-execute:C'
という二つの関数があります。これらは、コードブロックの中身を展開する関数と、コードブロックの中身をコンパイルして実行する関数です。関数名のコロンの後にあるのが言語 (つまり C) を示す識別子になっていて、実はこの文字列から、適用するモード名を生成していました。
何故、この様に大文字と小文字の `C' と `c' で異なるのか、私には意味が掴めませんでしたが、少なくとも私の環境には、`c-mode' (cc-mode パッケージの C 用のメジャーモード) はありますが、`C-mode' はありませんでした。しかし、ob-C は `org-babel-execute:C' となっているせいで、`C-mode' を要求してくるのです。仕方ないので、
(eval-after-load "org" '(progn (require 'ob-C) (require 'ob-sh) (require 'ob-R) (require 'ob-ruby) (require 'ob-python) (defalias 'C-mode 'c-mode)))
として、`C-mode' が要求されても良い様にしてあります。
さて、この ob-C ですが C 言語が持つ特徴を上手く隠蔽する仕組みを持っていました。
C プリプロセッサディレクティブである `#include' や `#define' をコード断片に記述しなくても補完してくれるのです。とは言え、推測までして補完してくれる訳ではなく、コードブロックの外側で指示する、ということですが。(まあ、推測なんてされても困ることの方が多そうですから、それで良いんですけどね)
例えば、
#+begin_src C :results output :includes '() fprintf (stdout, "size of long long = %d\n", sizeof (long long)); return (EXIT_SUCCESS); #+end_src
みたいな具合です。`#+begin_src' 行で、`:includes '(
#+begin_src C :results output #include#include fprintf (stdout, "size of long long = %d\n", sizeof (long long)); return (EXIT_SUCCESS); #+end_src
の様にわざわざ書かなくても良いのです。そんなに便利か? と言わざるを得ない様な相違でしかないかもしれませんが、何かコードの説明をするときなどに、問題領域だけを表記するのに役立つかもしれないなあ、なんて思ったり。
と、何か上のコードが中途半端で変だなあ、なんて思いませんか? そうなんですね、関数の態を成してないんですよね。実はこれも補完が効いているという話で、実は、ob-C は上のコードを、
#+begin_src C :results output #include#include int main () { fprintf (stdout, "size of long long = %d\n", sizeof (long long)); return (EXIT_SUCCESS); } #+end_src
と書いたものと同じ状態にしてコンパイラに渡しています。そのため、非常に簡略化した断片だけを記述して実行することができるのでした。
で、こうなると困ってしまうのが flymake です。実は、最初のコードである、
#+begin_src C :results output :includes '() fprintf (stdout, "size of long long = %d\n", sizeof (long long)); return (EXIT_SUCCESS); #+end_src
なんかは、コード編集のバッファで flymake が走ると真っ赤になってしまいます。それは非常に具合が悪いので、
;; 関数を跨がって状態を保持する変数。 (defvar org:flymake-C-main-append-num 0) (defvar org:flymake-C-includes-append-lines nil) (defvar org:flymake-C-includes-append-line-num 0) (defadvice flymake-save-buffer-in-file (around flymake-save-buffer-in-file-for-org activate) "org-babel のバッファ内容から、gcc で compile できる状態の一時バッファを作成 する。" (let *1 (if (> org:flymake-C-includes-append-line-num 0) (set 'org:flymake-C-includes-append-lines (concat (mapconcat (lambda (inc) (format "#include %s" inc)) (if (listp includes) includes (list includes)) "\n") "\n")) (set 'org:flymake-C-includes-append-lines nil))))
などとしてお茶を濁しました。ああ、時間が無くなってしまったので詳細は割愛……
flymake が syntax-check するときに main や include を補完してやって、そうすると行番号がバッファ上のものと異なるのでそれを調整しています。
*1:src (concat org:flymake-C-includes-append-lines
(buffer-substring (point-min) (point-max)))))
(with-temp-buffer
(insert
(if (string-match "^[ \t]*[intvod]+[ \t\n\r]*main[ \t]*(.*)" src)
(progn
(set 'org:flymake-C-main-append-num 0)
src)
(set 'org:flymake-C-main-append-num 1)
(format "int main() {\n%s\n}\n" src)))
ad-do-it)))
;; line-err-info 内の line-no を書き換える。
(defadvice flymake-add-err-info
(before flymake-add-err-info activate)
"一時バッファに `int main ()' や `#include <*.h>' などを挿入すると、gcc の
メッセージに含まれる行番号が実際のバッファ内容と異なるため調整する。"
(set 'line-err-info
(flymake-ler-set-line line-err-info
(- (flymake-ler-line line-err-info)
;; ここは、全部加算してしまう関数を用意した方が
;; 後々良さそうだ。
(+ org:flymake-C-main-append-num
org:flymake-C-includes-append-line-num)))))
(defadvice org-babel-parse-src-block-match
(after org-babel-parse-src-block-match-for-flymake activate)
"`#+begin_src C :includes