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 ' の様に、org-babel の syntax で include や define を指示したときに、compile する一時バッファに内容を反映する。" ;; 今のところ include のみ。 (let ((includes (cdr (assoc :includes (nth 2 ad-return-value))))) (set 'org:flymake-C-includes-append-line-num (length includes